Files
project-inter-server/test/loaders/ApiBasedRepositoryLoaderTests.test.ts

199 lines
6.7 KiB
TypeScript

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 { ISystem } from "../../src/entities/entities";
import {
fetchStopAndPolylineDataSuccessfulResponse
} from "../jsonSnapshots/fetchStopAndPolylineData/fetchStopAndPolylineDataSuccessfulResponse";
/**
* 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);
}
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: ISystem[] = [
{
name: "Chapman University",
id: "1",
},
{
name: "City of Monterey Park",
id: "2",
}
];
await Promise.all(systems.map(async (system) => {
await loader.repository.addOrUpdateSystem(system);
}));
await loader.fetchAndUpdateRouteDataForExistingSystemsInRepository();
expect(spy.mock.calls.length).toBe(2);
});
});
describe("fetchAndUpdateRouteDataForSystemId", () => {
it("updates route data in repository if there are systems and 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
// @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;
await assertAsyncCallbackThrowsApiResponseError(async () => {
await loader.fetchAndUpdateRouteDataForSystemId("263");
});
});
});
describe("fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId", () => {
it("updates stop and polyline data if there are systems and 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 () => {
// @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;
await assertAsyncCallbackThrowsApiResponseError(async () => {
await loader.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId("263");
});
})
});
describe("fetchAndUpdateShuttleDataForExistingSystems", () => {
it("updates shuttle data in repository if there are systems and response received", async () => {
});
it("throws the correct error if the API response contains no data", async () => {
});
});
describe("fetchAndUpdateEtaDataForExistingSystems", () => {
it("updates shuttle data in repository if there are systems and response received", async () => {
});
it("throws the correct error if the API response contains no data", async () => {
});
});
});