From b35fde38b61bcfe1cafffe93b11d10fe63a855b1 Mon Sep 17 00:00:00 2001 From: Kayla Brady <31781298+KaylaBrady@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:16:49 -0400 Subject: [PATCH] feat(Repository.routes): overwrite colors with line colors (#228) * feat(Repository.routes): overwrite colors with line colors * refactor: overwrite route color at parsing time --- lib/mbta_v3_api/json_api/object.ex | 12 ++- lib/mbta_v3_api/json_api/response.ex | 4 +- lib/mbta_v3_api/route.ex | 22 ++++- test/mbta_v3_api/repository_test.exs | 143 +++++++++++++++++++++++++++ test/mbta_v3_api/route_test.exs | 99 ++++++++++++++----- 5 files changed, 242 insertions(+), 38 deletions(-) diff --git a/lib/mbta_v3_api/json_api/object.ex b/lib/mbta_v3_api/json_api/object.ex index 7b1677dd..7cac102b 100644 --- a/lib/mbta_v3_api/json_api/object.ex +++ b/lib/mbta_v3_api/json_api/object.ex @@ -3,6 +3,7 @@ defmodule MBTAV3API.JsonApi.Object do Shared logic for all objects that can be represented as JSON:API resources. """ alias MBTAV3API.JsonApi + alias MBTAV3API.Route @doc """ JSON:API type name for the object this module represents. @@ -16,6 +17,7 @@ defmodule MBTAV3API.JsonApi.Object do Map of related objects that can be included to their struct modules. `%{trip: MBTAV3API.Trip, stops: MBTAV3API.Stop, parent_station: MBTAV3API.Stop}`, etc. Names should match `defstruct/1`. """ @callback includes :: %{atom() => module()} + @doc """ If needed, a custom serialize function. @@ -180,10 +182,12 @@ defmodule MBTAV3API.JsonApi.Object do Preserving references is probably not still useful now that we are never nesting objects. """ - @spec parse(JsonApi.Item.t()) :: t() - @spec parse(JsonApi.Reference.t()) :: JsonApi.Reference.t() - def parse(%JsonApi.Item{type: type} = item), do: module_for(type).parse(item) - def parse(%JsonApi.Reference{} = ref), do: ref + @spec parse(JsonApi.Item.t(), [JsonApi.Item.t()]) :: t() + @spec parse(JsonApi.Reference.t(), [JsonApi.Item.t()]) :: JsonApi.Reference.t() + def parse(item, included \\ []) + def parse(%JsonApi.Item{type: "route"} = item, included), do: Route.parse(item, included) + def parse(%JsonApi.Item{type: type} = item, _included), do: module_for(type).parse(item) + def parse(%JsonApi.Reference{} = ref, _included), do: ref @doc """ Gets the `id` of a single `JsonApi.Reference`. diff --git a/lib/mbta_v3_api/json_api/response.ex b/lib/mbta_v3_api/json_api/response.ex index 73109920..e71364ec 100644 --- a/lib/mbta_v3_api/json_api/response.ex +++ b/lib/mbta_v3_api/json_api/response.ex @@ -13,8 +13,8 @@ defmodule MBTAV3API.JsonApi.Response do @spec parse(JsonApi.t()) :: t(JsonApi.Object.t()) def parse(%JsonApi{data: data, included: included}) do %__MODULE__{ - data: Enum.map(data, &JsonApi.Object.parse/1), - included: included |> Enum.map(&JsonApi.Object.parse/1) |> to_full_map() + data: Enum.map(data, &JsonApi.Object.parse(&1, included)), + included: included |> Enum.map(&JsonApi.Object.parse(&1, included)) |> to_full_map() } end end diff --git a/lib/mbta_v3_api/route.ex b/lib/mbta_v3_api/route.ex index 287f9105..b225316c 100644 --- a/lib/mbta_v3_api/route.ex +++ b/lib/mbta_v3_api/route.ex @@ -56,22 +56,34 @@ defmodule MBTAV3API.Route do def serialize_filter_value(:type, type), do: serialize_type(type) def serialize_filter_value(_field, value), do: value - @spec parse(JsonApi.Item.t()) :: t() - def parse(%JsonApi.Item{} = item) do + @spec parse(JsonApi.Item.t(), [JsonApi.Object.t()]) :: t() + def parse(%JsonApi.Item{} = item, included_items \\ []) do + line_id = JsonApi.Object.get_one_id(item.relationships["line"]) + + line = Enum.find(included_items, fn item -> item.type == "line" && item.id == line_id end) + + # Override colors with line color when available. This way, OL Shuttle colors + # match the OL rather than matching other buses. + {color, text_color} = + case line do + %{attributes: %{"color" => color, "text_color" => text_color}} -> {color, text_color} + nil -> {item.attributes["color"], item.attributes["text_color"]} + end + %__MODULE__{ id: item.id, type: if type = item.attributes["type"] do parse_type(type) end, - color: item.attributes["color"], + color: color, direction_names: item.attributes["direction_names"], direction_destinations: item.attributes["direction_destinations"], long_name: item.attributes["long_name"], short_name: item.attributes["short_name"], sort_order: item.attributes["sort_order"], - text_color: item.attributes["text_color"], - line_id: JsonApi.Object.get_one_id(item.relationships["line"]), + text_color: text_color, + line_id: line_id, route_pattern_ids: JsonApi.Object.get_many_ids(item.relationships["route_patterns"]) } end diff --git a/test/mbta_v3_api/repository_test.exs b/test/mbta_v3_api/repository_test.exs index 224fe165..f1a1ef3e 100644 --- a/test/mbta_v3_api/repository_test.exs +++ b/test/mbta_v3_api/repository_test.exs @@ -3,6 +3,7 @@ defmodule MBTAV3API.RepositoryTest do import Mox + alias MBTAV3API.Route alias MBTAV3API.{Alert, Repository, RoutePattern, Stop} import Test.Support.Sigils @@ -222,6 +223,148 @@ defmodule MBTAV3API.RepositoryTest do }} = Repository.route_patterns([]) end + describe "routes/2" do + test "fetches routes" do + expect( + MobileAppBackend.HTTPMock, + :request, + fn %Req.Request{url: %URI{path: "/routes"}, options: _params} -> + {:ok, + Req.Response.json(%{ + data: [ + %{ + attributes: %{ + color: "ED8B00", + description: "Rapid Transit", + direction_destinations: [ + "Forest Hills", + "Oak Grove" + ], + direction_names: [ + "South", + "North" + ], + fare_class: "Rapid Transit", + long_name: "Orange Line", + short_name: "", + sort_order: 10_020, + text_color: "FFFFFF", + type: 1 + }, + id: "Orange", + links: %{ + self: "/routes/Orange" + }, + relationships: %{ + line: %{ + data: %{ + id: "line-Orange", + type: "line" + } + } + }, + type: "route" + } + ] + })} + end + ) + + assert {:ok, + %{ + data: [ + %Route{ + id: "Orange", + long_name: "Orange Line" + } + ] + }} = Repository.routes([]) + end + + test "overrides route color with line color" do + expect( + MobileAppBackend.HTTPMock, + :request, + fn %Req.Request{ + url: %URI{path: "/routes"}, + options: %{ + params: %{"include" => "line,route_patterns", "fields[route]" => "short_name"} + } + } -> + {:ok, + Req.Response.json(%{ + data: [ + %{ + attributes: %{ + color: "FFC72C", + description: "Rail Replacement Bus", + direction_destinations: [ + "Forest Hills", + "Back Bay" + ], + direction_names: [ + "South", + "North" + ], + fare_class: "Free", + long_name: "Forest Hills - Back Bay", + short_name: "Orange Line Shuttle", + sort_order: 60_491, + text_color: "000000", + type: 3 + }, + id: "Shuttle-BackBayForestHills", + links: %{ + self: "/routes/Shuttle-BackBayForestHills" + }, + relationships: %{ + line: %{ + data: %{ + id: "line-Orange", + type: "line" + } + } + }, + type: "route" + } + ], + included: [ + %{ + attributes: %{ + color: "ED8B00", + long_name: "Orange Line", + short_name: "", + sort_order: 10_020, + text_color: "FFFFFF" + }, + id: "line-Orange", + links: %{ + self: "/lines/line-Orange" + }, + type: "line" + } + ] + })} + end + ) + + assert {:ok, + %{ + data: [ + %Route{ + id: "Shuttle-BackBayForestHills", + color: "ED8B00", + text_color: "FFFFFF" + } + ] + }} = + Repository.routes( + include: [:line, :route_patterns], + fields: [route: [:short_name]] + ) + end + end + test "stops/2" do expect( MobileAppBackend.HTTPMock, diff --git a/test/mbta_v3_api/route_test.exs b/test/mbta_v3_api/route_test.exs index 5564f2f4..1c42a989 100644 --- a/test/mbta_v3_api/route_test.exs +++ b/test/mbta_v3_api/route_test.exs @@ -4,33 +4,78 @@ defmodule MBTAV3API.RouteTest do alias MBTAV3API.JsonApi alias MBTAV3API.Route - test "parse/1" do - assert Route.parse(%JsonApi.Item{ - id: "Green-C", - attributes: %{ - "color" => "00843D", - "direction_destinations" => ["Cleveland Circle", "Government Center"], - "direction_names" => ["West", "East"], - "long_name" => "Green Line C", - "short_name" => "C", - "sort_order" => 10_033, - "text_color" => "FFFFFF", - "type" => 0 - }, - relationships: %{ - "line" => %JsonApi.Reference{type: "line", id: "line-Green"} + describe "parse/1" do + test "parse route only" do + assert Route.parse(%JsonApi.Item{ + id: "Green-C", + attributes: %{ + "color" => "00843D", + "direction_destinations" => ["Cleveland Circle", "Government Center"], + "direction_names" => ["West", "East"], + "long_name" => "Green Line C", + "short_name" => "C", + "sort_order" => 10_033, + "text_color" => "FFFFFF", + "type" => 0 + }, + relationships: %{ + "line" => %JsonApi.Reference{type: "line", id: "line-Green"} + } + }) == %Route{ + id: "Green-C", + color: "00843D", + direction_destinations: ["Cleveland Circle", "Government Center"], + direction_names: ["West", "East"], + long_name: "Green Line C", + short_name: "C", + sort_order: 10_033, + text_color: "FFFFFF", + type: :light_rail, + line_id: "line-Green" } - }) == %Route{ - id: "Green-C", - color: "00843D", - direction_destinations: ["Cleveland Circle", "Government Center"], - direction_names: ["West", "East"], - long_name: "Green Line C", - short_name: "C", - sort_order: 10_033, - text_color: "FFFFFF", - type: :light_rail, - line_id: "line-Green" - } + end + + test "parse with included line overrides route color" do + assert %Route{ + id: "orange-shuttle", + color: "line_color", + direction_destinations: ["Oak Grove", "Forest Hills"], + direction_names: ["Northbound", "Southbound"], + long_name: "Orange Line Shuttle", + short_name: "OL Shuttle", + sort_order: 123, + text_color: "line_text_color", + type: :bus, + line_id: "line-Orange" + } == + Route.parse( + %JsonApi.Item{ + id: "orange-shuttle", + attributes: %{ + "color" => "bus_color", + "direction_destinations" => ["Oak Grove", "Forest Hills"], + "direction_names" => ["Northbound", "Southbound"], + "long_name" => "Orange Line Shuttle", + "short_name" => "OL Shuttle", + "sort_order" => 123, + "text_color" => "bus_text_color", + "type" => 3 + }, + relationships: %{ + "line" => %JsonApi.Reference{type: "line", id: "line-Orange"} + } + }, + [ + %JsonApi.Item{ + type: "line", + id: "line-Orange", + attributes: %{ + "color" => "line_color", + "text_color" => "line_text_color" + } + } + ] + ) + end end end