From 7ffe1eede7649963dbf0af6145fb82e76616a129 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 11:15:23 -0700 Subject: [PATCH 01/16] fix: make env file work with direnv --- env.example | 2 -- envrc.example | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 env.example create mode 100644 envrc.example diff --git a/env.example b/env.example deleted file mode 100644 index bdd3a33b..00000000 --- a/env.example +++ /dev/null @@ -1,2 +0,0 @@ -API_KEY= -API_URL=https://api-dev.mbtace.com/ diff --git a/envrc.example b/envrc.example new file mode 100644 index 00000000..e55f1c63 --- /dev/null +++ b/envrc.example @@ -0,0 +1,2 @@ +export API_KEY= +export API_URL=https://api-dev.mbtace.com/ From a56cb91f74481b984ce0d2c4725f932c58607528 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 11:17:13 -0700 Subject: [PATCH 02/16] feat: pull in stop API client from dotcom --- lib/mbta_v3_api/mbta_v3_api/stops.ex | 20 ++ lib/mbta_v3_api/stops/api.ex | 452 +++++++++++++++++++++++++++ 2 files changed, 472 insertions(+) create mode 100644 lib/mbta_v3_api/mbta_v3_api/stops.ex create mode 100644 lib/mbta_v3_api/stops/api.ex diff --git a/lib/mbta_v3_api/mbta_v3_api/stops.ex b/lib/mbta_v3_api/mbta_v3_api/stops.ex new file mode 100644 index 00000000..05c0f8a9 --- /dev/null +++ b/lib/mbta_v3_api/mbta_v3_api/stops.ex @@ -0,0 +1,20 @@ +defmodule MBTAV3API.Stops do + @moduledoc """ + + Responsible for fetching Stop data from the V3 API. + + """ + import MBTAV3API + + def all(params \\ []) do + get_json("/stops/", params) + end + + def by_gtfs_id(gtfs_id, params \\ [], opts \\ []) do + get_json( + "/stops/#{gtfs_id}", + params, + opts + ) + end +end diff --git a/lib/mbta_v3_api/stops/api.ex b/lib/mbta_v3_api/stops/api.ex new file mode 100644 index 00000000..c781109e --- /dev/null +++ b/lib/mbta_v3_api/stops/api.ex @@ -0,0 +1,452 @@ +defmodule Stops.Api do + @moduledoc """ + Wrapper around the remote stop information service. + """ + require Logger + alias JsonApi.Item + alias Stops.Stop + + @type fare_facility :: + :fare_vending_retailer + | :fare_vending_machine + | :fare_media_assistant + | :fare_media_assistance_facility + | :ticket_window + + @default_params [ + include: "parent_station,facilities,child_stops", + "fields[facility]": "long_name,type,properties,latitude,longitude,id", + "fields[stop]": + "address,name,latitude,longitude,address," <> + "municipality,wheelchair_boarding,location_type," <> + "platform_name,platform_code,description" + ] + + @accessible_facilities ~w(elevator escalator ramp portable_boarding_lift + tty_phone elevated_subplatform fully_elevated_platform + escalator_up escalator_down escalator_both)a + + @fare_facilities ~w( + fare_vending_retailer + fare_vending_machine + fare_media_assistant + fare_media_assistance_facility + ticket_window + )a + + @doc """ + Returns a Stop by its GTFS ID. + + If a stop is found, we return `{:ok, %Stop{}}`. If no stop exists with that + ID, we return `{:ok, nil}`. If there's an error fetching data, we return + that as an `{:error, any}` tuple. + """ + @spec by_gtfs_id(String.t()) :: {:ok, Stop.t() | nil} | {:error, any} + def by_gtfs_id(gtfs_id) do + gtfs_id + |> MBTAV3API.Stops.by_gtfs_id(@default_params) + |> extract_v3_response() + |> parse_v3_response() + end + + def all do + @default_params + |> MBTAV3API.Stops.all() + |> parse_v3_multiple() + end + + @spec by_route({Routes.Route.id_t(), 0 | 1, Keyword.t()}) :: [Stop.t()] + def by_route({route_id, direction_id, opts}) do + @default_params + |> Keyword.put(:route, route_id) + |> Keyword.put(:direction_id, direction_id) + |> Keyword.merge(opts) + |> MBTAV3API.Stops.all() + |> parse_v3_multiple() + end + + @spec by_route_type({0..4, Keyword.t()}) :: [Stop.t()] + def by_route_type({route_type, opts}) do + @default_params + |> Keyword.put(:route_type, route_type) + |> Keyword.merge(opts) + |> MBTAV3API.Stops.all() + |> parse_v3_multiple() + end + + # def by_trip(trip_id) do + # case MBTAV3API.Trips.by_id(trip_id, include: "stops") do + # %JsonApi{ + # data: [ + # %JsonApi.Item{ + # relationships: %{ + # "stops" => stops + # } + # } + # ] + # } -> + # stops + # |> Enum.map(&parse_v3_response/1) + # |> Enum.map(fn {:ok, stop} -> stop end) + + # _ -> + # [] + # end + # end + + @spec parse_v3_multiple(JsonApi.t() | {:error, any}) :: [Stop.t()] | {:error, any} + defp parse_v3_multiple({:error, _} = error) do + error + end + + defp parse_v3_multiple(api) do + api.data + |> Enum.map(&parse_v3_response/1) + |> Enum.map(fn {:ok, stop} -> stop end) + end + + @spec parent_id(Item.t()) :: Stop.id_t() | nil + defp parent_id(%Item{relationships: %{"parent_station" => [%Item{id: parent_id}]}}) do + parent_id + end + + defp parent_id(_) do + nil + end + + defp child_ids(%Item{relationships: %{"child_stops" => children}}) do + Enum.map(children, & &1.id) + end + + defp child_ids(%Item{}) do + [] + end + + @spec is_child?(Item.t()) :: boolean + defp is_child?(%Item{relationships: %{"parent_station" => [%Item{}]}}), do: true + defp is_child?(_), do: false + + @spec v3_name(Item.t()) :: String.t() + defp v3_name(%Item{attributes: %{"name" => name}}), do: name + + @spec extract_v3_response(JsonApi.t()) :: {:ok, Item.t()} | {:error, any} + defp extract_v3_response(%JsonApi{data: [item | _]}) do + {:ok, item} + end + + defp extract_v3_response({:error, _} = error) do + error + end + + @spec parse_v3_response(Item.t() | {:ok, Item.t()} | {:error, any}) :: + {:ok, Stop.t() | nil} + | {:error, any} + defp parse_v3_response({:ok, %Item{} = item}), do: parse_v3_response(item) + defp parse_v3_response({:error, [%JsonApi.Error{code: "not_found"} | _]}), do: {:ok, nil} + defp parse_v3_response({:error, _} = error), do: error + + defp parse_v3_response(%Item{} = item) do + fare_facilities = fare_facilities(item) + + stop = %Stop{ + id: item.id, + parent_id: parent_id(item), + child_ids: child_ids(item), + name: v3_name(item), + address: item.attributes["address"], + municipality: item.attributes["municipality"], + accessibility: merge_accessibility(v3_accessibility(item), item.attributes), + # parking_lots: v3_parking(item), + fare_facilities: fare_facilities, + bike_storage: bike_storage(item), + is_child?: is_child?(item), + station?: is_station?(item), + has_fare_machine?: MapSet.member?(fare_facilities, :fare_vending_machine), + has_charlie_card_vendor?: MapSet.member?(fare_facilities, :fare_media_assistant), + latitude: item.attributes["latitude"], + longitude: item.attributes["longitude"], + type: type(item), + platform_name: platform_name(item), + platform_code: platform_code(item), + description: description(item), + zone: zone_number(item) + } + + {:ok, stop} + end + + @spec is_station?(Item.t()) :: boolean + defp is_station?(%Item{} = item) do + item.attributes["location_type"] == 1 or item.relationships["facilities"] != [] + end + + defp type(%Item{attributes: %{"location_type" => 0}}) do + # A location where passengers board or disembark from a transit vehicle + :stop + end + + defp type(%Item{attributes: %{"location_type" => 1}}) do + # A physical structure or area that contains one or more stops. + :station + end + + defp type(%Item{attributes: %{"location_type" => 2}}) do + # A location where passengers can enter or exit a station from the street. + :entrance + end + + defp type(%Item{attributes: %{"location_type" => 3}}) do + # A generic nodes within a parent station which is neither a service stop + # nor a station entrance. These node stops will be used for locations such + # as (but not limited to) the ends of staircases, elevators, escalators, + # and fare gates. + :generic_node + end + + defp platform_name(%Item{attributes: %{"platform_name" => name}}), do: name + + defp platform_code(%Item{attributes: %{"platform_code" => code}}), do: code + + defp description(%Item{attributes: %{"description" => description}}), do: description + + @spec v3_accessibility(Item.t()) :: [String.t()] + defp v3_accessibility(item) do + {escalators, others} = + Enum.split_with(item.relationships["facilities"], &(&1.attributes["type"] == "ESCALATOR")) + + escalators = parse_escalator_direction(escalators) + other = MapSet.new(others, &facility_atom_from_string(&1.attributes["type"])) + matching_others = MapSet.intersection(other, MapSet.new(@accessible_facilities)) + Enum.map(escalators ++ MapSet.to_list(matching_others), &Atom.to_string/1) + end + + @type bike_storage_types :: + :bike_storage_rack + | :bike_storage_rack_covered + | :bike_storage_cage + + defp bike_storage(item) do + item + |> filter_facilities(MapSet.new([:bike_storage])) + |> Enum.map(&bike_storage_type/1) + |> MapSet.new() + end + + @spec bike_storage_type(Item.t()) :: bike_storage_types + def bike_storage_type(%Item{attributes: %{"properties" => properties}}) do + properties + |> Map.new(fn %{"name" => key, "value" => value} -> {key, value} end) + |> do_bike_storage_type() + end + + def do_bike_storage_type(%{"enclosed" => 1, "secured" => 1}) do + :bike_storage_cage + end + + def do_bike_storage_type(%{"enclosed" => 2, "secured" => 2}) do + :bike_storage_rack + end + + def do_bike_storage_type(%{"enclosed" => 1, "secured" => 2}) do + :bike_storage_rack_covered + end + + def do_bike_storage_type(_unknown) do + :bike_storage_rack + end + + @spec parse_escalator_direction([Item.t()]) :: [ + :escalator | :escalator_up | :escalator_down | :escalator_both + ] + defp parse_escalator_direction([]), do: [] + + defp parse_escalator_direction(escalators) do + directions = + escalators + |> Enum.map(& &1.attributes["properties"]) + |> List.flatten() + |> Enum.filter(&(&1["name"] == "direction")) + |> Enum.map(& &1["value"]) + + down? = "down" in directions + up? = "up" in directions + [do_escalator(down?, up?)] + end + + defp do_escalator(down?, up?) + defp do_escalator(true, false), do: :escalator_down + defp do_escalator(false, true), do: :escalator_up + defp do_escalator(true, true), do: :escalator_both + defp do_escalator(false, false), do: :escalator + + # @spec v3_parking(Item.t()) :: [Stop.ParkingLot.t()] + # defp v3_parking(item) do + # item.relationships["facilities"] + # |> Enum.filter(&(&1.attributes["type"] == "PARKING_AREA")) + # |> Enum.map(&parse_parking_area/1) + # end + + # @spec parse_parking_area(Item.t()) :: Stop.ParkingLot.t() + # defp parse_parking_area(parking_area) do + # parking_area.attributes["properties"] + # |> Enum.reduce(%{}, &property_acc/2) + # |> Map.put("name", parking_area.attributes["long_name"]) + # |> Map.put("latitude", parking_area.attributes["latitude"]) + # |> Map.put("longitude", parking_area.attributes["longitude"]) + # |> Map.put("id", parking_area.id) + # |> to_parking_lot + # end + + # @spec to_parking_lot(map) :: Stop.ParkingLot.t() + # defp to_parking_lot(props) do + # %Stop.ParkingLot{ + # id: Map.get(props, "id"), + # name: Map.get(props, "name"), + # address: Map.get(props, "address"), + # capacity: Stops.Helpers.struct_or_nil(Stop.ParkingLot.Capacity.parse(props)), + # payment: Stops.Helpers.struct_or_nil(Stop.ParkingLot.Payment.parse(props)), + # utilization: Stops.Helpers.struct_or_nil(Stop.ParkingLot.Utilization.parse(props)), + # note: Map.get(props, "note"), + # manager: Stops.Helpers.struct_or_nil(Stop.ParkingLot.Manager.parse(props)), + # latitude: Map.get(props, "latitude"), + # longitude: Map.get(props, "longitude") + # } + # end + + # defp property_acc(property, acc) do + # case property["name"] do + # "payment-form-accepted" -> + # payment = pretty_payment(property["value"]) + # Map.update(acc, "payment-form-accepted", [payment], &[payment | &1]) + + # _ -> + # Map.put(acc, property["name"], property["value"]) + # end + # end + + @spec pretty_payment(String.t()) :: String.t() + def pretty_payment("cash"), do: "Cash" + def pretty_payment("check"), do: "Check" + def pretty_payment("coin"), do: "Coin" + def pretty_payment("credit-debit-card"), do: "Credit/Debit Card" + def pretty_payment("e-zpass"), do: "EZ Pass" + def pretty_payment("invoice"), do: "Invoice" + def pretty_payment("mobile-app"), do: "Mobile App" + def pretty_payment("smartcard"), do: "Smart Card" + def pretty_payment("tapcard"), do: "Tap Card" + def pretty_payment(_), do: "" + + @spec merge_accessibility([String.t()], %{String.t() => any}) :: [String.t()] + defp merge_accessibility(accessibility, stop_attributes) + + defp merge_accessibility(accessibility, %{"wheelchair_boarding" => 0}) do + # if GTFS says we don't know what the accessibility situation is, then + # add "unknown" as the first attribute + ["unknown" | accessibility] + end + + defp merge_accessibility(accessibility, %{"wheelchair_boarding" => 1}) do + # make sure "accessibile" is the first list option + ["accessible" | accessibility] + end + + defp merge_accessibility(accessibility, _) do + accessibility + end + + @type gtfs_facility_type :: + :elevator + | :escalator + | :ramp + | :elevated_subplatform + | :fully_elevated_platform + | :portable_boarding_lift + | :bridge_plate + | :parking_area + | :pick_drop + | :taxi_stand + | :bike_storage + | :tty_phone + | :electric_car_chargers + | :fare_vending_retailer + | :other + + @spec facility_atom_from_string(String.t()) :: gtfs_facility_type + defp facility_atom_from_string("ELEVATOR"), do: :elevator + defp facility_atom_from_string("ESCALATOR"), do: :escalator + defp facility_atom_from_string("ESCALATOR_UP"), do: :escalator_up + defp facility_atom_from_string("ESCALATOR_DOWN"), do: :escalator_down + defp facility_atom_from_string("ESCALATOR_BOTH"), do: :escalator_both + defp facility_atom_from_string("RAMP"), do: :ramp + defp facility_atom_from_string("ELEVATED_SUBPLATFORM"), do: :elevated_subplatform + defp facility_atom_from_string("FULLY_ELEVATED_PLATFORM"), do: :fully_elevated_platform + defp facility_atom_from_string("PORTABLE_BOARDING_LIFT"), do: :portable_boarding_lift + defp facility_atom_from_string("BRIDGE_PLATE"), do: :bridge_plate + defp facility_atom_from_string("PARKING_AREA"), do: :parking_area + defp facility_atom_from_string("PICK_DROP"), do: :pick_drop + defp facility_atom_from_string("TAXI_STAND"), do: :taxi_stand + defp facility_atom_from_string("BIKE_STORAGE"), do: :bike_storage + defp facility_atom_from_string("TTY_PHONE"), do: :tty_phone + defp facility_atom_from_string("ELECTRIC_CAR_CHARGERS"), do: :electric_car_chargers + defp facility_atom_from_string("FARE_VENDING_RETAILER"), do: :fare_vending_retailer + defp facility_atom_from_string("FARE_VENDING_MACHINE"), do: :fare_vending_machine + defp facility_atom_from_string("FARE_MEDIA_ASSISTANT"), do: :fare_media_assistant + defp facility_atom_from_string("TICKET_WINDOW"), do: :ticket_window + defp facility_atom_from_string("OTHER"), do: :other + + defp facility_atom_from_string("FARE_MEDIA_ASSISTANCE_FACILITY"), + do: :fare_media_assistance_facility + + defp facility_atom_from_string(other) do + _ = Logger.warning("module=#{__MODULE__} unknown facility type: #{other}") + :other + end + + @spec fare_facilities(Item.t()) :: MapSet.t(fare_facility) + defp fare_facilities(%Item{relationships: %{"facilities" => facilities}}) do + Enum.reduce(facilities, MapSet.new(), &add_facility_type/2) + end + + @spec add_facility_type(Item.t(), MapSet.t(fare_facility)) :: + MapSet.t(fare_facility) + defp add_facility_type(%Item{attributes: %{"type" => type_str}}, acc) do + type = facility_atom_from_string(type_str) + + if @fare_facilities |> MapSet.new() |> MapSet.member?(type) do + MapSet.put(acc, type) + else + acc + end + end + + defp add_facility_type(%Item{}, acc) do + acc + end + + @spec filter_facilities(Item.t(), MapSet.t()) :: [Item.t()] + defp filter_facilities(%Item{relationships: %{"facilities" => facilities}}, facility_types) do + Enum.filter(facilities, &filter_facility_types(&1, facility_types)) + end + + def filter_facility_types(%Item{attributes: %{"type" => type_str}}, facility_types) do + MapSet.member?(facility_types, facility_atom_from_string(type_str)) + end + + @spec zone_number(Item.t()) :: String.t() | nil + defp zone_number(%Item{relationships: %{"zone" => zone}}) do + case zone do + [%Item{:id => id}] -> get_zone_number(id) + _ -> nil + end + end + + @spec get_zone_number(String.t() | nil) :: String.t() | nil + defp get_zone_number(nil), do: nil + + defp get_zone_number(zone) do + case zone do + "CR-zone-" <> zone_number -> zone_number + _ -> nil + end + end +end From 5162e1062bcab7d268e8a43660c4c433c322e744 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 11:18:32 -0700 Subject: [PATCH 03/16] feat: set up GraphQL API --- lib/mobile_app_backend_web/endpoint.ex | 2 +- lib/mobile_app_backend_web/resolvers/route.ex | 7 +++++++ lib/mobile_app_backend_web/resolvers/stop.ex | 8 ++++++++ lib/mobile_app_backend_web/router.ex | 10 ++++++++++ lib/mobile_app_backend_web/schema.ex | 15 +++++++++++++++ lib/mobile_app_backend_web/schema/route_types.ex | 16 ++++++++++++++++ lib/mobile_app_backend_web/schema/stop_types.ex | 16 ++++++++++++++++ mix.exs | 4 +++- mix.lock | 3 +++ 9 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 lib/mobile_app_backend_web/resolvers/route.ex create mode 100644 lib/mobile_app_backend_web/resolvers/stop.ex create mode 100644 lib/mobile_app_backend_web/schema.ex create mode 100644 lib/mobile_app_backend_web/schema/route_types.ex create mode 100644 lib/mobile_app_backend_web/schema/stop_types.ex diff --git a/lib/mobile_app_backend_web/endpoint.ex b/lib/mobile_app_backend_web/endpoint.ex index 929ef4cc..c1c9e5d8 100644 --- a/lib/mobile_app_backend_web/endpoint.ex +++ b/lib/mobile_app_backend_web/endpoint.ex @@ -41,7 +41,7 @@ defmodule MobileAppBackendWeb.Endpoint do plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] plug Plug.Parsers, - parsers: [:urlencoded, :multipart, :json], + parsers: [:urlencoded, :multipart, :json, Absinthe.Plug.Parser], pass: ["*/*"], json_decoder: Phoenix.json_library() diff --git a/lib/mobile_app_backend_web/resolvers/route.ex b/lib/mobile_app_backend_web/resolvers/route.ex new file mode 100644 index 00000000..d3e01db9 --- /dev/null +++ b/lib/mobile_app_backend_web/resolvers/route.ex @@ -0,0 +1,7 @@ +defmodule MobileAppBackendWeb.Resolvers.Route do + def by_stop(%Stops.Stop{id: stop_id}, _args, _resolution) do + {:ok, + Routes.Repo.by_stop_with_route_pattern(stop_id) + |> Enum.map(fn {route, route_patterns} -> Map.put(route, :route_patterns, route_patterns) end)} + end +end diff --git a/lib/mobile_app_backend_web/resolvers/stop.ex b/lib/mobile_app_backend_web/resolvers/stop.ex new file mode 100644 index 00000000..1c9d1c05 --- /dev/null +++ b/lib/mobile_app_backend_web/resolvers/stop.ex @@ -0,0 +1,8 @@ +defmodule MobileAppBackendWeb.Resolvers.Stop do + def find_stop(_parent, %{id: id}, _resolution) do + case Stops.Api.by_gtfs_id(id) do + {:ok, stop} when not is_nil(stop) -> {:ok, stop} + x -> {:error, x} + end + end +end diff --git a/lib/mobile_app_backend_web/router.ex b/lib/mobile_app_backend_web/router.ex index a2d4c726..fdf291e7 100644 --- a/lib/mobile_app_backend_web/router.ex +++ b/lib/mobile_app_backend_web/router.ex @@ -31,6 +31,16 @@ defmodule MobileAppBackendWeb.Router do get("/route/by-stop/:stop_id", RouteController, :by_stop) end + scope "/graphql" do + pipe_through :api + + forward "/graphiql", Absinthe.Plug.GraphiQL, + schema: MobileAppBackendWeb.Schema, + interface: :playground + + forward "/", Absinthe.Plug, schema: MobileAppBackendWeb.Schema + end + # Enable LiveDashboard and Swoosh mailbox preview in development if Application.compile_env(:mobile_app_backend, :dev_routes) do # If you want to use the LiveDashboard in production, you should put diff --git a/lib/mobile_app_backend_web/schema.ex b/lib/mobile_app_backend_web/schema.ex new file mode 100644 index 00000000..cc22cb4e --- /dev/null +++ b/lib/mobile_app_backend_web/schema.ex @@ -0,0 +1,15 @@ +defmodule MobileAppBackendWeb.Schema do + use Absinthe.Schema + import_types(MobileAppBackendWeb.Schema.RouteTypes) + import_types(MobileAppBackendWeb.Schema.StopTypes) + + alias MobileAppBackendWeb.Resolvers + + query do + @desc "Get a stop by ID" + field :stop, :stop do + arg(:id, non_null(:id)) + resolve(&Resolvers.Stop.find_stop/3) + end + end +end diff --git a/lib/mobile_app_backend_web/schema/route_types.ex b/lib/mobile_app_backend_web/schema/route_types.ex new file mode 100644 index 00000000..9c9f4fbf --- /dev/null +++ b/lib/mobile_app_backend_web/schema/route_types.ex @@ -0,0 +1,16 @@ +defmodule MobileAppBackendWeb.Schema.RouteTypes do + use Absinthe.Schema.Notation + + object :route do + field(:id, non_null(:id)) + field(:name, non_null(:string)) + field(:long_name, non_null(:string)) + + field(:route_patterns, list_of(:route_pattern)) + end + + object :route_pattern do + field(:id, non_null(:id)) + field(:name, non_null(:string)) + end +end diff --git a/lib/mobile_app_backend_web/schema/stop_types.ex b/lib/mobile_app_backend_web/schema/stop_types.ex new file mode 100644 index 00000000..187f02bd --- /dev/null +++ b/lib/mobile_app_backend_web/schema/stop_types.ex @@ -0,0 +1,16 @@ +defmodule MobileAppBackendWeb.Schema.StopTypes do + use Absinthe.Schema.Notation + + alias MobileAppBackendWeb.Resolvers + + object :stop do + field(:id, non_null(:id)) + field(:name, non_null(:string)) + field(:latitude, non_null(:float)) + field(:longitude, non_null(:float)) + + field :routes, list_of(:route) do + resolve(&Resolvers.Route.by_stop/3) + end + end +end diff --git a/mix.exs b/mix.exs index 59fa538a..23c0f363 100644 --- a/mix.exs +++ b/mix.exs @@ -65,7 +65,9 @@ defmodule MobileAppBackend.MixProject do {:bypass, "~> 2.1", only: :test}, {:httpoison, "~> 1.5"}, {:sentry, "~> 7.0"}, - {:con_cache, "~> 0.12.0"} + {:con_cache, "~> 0.12.0"}, + {:absinthe, "~> 1.7"}, + {:absinthe_plug, "~> 1.5"} ] end diff --git a/mix.lock b/mix.lock index 63f4523f..a2d9660a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,6 @@ %{ + "absinthe": {:hex, :absinthe, "1.7.6", "0b897365f98d068cfcb4533c0200a8e58825a4aeeae6ec33633ebed6de11773b", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7626951ca5eec627da960615b51009f3a774765406ff02722b1d818f17e5778"}, + "absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, @@ -31,6 +33,7 @@ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"}, From a0ab59d7730f6f73cc597b4ec79ec4163436cb88 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 11:36:27 -0700 Subject: [PATCH 04/16] chore: generate Credo config --- .credo.exs | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 .credo.exs diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 00000000..539d213b --- /dev/null +++ b/.credo.exs @@ -0,0 +1,216 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + {Credo.Check.Design.TagFIXME, []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.WrongTestFileExtension, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.OnePipePerLine, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} From dd8d17c17217e779be10b2e84175d3284dcb9f7f Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 11:36:46 -0700 Subject: [PATCH 05/16] fix: appease Credo --- .credo.exs | 4 ++-- lib/mbta_v3_api/routes/route.ex | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.credo.exs b/.credo.exs index 539d213b..df1ab970 100644 --- a/.credo.exs +++ b/.credo.exs @@ -89,7 +89,7 @@ # If you don't want TODO comments to cause `mix credo` to fail, just # set this value to 0 (zero). # - {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagTODO, [exit_status: 0]}, # ## Readability Checks @@ -99,7 +99,7 @@ {Credo.Check.Readability.LargeNumbers, []}, {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, {Credo.Check.Readability.ModuleAttributeNames, []}, - {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleDoc, false}, {Credo.Check.Readability.ModuleNames, []}, {Credo.Check.Readability.ParenthesesInCondition, []}, {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, diff --git a/lib/mbta_v3_api/routes/route.ex b/lib/mbta_v3_api/routes/route.ex index e535dd77..3f1d639c 100644 --- a/lib/mbta_v3_api/routes/route.ex +++ b/lib/mbta_v3_api/routes/route.ex @@ -133,8 +133,7 @@ defmodule Routes.Route do |> Atom.to_string() |> String.replace("_", " ") |> String.split(" ") - |> Enum.map(&String.capitalize/1) - |> Enum.join(" ") + |> Enum.map_join(" ", &String.capitalize/1) def type_name(unquote(type_atom)), do: unquote(type_string) end @@ -168,8 +167,7 @@ defmodule Routes.Route do defp bus_route_list(routes) when is_list(routes) do routes |> Enum.filter(&(icon_atom(&1) == :bus)) - |> Enum.map(& &1.name) - |> Enum.join(", ") + |> Enum.map_join(", ", & &1.name) end @spec direction_name(t, 0 | 1) :: String.t() From 22b4e66adcfd75cf2466caf3b24a491e4a4b257d Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 11:44:02 -0700 Subject: [PATCH 06/16] fix: appease Dialyzer --- lib/mbta_v3_api/schedules/trip.ex | 8 ++++---- lib/mbta_v3_api/stops/stop.ex | 32 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/lib/mbta_v3_api/schedules/trip.ex b/lib/mbta_v3_api/schedules/trip.ex index c1ff73fc..84f6444f 100644 --- a/lib/mbta_v3_api/schedules/trip.ex +++ b/lib/mbta_v3_api/schedules/trip.ex @@ -5,7 +5,7 @@ defmodule Schedules.Trip do alias RoutePatterns.RoutePattern alias Routes.Shape - alias Vehicles.Vehicle + # alias Vehicles.Vehicle @derive Jason.Encoder @@ -16,7 +16,7 @@ defmodule Schedules.Trip do :direction_id, :shape_id, :route_pattern_id, - :occupancy, + # :occupancy, bikes_allowed?: false ] @@ -29,7 +29,7 @@ defmodule Schedules.Trip do direction_id: 0 | 1, shape_id: Shape.id_t() | nil, route_pattern_id: RoutePattern.id_t() | nil, - bikes_allowed?: boolean, - occupancy: Vehicle.crowding() | nil + bikes_allowed?: boolean + # occupancy: Vehicle.crowding() | nil } end diff --git a/lib/mbta_v3_api/stops/stop.ex b/lib/mbta_v3_api/stops/stop.ex index 554b5714..b698e1a3 100644 --- a/lib/mbta_v3_api/stops/stop.ex +++ b/lib/mbta_v3_api/stops/stop.ex @@ -2,8 +2,7 @@ defmodule Stops.Stop do @moduledoc """ Domain model for a Stop. """ - # alias Stops.{Api, Stop} - alias Stops.Stop + alias Stops.{Api, Stop} @derive {Jason.Encoder, except: [:bike_storage, :fare_facilities]} @@ -44,9 +43,8 @@ defmodule Stops.Stop do accessibility: [String.t()], address: String.t() | nil, municipality: String.t() | nil, - parking_lots: [Stop.ParkingLot.t()], - # TODO: Restore fare_facilities once we've copied in Stops.Api - # fare_facilities: MapSet.t(Api.fare_facility()), + # parking_lots: [Stop.ParkingLot.t()], + fare_facilities: MapSet.t(Api.fare_facility()), bike_storage: [Api.bike_storage_types()], latitude: float, longitude: float, @@ -303,17 +301,17 @@ end # end # end -# defmodule Stops.Stop.ClosedStopInfo do -# @moduledoc """ -# Information about stations not in API data. -# """ -# @derive Jason.Encoder +defmodule Stops.Stop.ClosedStopInfo do + @moduledoc """ + Information about stations not in API data. + """ + @derive Jason.Encoder -# defstruct reason: "", -# info_link: "" + defstruct reason: "", + info_link: "" -# @type t :: %Stops.Stop.ClosedStopInfo{ -# reason: String.t(), -# info_link: String.t() -# } -# end + @type t :: %Stops.Stop.ClosedStopInfo{ + reason: String.t(), + info_link: String.t() + } +end From 98fede84966b146e057478d97bc886caf2b6cd1b Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 14:29:02 -0700 Subject: [PATCH 07/16] feat: bring in Stops.Repo from dotcom --- lib/mbta_v3_api/mbta_v3_api/supervisor.ex | 1 + lib/mbta_v3_api/stops/repo.ex | 207 +++++++++++++++++++ lib/mobile_app_backend_web/resolvers/stop.ex | 5 +- 3 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 lib/mbta_v3_api/stops/repo.ex diff --git a/lib/mbta_v3_api/mbta_v3_api/supervisor.ex b/lib/mbta_v3_api/mbta_v3_api/supervisor.ex index 29619433..0ba48ab0 100644 --- a/lib/mbta_v3_api/mbta_v3_api/supervisor.ex +++ b/lib/mbta_v3_api/mbta_v3_api/supervisor.ex @@ -12,6 +12,7 @@ defmodule MBTAV3API.Supervisor do children = [ RepoCache.Supervisor, Routes.Supervisor, + Stops.Repo, MBTAV3API.Cache ] diff --git a/lib/mbta_v3_api/stops/repo.ex b/lib/mbta_v3_api/stops/repo.ex new file mode 100644 index 00000000..09d929a9 --- /dev/null +++ b/lib/mbta_v3_api/stops/repo.ex @@ -0,0 +1,207 @@ +defmodule Stops.Repo do + @moduledoc """ + Matches the Ecto API, but fetches Stops from the Stop Info API instead. + """ + use RepoCache, ttl: :timer.hours(1) + alias Stops.{Api, Stop} + alias Routes.Route + + @type stop_feature :: + Route.route_type() + | Route.subway_lines_type() + | :access + | :parking_lot + | :"Green-B" + | :"Green-C" + | :"Green-D" + | :"Green-E" + @type stops_response :: [Stop.t()] | {:error, any} + @type stop_by_route :: (Route.id_t(), 0 | 1, Keyword.t() -> stops_response) + + # for {old_id, gtfs_id} <- + # "priv/stop_id_to_gtfs.csv" + # |> File.stream!() + # |> CSV.decode!(headers: true) + # |> Enum.map(&{&1 |> Map.get("atisId") |> String.split(","), Map.get(&1, "stopID")}) + # |> Enum.flat_map(fn {ids, gtfs_id} -> Enum.map(ids, &{&1, gtfs_id}) end) do + # def old_id_to_gtfs_id(unquote(old_id)) do + # unquote(gtfs_id) + # end + # end + + # def old_id_to_gtfs_id(_) do + # nil + # end + + @spec get(Stop.id_t()) :: Stop.t() | nil + def get(id) when is_binary(id) do + case stop(id) do + {:ok, s} -> s + _ -> nil + end + end + + @spec get!(Stop.id_t()) :: Stop.t() + def get!(id) do + case stop(id) do + {:ok, %Stop{} = s} -> s + _ -> raise Stops.NotFoundError, message: "Could not find stop #{id}" + end + end + + @spec has_parent?(Stop.t() | Stop.id_t() | nil) :: boolean + def has_parent?(nil), do: false + def has_parent?(%Stop{parent_id: nil}), do: false + def has_parent?(%Stop{parent_id: _}), do: true + + @spec get_parent(Stop.t() | Stop.id_t() | nil) :: Stop.t() | nil + def get_parent(nil), do: nil + + def get_parent(%Stop{parent_id: nil} = stop) do + stop + end + + def get_parent(%Stop{parent_id: parent_id}) when is_binary(parent_id) do + case stop(parent_id) do + {:ok, %Stop{} = stop} -> stop + _ -> nil + end + end + + def get_parent(id) when is_binary(id) do + id + |> get() + |> get_parent() + end + + @spec stop(Stop.id_t()) :: {:ok, Stop.t() | nil} | {:error, any} + defp stop(id) do + # the `cache` macro uses the function name as part of the key, and :stop + # makes more sense for this than :get, since other functions in this + # module will be working with those cache rows as well. + cache(id, &Api.by_gtfs_id/1) + end + + @spec by_route(Route.id_t(), 0 | 1, Keyword.t()) :: stops_response + def by_route(route_id, direction_id, opts \\ []) do + cache({route_id, direction_id, opts}, fn args -> + with stops when is_list(stops) <- Api.by_route(args) do + for stop <- stops do + # Put the stop in the cache under {:stop, id} key as well so it will + # also be cached for Stops.Repo.get/1 calls + ConCache.put(__MODULE__, {:stop, stop.id}, {:ok, stop}) + stop + end + end + end) + end + + @spec by_routes([Route.id_t()], 0 | 1, Keyword.t()) :: stops_response + def by_routes(route_ids, direction_id, opts \\ []) when is_list(route_ids) do + # once the V3 API supports multiple route_ids in this field, we can do it + # as a single lookup -ps + route_ids + |> Task.async_stream(&by_route(&1, direction_id, opts)) + |> Enum.flat_map(fn + {:ok, stops} -> stops + _ -> [] + end) + |> Enum.uniq() + end + + @spec by_route_type(Route.route_type(), Keyword.t()) :: stops_response + def by_route_type(route_type, opts \\ []) do + cache( + {route_type, opts}, + fn stop -> + stop + |> Stops.Api.by_route_type() + |> Enum.map(&get_parent/1) + |> Enum.uniq_by(& &1.id) + end + ) + end + + # @spec by_trip(Trip.id_t()) :: [Stop.t()] + # def by_trip(trip_id) do + # cache(trip_id, &Api.by_trip/1) + # end + + def stop_exists_on_route?(stop_id, route, direction_id) do + route + |> by_route(direction_id) + |> Enum.any?(&(&1.id == stop_id)) + end + + @doc """ + Returns a list of the features associated with the given stop + """ + @spec stop_features(Stop.t(), Keyword.t()) :: [stop_feature] + def stop_features(%Stop{} = stop, opts \\ []) do + excluded = Keyword.get(opts, :exclude, []) + + [ + route_features(stop.id, opts), + parking_features(stop.parking_lots), + accessibility_features(stop.accessibility) + ] + |> Enum.concat() + |> Enum.reject(&(&1 in excluded)) + |> Enum.sort_by(&sort_feature_icons/1) + end + + defp parking_features([]), do: [] + defp parking_features(_parking_lots), do: [:parking_lot] + + @spec route_features(String.t(), Keyword.t()) :: [stop_feature] + defp route_features(stop_id, opts) do + icon_fn = + if Keyword.get(opts, :expand_branches?) do + &branch_feature/1 + else + &Route.icon_atom/1 + end + + opts + |> Keyword.get(:connections) + |> get_stop_connections(stop_id) + |> Enum.map(icon_fn) + |> Enum.uniq() + end + + @spec get_stop_connections([Route.t()] | {:error, :not_fetched} | nil, Stop.id_t()) :: + [Route.t()] + defp get_stop_connections(connections, _stop_id) when is_list(connections) do + connections + end + + defp get_stop_connections(_, stop_id) do + Routes.Repo.by_stop(stop_id) + end + + def branch_feature(%Route{id: "Green-B"}), do: :"Green-B" + def branch_feature(%Route{id: "Green-C"}), do: :"Green-C" + def branch_feature(%Route{id: "Green-D"}), do: :"Green-D" + def branch_feature(%Route{id: "Green-E"}), do: :"Green-E" + def branch_feature(route), do: Route.icon_atom(route) + + @spec accessibility_features([String.t()]) :: [:access] + defp accessibility_features(["accessible" | _]), do: [:access] + defp accessibility_features(_), do: [] + + @spec sort_feature_icons(atom) :: integer + defp sort_feature_icons(:"Green-B"), do: 0 + defp sort_feature_icons(:"Green-C"), do: 1 + defp sort_feature_icons(:"Green-D"), do: 2 + defp sort_feature_icons(:"Green-E"), do: 3 + defp sort_feature_icons(:bus), do: 5 + defp sort_feature_icons(:commuter_rail), do: 6 + defp sort_feature_icons(:access), do: 7 + defp sort_feature_icons(:parking_lot), do: 8 + defp sort_feature_icons(_), do: 4 +end + +defmodule Stops.NotFoundError do + @moduledoc "Raised when we don't find a stop with the given GTFS ID" + defexception [:message] +end diff --git a/lib/mobile_app_backend_web/resolvers/stop.ex b/lib/mobile_app_backend_web/resolvers/stop.ex index 1c9d1c05..e8aafa34 100644 --- a/lib/mobile_app_backend_web/resolvers/stop.ex +++ b/lib/mobile_app_backend_web/resolvers/stop.ex @@ -1,8 +1,5 @@ defmodule MobileAppBackendWeb.Resolvers.Stop do def find_stop(_parent, %{id: id}, _resolution) do - case Stops.Api.by_gtfs_id(id) do - {:ok, stop} when not is_nil(stop) -> {:ok, stop} - x -> {:error, x} - end + {:ok, Stops.Repo.get!(id)} end end From 5a75e6e648d47fe36e4d86ca9352cac489f97af5 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 14:31:41 -0700 Subject: [PATCH 08/16] appease Credo again --- lib/mbta_v3_api/stops/repo.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mbta_v3_api/stops/repo.ex b/lib/mbta_v3_api/stops/repo.ex index 09d929a9..f8b009c5 100644 --- a/lib/mbta_v3_api/stops/repo.ex +++ b/lib/mbta_v3_api/stops/repo.ex @@ -3,8 +3,8 @@ defmodule Stops.Repo do Matches the Ecto API, but fetches Stops from the Stop Info API instead. """ use RepoCache, ttl: :timer.hours(1) - alias Stops.{Api, Stop} alias Routes.Route + alias Stops.{Api, Stop} @type stop_feature :: Route.route_type() From aa3dbd2b5fdb8a726dcebb1905c350e0719f3881 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 15:47:54 -0700 Subject: [PATCH 09/16] test: bring in dotcom/stops tests --- mix.exs | 3 +- mix.lock | 3 + test/mbta_v3_api/stops/api_test.exs | 172 ++++++++++++++++++++++++ test/mbta_v3_api/stops/repo_test.exs | 187 +++++++++++++++++++++++++++ test/test_helper.exs | 2 + 5 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 test/mbta_v3_api/stops/api_test.exs create mode 100644 test/mbta_v3_api/stops/repo_test.exs diff --git a/mix.exs b/mix.exs index 23c0f363..c87e2bf6 100644 --- a/mix.exs +++ b/mix.exs @@ -67,7 +67,8 @@ defmodule MobileAppBackend.MixProject do {:sentry, "~> 7.0"}, {:con_cache, "~> 0.12.0"}, {:absinthe, "~> 1.7"}, - {:absinthe_plug, "~> 1.5"} + {:absinthe_plug, "~> 1.5"}, + {:timex, "~> 3.7"} ] end diff --git a/mix.lock b/mix.lock index a2d9660a..368f87c6 100644 --- a/mix.lock +++ b/mix.lock @@ -5,6 +5,7 @@ "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "con_cache": {:hex, :con_cache, "0.12.1", "7553dcd51ee86fd52bd9ea9aa4b33e71bebf0b5fc5ab60e63d2e0bcaa260f937", [:mix], [{:exactor, "~> 2.2.0", [hex: :exactor, repo: "hexpm", optional: false]}], "hexpm", "e72ea94da0ad5d651fe4fa5e6c9b85e16b3583f0690af7fc4de191eac370bcae"}, "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, @@ -55,6 +56,8 @@ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, + "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, diff --git a/test/mbta_v3_api/stops/api_test.exs b/test/mbta_v3_api/stops/api_test.exs new file mode 100644 index 00000000..140f6b58 --- /dev/null +++ b/test/mbta_v3_api/stops/api_test.exs @@ -0,0 +1,172 @@ +defmodule Stops.ApiTest do + use ExUnit.Case + import Stops.Api + alias Stops.Stop + + describe "by_gtfs_id/1" do + test "uses the gtfs ID to find a stop" do + {:ok, stop} = by_gtfs_id("place-NHRML-0127") + + assert stop.id == "place-NHRML-0127" + assert stop.name == "Anderson/Woburn" + assert stop.station? + assert stop.accessibility != [] + assert stop.parking_lots == [] + + # for parking_lot <- stop.parking_lots do + # assert %Stop.ParkingLot{} = parking_lot + # assert parking_lot.capacity.total != nil + # manager = parking_lot.manager + # assert manager.name in ["Massport", "LAZ Parking"] + # end + end + + test "parses parent_id and child_ids" do + assert {:ok, %Stop{} = parent} = by_gtfs_id("place-sstat") + assert parent.parent_id == nil + assert parent.is_child? == false + assert parent.name == "South Station" + assert parent.type == :station + assert parent.platform_name == nil + assert parent.platform_code == nil + assert parent.description == nil + + for child_id <- [ + "70079", + "NEC-2287", + "NEC-2287-10", + "door-sstat-main", + "node-388-lobby", + "node-400-middle", + "node-400-rl", + "node-6476-ground", + "node-sstat-400sl-sl", + "node-sstat-finctrstair-lobby", + "node-sstat-north-farepaid", + "node-sstat-north-fareunpaid", + "node-sstat-outmainstair-lobby" + ] do + assert Enum.member?(parent.child_ids, child_id), + "#{child_id} not found in parent.child_ids" + end + + assert {:ok, %Stop{} = child} = by_gtfs_id("NEC-2287-01") + assert child.name == "South Station" + assert child.type == :stop + assert child.platform_name == "Commuter Rail - Track 1" + assert child.platform_code == "1" + assert child.description == "South Station - Commuter Rail - Track 1" + assert child.parent_id == "place-sstat" + assert child.child_ids == [] + assert child.is_child? == true + end + + test "parses fare facilities" do + assert {:ok, north_station} = by_gtfs_id("place-north") + + assert north_station.fare_facilities == + MapSet.new([:fare_media_assistant, :fare_vending_machine, :ticket_window]) + + assert north_station.has_fare_machine? + assert north_station.has_charlie_card_vendor? + + assert {:ok, bu_east} = by_gtfs_id("place-buest") + + assert bu_east.fare_facilities == MapSet.new([]) + + refute bu_east.has_fare_machine? + refute bu_east.has_charlie_card_vendor? + end + + # test "can use the GTFS accessibility data" do + # {:ok, stop} = by_gtfs_id("Yawkey") + # assert ["accessible" | _] = stop.accessibility + # end + + test "returns nil if stop is not found" do + assert by_gtfs_id("-1") == {:ok, nil} + end + + test "returns a stop even if the stop is not a station" do + {:ok, stop} = by_gtfs_id("411") + + assert stop.id == "411" + assert stop.name == "Warren St @ Brunswick St" + assert stop.latitude != nil + assert stop.longitude != nil + refute stop.station? + end + + test "returns an error if the API returns an error" do + bypass = Bypass.open() + v3_url = Application.get_env(:mobile_app_backend, :base_url) + + on_exit(fn -> + Application.put_env(:mobile_app_backend, :base_url, v3_url) + end) + + Application.put_env(:mobile_app_backend, :base_url, "http://localhost:#{bypass.port}") + + Bypass.expect(bypass, fn conn -> + Plug.Conn.resp(conn, 200, "") + end) + + assert {:error, _} = by_gtfs_id("error stop") + end + end + + test "all/0 returns error if API returns error" do + bypass = Bypass.open() + v3_url = Application.get_env(:mobile_app_backend, :base_url) + + on_exit(fn -> + Application.put_env(:mobile_app_backend, :base_url, v3_url) + end) + + Application.put_env(:mobile_app_backend, :base_url, "http://localhost:#{bypass.port}") + + Bypass.expect(bypass, fn conn -> + Plug.Conn.resp(conn, 200, "") + end) + + assert {:error, _} = all() + end + + test "by_route returns an error tuple if the V3 API returns an error" do + bypass = Bypass.open() + v3_url = Application.get_env(:mobile_app_backend, :base_url) + + on_exit(fn -> + Application.put_env(:mobile_app_backend, :base_url, v3_url) + end) + + Application.put_env(:mobile_app_backend, :base_url, "http://localhost:#{bypass.port}") + + Bypass.expect(bypass, fn conn -> + Plug.Conn.resp(conn, 200, "") + end) + + assert {:error, _} = by_route({"1", 0, []}) + end + + test "pretty payment falls back to empty string" do + assert pretty_payment("invalid") == "" + end + + # test "by_trip returns an empty list if the V3 API returns an error" do + # bypass = Bypass.open() + # v3_url = Application.get_env(:mobile_app_backend, :base_url) + + # on_exit(fn -> + # Application.put_env(:mobile_app_backend, :base_url, v3_url) + # end) + + # Application.put_env(:mobile_app_backend, :base_url, "http://localhost:#{bypass.port}") + + # Bypass.expect(bypass, fn conn -> + # Plug.Conn.resp(conn, 500, "") + # end) + + # assert [] = by_trip("1") + # end +end diff --git a/test/mbta_v3_api/stops/repo_test.exs b/test/mbta_v3_api/stops/repo_test.exs new file mode 100644 index 00000000..5afcf972 --- /dev/null +++ b/test/mbta_v3_api/stops/repo_test.exs @@ -0,0 +1,187 @@ +defmodule Stops.RepoTest do + @moduledoc """ + TODO: Mock API data in this whole file + """ + use ExUnit.Case + + import Stops.Repo + alias Stops.Stop + + describe "get/1" do + test "returns nil if the stop doesn't exist" do + assert get("get test: stop doesn't exist") == nil + end + + test "returns a stop" do + assert %Stop{} = get("place-pktrm") + end + end + + describe "get!/1" do + test "raises a Stops.NotFoundError if the stop isn't found" do + assert_raise Stops.NotFoundError, fn -> + get!("get! test: stop doesn't exist") + end + end + + test "returns a stop" do + assert %Stop{} = get!("place-pktrm") + end + end + + describe "get_parent/1" do + test "returns the parent stop for a child stop" do + north_station_cr = get("BNT-0000-01") + assert north_station_cr.is_child? == true + assert north_station_cr.parent_id == "place-north" + assert %Stop{id: "place-north"} = get_parent(north_station_cr) + end + + test "returns the same stop for a parent stop" do + north_station = get("place-north") + assert get_parent(north_station) == north_station + end + + test "takes ids" do + assert %Stop{id: "place-north"} = get_parent("BNT-0000-01") + assert %Stop{id: "place-north"} = get_parent("place-north") + end + end + + describe "by_route/3" do + test "returns a list of stops in order of their stop_sequence" do + response = by_route("CR-Lowell", 1) + + assert response != [] + assert match?(%Stop{id: "place-NHRML-0254", name: "Lowell"}, List.first(response)) + assert match?(%Stop{id: "place-north", name: "North Station"}, List.last(response)) + assert response == Enum.uniq(response) + end + + test "uses the parent station" do + response = by_route("CR-Fitchburg", 1) + assert Enum.all?(response, &(!has_parent?(&1))) + end + + test "does not include a parent station multiple times" do + # stops multiple times at Sullivan + response = by_route("86", 1) + + assert response != [] + refute (response |> Enum.at(1)).id == "place-sull" + end + + test "can take additional fields" do + today = Timex.today() + weekday = today |> Timex.shift(days: 7) |> Timex.beginning_of_week(:fri) + saturday = weekday |> Timex.shift(days: 1) + + assert by_route("351", 1, date: weekday) != + by_route("351", 1, date: saturday) + end + + test "caches per-stop as well" do + ConCache.delete(Stops.Repo, {:by_route, {"Red", 1, []}}) + ConCache.put(Stops.Repo, {:stop, "place-brntn"}, {:ok, "to-be-overwritten"}) + assert get("place-brntn") == "to-be-overwritten" + + by_route("Red", 1, []) + assert %Stops.Stop{} = get("place-brntn") + end + end + + describe "by_routes/3" do + test "can return stops from multiple route IDs" do + response = by_routes(["CR-Lowell", "CR-Haverhill"], 1) + assert Enum.find(response, &(&1.id == "place-NHRML-0254")) + assert Enum.find(response, &(&1.id == "place-WR-0329")) + # North Station only appears once + assert response |> Enum.filter(&(&1.id == "place-north")) |> length == 1 + end + end + + describe "by_route_type/2" do + test "can returns stops filtered by route type" do + # commuter rail + response = by_route_type(2) + + assert Enum.find(response, &(&1.id == "place-NHRML-0254")) + + # uses parent stop + assert Enum.find(response, &(&1.id == "North Station")) == nil + assert %Stop{} = Enum.find(response, &(&1.id == "place-north")) + + # doesn't duplicate stops + assert Enum.uniq(response) == response + + # doesn't include non-CR stops + refute Enum.find(response, &(&1.id == "place-boyls")) + end + end + + # describe "by_trip/2" do + # test "can return stops from a trip" do + # assert response = by_trip("58653355") + # assert [%Stop{} | _] = response + # end + + # test "returns empty list if no trip matches" do + # assert [] = by_trip("made up trip id") + # end + # end + + # describe "old_id_to_gtfs_id/1" do + # test "Returns nil when no id matches" do + # refute old_id_to_gtfs_id("made up stop id") + # end + + # test "Returns gtfs id from old site id" do + # assert old_id_to_gtfs_id("66") == "place-forhl" + # end + # end + + describe "stop_features/1" do + @south_station %Stop{id: "place-sstat"} + @braintree %Stop{id: "place-brntn"} + + test "Returns stop features for a given stop" do + features = stop_features(@braintree) + assert :commuter_rail in features + assert :red_line in features + assert :bus in features + end + + test "returns stop features in correct order" do + assert stop_features(@braintree) == [:red_line, :bus, :commuter_rail] + end + + test "accessibility added if relevant" do + features = stop_features(%{@braintree | accessibility: ["accessible"]}) + assert features == [:red_line, :bus, :commuter_rail, :access] + end + + # test "adds parking features if relevant" do + # stop = %{@south_station | parking_lots: [%Stop.ParkingLot{}]} + # assert :parking_lot in stop_features(stop) + # end + + test "excluded features are not returned" do + assert stop_features(@braintree, exclude: [:red_line]) == [:bus, :commuter_rail] + assert stop_features(@braintree, exclude: [:red_line, :commuter_rail]) == [:bus] + end + + test "South Station's features will include the Silver Line icon" do + features = stop_features(@south_station) + assert :silver_line in features + end + + test "includes specific green_line branches if specified" do + # when green line isn't expanded, keep it in GTFS order + features = stop_features(%Stop{id: "place-pktrm"}) + assert features == [:red_line, :green_line_b, :green_line_c, :green_line_d, :green_line_e] + # when green line is expanded, put the branches first + features = stop_features(%Stop{id: "place-pktrm"}, expand_branches?: true) + assert features == [:"Green-B", :"Green-C", :"Green-D", :"Green-E", :red_line] + end + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e7..ac4b717f 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,3 @@ +{:ok, _} = Application.ensure_all_started(:bypass) + ExUnit.start() From b015c3e16e3b5c6988759067194b12a552df6d4f Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 15:53:34 -0700 Subject: [PATCH 10/16] ci: load example environment for mix test --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69438120..542f7bf1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,8 @@ jobs: run: mix credo --strict - name: Check formatting run: mix format --check-formatted + - name: Load example environment + run: source envrc.example - name: Run tests run: mix test - uses: mbta/actions/dialyzer@v2 From 959dd3204282fb6d294acf357ea848b730e210fb Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 15:56:34 -0700 Subject: [PATCH 11/16] load sample environment more directly --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 542f7bf1..5afd34f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,10 +87,8 @@ jobs: run: mix credo --strict - name: Check formatting run: mix format --check-formatted - - name: Load example environment - run: source envrc.example - name: Run tests - run: mix test + run: source envrc.example && mix test - uses: mbta/actions/dialyzer@v2 - name: Check for unused dependencies run: mix deps.unlock --check-unused From e7ce73b2b8e8b9c00b7649415672d49b6f8a6713 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 16:08:45 -0700 Subject: [PATCH 12/16] don't run repo tests against real API --- test/mbta_v3_api/stops/repo_test.exs | 187 --------------------------- 1 file changed, 187 deletions(-) delete mode 100644 test/mbta_v3_api/stops/repo_test.exs diff --git a/test/mbta_v3_api/stops/repo_test.exs b/test/mbta_v3_api/stops/repo_test.exs deleted file mode 100644 index 5afcf972..00000000 --- a/test/mbta_v3_api/stops/repo_test.exs +++ /dev/null @@ -1,187 +0,0 @@ -defmodule Stops.RepoTest do - @moduledoc """ - TODO: Mock API data in this whole file - """ - use ExUnit.Case - - import Stops.Repo - alias Stops.Stop - - describe "get/1" do - test "returns nil if the stop doesn't exist" do - assert get("get test: stop doesn't exist") == nil - end - - test "returns a stop" do - assert %Stop{} = get("place-pktrm") - end - end - - describe "get!/1" do - test "raises a Stops.NotFoundError if the stop isn't found" do - assert_raise Stops.NotFoundError, fn -> - get!("get! test: stop doesn't exist") - end - end - - test "returns a stop" do - assert %Stop{} = get!("place-pktrm") - end - end - - describe "get_parent/1" do - test "returns the parent stop for a child stop" do - north_station_cr = get("BNT-0000-01") - assert north_station_cr.is_child? == true - assert north_station_cr.parent_id == "place-north" - assert %Stop{id: "place-north"} = get_parent(north_station_cr) - end - - test "returns the same stop for a parent stop" do - north_station = get("place-north") - assert get_parent(north_station) == north_station - end - - test "takes ids" do - assert %Stop{id: "place-north"} = get_parent("BNT-0000-01") - assert %Stop{id: "place-north"} = get_parent("place-north") - end - end - - describe "by_route/3" do - test "returns a list of stops in order of their stop_sequence" do - response = by_route("CR-Lowell", 1) - - assert response != [] - assert match?(%Stop{id: "place-NHRML-0254", name: "Lowell"}, List.first(response)) - assert match?(%Stop{id: "place-north", name: "North Station"}, List.last(response)) - assert response == Enum.uniq(response) - end - - test "uses the parent station" do - response = by_route("CR-Fitchburg", 1) - assert Enum.all?(response, &(!has_parent?(&1))) - end - - test "does not include a parent station multiple times" do - # stops multiple times at Sullivan - response = by_route("86", 1) - - assert response != [] - refute (response |> Enum.at(1)).id == "place-sull" - end - - test "can take additional fields" do - today = Timex.today() - weekday = today |> Timex.shift(days: 7) |> Timex.beginning_of_week(:fri) - saturday = weekday |> Timex.shift(days: 1) - - assert by_route("351", 1, date: weekday) != - by_route("351", 1, date: saturday) - end - - test "caches per-stop as well" do - ConCache.delete(Stops.Repo, {:by_route, {"Red", 1, []}}) - ConCache.put(Stops.Repo, {:stop, "place-brntn"}, {:ok, "to-be-overwritten"}) - assert get("place-brntn") == "to-be-overwritten" - - by_route("Red", 1, []) - assert %Stops.Stop{} = get("place-brntn") - end - end - - describe "by_routes/3" do - test "can return stops from multiple route IDs" do - response = by_routes(["CR-Lowell", "CR-Haverhill"], 1) - assert Enum.find(response, &(&1.id == "place-NHRML-0254")) - assert Enum.find(response, &(&1.id == "place-WR-0329")) - # North Station only appears once - assert response |> Enum.filter(&(&1.id == "place-north")) |> length == 1 - end - end - - describe "by_route_type/2" do - test "can returns stops filtered by route type" do - # commuter rail - response = by_route_type(2) - - assert Enum.find(response, &(&1.id == "place-NHRML-0254")) - - # uses parent stop - assert Enum.find(response, &(&1.id == "North Station")) == nil - assert %Stop{} = Enum.find(response, &(&1.id == "place-north")) - - # doesn't duplicate stops - assert Enum.uniq(response) == response - - # doesn't include non-CR stops - refute Enum.find(response, &(&1.id == "place-boyls")) - end - end - - # describe "by_trip/2" do - # test "can return stops from a trip" do - # assert response = by_trip("58653355") - # assert [%Stop{} | _] = response - # end - - # test "returns empty list if no trip matches" do - # assert [] = by_trip("made up trip id") - # end - # end - - # describe "old_id_to_gtfs_id/1" do - # test "Returns nil when no id matches" do - # refute old_id_to_gtfs_id("made up stop id") - # end - - # test "Returns gtfs id from old site id" do - # assert old_id_to_gtfs_id("66") == "place-forhl" - # end - # end - - describe "stop_features/1" do - @south_station %Stop{id: "place-sstat"} - @braintree %Stop{id: "place-brntn"} - - test "Returns stop features for a given stop" do - features = stop_features(@braintree) - assert :commuter_rail in features - assert :red_line in features - assert :bus in features - end - - test "returns stop features in correct order" do - assert stop_features(@braintree) == [:red_line, :bus, :commuter_rail] - end - - test "accessibility added if relevant" do - features = stop_features(%{@braintree | accessibility: ["accessible"]}) - assert features == [:red_line, :bus, :commuter_rail, :access] - end - - # test "adds parking features if relevant" do - # stop = %{@south_station | parking_lots: [%Stop.ParkingLot{}]} - # assert :parking_lot in stop_features(stop) - # end - - test "excluded features are not returned" do - assert stop_features(@braintree, exclude: [:red_line]) == [:bus, :commuter_rail] - assert stop_features(@braintree, exclude: [:red_line, :commuter_rail]) == [:bus] - end - - test "South Station's features will include the Silver Line icon" do - features = stop_features(@south_station) - assert :silver_line in features - end - - test "includes specific green_line branches if specified" do - # when green line isn't expanded, keep it in GTFS order - features = stop_features(%Stop{id: "place-pktrm"}) - assert features == [:red_line, :green_line_b, :green_line_c, :green_line_d, :green_line_e] - # when green line is expanded, put the branches first - features = stop_features(%Stop{id: "place-pktrm"}, expand_branches?: true) - assert features == [:"Green-B", :"Green-C", :"Green-D", :"Green-E", :red_line] - end - end -end From e1dbbc3380de3d55c0777af218d1151c19430e7b Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 29 Nov 2023 16:10:15 -0700 Subject: [PATCH 13/16] add GraphQL test --- envrc.example | 2 +- test/mobile_app_backend_web/schema_test.exs | 47 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 test/mobile_app_backend_web/schema_test.exs diff --git a/envrc.example b/envrc.example index e55f1c63..58b05124 100644 --- a/envrc.example +++ b/envrc.example @@ -1,2 +1,2 @@ -export API_KEY= +#export API_KEY= export API_URL=https://api-dev.mbtace.com/ diff --git a/test/mobile_app_backend_web/schema_test.exs b/test/mobile_app_backend_web/schema_test.exs new file mode 100644 index 00000000..492c75a2 --- /dev/null +++ b/test/mobile_app_backend_web/schema_test.exs @@ -0,0 +1,47 @@ +defmodule MobileAppBackendWeb.SchemaTest do + use MobileAppBackendWeb.ConnCase + + @stop_query """ + query { + stop(id: "place-boyls") { + id + name + routes { + id + name + routePatterns { + id + name + } + } + } + } + """ + + test "query: stop", %{conn: conn} do + conn = + post(conn, "/graphql", %{ + "query" => @stop_query + }) + + assert %{"data" => %{"stop" => stop_data}} = json_response(conn, 200) + assert %{"id" => "place-boyls", "name" => "Boylston", "routes" => routes} = stop_data + + routes = Enum.sort_by(routes, & &1["id"]) + + assert routes |> Enum.map(& &1["id"]) == [ + "Green-B", + "Green-C", + "Green-D", + "Green-E" + ] + + assert %{ + "id" => "Green-B", + "name" => "Green Line B", + "routePatterns" => route_patterns + } = hd(routes) + + assert length(route_patterns) > 0 + end +end From 750807a22fee045c4aeebbcb586ab9fe43bc690b Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Thu, 30 Nov 2023 12:22:07 -0700 Subject: [PATCH 14/16] use snapshot of real data for GraphQL test --- test/mbta_v3_api/stops/api_test.exs | 37 +- test/mobile_app_backend_web/schema_test.exs | 1448 +++++++++++++++++++ test/support/helpers.ex | 16 + 3 files changed, 1469 insertions(+), 32 deletions(-) diff --git a/test/mbta_v3_api/stops/api_test.exs b/test/mbta_v3_api/stops/api_test.exs index 140f6b58..906d4ade 100644 --- a/test/mbta_v3_api/stops/api_test.exs +++ b/test/mbta_v3_api/stops/api_test.exs @@ -1,6 +1,7 @@ defmodule Stops.ApiTest do use ExUnit.Case import Stops.Api + import Test.Support.Helpers alias Stops.Stop describe "by_gtfs_id/1" do @@ -98,14 +99,7 @@ defmodule Stops.ApiTest do end test "returns an error if the API returns an error" do - bypass = Bypass.open() - v3_url = Application.get_env(:mobile_app_backend, :base_url) - - on_exit(fn -> - Application.put_env(:mobile_app_backend, :base_url, v3_url) - end) - - Application.put_env(:mobile_app_backend, :base_url, "http://localhost:#{bypass.port}") + bypass = bypass_api() Bypass.expect(bypass, fn conn -> Plug.Conn.resp(conn, 200, "") @@ -116,14 +110,7 @@ defmodule Stops.ApiTest do end test "all/0 returns error if API returns error" do - bypass = Bypass.open() - v3_url = Application.get_env(:mobile_app_backend, :base_url) - - on_exit(fn -> - Application.put_env(:mobile_app_backend, :base_url, v3_url) - end) - - Application.put_env(:mobile_app_backend, :base_url, "http://localhost:#{bypass.port}") + bypass = bypass_api() Bypass.expect(bypass, fn conn -> Plug.Conn.resp(conn, 200, "") @@ -133,14 +120,7 @@ defmodule Stops.ApiTest do end test "by_route returns an error tuple if the V3 API returns an error" do - bypass = Bypass.open() - v3_url = Application.get_env(:mobile_app_backend, :base_url) - - on_exit(fn -> - Application.put_env(:mobile_app_backend, :base_url, v3_url) - end) - - Application.put_env(:mobile_app_backend, :base_url, "http://localhost:#{bypass.port}") + bypass = bypass_api() Bypass.expect(bypass, fn conn -> Plug.Conn.resp(conn, 200, "") @@ -154,14 +134,7 @@ defmodule Stops.ApiTest do end # test "by_trip returns an empty list if the V3 API returns an error" do - # bypass = Bypass.open() - # v3_url = Application.get_env(:mobile_app_backend, :base_url) - - # on_exit(fn -> - # Application.put_env(:mobile_app_backend, :base_url, v3_url) - # end) - - # Application.put_env(:mobile_app_backend, :base_url, "http://localhost:#{bypass.port}") + # bypass = bypass_api() # Bypass.expect(bypass, fn conn -> # Plug.Conn.resp(conn, 500, "") diff --git a/test/mobile_app_backend_web/schema_test.exs b/test/mobile_app_backend_web/schema_test.exs index 492c75a2..36ab0b4a 100644 --- a/test/mobile_app_backend_web/schema_test.exs +++ b/test/mobile_app_backend_web/schema_test.exs @@ -1,6 +1,8 @@ defmodule MobileAppBackendWeb.SchemaTest do use MobileAppBackendWeb.ConnCase + import Test.Support.Helpers + @stop_query """ query { stop(id: "place-boyls") { @@ -19,6 +21,1452 @@ defmodule MobileAppBackendWeb.SchemaTest do """ test "query: stop", %{conn: conn} do + bypass = bypass_api() + + Bypass.expect(bypass, "GET", "/stops/place-boyls", fn conn -> + Phoenix.Controller.json(conn, %{ + data: %{ + attributes: %{ + address: "Boylston St and Tremont St, Boston, MA", + description: nil, + latitude: 42.35302, + location_type: 1, + longitude: -71.06459, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 2 + }, + id: "place-boyls", + links: %{self: "/stops/place-boyls"}, + relationships: %{ + child_stops: %{ + data: [ + %{id: "70158", type: "stop"}, + %{id: "70159", type: "stop"}, + %{id: "door-boyls-inbound", type: "stop"}, + %{id: "door-boyls-outbound", type: "stop"}, + %{id: "node-boyls-in-farepaid", type: "stop"}, + %{id: "node-boyls-in-fareunpaid", type: "stop"}, + %{id: "node-boyls-instair-platform", type: "stop"}, + %{id: "node-boyls-out-farepaid", type: "stop"}, + %{id: "node-boyls-out-fareunpaid", type: "stop"}, + %{id: "node-boyls-outstair-platform", type: "stop"} + ] + }, + facilities: %{ + data: [ + %{id: "fvm-201221", type: "facility"}, + %{id: "fvm-201222", type: "facility"}, + %{id: "fvm-201223", type: "facility"}, + %{id: "fvm-201224", type: "facility"}, + %{id: "fvm-202157", type: "facility"} + ], + links: %{related: "/facilities/?filter[stop]=place-boyls"} + }, + parent_station: %{data: nil}, + zone: %{data: nil} + }, + type: "stop" + }, + included: [ + %{ + attributes: %{ + address: nil, + description: "Boylston - Green Line - Park Street & North", + latitude: 42.352531, + location_type: 0, + longitude: -71.064682, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: "Park Street & North", + wheelchair_boarding: 2 + }, + id: "70158", + links: %{self: "/stops/70158"}, + relationships: %{ + facilities: %{links: %{related: "/facilities/?filter[stop]=70158"}}, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: %{id: "RapidTransit", type: "zone"}} + }, + type: "stop" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Green Line - Copley & West", + latitude: 42.353214, + location_type: 0, + longitude: -71.064545, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: "Copley & West", + wheelchair_boarding: 2 + }, + id: "70159", + links: %{self: "/stops/70159"}, + relationships: %{ + facilities: %{links: %{related: "/facilities/?filter[stop]=70159"}}, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: %{id: "RapidTransit", type: "zone"}} + }, + type: "stop" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Boston Common, Street", + latitude: 42.352531, + location_type: 2, + longitude: -71.064685, + municipality: "Boston", + name: "Boylston - Boston Common, Street", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 2 + }, + id: "door-boyls-inbound", + links: %{self: "/stops/door-boyls-inbound"}, + relationships: %{ + facilities: %{ + links: %{related: "/facilities/?filter[stop]=door-boyls-inbound"} + }, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: nil} + }, + type: "stop" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Boston Common, Street", + latitude: 42.353214, + location_type: 2, + longitude: -71.064546, + municipality: "Boston", + name: "Boylston - Boston Common, Street", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 2 + }, + id: "door-boyls-outbound", + links: %{self: "/stops/door-boyls-outbound"}, + relationships: %{ + facilities: %{ + links: %{related: "/facilities/?filter[stop]=door-boyls-outbound"} + }, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: nil} + }, + type: "stop" + }, + %{ + attributes: %{ + latitude: nil, + long_name: "Boylston fare vending machine 201221", + longitude: nil, + properties: [ + %{name: "enclosed", value: 1}, + %{name: "excludes-stop", value: "door-boyls-outbound"}, + %{name: "payment-form-accepted", value: "cash"}, + %{name: "payment-form-accepted", value: "credit-debit-card"} + ], + type: "FARE_VENDING_MACHINE" + }, + id: "fvm-201221", + links: %{self: "/facilities/fvm-201221"}, + relationships: %{ + stop: %{data: %{id: "place-boyls", type: "stop"}} + }, + type: "facility" + }, + %{ + attributes: %{ + latitude: nil, + long_name: "Boylston fare vending machine 201222", + longitude: nil, + properties: [ + %{name: "enclosed", value: 1}, + %{name: "excludes-stop", value: "door-boyls-outbound"}, + %{name: "payment-form-accepted", value: "cash"}, + %{name: "payment-form-accepted", value: "credit-debit-card"} + ], + type: "FARE_VENDING_MACHINE" + }, + id: "fvm-201222", + links: %{self: "/facilities/fvm-201222"}, + relationships: %{ + stop: %{data: %{id: "place-boyls", type: "stop"}} + }, + type: "facility" + }, + %{ + attributes: %{ + latitude: nil, + long_name: "Boylston fare vending machine 201223", + longitude: nil, + properties: [ + %{name: "enclosed", value: 1}, + %{name: "excludes-stop", value: "door-boyls-inbound"}, + %{name: "payment-form-accepted", value: "cash"}, + %{name: "payment-form-accepted", value: "credit-debit-card"} + ], + type: "FARE_VENDING_MACHINE" + }, + id: "fvm-201223", + links: %{self: "/facilities/fvm-201223"}, + relationships: %{ + stop: %{data: %{id: "place-boyls", type: "stop"}} + }, + type: "facility" + }, + %{ + attributes: %{ + latitude: nil, + long_name: "Boylston fare vending machine 201224", + longitude: nil, + properties: [ + %{name: "enclosed", value: 1}, + %{name: "excludes-stop", value: "door-boyls-inbound"}, + %{name: "payment-form-accepted", value: "cash"}, + %{name: "payment-form-accepted", value: "credit-debit-card"} + ], + type: "FARE_VENDING_MACHINE" + }, + id: "fvm-201224", + links: %{self: "/facilities/fvm-201224"}, + relationships: %{ + stop: %{data: %{id: "place-boyls", type: "stop"}} + }, + type: "facility" + }, + %{ + attributes: %{ + latitude: nil, + long_name: "Boylston fare vending machine 202157", + longitude: nil, + properties: [ + %{name: "enclosed", value: 1}, + %{name: "excludes-stop", value: "door-boyls-inbound"}, + %{name: "payment-form-accepted", value: "credit-debit-card"} + ], + type: "FARE_VENDING_MACHINE" + }, + id: "fvm-202157", + links: %{self: "/facilities/fvm-202157"}, + relationships: %{ + stop: %{data: %{id: "place-boyls", type: "stop"}} + }, + type: "facility" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Paid side of fare gates", + latitude: nil, + location_type: 3, + longitude: nil, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 1 + }, + id: "node-boyls-in-farepaid", + links: %{self: "/stops/node-boyls-in-farepaid"}, + relationships: %{ + facilities: %{ + links: %{related: "/facilities/?filter[stop]=node-boyls-in-farepaid"} + }, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: nil} + }, + type: "stop" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Unpaid side of fare gates", + latitude: nil, + location_type: 3, + longitude: nil, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 1 + }, + id: "node-boyls-in-fareunpaid", + links: %{self: "/stops/node-boyls-in-fareunpaid"}, + relationships: %{ + facilities: %{ + links: %{related: "/facilities/?filter[stop]=node-boyls-in-fareunpaid"} + }, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: nil} + }, + type: "stop" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Bottom of Park Street & East stairs", + latitude: nil, + location_type: 3, + longitude: nil, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 1 + }, + id: "node-boyls-instair-platform", + links: %{self: "/stops/node-boyls-instair-platform"}, + relationships: %{ + facilities: %{ + links: %{related: "/facilities/?filter[stop]=node-boyls-instair-platform"} + }, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: nil} + }, + type: "stop" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Paid side of fare gates", + latitude: nil, + location_type: 3, + longitude: nil, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 1 + }, + id: "node-boyls-out-farepaid", + links: %{self: "/stops/node-boyls-out-farepaid"}, + relationships: %{ + facilities: %{ + links: %{related: "/facilities/?filter[stop]=node-boyls-out-farepaid"} + }, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: nil} + }, + type: "stop" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Unpaid side of fare gates", + latitude: nil, + location_type: 3, + longitude: nil, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 1 + }, + id: "node-boyls-out-fareunpaid", + links: %{self: "/stops/node-boyls-out-fareunpaid"}, + relationships: %{ + facilities: %{ + links: %{related: "/facilities/?filter[stop]=node-boyls-out-fareunpaid"} + }, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: nil} + }, + type: "stop" + }, + %{ + attributes: %{ + address: nil, + description: "Boylston - Bottom of Copley & West stairs", + latitude: nil, + location_type: 3, + longitude: nil, + municipality: "Boston", + name: "Boylston", + platform_code: nil, + platform_name: nil, + wheelchair_boarding: 1 + }, + id: "node-boyls-outstair-platform", + links: %{self: "/stops/node-boyls-outstair-platform"}, + relationships: %{ + facilities: %{ + links: %{ + related: "/facilities/?filter[stop]=node-boyls-outstair-platform" + } + }, + parent_station: %{data: %{id: "place-boyls", type: "stop"}}, + zone: %{data: nil} + }, + type: "stop" + } + ], + jsonapi: %{version: "1.0"} + }) + end) + + Bypass.expect(bypass, "GET", "/routes/", fn conn -> + Phoenix.Controller.json(conn, %{ + data: [ + %{ + attributes: %{ + color: "00843D", + description: "Rapid Transit", + direction_destinations: ["Boston College", "Government Center"], + direction_names: ["West", "East"], + fare_class: "Rapid Transit", + long_name: "Green Line B", + short_name: "B", + sort_order: 10_032, + text_color: "FFFFFF", + type: 0 + }, + id: "Green-B", + links: %{self: "/routes/Green-B"}, + relationships: %{ + line: %{data: %{id: "line-Green", type: "line"}}, + route_patterns: %{ + data: [ + %{id: "Green-B-812-0", type: "route_pattern"}, + %{id: "Green-B-812-0_70202_170137_2", type: "route_pattern"}, + %{id: "Green-B-812-1", type: "route_pattern"}, + %{id: "Green-B-812-1_170136_70201_0", type: "route_pattern"}, + %{id: "Green-B-816-0", type: "route_pattern"}, + %{id: "Green-B-816-0_70202_170137_0_70206_70202_0", type: "route_pattern"}, + %{id: "Green-B-816-0_70202_170137_2", type: "route_pattern"}, + %{id: "Green-B-816-1", type: "route_pattern"}, + %{id: "Green-B-816-1_170136_70201_0", type: "route_pattern"}, + %{id: "Green-B-816-1_170136_70201_2_70201_70205_2", type: "route_pattern"} + ] + } + }, + type: "route" + }, + %{ + attributes: %{ + color: "00843D", + description: "Rapid Transit", + direction_destinations: ["Cleveland Circle", "Government Center"], + direction_names: ["West", "East"], + fare_class: "Rapid Transit", + long_name: "Green Line C", + short_name: "C", + sort_order: 10_033, + text_color: "FFFFFF", + type: 0 + }, + id: "Green-C", + links: %{self: "/routes/Green-C"}, + relationships: %{ + line: %{data: %{id: "line-Green", type: "line"}}, + route_patterns: %{ + data: [ + %{id: "Green-C-832-0", type: "route_pattern"}, + %{id: "Green-C-832-0_70202_70151_2", type: "route_pattern"}, + %{id: "Green-C-832-1", type: "route_pattern"}, + %{id: "Green-C-832-1_70150_70201_0", type: "route_pattern"}, + %{id: "Green-C-836-0", type: "route_pattern"}, + %{id: "Green-C-836-0_70206_70202_0", type: "route_pattern"}, + %{id: "Green-C-836-0_70206_70202_2_70202_70151_2", type: "route_pattern"}, + %{id: "Green-C-836-1", type: "route_pattern"}, + %{id: "Green-C-836-1_70201_70205_0_70150_70201_0", type: "route_pattern"}, + %{id: "Green-C-836-1_70201_70205_2", type: "route_pattern"} + ] + } + }, + type: "route" + }, + %{ + attributes: %{ + color: "00843D", + description: "Rapid Transit", + direction_destinations: ["Riverside", "Union Square"], + direction_names: ["West", "East"], + fare_class: "Rapid Transit", + long_name: "Green Line D", + short_name: "D", + sort_order: 10_034, + text_color: "FFFFFF", + type: 0 + }, + id: "Green-D", + links: %{self: "/routes/Green-D"}, + relationships: %{ + line: %{data: %{id: "line-Green", type: "line"}}, + route_patterns: %{ + data: [ + %{id: "Green-D-855-0", type: "route_pattern"}, + %{id: "Green-D-855-0_70206_70202_0", type: "route_pattern"}, + %{id: "Green-D-855-0_70206_70202_2_70202_70151_2", type: "route_pattern"}, + %{id: "Green-D-855-0_70504_70206_2", type: "route_pattern"}, + %{ + id: "Green-D-855-0_70504_70206_2_70206_70202_2_70202_70151_2", + type: "route_pattern" + }, + %{id: "Green-D-855-1", type: "route_pattern"}, + %{id: "Green-D-855-1_70201_70205_0_70150_70201_0", type: "route_pattern"}, + %{id: "Green-D-855-1_70201_70205_2", type: "route_pattern"}, + %{id: "Green-D-855-1_70205_70503_0", type: "route_pattern"}, + %{ + id: "Green-D-855-1_70205_70503_0_70201_70205_0_70150_70201_0", + type: "route_pattern" + }, + %{id: "Green-D-856-0", type: "route_pattern"}, + %{id: "Green-D-856-0_70206_70202_0", type: "route_pattern"}, + %{id: "Green-D-856-0_70206_70202_2_70202_70151_2", type: "route_pattern"}, + %{id: "Green-D-856-1", type: "route_pattern"}, + %{id: "Green-D-856-1_70201_70205_0_70150_70201_0", type: "route_pattern"}, + %{id: "Green-D-856-1_70201_70205_2", type: "route_pattern"} + ] + } + }, + type: "route" + }, + %{ + attributes: %{ + color: "00843D", + description: "Rapid Transit", + direction_destinations: ["Heath Street", "Medford/Tufts"], + direction_names: ["West", "East"], + fare_class: "Rapid Transit", + long_name: "Green Line E", + short_name: "E", + sort_order: 10_035, + text_color: "FFFFFF", + type: 0 + }, + id: "Green-E", + links: %{self: "/routes/Green-E"}, + relationships: %{ + line: %{data: %{id: "line-Green", type: "line"}}, + route_patterns: %{ + data: [ + %{id: "Green-E-86-1", type: "route_pattern"}, + %{id: "Green-E-886-0", type: "route_pattern"}, + %{id: "Green-E-886-0_70206_70260_0", type: "route_pattern"}, + %{id: "Green-E-886-0_70512_70206_2", type: "route_pattern"}, + %{id: "Green-E-886-1", type: "route_pattern"}, + %{id: "Green-E-886-1_70205_70511_0", type: "route_pattern"}, + %{id: "Green-E-886-1_70260_70205_2", type: "route_pattern"} + ] + } + }, + type: "route" + } + ], + included: [ + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Babcock Street - Boston College", + sort_order: 100_320_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-B-812-0_70202_170137_2", + links: %{self: "/route_patterns/Green-B-812-0_70202_170137_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397501-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Cleveland Circle - Kenmore", + sort_order: 100_331_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-C-832-1_70150_70201_0", + links: %{self: "/route_patterns/Green-C-832-1_70150_70201_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397576-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Medford/Tufts - North Station", + sort_order: 100_340_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-856-0_70206_70202_0", + links: %{self: "/route_patterns/Green-D-856-0_70206_70202_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398269-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: true, + direction_id: 0, + name: "Government Center - Boston College", + sort_order: 100_320_000, + time_desc: nil, + typicality: 1 + }, + id: "Green-B-812-0", + links: %{self: "/route_patterns/Green-B-812-0"}, + relationships: %{ + representative_trip: %{data: %{id: "canonical-Green-B-C1-0", type: "trip"}}, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Kenmore - Cleveland Circle", + sort_order: 100_330_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-C-836-0_70206_70202_2_70202_70151_2", + links: %{self: "/route_patterns/Green-C-836-0_70206_70202_2_70202_70151_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397581-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD22", + type: "trip" + } + }, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: true, + direction_id: 0, + name: "Medford/Tufts - Heath Street", + sort_order: 100_350_000, + time_desc: nil, + typicality: 1 + }, + id: "Green-E-886-0", + links: %{self: "/route_patterns/Green-E-886-0"}, + relationships: %{ + representative_trip: %{data: %{id: "canonical-Green-E-C1-0", type: "trip"}}, + route: %{data: %{id: "Green-E", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Riverside - North Station", + sort_order: 100_341_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-855-1_70205_70503_0", + links: %{self: "/route_patterns/Green-D-855-1_70205_70503_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: "58398331-20:30-HayGLHayGLHayGLHayGLNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Medford/Tufts - North Station", + sort_order: 100_320_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-B-816-0_70202_170137_0_70206_70202_0", + links: %{self: "/route_patterns/Green-B-816-0_70202_170137_0_70206_70202_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397599-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD00", + type: "trip" + } + }, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: true, + direction_id: 0, + name: "Government Center - Cleveland Circle", + sort_order: 100_330_000, + time_desc: nil, + typicality: 1 + }, + id: "Green-C-832-0", + links: %{self: "/route_patterns/Green-C-832-0"}, + relationships: %{ + representative_trip: %{data: %{id: "canonical-Green-C-C1-0", type: "trip"}}, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "North Station - Medford/Tufts", + sort_order: 100_331_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-C-836-1_70201_70205_2", + links: %{self: "/route_patterns/Green-C-836-1_70201_70205_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397582-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Riverside - Kenmore", + sort_order: 100_341_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-856-1_70201_70205_0_70150_70201_0", + links: %{self: "/route_patterns/Green-D-856-1_70201_70205_0_70150_70201_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398268-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD00", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Medford/Tufts - North Station", + sort_order: 100_330_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-C-836-0_70206_70202_0", + links: %{self: "/route_patterns/Green-C-836-0_70206_70202_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397581-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "East Somerville - Medford/Tufts", + sort_order: 100_351_040, + time_desc: nil, + typicality: 3 + }, + id: "Green-E-86-1", + links: %{self: "/route_patterns/Green-E-86-1"}, + relationships: %{ + representative_trip: %{data: %{id: "58485809", type: "trip"}}, + route: %{data: %{id: "Green-E", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Babcock Street - Boston College", + sort_order: 100_320_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-B-816-0_70202_170137_2", + links: %{self: "/route_patterns/Green-B-816-0_70202_170137_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397599-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Boston College - Babcock Street", + sort_order: 100_321_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-B-816-1_170136_70201_0", + links: %{self: "/route_patterns/Green-B-816-1_170136_70201_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397508-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: true, + direction_id: 1, + name: "Heath Street - Medford/Tufts", + sort_order: 100_351_000, + time_desc: nil, + typicality: 1 + }, + id: "Green-E-886-1", + links: %{self: "/route_patterns/Green-E-886-1"}, + relationships: %{ + representative_trip: %{data: %{id: "canonical-Green-E-C1-1", type: "trip"}}, + route: %{data: %{id: "Green-E", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: true, + direction_id: 1, + name: "Boston College - Government Center", + sort_order: 100_321_000, + time_desc: nil, + typicality: 1 + }, + id: "Green-B-812-1", + links: %{self: "/route_patterns/Green-B-812-1"}, + relationships: %{ + representative_trip: %{data: %{id: "canonical-Green-B-C1-1", type: "trip"}}, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "North Station - Medford/Tufts", + sort_order: 100_341_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-856-1_70201_70205_2", + links: %{self: "/route_patterns/Green-D-856-1_70201_70205_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398268-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Medford/Tufts - Cleveland Circle", + sort_order: 100_330_040, + time_desc: "Mornings only", + typicality: 3 + }, + id: "Green-C-836-0", + links: %{self: "/route_patterns/Green-C-836-0"}, + relationships: %{ + representative_trip: %{data: %{id: "58485660", type: "trip"}}, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "North Station - Heath Street", + sort_order: 100_350_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-E-886-0_70512_70206_2", + links: %{self: "/route_patterns/Green-E-886-0_70512_70206_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: "58397680-20:30-HayGLHayGLHayGLHayGLNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-E", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "North Station - Riverside", + sort_order: 100_340_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-855-0_70504_70206_2", + links: %{self: "/route_patterns/Green-D-855-0_70504_70206_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: "58398315-20:30-HayGLHayGLHayGLHayGLNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Boston College - Medford/Tufts", + sort_order: 100_321_040, + time_desc: "Early mornings only", + typicality: 3 + }, + id: "Green-B-816-1", + links: %{self: "/route_patterns/Green-B-816-1"}, + relationships: %{ + representative_trip: %{data: %{id: "58486222", type: "trip"}}, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Kenmore - Riverside", + sort_order: 100_340_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-855-0_70504_70206_2_70206_70202_2_70202_70151_2", + links: %{ + self: "/route_patterns/Green-D-855-0_70504_70206_2_70206_70202_2_70202_70151_2" + }, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398315-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD222", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Union Square - North Station", + sort_order: 100_340_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-855-0_70206_70202_0", + links: %{self: "/route_patterns/Green-D-855-0_70206_70202_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398254-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Heath Street - North Station", + sort_order: 100_351_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-E-886-1_70205_70511_0", + links: %{self: "/route_patterns/Green-E-886-1_70205_70511_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: "58397671-20:30-HayGLHayGLHayGLHayGLNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-E", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Riverside - Kenmore", + sort_order: 100_341_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-855-1_70205_70503_0_70201_70205_0_70150_70201_0", + links: %{ + self: "/route_patterns/Green-D-855-1_70205_70503_0_70201_70205_0_70150_70201_0" + }, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398331-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD000", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "North Station - Union Square", + sort_order: 100_341_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-855-1_70201_70205_2", + links: %{self: "/route_patterns/Green-D-855-1_70201_70205_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398253-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Medford/Tufts - North Station", + sort_order: 100_350_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-E-886-0_70206_70260_0", + links: %{self: "/route_patterns/Green-E-886-0_70206_70260_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397672-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-E", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Kenmore - Cleveland Circle", + sort_order: 100_330_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-C-832-0_70202_70151_2", + links: %{self: "/route_patterns/Green-C-832-0_70202_70151_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397575-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Cleveland Circle - Medford/Tufts", + sort_order: 100_331_040, + time_desc: "Early mornings only", + typicality: 3 + }, + id: "Green-C-836-1", + links: %{self: "/route_patterns/Green-C-836-1"}, + relationships: %{ + representative_trip: %{data: %{id: "58485669", type: "trip"}}, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "North Station - Medford/Tufts", + sort_order: 100_321_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-B-816-1_170136_70201_2_70201_70205_2", + links: %{self: "/route_patterns/Green-B-816-1_170136_70201_2_70201_70205_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397508-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD22", + type: "trip" + } + }, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "North Station - Medford/Tufts", + sort_order: 100_351_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-E-886-1_70260_70205_2", + links: %{self: "/route_patterns/Green-E-886-1_70260_70205_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397597-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", + type: "trip" + } + }, + route: %{data: %{id: "Green-E", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Medford/Tufts - Boston College", + sort_order: 100_320_040, + time_desc: "Mornings only", + typicality: 3 + }, + id: "Green-B-816-0", + links: %{self: "/route_patterns/Green-B-816-0"}, + relationships: %{ + representative_trip: %{data: %{id: "58486194", type: "trip"}}, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: true, + direction_id: 1, + name: "Riverside - Union Square", + sort_order: 100_341_000, + time_desc: nil, + typicality: 1 + }, + id: "Green-D-855-1", + links: %{self: "/route_patterns/Green-D-855-1"}, + relationships: %{ + representative_trip: %{data: %{id: "canonical-Green-D-C1-1", type: "trip"}}, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Riverside - Medford/Tufts", + sort_order: 100_341_040, + time_desc: "Weekday early mornings only", + typicality: 3 + }, + id: "Green-D-856-1", + links: %{self: "/route_patterns/Green-D-856-1"}, + relationships: %{ + representative_trip: %{data: %{id: "58398268", type: "trip"}}, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Boston College - Babcock Street", + sort_order: 100_321_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-B-812-1_170136_70201_0", + links: %{self: "/route_patterns/Green-B-812-1_170136_70201_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397500-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", + type: "trip" + } + }, + route: %{data: %{id: "Green-B", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Kenmore - Riverside", + sort_order: 100_340_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-855-0_70206_70202_2_70202_70151_2", + links: %{self: "/route_patterns/Green-D-855-0_70206_70202_2_70202_70151_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398254-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD22", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Kenmore - Riverside", + sort_order: 100_340_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-856-0_70206_70202_2_70202_70151_2", + links: %{self: "/route_patterns/Green-D-856-0_70206_70202_2_70202_70151_2"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398269-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD22", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Riverside - Kenmore", + sort_order: 100_341_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-D-855-1_70201_70205_0_70150_70201_0", + links: %{self: "/route_patterns/Green-D-855-1_70201_70205_0_70150_70201_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58398253-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD00", + type: "trip" + } + }, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: true, + direction_id: 1, + name: "Cleveland Circle - Government Center", + sort_order: 100_331_000, + time_desc: nil, + typicality: 1 + }, + id: "Green-C-832-1", + links: %{self: "/route_patterns/Green-C-832-1"}, + relationships: %{ + representative_trip: %{data: %{id: "canonical-Green-C-C1-1", type: "trip"}}, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 0, + name: "Medford/Tufts - Riverside", + sort_order: 100_340_040, + time_desc: "Weekday mornings only", + typicality: 3 + }, + id: "Green-D-856-0", + links: %{self: "/route_patterns/Green-D-856-0"}, + relationships: %{ + representative_trip: %{data: %{id: "58398269", type: "trip"}}, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: false, + direction_id: 1, + name: "Cleveland Circle - Kenmore", + sort_order: 100_331_990, + time_desc: nil, + typicality: 4 + }, + id: "Green-C-836-1_70201_70205_0_70150_70201_0", + links: %{self: "/route_patterns/Green-C-836-1_70201_70205_0_70150_70201_0"}, + relationships: %{ + representative_trip: %{ + data: %{ + id: + "58397582-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD00", + type: "trip" + } + }, + route: %{data: %{id: "Green-C", type: "route"}} + }, + type: "route_pattern" + }, + %{ + attributes: %{ + canonical: true, + direction_id: 0, + name: "Union Square - Riverside", + sort_order: 100_340_000, + time_desc: nil, + typicality: 1 + }, + id: "Green-D-855-0", + links: %{self: "/route_patterns/Green-D-855-0"}, + relationships: %{ + representative_trip: %{data: %{id: "canonical-Green-D-C1-0", type: "trip"}}, + route: %{data: %{id: "Green-D", type: "route"}} + }, + type: "route_pattern" + } + ], + jsonapi: %{version: "1.0"} + }) + end) + conn = post(conn, "/graphql", %{ "query" => @stop_query diff --git a/test/support/helpers.ex b/test/support/helpers.ex index a59aae50..88078fbb 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -1,6 +1,8 @@ defmodule Test.Support.Helpers do @moduledoc "Test helpers" + alias Test.Support.Helpers + defmacro reassign_env(app, var, value) do quote do old_value = Application.get_env(unquote(app), unquote(var)) @@ -16,6 +18,20 @@ defmodule Test.Support.Helpers do end end + defmacro bypass_api do + quote do + bypass = Bypass.open() + + Helpers.reassign_env( + :mobile_app_backend, + :base_url, + "http://localhost:#{bypass.port}" + ) + + bypass + end + end + defmacro set_log_level(log_level) do quote do old_log_level = Logger.level() From 4673e416ba1dfb7d68a55c9beb95414252283748 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Thu, 30 Nov 2023 14:16:44 -0700 Subject: [PATCH 15/16] remove non-canonical route patterns --- test/mobile_app_backend_web/schema_test.exs | 812 +------------------- 1 file changed, 4 insertions(+), 808 deletions(-) diff --git a/test/mobile_app_backend_web/schema_test.exs b/test/mobile_app_backend_web/schema_test.exs index 36ab0b4a..6a3c6b2d 100644 --- a/test/mobile_app_backend_web/schema_test.exs +++ b/test/mobile_app_backend_web/schema_test.exs @@ -436,15 +436,7 @@ defmodule MobileAppBackendWeb.SchemaTest do route_patterns: %{ data: [ %{id: "Green-B-812-0", type: "route_pattern"}, - %{id: "Green-B-812-0_70202_170137_2", type: "route_pattern"}, - %{id: "Green-B-812-1", type: "route_pattern"}, - %{id: "Green-B-812-1_170136_70201_0", type: "route_pattern"}, - %{id: "Green-B-816-0", type: "route_pattern"}, - %{id: "Green-B-816-0_70202_170137_0_70206_70202_0", type: "route_pattern"}, - %{id: "Green-B-816-0_70202_170137_2", type: "route_pattern"}, - %{id: "Green-B-816-1", type: "route_pattern"}, - %{id: "Green-B-816-1_170136_70201_0", type: "route_pattern"}, - %{id: "Green-B-816-1_170136_70201_2_70201_70205_2", type: "route_pattern"} + %{id: "Green-B-812-1", type: "route_pattern"} ] } }, @@ -470,15 +462,7 @@ defmodule MobileAppBackendWeb.SchemaTest do route_patterns: %{ data: [ %{id: "Green-C-832-0", type: "route_pattern"}, - %{id: "Green-C-832-0_70202_70151_2", type: "route_pattern"}, - %{id: "Green-C-832-1", type: "route_pattern"}, - %{id: "Green-C-832-1_70150_70201_0", type: "route_pattern"}, - %{id: "Green-C-836-0", type: "route_pattern"}, - %{id: "Green-C-836-0_70206_70202_0", type: "route_pattern"}, - %{id: "Green-C-836-0_70206_70202_2_70202_70151_2", type: "route_pattern"}, - %{id: "Green-C-836-1", type: "route_pattern"}, - %{id: "Green-C-836-1_70201_70205_0_70150_70201_0", type: "route_pattern"}, - %{id: "Green-C-836-1_70201_70205_2", type: "route_pattern"} + %{id: "Green-C-832-1", type: "route_pattern"} ] } }, @@ -504,27 +488,7 @@ defmodule MobileAppBackendWeb.SchemaTest do route_patterns: %{ data: [ %{id: "Green-D-855-0", type: "route_pattern"}, - %{id: "Green-D-855-0_70206_70202_0", type: "route_pattern"}, - %{id: "Green-D-855-0_70206_70202_2_70202_70151_2", type: "route_pattern"}, - %{id: "Green-D-855-0_70504_70206_2", type: "route_pattern"}, - %{ - id: "Green-D-855-0_70504_70206_2_70206_70202_2_70202_70151_2", - type: "route_pattern" - }, - %{id: "Green-D-855-1", type: "route_pattern"}, - %{id: "Green-D-855-1_70201_70205_0_70150_70201_0", type: "route_pattern"}, - %{id: "Green-D-855-1_70201_70205_2", type: "route_pattern"}, - %{id: "Green-D-855-1_70205_70503_0", type: "route_pattern"}, - %{ - id: "Green-D-855-1_70205_70503_0_70201_70205_0_70150_70201_0", - type: "route_pattern" - }, - %{id: "Green-D-856-0", type: "route_pattern"}, - %{id: "Green-D-856-0_70206_70202_0", type: "route_pattern"}, - %{id: "Green-D-856-0_70206_70202_2_70202_70151_2", type: "route_pattern"}, - %{id: "Green-D-856-1", type: "route_pattern"}, - %{id: "Green-D-856-1_70201_70205_0_70150_70201_0", type: "route_pattern"}, - %{id: "Green-D-856-1_70201_70205_2", type: "route_pattern"} + %{id: "Green-D-855-1", type: "route_pattern"} ] } }, @@ -549,13 +513,8 @@ defmodule MobileAppBackendWeb.SchemaTest do line: %{data: %{id: "line-Green", type: "line"}}, route_patterns: %{ data: [ - %{id: "Green-E-86-1", type: "route_pattern"}, %{id: "Green-E-886-0", type: "route_pattern"}, - %{id: "Green-E-886-0_70206_70260_0", type: "route_pattern"}, - %{id: "Green-E-886-0_70512_70206_2", type: "route_pattern"}, - %{id: "Green-E-886-1", type: "route_pattern"}, - %{id: "Green-E-886-1_70205_70511_0", type: "route_pattern"}, - %{id: "Green-E-886-1_70260_70205_2", type: "route_pattern"} + %{id: "Green-E-886-1", type: "route_pattern"} ] } }, @@ -563,75 +522,6 @@ defmodule MobileAppBackendWeb.SchemaTest do } ], included: [ - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Babcock Street - Boston College", - sort_order: 100_320_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-B-812-0_70202_170137_2", - links: %{self: "/route_patterns/Green-B-812-0_70202_170137_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397501-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-B", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Cleveland Circle - Kenmore", - sort_order: 100_331_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-C-832-1_70150_70201_0", - links: %{self: "/route_patterns/Green-C-832-1_70150_70201_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397576-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-C", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Medford/Tufts - North Station", - sort_order: 100_340_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-856-0_70206_70202_0", - links: %{self: "/route_patterns/Green-D-856-0_70206_70202_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398269-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, %{ attributes: %{ canonical: true, @@ -649,29 +539,6 @@ defmodule MobileAppBackendWeb.SchemaTest do }, type: "route_pattern" }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Kenmore - Cleveland Circle", - sort_order: 100_330_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-C-836-0_70206_70202_2_70202_70151_2", - links: %{self: "/route_patterns/Green-C-836-0_70206_70202_2_70202_70151_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397581-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD22", - type: "trip" - } - }, - route: %{data: %{id: "Green-C", type: "route"}} - }, - type: "route_pattern" - }, %{ attributes: %{ canonical: true, @@ -689,51 +556,6 @@ defmodule MobileAppBackendWeb.SchemaTest do }, type: "route_pattern" }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Riverside - North Station", - sort_order: 100_341_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-855-1_70205_70503_0", - links: %{self: "/route_patterns/Green-D-855-1_70205_70503_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: "58398331-20:30-HayGLHayGLHayGLHayGLNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Medford/Tufts - North Station", - sort_order: 100_320_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-B-816-0_70202_170137_0_70206_70202_0", - links: %{self: "/route_patterns/Green-B-816-0_70202_170137_0_70206_70202_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397599-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD00", - type: "trip" - } - }, - route: %{data: %{id: "Green-B", type: "route"}} - }, - type: "route_pattern" - }, %{ attributes: %{ canonical: true, @@ -751,138 +573,6 @@ defmodule MobileAppBackendWeb.SchemaTest do }, type: "route_pattern" }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "North Station - Medford/Tufts", - sort_order: 100_331_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-C-836-1_70201_70205_2", - links: %{self: "/route_patterns/Green-C-836-1_70201_70205_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397582-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-C", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Riverside - Kenmore", - sort_order: 100_341_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-856-1_70201_70205_0_70150_70201_0", - links: %{self: "/route_patterns/Green-D-856-1_70201_70205_0_70150_70201_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398268-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD00", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Medford/Tufts - North Station", - sort_order: 100_330_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-C-836-0_70206_70202_0", - links: %{self: "/route_patterns/Green-C-836-0_70206_70202_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397581-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-C", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "East Somerville - Medford/Tufts", - sort_order: 100_351_040, - time_desc: nil, - typicality: 3 - }, - id: "Green-E-86-1", - links: %{self: "/route_patterns/Green-E-86-1"}, - relationships: %{ - representative_trip: %{data: %{id: "58485809", type: "trip"}}, - route: %{data: %{id: "Green-E", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Babcock Street - Boston College", - sort_order: 100_320_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-B-816-0_70202_170137_2", - links: %{self: "/route_patterns/Green-B-816-0_70202_170137_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397599-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-B", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Boston College - Babcock Street", - sort_order: 100_321_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-B-816-1_170136_70201_0", - links: %{self: "/route_patterns/Green-B-816-1_170136_70201_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397508-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-B", type: "route"}} - }, - type: "route_pattern" - }, %{ attributes: %{ canonical: true, @@ -917,351 +607,6 @@ defmodule MobileAppBackendWeb.SchemaTest do }, type: "route_pattern" }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "North Station - Medford/Tufts", - sort_order: 100_341_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-856-1_70201_70205_2", - links: %{self: "/route_patterns/Green-D-856-1_70201_70205_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398268-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Medford/Tufts - Cleveland Circle", - sort_order: 100_330_040, - time_desc: "Mornings only", - typicality: 3 - }, - id: "Green-C-836-0", - links: %{self: "/route_patterns/Green-C-836-0"}, - relationships: %{ - representative_trip: %{data: %{id: "58485660", type: "trip"}}, - route: %{data: %{id: "Green-C", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "North Station - Heath Street", - sort_order: 100_350_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-E-886-0_70512_70206_2", - links: %{self: "/route_patterns/Green-E-886-0_70512_70206_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: "58397680-20:30-HayGLHayGLHayGLHayGLNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-E", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "North Station - Riverside", - sort_order: 100_340_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-855-0_70504_70206_2", - links: %{self: "/route_patterns/Green-D-855-0_70504_70206_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: "58398315-20:30-HayGLHayGLHayGLHayGLNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Boston College - Medford/Tufts", - sort_order: 100_321_040, - time_desc: "Early mornings only", - typicality: 3 - }, - id: "Green-B-816-1", - links: %{self: "/route_patterns/Green-B-816-1"}, - relationships: %{ - representative_trip: %{data: %{id: "58486222", type: "trip"}}, - route: %{data: %{id: "Green-B", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Kenmore - Riverside", - sort_order: 100_340_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-855-0_70504_70206_2_70206_70202_2_70202_70151_2", - links: %{ - self: "/route_patterns/Green-D-855-0_70504_70206_2_70206_70202_2_70202_70151_2" - }, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398315-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD222", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Union Square - North Station", - sort_order: 100_340_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-855-0_70206_70202_0", - links: %{self: "/route_patterns/Green-D-855-0_70206_70202_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398254-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Heath Street - North Station", - sort_order: 100_351_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-E-886-1_70205_70511_0", - links: %{self: "/route_patterns/Green-E-886-1_70205_70511_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: "58397671-20:30-HayGLHayGLHayGLHayGLNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-E", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Riverside - Kenmore", - sort_order: 100_341_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-855-1_70205_70503_0_70201_70205_0_70150_70201_0", - links: %{ - self: "/route_patterns/Green-D-855-1_70205_70503_0_70201_70205_0_70150_70201_0" - }, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398331-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD000", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "North Station - Union Square", - sort_order: 100_341_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-855-1_70201_70205_2", - links: %{self: "/route_patterns/Green-D-855-1_70201_70205_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398253-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Medford/Tufts - North Station", - sort_order: 100_350_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-E-886-0_70206_70260_0", - links: %{self: "/route_patterns/Green-E-886-0_70206_70260_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397672-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-E", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Kenmore - Cleveland Circle", - sort_order: 100_330_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-C-832-0_70202_70151_2", - links: %{self: "/route_patterns/Green-C-832-0_70202_70151_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397575-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-C", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Cleveland Circle - Medford/Tufts", - sort_order: 100_331_040, - time_desc: "Early mornings only", - typicality: 3 - }, - id: "Green-C-836-1", - links: %{self: "/route_patterns/Green-C-836-1"}, - relationships: %{ - representative_trip: %{data: %{id: "58485669", type: "trip"}}, - route: %{data: %{id: "Green-C", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "North Station - Medford/Tufts", - sort_order: 100_321_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-B-816-1_170136_70201_2_70201_70205_2", - links: %{self: "/route_patterns/Green-B-816-1_170136_70201_2_70201_70205_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397508-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD22", - type: "trip" - } - }, - route: %{data: %{id: "Green-B", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "North Station - Medford/Tufts", - sort_order: 100_351_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-E-886-1_70260_70205_2", - links: %{self: "/route_patterns/Green-E-886-1_70260_70205_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397597-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD2", - type: "trip" - } - }, - route: %{data: %{id: "Green-E", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Medford/Tufts - Boston College", - sort_order: 100_320_040, - time_desc: "Mornings only", - typicality: 3 - }, - id: "Green-B-816-0", - links: %{self: "/route_patterns/Green-B-816-0"}, - relationships: %{ - representative_trip: %{data: %{id: "58486194", type: "trip"}}, - route: %{data: %{id: "Green-B", type: "route"}} - }, - type: "route_pattern" - }, %{ attributes: %{ canonical: true, @@ -1279,115 +624,6 @@ defmodule MobileAppBackendWeb.SchemaTest do }, type: "route_pattern" }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Riverside - Medford/Tufts", - sort_order: 100_341_040, - time_desc: "Weekday early mornings only", - typicality: 3 - }, - id: "Green-D-856-1", - links: %{self: "/route_patterns/Green-D-856-1"}, - relationships: %{ - representative_trip: %{data: %{id: "58398268", type: "trip"}}, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Boston College - Babcock Street", - sort_order: 100_321_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-B-812-1_170136_70201_0", - links: %{self: "/route_patterns/Green-B-812-1_170136_70201_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397500-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD0", - type: "trip" - } - }, - route: %{data: %{id: "Green-B", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Kenmore - Riverside", - sort_order: 100_340_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-855-0_70206_70202_2_70202_70151_2", - links: %{self: "/route_patterns/Green-D-855-0_70206_70202_2_70202_70151_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398254-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD22", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Kenmore - Riverside", - sort_order: 100_340_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-856-0_70206_70202_2_70202_70151_2", - links: %{self: "/route_patterns/Green-D-856-0_70206_70202_2_70202_70151_2"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398269-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD22", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Riverside - Kenmore", - sort_order: 100_341_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-D-855-1_70201_70205_0_70150_70201_0", - links: %{self: "/route_patterns/Green-D-855-1_70201_70205_0_70150_70201_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58398253-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD00", - type: "trip" - } - }, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, %{ attributes: %{ canonical: true, @@ -1405,46 +641,6 @@ defmodule MobileAppBackendWeb.SchemaTest do }, type: "route_pattern" }, - %{ - attributes: %{ - canonical: false, - direction_id: 0, - name: "Medford/Tufts - Riverside", - sort_order: 100_340_040, - time_desc: "Weekday mornings only", - typicality: 3 - }, - id: "Green-D-856-0", - links: %{self: "/route_patterns/Green-D-856-0"}, - relationships: %{ - representative_trip: %{data: %{id: "58398269", type: "trip"}}, - route: %{data: %{id: "Green-D", type: "route"}} - }, - type: "route_pattern" - }, - %{ - attributes: %{ - canonical: false, - direction_id: 1, - name: "Cleveland Circle - Kenmore", - sort_order: 100_331_990, - time_desc: nil, - typicality: 4 - }, - id: "Green-C-836-1_70201_70205_0_70150_70201_0", - links: %{self: "/route_patterns/Green-C-836-1_70201_70205_0_70150_70201_0"}, - relationships: %{ - representative_trip: %{ - data: %{ - id: - "58397582-20:30-BabcockGovernmentCenterGovtCtrKenmoreCDGovtCtrNorthStaGovtCtrNorthStaHeathNorthSusNorthMedfordNorthUnionSuspendD00", - type: "trip" - } - }, - route: %{data: %{id: "Green-C", type: "route"}} - }, - type: "route_pattern" - }, %{ attributes: %{ canonical: true, From 67bf135c5f7502c4a19cb5d7422ec1c6c0dc877d Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Thu, 30 Nov 2023 14:18:54 -0700 Subject: [PATCH 16/16] remove doors, nodes, and fare vending machines --- test/mobile_app_backend_web/schema_test.exs | 311 +------------------- 1 file changed, 2 insertions(+), 309 deletions(-) diff --git a/test/mobile_app_backend_web/schema_test.exs b/test/mobile_app_backend_web/schema_test.exs index 6a3c6b2d..5d8c36f1 100644 --- a/test/mobile_app_backend_web/schema_test.exs +++ b/test/mobile_app_backend_web/schema_test.exs @@ -44,25 +44,11 @@ defmodule MobileAppBackendWeb.SchemaTest do child_stops: %{ data: [ %{id: "70158", type: "stop"}, - %{id: "70159", type: "stop"}, - %{id: "door-boyls-inbound", type: "stop"}, - %{id: "door-boyls-outbound", type: "stop"}, - %{id: "node-boyls-in-farepaid", type: "stop"}, - %{id: "node-boyls-in-fareunpaid", type: "stop"}, - %{id: "node-boyls-instair-platform", type: "stop"}, - %{id: "node-boyls-out-farepaid", type: "stop"}, - %{id: "node-boyls-out-fareunpaid", type: "stop"}, - %{id: "node-boyls-outstair-platform", type: "stop"} + %{id: "70159", type: "stop"} ] }, facilities: %{ - data: [ - %{id: "fvm-201221", type: "facility"}, - %{id: "fvm-201222", type: "facility"}, - %{id: "fvm-201223", type: "facility"}, - %{id: "fvm-201224", type: "facility"}, - %{id: "fvm-202157", type: "facility"} - ], + data: [], links: %{related: "/facilities/?filter[stop]=place-boyls"} }, parent_station: %{data: nil}, @@ -114,299 +100,6 @@ defmodule MobileAppBackendWeb.SchemaTest do zone: %{data: %{id: "RapidTransit", type: "zone"}} }, type: "stop" - }, - %{ - attributes: %{ - address: nil, - description: "Boylston - Boston Common, Street", - latitude: 42.352531, - location_type: 2, - longitude: -71.064685, - municipality: "Boston", - name: "Boylston - Boston Common, Street", - platform_code: nil, - platform_name: nil, - wheelchair_boarding: 2 - }, - id: "door-boyls-inbound", - links: %{self: "/stops/door-boyls-inbound"}, - relationships: %{ - facilities: %{ - links: %{related: "/facilities/?filter[stop]=door-boyls-inbound"} - }, - parent_station: %{data: %{id: "place-boyls", type: "stop"}}, - zone: %{data: nil} - }, - type: "stop" - }, - %{ - attributes: %{ - address: nil, - description: "Boylston - Boston Common, Street", - latitude: 42.353214, - location_type: 2, - longitude: -71.064546, - municipality: "Boston", - name: "Boylston - Boston Common, Street", - platform_code: nil, - platform_name: nil, - wheelchair_boarding: 2 - }, - id: "door-boyls-outbound", - links: %{self: "/stops/door-boyls-outbound"}, - relationships: %{ - facilities: %{ - links: %{related: "/facilities/?filter[stop]=door-boyls-outbound"} - }, - parent_station: %{data: %{id: "place-boyls", type: "stop"}}, - zone: %{data: nil} - }, - type: "stop" - }, - %{ - attributes: %{ - latitude: nil, - long_name: "Boylston fare vending machine 201221", - longitude: nil, - properties: [ - %{name: "enclosed", value: 1}, - %{name: "excludes-stop", value: "door-boyls-outbound"}, - %{name: "payment-form-accepted", value: "cash"}, - %{name: "payment-form-accepted", value: "credit-debit-card"} - ], - type: "FARE_VENDING_MACHINE" - }, - id: "fvm-201221", - links: %{self: "/facilities/fvm-201221"}, - relationships: %{ - stop: %{data: %{id: "place-boyls", type: "stop"}} - }, - type: "facility" - }, - %{ - attributes: %{ - latitude: nil, - long_name: "Boylston fare vending machine 201222", - longitude: nil, - properties: [ - %{name: "enclosed", value: 1}, - %{name: "excludes-stop", value: "door-boyls-outbound"}, - %{name: "payment-form-accepted", value: "cash"}, - %{name: "payment-form-accepted", value: "credit-debit-card"} - ], - type: "FARE_VENDING_MACHINE" - }, - id: "fvm-201222", - links: %{self: "/facilities/fvm-201222"}, - relationships: %{ - stop: %{data: %{id: "place-boyls", type: "stop"}} - }, - type: "facility" - }, - %{ - attributes: %{ - latitude: nil, - long_name: "Boylston fare vending machine 201223", - longitude: nil, - properties: [ - %{name: "enclosed", value: 1}, - %{name: "excludes-stop", value: "door-boyls-inbound"}, - %{name: "payment-form-accepted", value: "cash"}, - %{name: "payment-form-accepted", value: "credit-debit-card"} - ], - type: "FARE_VENDING_MACHINE" - }, - id: "fvm-201223", - links: %{self: "/facilities/fvm-201223"}, - relationships: %{ - stop: %{data: %{id: "place-boyls", type: "stop"}} - }, - type: "facility" - }, - %{ - attributes: %{ - latitude: nil, - long_name: "Boylston fare vending machine 201224", - longitude: nil, - properties: [ - %{name: "enclosed", value: 1}, - %{name: "excludes-stop", value: "door-boyls-inbound"}, - %{name: "payment-form-accepted", value: "cash"}, - %{name: "payment-form-accepted", value: "credit-debit-card"} - ], - type: "FARE_VENDING_MACHINE" - }, - id: "fvm-201224", - links: %{self: "/facilities/fvm-201224"}, - relationships: %{ - stop: %{data: %{id: "place-boyls", type: "stop"}} - }, - type: "facility" - }, - %{ - attributes: %{ - latitude: nil, - long_name: "Boylston fare vending machine 202157", - longitude: nil, - properties: [ - %{name: "enclosed", value: 1}, - %{name: "excludes-stop", value: "door-boyls-inbound"}, - %{name: "payment-form-accepted", value: "credit-debit-card"} - ], - type: "FARE_VENDING_MACHINE" - }, - id: "fvm-202157", - links: %{self: "/facilities/fvm-202157"}, - relationships: %{ - stop: %{data: %{id: "place-boyls", type: "stop"}} - }, - type: "facility" - }, - %{ - attributes: %{ - address: nil, - description: "Boylston - Paid side of fare gates", - latitude: nil, - location_type: 3, - longitude: nil, - municipality: "Boston", - name: "Boylston", - platform_code: nil, - platform_name: nil, - wheelchair_boarding: 1 - }, - id: "node-boyls-in-farepaid", - links: %{self: "/stops/node-boyls-in-farepaid"}, - relationships: %{ - facilities: %{ - links: %{related: "/facilities/?filter[stop]=node-boyls-in-farepaid"} - }, - parent_station: %{data: %{id: "place-boyls", type: "stop"}}, - zone: %{data: nil} - }, - type: "stop" - }, - %{ - attributes: %{ - address: nil, - description: "Boylston - Unpaid side of fare gates", - latitude: nil, - location_type: 3, - longitude: nil, - municipality: "Boston", - name: "Boylston", - platform_code: nil, - platform_name: nil, - wheelchair_boarding: 1 - }, - id: "node-boyls-in-fareunpaid", - links: %{self: "/stops/node-boyls-in-fareunpaid"}, - relationships: %{ - facilities: %{ - links: %{related: "/facilities/?filter[stop]=node-boyls-in-fareunpaid"} - }, - parent_station: %{data: %{id: "place-boyls", type: "stop"}}, - zone: %{data: nil} - }, - type: "stop" - }, - %{ - attributes: %{ - address: nil, - description: "Boylston - Bottom of Park Street & East stairs", - latitude: nil, - location_type: 3, - longitude: nil, - municipality: "Boston", - name: "Boylston", - platform_code: nil, - platform_name: nil, - wheelchair_boarding: 1 - }, - id: "node-boyls-instair-platform", - links: %{self: "/stops/node-boyls-instair-platform"}, - relationships: %{ - facilities: %{ - links: %{related: "/facilities/?filter[stop]=node-boyls-instair-platform"} - }, - parent_station: %{data: %{id: "place-boyls", type: "stop"}}, - zone: %{data: nil} - }, - type: "stop" - }, - %{ - attributes: %{ - address: nil, - description: "Boylston - Paid side of fare gates", - latitude: nil, - location_type: 3, - longitude: nil, - municipality: "Boston", - name: "Boylston", - platform_code: nil, - platform_name: nil, - wheelchair_boarding: 1 - }, - id: "node-boyls-out-farepaid", - links: %{self: "/stops/node-boyls-out-farepaid"}, - relationships: %{ - facilities: %{ - links: %{related: "/facilities/?filter[stop]=node-boyls-out-farepaid"} - }, - parent_station: %{data: %{id: "place-boyls", type: "stop"}}, - zone: %{data: nil} - }, - type: "stop" - }, - %{ - attributes: %{ - address: nil, - description: "Boylston - Unpaid side of fare gates", - latitude: nil, - location_type: 3, - longitude: nil, - municipality: "Boston", - name: "Boylston", - platform_code: nil, - platform_name: nil, - wheelchair_boarding: 1 - }, - id: "node-boyls-out-fareunpaid", - links: %{self: "/stops/node-boyls-out-fareunpaid"}, - relationships: %{ - facilities: %{ - links: %{related: "/facilities/?filter[stop]=node-boyls-out-fareunpaid"} - }, - parent_station: %{data: %{id: "place-boyls", type: "stop"}}, - zone: %{data: nil} - }, - type: "stop" - }, - %{ - attributes: %{ - address: nil, - description: "Boylston - Bottom of Copley & West stairs", - latitude: nil, - location_type: 3, - longitude: nil, - municipality: "Boston", - name: "Boylston", - platform_code: nil, - platform_name: nil, - wheelchair_boarding: 1 - }, - id: "node-boyls-outstair-platform", - links: %{self: "/stops/node-boyls-outstair-platform"}, - relationships: %{ - facilities: %{ - links: %{ - related: "/facilities/?filter[stop]=node-boyls-outstair-platform" - } - }, - parent_station: %{data: %{id: "place-boyls", type: "stop"}}, - zone: %{data: nil} - }, - type: "stop" } ], jsonapi: %{version: "1.0"}