Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Schedules): Fix alerts not showing up for stops with inbound trains #2029

Merged
merged 4 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions lib/predictions/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ defmodule Predictions.Repo do
opts
|> add_all_optional_params()
|> cache_fetch()
|> filter_by_min_time(Keyword.get(opts, :min_time))
|> filter_predictions(Keyword.get(opts, :min_time))
|> load_from_other_repos
end

def all_no_cache(opts) when is_list(opts) and opts != [] do
opts
|> add_all_optional_params()
|> fetch()
|> filter_predictions()
|> load_from_other_repos
end

Expand All @@ -53,6 +54,20 @@ defmodule Predictions.Repo do
end
end

@spec filter_predictions([Parser.record()] | {:error, any}, DateTime.t() | nil) ::
[Parser.record()] | {:error, any}
defp filter_predictions(predictions, min_time \\ nil)

defp filter_predictions({:error, error}, _) do
{:error, error}
end

defp filter_predictions(predictions, min_time) do
Enum.filter(predictions, fn prediction ->
has_departure_time?(prediction) && after_min_time?(prediction, min_time)
end)
end

defp fetch(params) do
_ = Logger.info("predictions_repo_all_cache=cache_miss")

Expand Down Expand Up @@ -98,19 +113,17 @@ defmodule Predictions.Repo do
[]
end

@spec filter_by_min_time([Parser.record()] | {:error, any}, DateTime.t() | nil) ::
[Parser.record()] | {:error, any}
defp filter_by_min_time({:error, error}, _) do
{:error, error}
defp has_departure_time?(
{_id, _trip_id, _stop_id, _route_id, _direction_id, _arrival, departure, _time,
_stop_sequence, _schedule_relationship, _track, _status, _departing?,
_vehicle_id} = _prediction
) do
departure != nil
end

defp filter_by_min_time(predictions, nil) do
predictions
end
defp has_departure_time?(_), do: false

defp filter_by_min_time(predictions, %DateTime{} = min_time) do
Enum.filter(predictions, &after_min_time?(&1, min_time))
end
defp after_min_time?(_, nil), do: true

