Files
project-inter-server/test/repositories/ApiBasedRepositoryTests.test.ts

636 lines
17 KiB
TypeScript

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";
/**
* 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();
})
// Snapshots taken from the Passio GO! API
const genericEtaDataByStopId = {
"ETAs": {
"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"
}
},
]
},
}
const genericShuttleDataBySystemId = {
"alertCRC": "23c1b91c",
"buses": {
"402840": [
{
"deviceId": 402840,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "08",
"busId": 5577,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.791781800",
"longitude": "-117.858964600",
"calculatedCourse": "351.796001302109",
"outOfService": 0,
"more": "102",
"totalCap": 20,
"color": "#d62728",
"busName": "08",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
],
"404873": [
{
"deviceId": 404873,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "10",
"busId": 7105,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.789331300",
"longitude": "-117.888790600",
"calculatedCourse": "76.005762226701",
"outOfService": 0,
"more": "101",
"totalCap": 20,
"color": "#d62728",
"busName": "10",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
],
"421421": [
{
"deviceId": 421421,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "17",
"busId": 12502,
"userId": "263",
"routeBlockId": "142660",
"latitude": "33.790699500",
"longitude": "-117.890385500",
"calculatedCourse": "10.65684824528148",
"outOfService": 0,
"more": "101",
"totalCap": 32,
"color": "#bd9e39",
"busName": "17",
"busType": "Shuttle Bus",
"routeId": "54256",
"route": "Gold Route",
"outdated": 0
}
],
"441065": [
{
"deviceId": 441065,
"created": "08:19 PM",
"createdTime": "08:19 PM",
"paxLoad": 0,
"bus": "07",
"busId": 5576,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.793278900",
"longitude": "-117.852629400",
"calculatedCourse": "299.74488110904485",
"outOfService": 0,
"more": "22",
"totalCap": 20,
"color": "#d62728",
"busName": "07",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
]
},
"microtime": 0.023222923278808594,
"time": {
"263": "08:24 PM"
}
}
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(),
}
],
},
}
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();
});
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 () => {
});
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 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 old data if not expired", async () => {
updateGlobalFetchMockJson(genericShuttleDataBySystemId);
const expectedShuttle = {
coordinates: {
latitude: 33.791781,
longitude: -117.8589646,
},
name: "08",
routeId: "53966",
systemId: "1",
id: "5577",
millisecondsSinceEpoch: Date.now() - 1000,
}
const initialCache: ApiBasedRepositoryCache = {
shuttleByShuttleId: {
"5577": expectedShuttle
}
};
const ttls: ApiBasedRepositoryMillisecondTTLs = {
shuttleByShuttleId: 100000,
};
const repository = new ApiBasedRepository(initialCache, ttls);
const shuttle = await repository.getShuttleById("5577");
expect(shuttle).toEqual(expectedShuttle);
});
test("getShuttleById returns fresh data if expired", async () => {
updateGlobalFetchMockJson(genericShuttleDataBySystemId);
const initialCacheShuttle = {
coordinates: {
latitude: 33.791781,
longitude: -117.8589646,
},
name: "08",
routeId: "53966",
systemId: "1",
id: "5577",
millisecondsSinceEpoch: Date.now() - 100000,
}
const initialCache: ApiBasedRepositoryCache = {
shuttleByShuttleId: {
"5577": initialCacheShuttle
}
};
const ttls: ApiBasedRepositoryMillisecondTTLs = {
shuttleByShuttleId: 1000,
};
const repository = new ApiBasedRepository(initialCache, ttls);
const shuttle = await repository.getShuttleById("5577");
expect(shuttle?.id).toEqual(initialCacheShuttle.id);
expect(initialCacheShuttle.millisecondsSinceEpoch).not.toEqual(shuttle?.millisecondsSinceEpoch);
});
});
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);
});
});