add error pathway and test case for validation

This commit is contained in:
2025-01-22 14:01:57 -08:00
parent 6c9f4691fd
commit 1b9a77209f
6 changed files with 488 additions and 19 deletions

View File

@@ -0,0 +1,5 @@
// Not an actual response snapshot
// Simulate a case where `all` property of response is unavailable
export const fetchSystemDataFailedResponse = {
"error": "no systems",
};

View File

@@ -1,4 +1,4 @@
export const getAllSystemsResponse = {
export const fetchSystemDataSuccessfulResponse = {
"all": [
{
"fullname": "Chapman University",

View File

@@ -1,13 +1,25 @@
import { beforeEach, describe, expect, it, jest, test } from "@jest/globals";
import { ApiBasedRepositoryLoader } from "../../src/loaders/ApiBasedRepositoryLoader";
import { ApiBasedRepositoryLoader, ApiResponseError } from "../../src/loaders/ApiBasedRepositoryLoader";
import { UnoptimizedInMemoryRepository } from "../../src/repositories/UnoptimizedInMemoryRepository";
import { getAllSystemsResponse } from "../jsonSnapshots/getAllSystemsResponse";
import { fetchSystemDataSuccessfulResponse } from "../jsonSnapshots/fetchSystemData/fetchSystemDataSuccessfulResponse";
import { fetchSystemDataFailedResponse } from "../jsonSnapshots/fetchSystemData/fetchSystemDataFailedResponse";
function updateGlobalFetchMockJson(obj: any) {
/**
* Function to update behavior of the global `fetch` function.
* Note that the Passio GO API returns status code 200 for failed responses.
* @param obj
* @param status
*/
function updateGlobalFetchMockJson(
obj: any,
status: number = 200
) {
// @ts-ignore
global.fetch = jest.fn(() => {
return Promise.resolve({
json: () => Promise.resolve(obj)
json: () => Promise.resolve(obj),
status,
ok: status.toString().startsWith("2"), // 200-level codes are OK
})
}) as jest.Mock;
}
@@ -30,8 +42,8 @@ describe("ApiBasedRepositoryLoader", () => {
describe("fetchAndUpdateSystemData", () => {
it("updates system data in repository if response received", async () => {
const numberOfSystemsInResponse = getAllSystemsResponse.all.length;
updateGlobalFetchMockJson(getAllSystemsResponse);
const numberOfSystemsInResponse = fetchSystemDataSuccessfulResponse.all.length;
updateGlobalFetchMockJson(fetchSystemDataSuccessfulResponse);
await loader.fetchAndUpdateSystemData();
@@ -44,7 +56,12 @@ describe("ApiBasedRepositoryLoader", () => {
});
it("throws the correct error if the API response contains no data", async () => {
updateGlobalFetchMockJson(fetchSystemDataFailedResponse);
// Jest is so confusing
await expect(async () => {
await loader.fetchAndUpdateSystemData();
}).rejects.toThrow(ApiResponseError);
});
});

422
test/loaders/temp.ts Normal file
View File

@@ -0,0 +1,422 @@
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);
// });
// });