defp after_min_time?(
{
Expand Down
110 changes: 109 additions & 1 deletion test/predictions/repo_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
defmodule Predictions.RepoTest do
use ExUnit.Case, async: false
@moduletag :external

import Mox

Expand All @@ -21,6 +20,7 @@ defmodule Predictions.RepoTest do
setup :verify_on_exit!

describe "all/1" do
@tag :external
test "returns a list" do
expect(MBTA.Api.Mock, :get_json, fn _, _ ->
%JsonApi{data: []}
Expand All @@ -31,6 +31,7 @@ defmodule Predictions.RepoTest do
assert is_list(predictions)
end

@tag :external
test "can filter by trip" do
expect(MBTA.Api.Mock, :get_json, fn _, _ ->
%JsonApi{data: []}
Expand All @@ -43,6 +44,7 @@ defmodule Predictions.RepoTest do
end
end

@tag :external
test "filters by min_time" do
min_time = Util.now() |> Timex.shift(minutes: 15)

Expand All @@ -58,6 +60,104 @@ defmodule Predictions.RepoTest do
end
end

test "filters out predictions with no departure" do
five_minutes_in_future = DateTime.add(Timex.now(), 5, :minute)

five_minutes_in_future_string =
Timex.format!(five_minutes_in_future, "{ISO:Extended:Z}")

# Set as constant to take advantage of caching (less calls to get_json)
route_id = Faker.Pizza.cheese()

prediction_json = fn %{departure_time: departure_time, arrival_time: arrival_time} ->
%JsonApi.Item{
attributes: %{
"departure_time" => departure_time,
"arrival_time" => arrival_time,
"direction_id" => Faker.random_between(0, 1)
},
relationships: %{
"route" => [
%{
id: route_id
}
],
"trip" => [],
"vehicle" => [],
"stop" => [
%{id: Faker.Pizza.topping()}
]
}
}
end

expect(MBTA.Api.Mock, :get_json, fn _, _ ->
%JsonApi{
data: [
prediction_json.(%{departure_time: nil, arrival_time: five_minutes_in_future_string}),
prediction_json.(%{
departure_time: five_minutes_in_future_string,
arrival_time: five_minutes_in_future_string
})
]
}
end)

test_stop_data = %JsonApi{
data: [
%JsonApi.Item{
id: Faker.Pizza.cheese(),
attributes: %{
"name" => Faker.Pizza.combo(),
"location_type" => Faker.random_between(0, 1),
"platform_name" => Faker.Pizza.company(),
"platform_code" => Faker.Pizza.company(),
"description" => Faker.Pizza.topping()
},
relationships: %{
"facilities" => %{},
"zone" => Faker.Pizza.topping()
}
}
]
}

test_route_data = %JsonApi{
data: [
%JsonApi.Item{
id: Faker.Pizza.cheese(),
attributes: %{
"type" => "1",
"long_name" => Faker.Pizza.topping(),
"direction_names" => [Faker.Pizza.meat(), Faker.Pizza.meat()],
"direction_destinations" => [Faker.Pizza.company(), Faker.Pizza.company()]
},
relationships: %{}
}
]
}

# Route for calculating display time
expect(MBTA.Api.Mock, :get_json, fn _, _ ->
test_route_data
end)

# Parent Stop for prediction
expect(MBTA.Api.Mock, :get_json, fn _, _ ->
test_stop_data
end)

# Route for generating struct
expect(MBTA.Api.Mock, :get_json, fn _, _ ->
test_route_data
end)

predictions = Repo.all(route: Faker.Pizza.cheese())

assert Kernel.length(predictions) == 1
end

@tag :external
test "returns a list even if the server is down" do
expect(MBTA.Api.Mock, :get_json, fn _, _ ->
{:error, %HTTPoison.Error{reason: :econnrefused}}
Expand All @@ -66,6 +166,7 @@ defmodule Predictions.RepoTest do
assert Repo.all(route: "test_down_server") == []
end

@tag :external
test "returns valid entries even if some don't parse" do
expect(MBTA.Api.Mock, :get_json, fn _, _, _ ->
%JsonApi{data: []}
Expand All @@ -81,6 +182,7 @@ defmodule Predictions.RepoTest do
refute Repo.all(route: "Red", trip: "made_up_trip") == []
end

@tag :external
test "caches trips that are retrieved", %{cache: cache} do
expect(MBTA.Api.Mock, :get_json, fn _, _ ->
%JsonApi{data: []}
Expand All @@ -91,6 +193,7 @@ defmodule Predictions.RepoTest do
assert {:ok, %Schedules.Trip{id: "trip"}} = cache.get({:trip, "trip"})
end

@tag :external
test "returns an empty list if the API returns an error" do
# make sure it's cached
expect(MBTA.Api.Mock, :get_json, fn _, _, _ ->
Expand Down Expand Up @@ -122,6 +225,7 @@ defmodule Predictions.RepoTest do
end

describe "load_from_other_repos/1" do
@tag :external
test "turns a list of records into structs" do
prediction = {
"prediction_id",
Expand Down Expand Up @@ -159,6 +263,7 @@ defmodule Predictions.RepoTest do
end
end

@tag :external
test "drops prediction if stop_id is nil" do
prediction = {
"prediction_id",
Expand All @@ -180,6 +285,7 @@ defmodule Predictions.RepoTest do
assert Predictions.Repo.load_from_other_repos([prediction]) == []
end

@tag :external
test "drops prediction if stop doesn't exist" do
prediction = {
"prediction_id",
Expand All @@ -206,6 +312,7 @@ defmodule Predictions.RepoTest do
assert log =~ "Discarding prediction"
end

@tag :external
test "drops subway prediction if it is in the past" do
prediction_in_the_past = {
"past_prediction",
Expand Down Expand Up @@ -263,6 +370,7 @@ defmodule Predictions.RepoTest do
assert Enum.count(total_predictions) == 2
end

@tag :external
test "does not drop prediction though it is in the past" do
# predictions in the past are only dropped for subway
bus_prediction_in_the_past = {
Expand Down
Loading