Move all tests to subdirectories underneath code to be tested

This commit is contained in:
2025-07-31 22:35:49 -04:00
parent 0fd8de13f9
commit b7299b8359
20 changed files with 79 additions and 79 deletions

View File

@@ -0,0 +1,68 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
import { TimedApiBasedRepositoryLoader } from "../TimedApiBasedRepositoryLoader";
import { resetGlobalFetchMockJson } from "../../../test/testHelpers/fetchMockHelpers";
import { UnoptimizedInMemoryShuttleRepository } from "../../repositories/shuttle/UnoptimizedInMemoryShuttleRepository";
import { ApiBasedShuttleRepositoryLoader } from "../shuttle/ApiBasedShuttleRepositoryLoader";
describe("TimedApiBasedRepositoryLoader", () => {
let timedLoader: TimedApiBasedRepositoryLoader;
let spies: any;
beforeEach(() => {
jest.useFakeTimers();
jest.spyOn(global, "setTimeout");
resetGlobalFetchMockJson();
const mockLoader = new ApiBasedShuttleRepositoryLoader(
"1",
"1",
new UnoptimizedInMemoryShuttleRepository(),
);
timedLoader = new TimedApiBasedRepositoryLoader(
mockLoader,
);
spies = {
fetchAndUpdateAll: jest.spyOn(mockLoader, 'fetchAndUpdateAll'),
};
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 timedLoader.start();
expect(timedLoader["shouldBeRunning"]).toBe(true);
Object.values(spies).forEach((spy: any) => {
expect(spy).toHaveBeenCalled();
});
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), timedLoader.timeoutMs);
expect(timedLoader.timeoutMs).not.toBeUndefined();
});
it("does nothing if timer is already running", async () => {
await timedLoader.start();
await timedLoader.start();
Object.values(spies).forEach((spy: any) => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});
describe("stop", () => {
it("should update internal state", async () => {
timedLoader.stop();
expect(timedLoader['shouldBeRunning']).toBe(false);
});
});
});

View File

@@ -0,0 +1,113 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
import {
ChapmanApiBasedParkingRepositoryLoader
} from "../ChapmanApiBasedParkingRepositoryLoader";
import { InMemoryParkingRepository } from "../../../repositories/parking/InMemoryParkingRepository";
import {
resetGlobalFetchMockJson,
updateGlobalFetchMockJson,
updateGlobalFetchMockJsonToThrowSyntaxError
} from "../../../../test/testHelpers/fetchMockHelpers";
import {
chapmanParkingStructureData
} from "../../../../test/jsonSnapshots/chapmanParkingStructureData/chapmanParkingStructureData";
import { IParkingStructure } from "../../../entities/ParkingRepositoryEntities";
import { assertAsyncCallbackThrowsApiResponseError } from "../../../../test/testHelpers/assertAsyncCallbackThrowsApiResponseError";
describe("ChapmanApiBasedParkingRepositoryLoader", () => {
let loader: ChapmanApiBasedParkingRepositoryLoader;
beforeEach(() => {
loader = new ChapmanApiBasedParkingRepositoryLoader(
new InMemoryParkingRepository(),
);
resetGlobalFetchMockJson();
});
describe("fetchAndUpdateAll", () => {
it("calls all the correct methods", async () => {
const spies = {
fetchAndUpdateParkingStructures: jest.spyOn(loader, "fetchAndUpdateParkingStructures"),
};
Object.values(spies).forEach((spy: any) => {
spy.mockResolvedValue(undefined);
});
await loader.fetchAndUpdateAll();
Object.values(spies).forEach((spy: any) => {
expect(spy).toHaveBeenCalled();
});
});
});
describe("fetchAndUpdateParkingStructures", () => {
it("fetches and update parking structures with unique IDs", async () => {
updateGlobalFetchMockJson(chapmanParkingStructureData);
await loader.fetchAndUpdateParkingStructures();
let expectedStructures: IParkingStructure[] = [
{
address: "300 E Walnut, Orange, CA 92867",
capacity: 871,
spotsAvailable: 211,
coordinates: {
latitude: 33.7945513,
longitude: -117.8518707,
},
name: "Anderson Structure",
id: "",
updatedTime: new Date(),
},
{
address: "200 W Sycamore Ave, Orange, CA 92866-1053",
capacity: 692,
spotsAvailable: 282,
coordinates: {
latitude: 33.792937,
longitude: -117.854782
},
name: "Barrera",
id: "",
updatedTime: new Date(),
}
];
expectedStructures[0].id = ChapmanApiBasedParkingRepositoryLoader.generateId(expectedStructures[0].address);
expectedStructures[1].id = ChapmanApiBasedParkingRepositoryLoader.generateId(expectedStructures[1].address);
const structuresFromLoader = await loader.repository.getParkingStructures();
// Set updatedTimeMs on expected data to avoid comparison
expectedStructures[0].updatedTime = structuresFromLoader[0].updatedTime;
expectedStructures[1].updatedTime = structuresFromLoader[1].updatedTime;
expect(structuresFromLoader).toEqual(expectedStructures);
});
it("throws ApiResponseError if data is incorrect", async () => {
updateGlobalFetchMockJsonToThrowSyntaxError();
await assertAsyncCallbackThrowsApiResponseError(async () => {
await loader.fetchAndUpdateParkingStructures();
});
})
});
describe("constructIParkingStructureFromJson", () => {
it("normalizes the spots available if it's over the capacity", async () => {
const sampleJsonStructure: any = {
Capacity: 10,
Latitude: 1,
Longitude: 1,
Address: "300 E Walnut, Orange, CA 92867",
Name: "Anderson Structure",
CurrentCount: 11,
};
const returnedStructure = loader.constructIParkingStructureFromJson(sampleJsonStructure);
expect(returnedStructure.spotsAvailable).toEqual(returnedStructure.capacity);
});
});
});

