import { beforeEach, describe, expect, it, jest, test } from "@jest/globals"; import { ApiBasedShuttleRepositoryLoader, ApiResponseError } from "../../src/loaders/ApiBasedShuttleRepositoryLoader"; import { UnoptimizedInMemoryShuttleRepository } from "../../src/repositories/UnoptimizedInMemoryShuttleRepository"; import { fetchSystemDataSuccessfulResponse } from "../jsonSnapshots/fetchSystemData/fetchSystemDataSuccessfulResponse"; import { fetchSystemDataFailedResponse } from "../jsonSnapshots/fetchSystemData/fetchSystemDataFailedResponse"; import { fetchRouteDataSuccessfulResponse } from "../jsonSnapshots/fetchRouteData/fetchRouteDataSuccessfulResponse"; import { fetchStopAndPolylineDataSuccessfulResponse } from "../jsonSnapshots/fetchStopAndPolylineData/fetchStopAndPolylineDataSuccessfulResponse"; import { generateMockRoutes, generateMockShuttles, generateMockStops, generateMockPassioSystems } from "../testHelpers/mockDataGenerators"; import { fetchShuttleDataSuccessfulResponse } from "../jsonSnapshots/fetchShuttleData/fetchShuttleDataSuccessfulResponse"; import { fetchEtaDataSuccessfulResponse } from "../jsonSnapshots/fetchEtaData/fetchEtaDataSuccessfulResponse"; import { resetGlobalFetchMockJson, updateGlobalFetchMockJson, updateGlobalFetchMockJsonToThrowSyntaxError } from "../testHelpers/fetchMockHelpers"; async function assertAsyncCallbackThrowsApiResponseError(callback: () => Promise) { await expect(callback).rejects.toThrow(ApiResponseError); } describe("ApiBasedRepositoryLoader", () => { let loader: ApiBasedShuttleRepositoryLoader; beforeEach(() => { loader = new ApiBasedShuttleRepositoryLoader("263", new UnoptimizedInMemoryShuttleRepository()); resetGlobalFetchMockJson(); }); describe("fetchAndUpdateRouteDataForSystem", () => { const systemId = "263"; it("updates route data in repository if response received", async () => { // Arrange // Test pruning const routesToPrune = generateMockRoutes(); await Promise.all(routesToPrune.map(async (route) => { route.systemId = systemId; await loader.repository.addOrUpdateRoute(route); })); updateGlobalFetchMockJson(fetchRouteDataSuccessfulResponse); // Act await loader.fetchAndUpdateRouteDataForSystem(); // Assert const routes = await loader.repository.getRoutesBySystemId(systemId); expect(routes.length).toEqual(fetchRouteDataSuccessfulResponse.all.length) }); it("throws the correct error if the API response contains no data", async () => { // The Passio API returns some invalid JSON if there is no data, // so simulate a JSON parsing error updateGlobalFetchMockJsonToThrowSyntaxError(); await assertAsyncCallbackThrowsApiResponseError(async () => { await loader.fetchAndUpdateRouteDataForSystem(); }); }); }); describe("fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId", () => { const systemId = "263"; it("updates stop and polyline data if response received", async () => { // Arrange // Test pruning of stops only const stopsToPrune = generateMockStops(); await Promise.all(stopsToPrune.map(async (stop) => { stop.systemId = systemId; await loader.repository.addOrUpdateStop(stop); })); updateGlobalFetchMockJson(fetchStopAndPolylineDataSuccessfulResponse); const stopsArray = Object.values(fetchStopAndPolylineDataSuccessfulResponse.stops); await loader.fetchAndUpdateStopAndPolylineDataForRoutesInSystem(); const stops = await loader.repository.getStopsBySystemId(systemId); expect(stops.length).toEqual(stopsArray.length); await Promise.all(stops.map(async (stop) => { const orderedStops = await loader.repository.getOrderedStopsByStopId(stop.id) expect(orderedStops.length).toBeGreaterThan(0); })); const routes = await loader.repository.getRoutesBySystemId(systemId); routes.forEach((route) => { expect(route.polylineCoordinates.length).toBeGreaterThan(0); }); }); it("throws the correct error if the API response contains no data", async () => { updateGlobalFetchMockJsonToThrowSyntaxError(); await assertAsyncCallbackThrowsApiResponseError(async () => { await loader.fetchAndUpdateStopAndPolylineDataForRoutesInSystem(); }); }) }); describe("fetchAndUpdateShuttleDataForSystem", () => { const systemId = "263"; it("updates shuttle data in repository if response received", async () => { const shuttlesToPrune = generateMockShuttles(); await Promise.all(shuttlesToPrune.map(async (shuttle) => { shuttle.systemId = systemId; await loader.repository.addOrUpdateShuttle(shuttle); })) updateGlobalFetchMockJson(fetchShuttleDataSuccessfulResponse); const busesInResponse = Object.values(fetchShuttleDataSuccessfulResponse.buses); await loader.fetchAndUpdateShuttleDataForSystem(); const shuttles = await loader.repository.getShuttlesBySystemId(systemId); expect(shuttles.length).toEqual(busesInResponse.length); }); it("throws the correct error if the API response contains no data", async () => { updateGlobalFetchMockJsonToThrowSyntaxError(); await assertAsyncCallbackThrowsApiResponseError(async () => { await loader.fetchAndUpdateShuttleDataForSystem(); }); }); }); describe("fetchAndUpdateEtaDataForExistingStopsForSystem", () => { it("calls fetchAndUpdateEtaDataForStopId for every stop in repository", async () => { const spy = jest.spyOn(loader, "fetchAndUpdateEtaDataForStopId"); const stops = generateMockStops(); stops.forEach((stop) => { stop.systemId = "263"; }); await Promise.all(stops.map(async (stop) => { await loader.repository.addOrUpdateStop(stop); })); await loader.fetchAndUpdateEtaDataForExistingStopsForSystem(); expect(spy.mock.calls.length).toEqual(stops.length); }); }); describe("fetchAndUpdateEtaDataForStopId", () => { const stopId = "177666"; it("updates ETA data for stop id if response received", async () => { updateGlobalFetchMockJson(fetchEtaDataSuccessfulResponse); // @ts-ignore const etasFromResponse = fetchEtaDataSuccessfulResponse.ETAs[stopId] await loader.fetchAndUpdateEtaDataForStopId(stopId); const etas = await loader.repository.getEtasForStopId(stopId); expect(etas.length).toEqual(etasFromResponse.length); }); it("throws the correct error if the API response contains no data", async () => { updateGlobalFetchMockJsonToThrowSyntaxError(); await assertAsyncCallbackThrowsApiResponseError(async () => { await loader.fetchAndUpdateEtaDataForStopId("263"); }); }); }); });