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

chore/test-timed-api-based-repository-loader
This commit is contained in:
2025-01-22 16:25:05 -08:00
committed by GitHub
4 changed files with 131 additions and 43 deletions

View File

@@ -2,8 +2,6 @@ import { GetterSetterRepository } from "../repositories/GetterSetterRepository";
import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
import { ApiBasedRepositoryLoader } from "./ApiBasedRepositoryLoader";
const timeout = 10000;
// Ideas to break this into smaller pieces in the future:
// Have one repository data loader running for each supported system
// Each data loader independently updates data based on frequency of usage
@@ -22,6 +20,8 @@ export class TimedApiBasedRepositoryLoader extends ApiBasedRepositoryLoader {
private shouldBeRunning: boolean = false;
private timer: any;
readonly timeout = 10000;
constructor(
repository: GetterSetterRepository,
) {
@@ -61,7 +61,6 @@ export class TimedApiBasedRepositoryLoader extends ApiBasedRepositoryLoader {
console.error(e);
}
this.timer = setTimeout(this.startFetchDataAndUpdate, timeout);
this.timer = setTimeout(this.startFetchDataAndUpdate, this.timeout);
}
}

View File

@@ -13,50 +13,16 @@ 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({});
}
import {
resetGlobalFetchMockJson,
updateGlobalFetchMockJson,
updateGlobalFetchMockJsonToThrowSyntaxError
} from "../mockHelpers/fetchMockHelpers";
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;

View File

@@ -0,0 +1,82 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
import { TimedApiBasedRepositoryLoader } from "../../src/loaders/TimedApiBasedRepositoryLoader";
import { resetGlobalFetchMockJson } from "../mockHelpers/fetchMockHelpers";
import { GetterSetterRepository } from "../../src/repositories/GetterSetterRepository";
describe("TimedApiBasedRepositoryLoader", () => {
let repositoryMock: GetterSetterRepository;
let loader: TimedApiBasedRepositoryLoader;
let spies: any;
beforeAll(() => {
jest.useFakeTimers();
jest.spyOn(global, "setTimeout");
});
beforeEach(() => {
resetGlobalFetchMockJson();
repositoryMock = {
clearSystemData: jest.fn(),
clearRouteData: jest.fn(),
clearStopData: jest.fn(),
clearShuttleData: jest.fn(),
clearEtaData: jest.fn(),
} as unknown as GetterSetterRepository;
loader = new TimedApiBasedRepositoryLoader(repositoryMock);
spies = {
fetchAndUpdateSystemData: jest.spyOn(loader, 'fetchAndUpdateSystemData'),
fetchAndUpdateRouteDataForExistingSystemsInRepository: jest.spyOn(loader, 'fetchAndUpdateRouteDataForExistingSystemsInRepository'),
fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository: jest.spyOn(loader, 'fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository'),
fetchAndUpdateShuttleDataForExistingSystemsInRepository: jest.spyOn(loader, 'fetchAndUpdateShuttleDataForExistingSystemsInRepository'),
fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository: jest.spyOn(loader, 'fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository')
};
Object.values(spies).forEach((spy: any) => {
spy.mockResolvedValue(undefined);
});
});
afterEach(() => {
jest.clearAllMocks();
jest.clearAllTimers();
})
describe("start", () => {
it("should update internal state, call data fetching methods, and start a timer", async () => {
await loader.start();
expect(loader["shouldBeRunning"]).toBe(true);
Object.values(repositoryMock).forEach((mockFn) => {
expect(mockFn).toHaveBeenCalled();
});
Object.values(spies).forEach((spy: any) => {
expect(spy).toHaveBeenCalled();
});
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), loader.timeout);
expect(loader.timeout).not.toBeUndefined();
});
it("does nothing if timer is already running", async () => {
await loader.start();
await loader.start();
Object.values(repositoryMock).forEach((mockFn) => {
expect(mockFn).toHaveBeenCalledTimes(1);
});
Object.values(spies).forEach((spy: any) => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});
describe("stop", () => {
it("should update internal state", async () => {
loader.stop();
expect(loader['shouldBeRunning']).toBe(false);
});
});
});

View File

@@ -0,0 +1,41 @@
import { jest } from "@jest/globals";
/**
* 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
*/
export 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
})
});
}
/**
* Reset the global fetch function mock's JSON to return an empty object.
* @param obj
*/
export function resetGlobalFetchMockJson() {
updateGlobalFetchMockJson({});
}
export 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;
}