View File

@@ -0,0 +1,195 @@
import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
import { ApiBasedShuttleRepositoryLoader } from "../ApiBasedShuttleRepositoryLoader";
import { UnoptimizedInMemoryShuttleRepository } from "../../../repositories/shuttle/UnoptimizedInMemoryShuttleRepository";
import { fetchRouteDataSuccessfulResponse } from "../../../../test/jsonSnapshots/fetchRouteData/fetchRouteDataSuccessfulResponse";
import {
fetchStopAndPolylineDataSuccessfulResponse
} from "../../../../test/jsonSnapshots/fetchStopAndPolylineData/fetchStopAndPolylineDataSuccessfulResponse";
import { generateMockRoutes, generateMockShuttles, generateMockStops } from "../../../../test/testHelpers/mockDataGenerators";
import {
fetchShuttleDataSuccessfulResponse
} from "../../../../test/jsonSnapshots/fetchShuttleData/fetchShuttleDataSuccessfulResponse";
import { fetchEtaDataSuccessfulResponse } from "../../../../test/jsonSnapshots/fetchEtaData/fetchEtaDataSuccessfulResponse";
import {
resetGlobalFetchMockJson,
updateGlobalFetchMockJson,
updateGlobalFetchMockJsonToThrowSyntaxError
} from "../../../../test/testHelpers/fetchMockHelpers";
import { assertAsyncCallbackThrowsApiResponseError } from "../../../../test/testHelpers/assertAsyncCallbackThrowsApiResponseError";
describe("ApiBasedShuttleRepositoryLoader", () => {
let loader: ApiBasedShuttleRepositoryLoader;
beforeEach(() => {
loader = new ApiBasedShuttleRepositoryLoader("263", "1", new UnoptimizedInMemoryShuttleRepository());
resetGlobalFetchMockJson();
});
afterEach(() => {
jest.clearAllMocks();
});
const systemId = "1";
describe("fetchAndUpdateAll", () => {
it("calls all the correct methods", async () => {
const spies = {
fetchAndUpdateRouteDataForSystem: jest.spyOn(loader, "fetchAndUpdateRouteDataForSystem"),
fetchAndUpdateStopAndPolylineDataForRoutesInSystem: jest.spyOn(loader, "fetchAndUpdateStopAndPolylineDataForRoutesInSystem"),
fetchAndUpdateShuttleDataForSystem: jest.spyOn(loader, "fetchAndUpdateShuttleDataForSystem"),
fetchAndUpdateEtaDataForExistingStopsForSystem: jest.spyOn(loader, "fetchAndUpdateEtaDataForExistingStopsForSystem"),
};
Object.values(spies).forEach((spy: any) => {
spy.mockResolvedValue(undefined);
});
await loader.fetchAndUpdateAll();
Object.values(spies).forEach((spy: any) => {
expect(spy).toHaveBeenCalled();
});
});
});
describe("fetchAndUpdateRouteDataForSystem", () => {
it("updates route data in repository if response received", async () => {
// Arrange
// Test pruning
const routesToPrune = generateMockRoutes();
await Promise.all(routesToPrune.map(async (route) => {
route.systemId = systemId;
await loader.repository.addOrUpdateRoute(route);
}));
updateGlobalFetchMockJson(fetchRouteDataSuccessfulResponse);
// Act
await loader.fetchAndUpdateRouteDataForSystem();
// Assert
const routes = await loader.repository.getRoutes();
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.fetchAndUpdateRouteDataForSystem();
});
});
});
describe("fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId", () => {
it("updates stop and polyline data if response received", async () => {
// Arrange
// Test pruning of stops only
const stopsToPrune = generateMockStops();
await Promise.all(stopsToPrune.map(async (stop) => {
stop.systemId = systemId;
await loader.repository.addOrUpdateStop(stop);
}));
updateGlobalFetchMockJson(fetchStopAndPolylineDataSuccessfulResponse);
const stopsArray = Object.values(fetchStopAndPolylineDataSuccessfulResponse.stops);
await loader.fetchAndUpdateStopAndPolylineDataForRoutesInSystem();
const stops = await loader.repository.getStops();
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.getRoutes();
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.fetchAndUpdateStopAndPolylineDataForRoutesInSystem();
});
})
});
describe("fetchAndUpdateShuttleDataForSystem", () => {
it("updates shuttle data in repository if response received", async () => {
const shuttlesToPrune = generateMockShuttles();
await Promise.all(shuttlesToPrune.map(async (shuttle) => {
shuttle.systemId = systemId;
await loader.repository.addOrUpdateShuttle(shuttle);
}))
updateGlobalFetchMockJson(fetchShuttleDataSuccessfulResponse);
const busesInResponse = Object.values(fetchShuttleDataSuccessfulResponse.buses);
await loader.fetchAndUpdateShuttleDataForSystem();
const shuttles = await loader.repository.getShuttles();
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.fetchAndUpdateShuttleDataForSystem();
});
});
});
describe("fetchAndUpdateEtaDataForExistingStopsForSystem", () => {
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.fetchAndUpdateEtaDataForExistingStopsForSystem();
expect(spy.mock.calls.length).toEqual(stops.length);
});
});
describe("fetchAndUpdateEtaDataForStopId", () => {
const stopId = "177666";
it("updates ETA data for stop id if response received", async () => {
updateGlobalFetchMockJson(fetchEtaDataSuccessfulResponse);
// @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");
});
});
});
});