import { beforeEach, describe, expect, jest, test } from "@jest/globals"; import { ApiBasedRepository, ApiBasedRepositoryCache, ApiBasedRepositoryMillisecondTTLs } from "../../src/repositories/ApiBasedRepository"; import { IEta, IShuttle, IStop } from "../../src/entities/entities"; import { genericEtaDataByStopId } from "../jsonSnapshots/genericEtaDataBySystemId"; import { genericShuttleDataBySystemId } from "../jsonSnapshots/genericShuttleDataBySystemId"; /** * 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(); }) describe("getEtaForShuttleAndStopId", () => { test("getEtaForShuttleAndStopId returns correct ETA data", async () => { updateGlobalFetchMockJson(genericEtaDataByStopId); const initialCache: ApiBasedRepositoryCache = { etasForStopId: { "177666": [ { secondsRemaining: 587, shuttleId: "5577", stopId: "177666", millisecondsSinceEpoch: Date.now(), }, { secondsRemaining: 226, shuttleId: "9909", stopId: "177666", millisecondsSinceEpoch: Date.now(), } ], }, } const repository = new ApiBasedRepository(initialCache); repository.getShuttleById = jest.fn(async () => { const shuttle: IShuttle = { id: "5577", name: "08", coordinates: { latitude: 33.7933406, longitude: -117.8539321, }, routeId: "53966", systemId: "1", }; return shuttle; }); repository.updateEtasForSystemIfTTL = jest.fn(async () => { }); const result = await repository.getEtaForShuttleAndStopId("5577", "177666"); expect(result?.secondsRemaining).toEqual(587); expect(result?.millisecondsSinceEpoch).toBeDefined(); expect(result?.shuttleId).toEqual("5577"); expect(result?.stopId).toEqual("177666"); }); 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 () => { 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 () => { }); repository.getShuttleById = jest.fn(async () => { const shuttle: IShuttle = { id: "5577", name: "08", coordinates: { latitude: 33.7933406, longitude: -117.8539321, }, routeId: "53966", systemId: "1", }; return shuttle; }); const result = await repository.getEtasForShuttleId("5577"); // @ts-ignore expect(result).toEqual(initialCache.etasForShuttleId["5577"]); }); 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.getStopById = jest.fn(async () => { const stop: IStop = { name: "Chapman Court", systemId: "1", id: "177666", coordinates: { latitude: 33.796796, longitude: -117.889293 }, }; return stop; }); repository.updateEtasForSystemIfTTL = jest.fn(async () => { }); const result = await repository.getEtasForStopId("177666"); expect(result).toEqual(initialCache.etasForStopId!["177666"]); }); 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 sampleStop: IStop = { name: "Chapman Court", systemId: "1", id: "177666", coordinates: { latitude: 33.796796, longitude: -117.889293 }, } const repository = new ApiBasedRepository(); repository.getStopsBySystemId = jest.fn(async () => { return [ sampleStop ]; }); repository.getStopById = jest.fn(async () => { return sampleStop; }); 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 () => { updateGlobalFetchMockJson(genericShuttleDataBySystemId); const initialCache: ApiBasedRepositoryCache = {}; const repository = new ApiBasedRepository(initialCache); const shuttle = await repository.getShuttleById("5577"); expect(shuttle).toBeNull(); }); test("getShuttleById returns data if present", async () => { updateGlobalFetchMockJson(genericShuttleDataBySystemId); const initialCacheShuttle = { coordinates: { latitude: 33.7917818, longitude: -117.8589646, }, name: "08", routeId: "53966", systemId: "1", id: "5577", } const initialCache: ApiBasedRepositoryCache = { shuttleByShuttleId: { "5577": initialCacheShuttle } }; const ttls: ApiBasedRepositoryMillisecondTTLs = { shuttleByShuttleId: 1000, }; const repository = new ApiBasedRepository(initialCache, ttls); repository.updateStopsForSystemIdIfTTL = jest.fn(async () => { }) const shuttle = await repository.getShuttleById("5577"); expect(shuttle).toEqual(initialCacheShuttle); }); }); // TODO: enable when implemented // describe("getShuttlesBySystemId", () => { // test("getShuttlesBySystemId returns old data if not expired", async () => { // updateGlobalFetchMockJson(genericShuttleDataBySystemId); // // const initialCacheShuttle = { // coordinates: { // latitude: 33.791781, // longitude: -117.8589646, // }, // name: "08", // routeId: "53966", // systemId: "1", // id: "5577", // millisecondsSinceEpoch: Date.now() - 1000, // }; // // const initialCache: ApiBasedRepositoryCache = { // shuttlesBySystemId: { // "1": [ // initialCacheShuttle, // ] // }, // shuttleByShuttleId: { // "5577": initialCacheShuttle, // } // }; // // const ttls: ApiBasedRepositoryMillisecondTTLs = { // shuttleByShuttleId: 100000, // shuttlesBySystemId: 100000, // }; // // const repository = new ApiBasedRepository(initialCache, ttls); // const shuttles = await repository.getShuttlesBySystemId("1"); // expect(shuttles.length).toEqual(1); // expect(shuttles[0].id).toEqual(initialCacheShuttle.id); // }); // // test("getShuttlesBySystemId returns fresh data if expired", async () => { // updateGlobalFetchMockJson(genericShuttleDataBySystemId); // // // TODO: move construction of shuttle into method // const initialCacheShuttle = { // coordinates: { // latitude: 33.791781, // longitude: -117.8589646, // }, // name: "08", // routeId: "53966", // systemId: "1", // id: "5577", // millisecondsSinceEpoch: Date.now() - 100000, // }; // // const initialCache: ApiBasedRepositoryCache = { // shuttlesBySystemId: { // "1": [ // initialCacheShuttle, // ] // }, // shuttleByShuttleId: { // "5577": initialCacheShuttle, // } // }; // // const ttls: ApiBasedRepositoryMillisecondTTLs = { // shuttleByShuttleId: 1000, // shuttlesBySystemId: 1000, // }; // // const repository = new ApiBasedRepository(initialCache, ttls); // const shuttles = await repository.getShuttlesBySystemId("1"); // // expect(shuttles.length).toEqual(1); // expect(shuttles[0].id).toEqual("5577"); // expect(shuttles[0].millisecondsSinceEpoch).not.toEqual(initialCacheShuttle.millisecondsSinceEpoch); // }); // // test("getShuttlesBySystemId returns fresh data if no seeded data", async () => { // updateGlobalFetchMockJson(genericShuttleDataBySystemId); // // const repository = new ApiBasedRepository(); // const shuttles = await repository.getShuttlesBySystemId("1"); // // expect(shuttles.length).toEqual(1); // }); // });