import { beforeEach, describe, expect, jest, test } from "@jest/globals"; import { ApiBasedRepository, ApiBasedRepositoryCache, ApiBasedRepositoryMillisecondTTLs } from "../../src/repositories/ApiBasedRepository"; import { IEta } from "../../src/entities/entities"; /** * Update the global fetch function to return a specific object. * @param obj */ function updateGlobalFetchMockJson(obj: any) { // @ts-ignore global.fetch = jest.fn(() => { return Promise.resolve({ json: () => Promise.resolve(obj) }) }) as jest.Mock; } /** * Reset the global fetch function mock's JSON to return an empty object. * @param obj */ function resetGlobalFetchMockJson() { updateGlobalFetchMockJson({}) } beforeEach(() => { resetGlobalFetchMockJson(); }) // Snapshot taken from the Passio GO! API const genericEtaDataByStopId = { "177666": [ { "OOS": 0, "busName": "08", "distance": 1, "speed": 10.028535400123669, "routeBlockId": "142270", "actualRouteBlockId": "142270", "arrived": null, "eta": "10 min ", "color": "#000000", "bg": "#ffea3f", "order": 0, "dwell": null, "stopsAmount": 2, "secondsSpent": 587, "etaR": "10", "error": null, "outdated": 0, "routeId": "53966", "serviceTime": "", "scheduleTimes": [], "goShowSchedule": 0, "looping": "1", "routeGroupId": "6703", "busId": 5577, "tripId": 751430, "deviceId": 402840, "created": "2025-01-07 15:00:09", "routePointPosition": 6, "routeStopPosition": 1, "stopRoutePointPosition": 217, "timezoneOffset": -10800, "busLatLng": [ 33.7933406, -117.8539321 ], "busProjectionLatlng": { "lat": 33.79331052666975, "lng": -117.85392945849208 }, "busProjectionError": 3, "stopId": "177666", "theStop": { "name": "Chapman Court", "position": 3, "userId": "263", "routeStopId": "1348785", "busId": 5577, "routeName": "Red Route", "shortName": null, "routeId": "53966", "stopId": "177666" } }, { "OOS": 0, "busName": "07", "distance": 1, "speed": 12.160256921380398, "routeBlockId": "142270", "actualRouteBlockId": "142270", "arrived": null, "eta": "11 min ", "color": "#000000", "bg": "#ffea3f", "order": 0, "dwell": null, "stopsAmount": 2, "secondsSpent": 635, "etaR": "11", "error": null, "outdated": 0, "routeId": "53966", "serviceTime": "", "scheduleTimes": [], "goShowSchedule": 0, "looping": "1", "routeGroupId": "6703", "busId": 5576, "tripId": 751430, "deviceId": 441065, "created": "2025-01-07 15:00:10", "routePointPosition": 448, "routeStopPosition": 4, "stopRoutePointPosition": 217, "timezoneOffset": -10800, "busLatLng": [ 33.7933284, -117.855132 ], "busProjectionLatlng": { "lat": 33.79332033922653, "lng": -117.85513217762522 }, "busProjectionError": 1, "stopId": "177666", "theStop": { "name": "Chapman Court", "position": 3, "userId": "263", "routeStopId": "1348785", "busId": 5576, "routeName": "Red Route", "shortName": null, "routeId": "53966", "stopId": "177666" } }, ] } describe("getEtaForShuttleAndStopId", () => { test("getEtaForShuttleAndStopId returns correct ETA data", async () => { updateGlobalFetchMockJson(genericEtaDataByStopId); const repository = new ApiBasedRepository(); const result = await repository.getEtaForShuttleAndStopId("5577", "177666"); expect(result?.secondsRemaining).toEqual(587); expect(result?.millisecondsSinceEpoch).toBeDefined(); }); test("getEtaForShuttleAndStopId returns null if API call is invalid and cache is empty", async () => { const repository = new ApiBasedRepository(); const result = await repository.getEtaForShuttleAndStopId("5577", "177666"); expect(result).toEqual(null); }); }); describe("getEtasForShuttleId", () => { test("getEtasForShuttleId returns correct ETA data", async () => { // Because I'm testing updateEtasForSystemIfTTL separately, // stub it out here updateGlobalFetchMockJson(genericEtaDataByStopId); const initialCache: ApiBasedRepositoryCache = { etasForStopId: {}, etasForShuttleId: { "5577": [ { secondsRemaining: 587, shuttleId: "5577", stopId: "177666", millisecondsSinceEpoch: Date.now(), } ] }, }; const ttls: ApiBasedRepositoryMillisecondTTLs = { etasForShuttleId: 100000, etasForStopId: 100000, }; const repository = new ApiBasedRepository(initialCache, ttls); repository.updateEtasForSystemIfTTL = jest.fn(async () => { }); const result = await repository.getEtasForShuttleId("5577"); expect(result).toEqual(initialCache.etasForShuttleId); }); test("getEtasForShuttleId returns empty array if no data available", async () => { const repository = new ApiBasedRepository(); repository.updateEtasForSystemIfTTL = jest.fn(async () => { }); const result = await repository.getEtasForShuttleId("5577"); expect(result).toEqual([]); }); }); describe("getEtasForStopId", () => { test("getEtasForStopId returns correct ETA data", async () => { // Because I'm testing updateEtasForSystemIfTTL separately, // stub it out here updateGlobalFetchMockJson(genericEtaDataByStopId); const initialCache: ApiBasedRepositoryCache = { etasForStopId: { "177666": [ { secondsRemaining: 587, shuttleId: "5577", stopId: "177666", millisecondsSinceEpoch: Date.now(), } ] }, etasForShuttleId: {} }; const ttls: ApiBasedRepositoryMillisecondTTLs = { etasForShuttleId: 100000, etasForStopId: 100000, }; const repository = new ApiBasedRepository(initialCache, ttls); repository.updateEtasForSystemIfTTL = jest.fn(async () => { }); const result = await repository.getEtasForStopId("177666"); expect(result).toEqual(initialCache.etasForShuttleId); }); test("getEtasForStopId returns empty array if no data available", async () => { const repository = new ApiBasedRepository(); repository.updateEtasForSystemIfTTL = jest.fn(async () => { }); const result = await repository.getEtasForShuttleId("5577"); expect(result).toEqual([]); }); }); describe("updateEtasForSystemIfTTL", () => { test("updateEtasForSystemIfTTL does nothing if data is not TTL", async () => { updateGlobalFetchMockJson(genericEtaDataByStopId); // If ETA data is not TTL, then don't do anything const expectedEta: IEta = { secondsRemaining: 587, shuttleId: "5577", stopId: "177666", millisecondsSinceEpoch: Date.now() - 1000, }; const initialCache: ApiBasedRepositoryCache = { etasForShuttleId: { "5577": [ expectedEta, ], }, etasForStopId: { "177666": [ expectedEta, ], }, stopsBySystemId: { "1": [ { systemId: "1", millisecondsSinceEpoch: Date.now() - 1000, name: "Chapman Court", id: "177666", coordinates: { latitude: 33.796796, longitude: -117.889293 }, } ], }, }; const ttls: ApiBasedRepositoryMillisecondTTLs = { etasForShuttleId: 100000, etasForStopId: 100000, }; const repository = new ApiBasedRepository(initialCache, ttls); await repository.updateEtasForSystemIfTTL("1"); const updatedResult = await repository.getEtaForShuttleAndStopId( "5577", "177666", ); expect(updatedResult?.millisecondsSinceEpoch).toEqual(expectedEta.millisecondsSinceEpoch); }); test("updateEtasForSystemIfTTL updates all ETA data if data is TTL", async () => { updateGlobalFetchMockJson(genericEtaDataByStopId); const repository = new ApiBasedRepository(); repository.getStopsBySystemId = jest.fn(async () => { return [ { name: "Chapman Court", systemId: "1", id: "177666", coordinates: { latitude: 33.796796, longitude: -117.889293 }, } ]; }); await repository.updateEtasForSystemIfTTL("1"); const updatedResult = await repository.getEtasForStopId("177666"); expect(updatedResult.length).toEqual(2); }); }); describe("getShuttleById", () => { test("getShuttleById returns null if unseeded cache", async () => { }); test("getShuttleById returns old data if not expired", async () => { }); test("getShuttleById returns fresh data if expired", async () => { }); });