Merge pull request #9 from brendan-ch/chore/test-api-based-repository-loader

chore/test-api-based-repository-loader
This commit is contained in:
2025-01-22 15:55:57 -08:00
committed by GitHub
10 changed files with 16641 additions and 102 deletions

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ExceptionCaughtLocallyJS" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@@ -1,53 +1,78 @@
import { GetterSetterRepository } from "../repositories/GetterSetterRepository"; import { GetterSetterRepository } from "../repositories/GetterSetterRepository";
import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities"; import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
const systemIdsToSupport = ["263"]; export class ApiResponseError extends Error {
const baseUrl = "https://passiogo.com/mapGetData.php"; constructor(message: string) {
super(message);
this.name = "ApiResponseError";
}
}
export class ApiBasedRepositoryLoader { export class ApiBasedRepositoryLoader {
supportedSystemIds = ["263"];
baseUrl = "https://passiogo.com/mapGetData.php";
constructor( constructor(
protected repository: GetterSetterRepository, public repository: GetterSetterRepository,
) { ) {
} }
protected async fetchAndUpdateSystemData() { public async fetchAndUpdateSystemData() {
const params = { const params = {
getSystems: "2", getSystems: "2",
}; };
const query = new URLSearchParams(params).toString(); const query = new URLSearchParams(params).toString();
const response = await fetch(`${baseUrl}?${query}`);
const json = await response.json()
if (typeof json.all === "object") { try {
// filter down to supported systems const response = await fetch(`${this.baseUrl}?${query}`);
const filteredSystems = json.all.filter((jsonSystem: any) => systemIdsToSupport.includes(jsonSystem.id)); const json = await response.json();
await Promise.all(filteredSystems.map(async (system: any) => {
const constructedSystem: ISystem = {
id: system.id,
name: system.fullname,
};
await this.repository.addOrUpdateSystem(constructedSystem); if (!response.ok) {
})); throw new Error(`HTTP error with status ${response.status}`)
}
if (typeof json.all === "object") {
// filter down to supported systems
const filteredSystems = json.all.filter((jsonSystem: any) => this.supportedSystemIds.includes(jsonSystem.id));
await Promise.all(filteredSystems.map(async (system: any) => {
const constructedSystem: ISystem = {
id: system.id,
name: system.fullname,
};
await this.repository.addOrUpdateSystem(constructedSystem);
}));
} else {
throw new Error("Received JSON object does not contain `all` field")
}
} catch(e: any) {
throw new ApiResponseError(e.message);
} }
} }
protected async fetchAndUpdateRouteDataForExistingSystems() { public async fetchAndUpdateRouteDataForExistingSystemsInRepository() {
const systems = await this.repository.getSystems(); const systems = await this.repository.getSystems();
await Promise.all(systems.map(async (system) => { await Promise.all(systems.map(async (system) => {
const params = { await this.fetchAndUpdateRouteDataForSystemId(system.id);
getRoutes: "2", }));
}; }
const formDataJsonObject = { public async fetchAndUpdateRouteDataForSystemId(systemId: string) {
"systemSelected0": system.id, const params = {
"amount": "1", getRoutes: "2",
} };
const formData = new FormData();
formData.set("json", JSON.stringify(formDataJsonObject));
const query = new URLSearchParams(params).toString(); const formDataJsonObject = {
const response = await fetch(`${baseUrl}?${query}`, { "systemSelected0": systemId,
"amount": "1",
}
const formData = new FormData();
formData.set("json", JSON.stringify(formDataJsonObject));
const query = new URLSearchParams(params).toString();
try {
const response = await fetch(`${this.baseUrl}?${query}`, {
method: "POST", method: "POST",
body: formData, body: formData,
}); });
@@ -60,61 +85,80 @@ export class ApiBasedRepositoryLoader {
color: jsonRoute.color, color: jsonRoute.color,
id: jsonRoute.myid, id: jsonRoute.myid,
polylineCoordinates: [], polylineCoordinates: [],
systemId: system.id, systemId: systemId,
}; };
await this.repository.addOrUpdateRoute(constructedRoute); await this.repository.addOrUpdateRoute(constructedRoute);
})) }))
} }
} catch(e: any) {
throw new ApiResponseError(e.message);
}
}
public async fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository() {
const systems = await this.repository.getSystems();
await Promise.all(systems.map(async (system: ISystem) => {
await this.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId(system.id);
})); }));
} }
protected async fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystems() { public async fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId(systemId: string) {
// Fetch from the API // Fetch from the API
// Pass JSON output into two different methods to update repository // Pass JSON output into two different methods to update repository
const systems = await this.repository.getSystems(); const params = {
await Promise.all(systems.map(async (system: ISystem) => { getStops: "2",
const params = { };
getStops: "2",
};
const formDataJsonObject = { const formDataJsonObject = {
"s0": system.id, "s0": systemId,
"sA": 1 "sA": 1
}; };
const formData = new FormData(); const formData = new FormData();
formData.set("json", JSON.stringify(formDataJsonObject)); formData.set("json", JSON.stringify(formDataJsonObject));
const query = new URLSearchParams(params).toString(); const query = new URLSearchParams(params).toString();
const response = await fetch(`${baseUrl}?${query}`, {
try {
const response = await fetch(`${this.baseUrl}?${query}`, {
method: "POST", method: "POST",
body: formData, body: formData,
}); });
const json = await response.json(); const json = await response.json();
await this.updateStopDataForSystemAndApiResponse(system, json); await this.updateStopDataForSystemAndApiResponse(systemId, json);
await this.updateOrderedStopDataForExistingStops(json); await this.updateOrderedStopDataForExistingStops(json);
await this.updatePolylineDataForExistingRoutesAndApiResponse(json); await this.updatePolylineDataForExistingRoutesAndApiResponse(json);
} catch(e: any) {
throw new ApiResponseError(e.message);
}
}
public async fetchAndUpdateShuttleDataForExistingSystemsInRepository() {
const systems = await this.repository.getSystems();
await Promise.all(systems.map(async (system: ISystem) => {
const systemId = system.id;
await this.fetchAndUpdateShuttleDataForSystemId(systemId);
})); }));
} }
protected async fetchAndUpdateShuttleDataForExistingSystems() { public async fetchAndUpdateShuttleDataForSystemId(systemId: string) {
const systems = await this.repository.getSystems(); const params = {
await Promise.all(systems.map(async (system: ISystem) => { getBuses: "2"
const params = { };
getBuses: "2"
};
const formDataJsonObject = { const formDataJsonObject = {
"s0": system.id, "s0": systemId,
"sA": "1" "sA": "1"
}; };
const formData = new FormData(); const formData = new FormData();
formData.set("json", JSON.stringify(formDataJsonObject)); formData.set("json", JSON.stringify(formDataJsonObject));
const query = new URLSearchParams(params).toString(); const query = new URLSearchParams(params).toString();
const response = await fetch(`${baseUrl}?${query}`, {
try {
const response = await fetch(`${this.baseUrl}?${query}`, {
method: "POST", method: "POST",
body: formData, body: formData,
}); });
@@ -133,55 +177,70 @@ export class ApiBasedRepositoryLoader {
longitude: parseFloat(jsonBus.longitude), longitude: parseFloat(jsonBus.longitude),
}, },
routeId: jsonBus.routeId, routeId: jsonBus.routeId,
systemId: system.id, systemId: systemId,
id: `${jsonBus.busId}` id: `${jsonBus.busId}`
} }
await this.repository.addOrUpdateShuttle(constructedShuttle); await this.repository.addOrUpdateShuttle(constructedShuttle);
})) }));
} }
})); } catch(e: any) {
throw new ApiResponseError(e.message);
}
} }
protected async fetchAndUpdateEtaDataForExistingOrderedStops() { public async fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository() {
// TODO implement once I figure out how to associate ETA data with shuttles
const systems = await this.repository.getSystems() const systems = await this.repository.getSystems()
await Promise.all(systems.map(async (system: ISystem) => { await Promise.all(systems.map(async (system: ISystem) => {
const stops = await this.repository.getStopsBySystemId(system.id); const systemId = system.id;
await Promise.all(stops.map(async (stop) => { await this.fetchAndUpdateEtaDataForExistingStopsForSystemId(systemId);
const params = {
eta: "3",
stopIds: stop.id,
};
const query = new URLSearchParams(params).toString();
const response = await fetch(`${baseUrl}?${query}`, {
method: "GET",
});
const json = await response.json();
if (json.ETAs && json.ETAs[stop.id]) {
// Continue with the parsing
json.ETAs[stop.id].forEach((jsonEta: any) => {
// Update cache
const shuttleId: string = jsonEta.busId;
const eta: IEta = {
secondsRemaining: jsonEta.secondsSpent,
shuttleId: `${shuttleId}`,
stopId: stop.id,
millisecondsSinceEpoch: Date.now(),
};
this.repository.addOrUpdateEta(eta);
});
}
}));
})) }))
} }
protected async updateStopDataForSystemAndApiResponse(system: ISystem, json: any) { public async fetchAndUpdateEtaDataForExistingStopsForSystemId(systemId: string) {
const stops = await this.repository.getStopsBySystemId(systemId);
await Promise.all(stops.map(async (stop) => {
let stopId = stop.id;
await this.fetchAndUpdateEtaDataForStopId(stopId);
}));
}
public async fetchAndUpdateEtaDataForStopId(stopId: string) {
const params = {
eta: "3",
stopIds: stopId,
};
const query = new URLSearchParams(params).toString();
try {
const response = await fetch(`${this.baseUrl}?${query}`, {
method: "GET",
});
const json = await response.json();
if (json.ETAs && json.ETAs[stopId]) {
// Continue with the parsing
json.ETAs[stopId].forEach((jsonEta: any) => {
// Update cache
const shuttleId: string = jsonEta.busId;
const eta: IEta = {
secondsRemaining: jsonEta.secondsSpent,
shuttleId: `${shuttleId}`,
stopId: stopId,
millisecondsSinceEpoch: Date.now(),
};
this.repository.addOrUpdateEta(eta);
});
}
} catch(e: any) {
throw new ApiResponseError(e.message);
}
}
protected async updateStopDataForSystemAndApiResponse(systemId: string, json: any) {
if (json.stops) { if (json.stops) {
const jsonStops = Object.values(json.stops); const jsonStops = Object.values(json.stops);
@@ -189,7 +248,7 @@ export class ApiBasedRepositoryLoader {
const constructedStop: IStop = { const constructedStop: IStop = {
name: stop.name, name: stop.name,
id: stop.id, id: stop.id,
systemId: system.id, systemId,
coordinates: { coordinates: {
latitude: parseFloat(stop.latitude), latitude: parseFloat(stop.latitude),
longitude: parseFloat(stop.longitude), longitude: parseFloat(stop.longitude),

View File

@@ -50,13 +50,13 @@ export class TimedApiBasedRepositoryLoader extends ApiBasedRepositoryLoader {
await this.repository.clearSystemData(); await this.repository.clearSystemData();
await this.fetchAndUpdateSystemData(); await this.fetchAndUpdateSystemData();
await this.repository.clearRouteData(); await this.repository.clearRouteData();
await this.fetchAndUpdateRouteDataForExistingSystems(); await this.fetchAndUpdateRouteDataForExistingSystemsInRepository();
await this.repository.clearStopData(); await this.repository.clearStopData();
await this.fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystems(); await this.fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository();
await this.repository.clearShuttleData(); await this.repository.clearShuttleData();
await this.fetchAndUpdateShuttleDataForExistingSystems(); await this.fetchAndUpdateShuttleDataForExistingSystemsInRepository();
await this.repository.clearEtaData(); await this.repository.clearEtaData();
await this.fetchAndUpdateEtaDataForExistingOrderedStops(); await this.fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View File

@@ -1,5 +1,5 @@
// Snapshot taken from the Passio GO! API // Snapshot taken from the Passio GO! API
export const genericEtaDataByStopId = { export const fetchEtaDataSuccessfulResponse = {
"ETAs": { "ETAs": {
"177666": [ "177666": [
{ {

View File

@@ -0,0 +1,153 @@
export const fetchRouteDataSuccessfulResponse = {
"all": [
{
"name": "Rinker Route",
"shortName": null,
"color": "#696969",
"userId": "263",
"myid": "53970",
"mapApp": "1",
"archive": "0",
"goPrefixRouteName": "1",
"goShowSchedule": 0,
"outdated": "0",
"id": "5902",
"distance": 1900,
"latitude": "33.656851000",
"longitude": "-117.733221000",
"timezone": "America/Los_Angeles",
"fullname": "Chapman University",
"nameOrig": "Rinker Route",
"groupId": "6706",
"groupColor": "#696969"
},
{
"name": "Red Route",
"shortName": null,
"color": "#d62728",
"userId": "263",
"myid": "53966",
"mapApp": "1",
"archive": "0",
"goPrefixRouteName": "1",
"goShowSchedule": 0,
"outdated": "0",
"id": "2032",
"distance": 1905,
"latitude": "33.793325000",
"longitude": "-117.852810000",
"timezone": "America/Los_Angeles",
"fullname": "Chapman University",
"nameOrig": "Red Route",
"groupId": "6703",
"groupColor": "#d62728"
},
{
"name": "Teal Route",
"shortName": null,
"color": "#096e90",
"userId": "263",
"myid": "54191",
"mapApp": "1",
"archive": "0",
"goPrefixRouteName": "1",
"goShowSchedule": 0,
"outdated": "0",
"id": "2032",
"distance": 1905,
"latitude": "33.793325000",
"longitude": "-117.852810000",
"timezone": "America/Los_Angeles",
"fullname": "Chapman University",
"nameOrig": "Teal Route",
"groupId": "6705",
"groupColor": "#096e90"
},
{
"name": "Gold Route",
"shortName": null,
"color": "#bd9e39",
"userId": "263",
"myid": "54256",
"mapApp": "1",
"archive": "0",
"goPrefixRouteName": "1",
"goShowSchedule": 0,
"outdated": "0",
"id": "2032",
"distance": 1905,
"latitude": "33.793325000",
"longitude": "-117.852810000",
"timezone": "America/Los_Angeles",
"fullname": "Chapman University",
"nameOrig": "Gold Route",
"groupId": "6707",
"groupColor": "#bd9e39"
},
{
"name": "Black Route",
"shortName": null,
"color": "#000000",
"userId": "263",
"myid": "54257",
"mapApp": "1",
"archive": "0",
"goPrefixRouteName": "1",
"goShowSchedule": 0,
"outdated": "0",
"id": "2032",
"distance": 1905,
"latitude": "33.793325000",
"longitude": "-117.852810000",
"timezone": "America/Los_Angeles",
"fullname": "Chapman University",
"nameOrig": "Black Route",
"groupId": "6704",
"groupColor": "#000000"
},
{
"name": "Weekend Route",
"shortName": null,
"color": "#510094",
"userId": "263",
"myid": "54282",
"mapApp": "1",
"archive": "0",
"goPrefixRouteName": "1",
"goShowSchedule": 0,
"outdated": "1",
"id": "2032",
"distance": 1905,
"latitude": "33.793325000",
"longitude": "-117.852810000",
"timezone": "America/Los_Angeles",
"fullname": "Chapman University",
"nameOrig": "Weekend Route",
"groupId": "6710",
"groupColor": "#510094",
"serviceTime": "is not provided: no bus on the route",
"serviceTimeShort": "No bus in service"
},
{
"name": "Parking Lot",
"shortName": null,
"color": "#000000",
"userId": "263",
"myid": "54529",
"mapApp": "1",
"archive": "0",
"goPrefixRouteName": "1",
"goShowSchedule": 0,
"outdated": "0",
"id": "2032",
"distance": 1905,
"latitude": "33.793325000",
"longitude": "-117.852810000",
"timezone": "America/Los_Angeles",
"fullname": "Chapman University",
"nameOrig": "Parking Lot",
"groupId": "6731",
"groupColor": "#393838"
}
]
}

View File

@@ -1,5 +1,5 @@
// Snapshot taken from the Passio GO! API // Snapshot taken from the Passio GO! API
export const genericShuttleDataBySystemId = { export const fetchShuttleDataSuccessfulResponse = {
"alertCRC": "23c1b91c", "alertCRC": "23c1b91c",
"buses": { "buses": {
"402840": [ "402840": [

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",
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,279 @@
import { beforeEach, describe, expect, it, jest, test } from "@jest/globals";
import { ApiBasedRepositoryLoader, ApiResponseError } from "../../src/loaders/ApiBasedRepositoryLoader";
import { UnoptimizedInMemoryRepository } from "../../src/repositories/UnoptimizedInMemoryRepository";
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 { generateMockStops, generateMockSystems } from "../generators";
import { IStop } from "../../src/entities/entities";
import {
fetchShuttleDataSuccessfulResponse
} from "../jsonSnapshots/fetchShuttleData/fetchShuttleDataSuccessfulResponse";
import { fetchEtaDataSuccessfulResponse } from "../jsonSnapshots/fetchEtaData/fetchEtaDataSuccessfulResponse";
/**
* 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),
status,
ok: status.toString().startsWith("2"), // 200-level codes are OK
})
}) as jest.Mock;
}
/**
* Reset the global fetch function mock's JSON to return an empty object.
* @param obj
*/
function resetGlobalFetchMockJson() {
updateGlobalFetchMockJson({});
}
async function assertAsyncCallbackThrowsApiResponseError(callback: () => Promise<any>) {
await expect(callback).rejects.toThrow(ApiResponseError);
}
function updateGlobalFetchMockJsonToThrowSyntaxError() {
// @ts-ignore
global.fetch = jest.fn(() => {
return Promise.resolve({
json: () => Promise.reject(new SyntaxError("Unable to parse JSON")),
status: 200,
ok: true,
})
}) as jest.Mock;
}
describe("ApiBasedRepositoryLoader", () => {
let loader: ApiBasedRepositoryLoader;
beforeEach(() => {
loader = new ApiBasedRepositoryLoader(new UnoptimizedInMemoryRepository());
resetGlobalFetchMockJson();
});
describe("fetchAndUpdateSystemData", () => {
it("updates system data in repository if response received", async () => {
const numberOfSystemsInResponse = fetchSystemDataSuccessfulResponse.all.length;
updateGlobalFetchMockJson(fetchSystemDataSuccessfulResponse);
await loader.fetchAndUpdateSystemData();
const systems = await loader.repository.getSystems();
if (loader.supportedSystemIds.length < numberOfSystemsInResponse) {
expect(systems).toHaveLength(loader.supportedSystemIds.length);
} else {
expect(systems).toHaveLength(numberOfSystemsInResponse);
}
});
it("throws the correct error if the API response contains no data", async () => {
updateGlobalFetchMockJson(fetchSystemDataFailedResponse);
await assertAsyncCallbackThrowsApiResponseError(async () => {
await loader.fetchAndUpdateSystemData();
});
});
it("throws the correct error if HTTP status code is not 200", async () => {
updateGlobalFetchMockJson(fetchSystemDataFailedResponse, 400);
await assertAsyncCallbackThrowsApiResponseError(async () => {
await loader.fetchAndUpdateSystemData();
});
});
});
describe("fetchAndUpdateRouteDataForExistingSystemsInRepository", () => {
test("calls fetchAndUpdateRouteDataForSystemId for all systems in repository", async () => {
const spy = jest.spyOn(loader, "fetchAndUpdateRouteDataForSystemId");
const systems = generateMockSystems();
await Promise.all(systems.map(async (system) => {
await loader.repository.addOrUpdateSystem(system);
}));
await loader.fetchAndUpdateRouteDataForExistingSystemsInRepository();
expect(spy.mock.calls.length).toBe(systems.length);
});
});
describe("fetchAndUpdateRouteDataForSystemId", () => {
it("updates route data in repository if response received", async () => {
updateGlobalFetchMockJson(fetchRouteDataSuccessfulResponse);
await loader.fetchAndUpdateRouteDataForSystemId("263");
const routes = await loader.repository.getRoutesBySystemId("263");
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.fetchAndUpdateRouteDataForSystemId("263");
});
});
});
describe("fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository", () => {
it("calls fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId for every system", async () => {
const spy = jest.spyOn(loader, "fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId");
const systems = generateMockSystems();
await Promise.all(systems.map(async (system) => {
await loader.repository.addOrUpdateSystem(system);
}));
await loader.fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository();
expect(spy.mock.calls.length).toBe(systems.length);
});
})
describe("fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId", () => {
it("updates stop and polyline data if response received", async () => {
updateGlobalFetchMockJson(fetchStopAndPolylineDataSuccessfulResponse);
const stopsArray = Object.values(fetchStopAndPolylineDataSuccessfulResponse.stops);
await loader.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId("263");
const stops = await loader.repository.getStopsBySystemId("263");
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("263");
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.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId("263");
});
})
});
describe("fetchAndUpdateShuttleDataForExistingSystemsInRepository", () => {
it("calls fetchAndUpdateShuttleDataForSystemId for every system", async () => {
const spy = jest.spyOn(loader, "fetchAndUpdateShuttleDataForSystemId");
const systems = generateMockSystems();
await Promise.all(systems.map(async (system) => {
await loader.repository.addOrUpdateSystem(system);
}))
await loader.fetchAndUpdateShuttleDataForExistingSystemsInRepository();
expect(spy.mock.calls.length).toBe(systems.length);
});
});
describe("fetchAndUpdateShuttleDataForSystemId", () => {
it("updates shuttle data in repository if response received", async () => {
updateGlobalFetchMockJson(fetchShuttleDataSuccessfulResponse);
const busesInResponse = Object.values(fetchShuttleDataSuccessfulResponse.buses);
await loader.fetchAndUpdateShuttleDataForSystemId("263");
const shuttles = await loader.repository.getShuttlesBySystemId("263");
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.fetchAndUpdateShuttleDataForSystemId("263");
});
});
});
describe("fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository", () => {
it("calls fetchAndUpdateEtaDataFoExistingStopsForSystemId for every system in repository", async () => {
const spy = jest.spyOn(loader, "fetchAndUpdateEtaDataForExistingStopsForSystemId");
const systems = generateMockSystems();
await Promise.all(systems.map(async (system) => {
await loader.repository.addOrUpdateSystem(system);
}));
await loader.fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository();
expect(spy.mock.calls.length).toBe(systems.length);
});
});
describe("fetchAndUpdateEtaDataForExistingStopsForSystemId", () => {
it("calls fetchAndUpdateEtaDataForStopId for every stop in repository", async () => {
const spy = jest.spyOn(loader, "fetchAndUpdateEtaDataForStopId");
const stops = generateMockStops();
stops.forEach((stop) => {
stop.systemId = "1";
});
await Promise.all(stops.map(async (stop) => {
await loader.repository.addOrUpdateStop(stop);
}));
await loader.fetchAndUpdateEtaDataForExistingStopsForSystemId("1");
expect(spy.mock.calls.length).toEqual(stops.length);
});
});
describe("fetchAndUpdateEtaDataForStopId", () => {
it("updates ETA data for stop id if response received", async () => {
updateGlobalFetchMockJson(fetchEtaDataSuccessfulResponse);
const stopId = "177666";
// @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");
});
});
});
});