mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-17 16:00:32 +00:00
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:
@@ -2,8 +2,6 @@ import { GetterSetterRepository } from "../repositories/GetterSetterRepository";
|
|||||||
import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
|
import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
|
||||||
import { ApiBasedRepositoryLoader } from "./ApiBasedRepositoryLoader";
|
import { ApiBasedRepositoryLoader } from "./ApiBasedRepositoryLoader";
|
||||||
|
|
||||||
const timeout = 10000;
|
|
||||||
|
|
||||||
// Ideas to break this into smaller pieces in the future:
|
// Ideas to break this into smaller pieces in the future:
|
||||||
// Have one repository data loader running for each supported system
|
// Have one repository data loader running for each supported system
|
||||||
// Each data loader independently updates data based on frequency of usage
|
// 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 shouldBeRunning: boolean = false;
|
||||||
private timer: any;
|
private timer: any;
|
||||||
|
|
||||||
|
readonly timeout = 10000;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
repository: GetterSetterRepository,
|
repository: GetterSetterRepository,
|
||||||
) {
|
) {
|
||||||
@@ -61,7 +61,6 @@ export class TimedApiBasedRepositoryLoader extends ApiBasedRepositoryLoader {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timer = setTimeout(this.startFetchDataAndUpdate, timeout);
|
this.timer = setTimeout(this.startFetchDataAndUpdate, this.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -13,50 +13,16 @@ import {
|
|||||||
fetchShuttleDataSuccessfulResponse
|
fetchShuttleDataSuccessfulResponse
|
||||||
} from "../jsonSnapshots/fetchShuttleData/fetchShuttleDataSuccessfulResponse";
|
} from "../jsonSnapshots/fetchShuttleData/fetchShuttleDataSuccessfulResponse";
|
||||||
import { fetchEtaDataSuccessfulResponse } from "../jsonSnapshots/fetchEtaData/fetchEtaDataSuccessfulResponse";
|
import { fetchEtaDataSuccessfulResponse } from "../jsonSnapshots/fetchEtaData/fetchEtaDataSuccessfulResponse";
|
||||||
|
import {
|
||||||
/**
|
resetGlobalFetchMockJson,
|
||||||
* Function to update behavior of the global `fetch` function.
|
updateGlobalFetchMockJson,
|
||||||
* Note that the Passio GO API returns status code 200 for failed responses.
|
updateGlobalFetchMockJsonToThrowSyntaxError
|
||||||
* @param obj
|
} from "../mockHelpers/fetchMockHelpers";
|
||||||
* @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>) {
|
async function assertAsyncCallbackThrowsApiResponseError(callback: () => Promise<any>) {
|
||||||
await expect(callback).rejects.toThrow(ApiResponseError);
|
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", () => {
|
describe("ApiBasedRepositoryLoader", () => {
|
||||||
let loader: ApiBasedRepositoryLoader;
|
let loader: ApiBasedRepositoryLoader;
|
||||||
|
|
||||||
|
|||||||
82
test/loaders/TimedApiBasedRepositoryLoaderTests.test.ts
Normal file
82
test/loaders/TimedApiBasedRepositoryLoaderTests.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
41
test/mockHelpers/fetchMockHelpers.ts
Normal file
41
test/mockHelpers/fetchMockHelpers.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user