mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-17 07:50:31 +00:00
Merge pull request #11 from brendan-ch/bugfix/fix-data-flickering
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { GetterSetterRepository } from "../repositories/GetterSetterRepository";
|
import { GetterSetterRepository } from "../repositories/GetterSetterRepository";
|
||||||
import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
|
import { IEntityWithId, IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
|
||||||
|
|
||||||
export class ApiResponseError extends Error {
|
export class ApiResponseError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
@@ -8,6 +8,11 @@ export class ApiResponseError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class which can load data into a repository from the
|
||||||
|
* Passio Go API. Supports automatic pruning of all data types
|
||||||
|
* which inherit from `IEntityWithId`.
|
||||||
|
*/
|
||||||
export class ApiBasedRepositoryLoader {
|
export class ApiBasedRepositoryLoader {
|
||||||
supportedSystemIds = ["263"];
|
supportedSystemIds = ["263"];
|
||||||
baseUrl = "https://passiogo.com/mapGetData.php";
|
baseUrl = "https://passiogo.com/mapGetData.php";
|
||||||
@@ -17,12 +22,25 @@ export class ApiBasedRepositoryLoader {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async constructExistingEntityIdSet<T extends IEntityWithId>(entitySearchCallback: () => Promise<T[]>) {
|
||||||
|
const existingEntities = await entitySearchCallback();
|
||||||
|
const ids = new Set<string>();
|
||||||
|
existingEntities.forEach((entity) => {
|
||||||
|
ids.add(entity.id);
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
public 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 systemIds = await this.constructExistingEntityIdSet(async () => {
|
||||||
|
return await this.repository.getSystems();
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${this.baseUrl}?${query}`);
|
const response = await fetch(`${this.baseUrl}?${query}`);
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
@@ -41,10 +59,16 @@ export class ApiBasedRepositoryLoader {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await this.repository.addOrUpdateSystem(constructedSystem);
|
await this.repository.addOrUpdateSystem(constructedSystem);
|
||||||
|
systemIds.delete(constructedSystem.id);
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Received JSON object does not contain `all` field")
|
throw new Error("Received JSON object does not contain `all` field")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prune systems
|
||||||
|
await Promise.all(Array.from(systemIds).map(async (systemId) => {
|
||||||
|
await this.repository.removeSystemIfExists(systemId);
|
||||||
|
}));
|
||||||
} catch(e: any) {
|
} catch(e: any) {
|
||||||
throw new ApiResponseError(e.message);
|
throw new ApiResponseError(e.message);
|
||||||
}
|
}
|
||||||
@@ -58,6 +82,10 @@ export class ApiBasedRepositoryLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async fetchAndUpdateRouteDataForSystemId(systemId: string) {
|
public async fetchAndUpdateRouteDataForSystemId(systemId: string) {
|
||||||
|
const routeIdsToPrune = await this.constructExistingEntityIdSet(async () => {
|
||||||
|
return await this.repository.getRoutesBySystemId(systemId);
|
||||||
|
});
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
getRoutes: "2",
|
getRoutes: "2",
|
||||||
};
|
};
|
||||||
@@ -89,8 +117,14 @@ export class ApiBasedRepositoryLoader {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await this.repository.addOrUpdateRoute(constructedRoute);
|
await this.repository.addOrUpdateRoute(constructedRoute);
|
||||||
|
|
||||||
|
routeIdsToPrune.delete(constructedRoute.id);
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Promise.all(Array.from(routeIdsToPrune).map(async (routeId) => {
|
||||||
|
await this.repository.removeRouteIfExists(routeId);
|
||||||
|
}));
|
||||||
} catch(e: any) {
|
} catch(e: any) {
|
||||||
throw new ApiResponseError(e.message);
|
throw new ApiResponseError(e.message);
|
||||||
}
|
}
|
||||||
@@ -106,6 +140,10 @@ export class ApiBasedRepositoryLoader {
|
|||||||
public async fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId(systemId: string) {
|
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 stopIdsToPrune = await this.constructExistingEntityIdSet(async () => {
|
||||||
|
return await this.repository.getStopsBySystemId(systemId);
|
||||||
|
});
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
getStops: "2",
|
getStops: "2",
|
||||||
};
|
};
|
||||||
@@ -126,9 +164,13 @@ export class ApiBasedRepositoryLoader {
|
|||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
await this.updateStopDataForSystemAndApiResponse(systemId, json);
|
await this.updateStopDataForSystemAndApiResponse(systemId, json, stopIdsToPrune);
|
||||||
await this.updateOrderedStopDataForExistingStops(json);
|
await this.updateOrderedStopDataForExistingStops(json);
|
||||||
await this.updatePolylineDataForExistingRoutesAndApiResponse(json);
|
await this.updatePolylineDataForExistingRoutesAndApiResponse(json);
|
||||||
|
|
||||||
|
await Promise.all(Array.from(stopIdsToPrune).map(async (stopId) => {
|
||||||
|
await this.repository.removeStopIfExists(stopId);
|
||||||
|
}));
|
||||||
} catch(e: any) {
|
} catch(e: any) {
|
||||||
throw new ApiResponseError(e.message);
|
throw new ApiResponseError(e.message);
|
||||||
}
|
}
|
||||||
@@ -143,6 +185,10 @@ export class ApiBasedRepositoryLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async fetchAndUpdateShuttleDataForSystemId(systemId: string) {
|
public async fetchAndUpdateShuttleDataForSystemId(systemId: string) {
|
||||||
|
const shuttleIdsToPrune = await this.constructExistingEntityIdSet(async () => {
|
||||||
|
return await this.repository.getShuttlesBySystemId(systemId);
|
||||||
|
});
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
getBuses: "2"
|
getBuses: "2"
|
||||||
};
|
};
|
||||||
@@ -182,8 +228,14 @@ export class ApiBasedRepositoryLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.repository.addOrUpdateShuttle(constructedShuttle);
|
await this.repository.addOrUpdateShuttle(constructedShuttle);
|
||||||
|
|
||||||
|
shuttleIdsToPrune.delete(constructedShuttle.id);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Promise.all(Array.from(shuttleIdsToPrune).map(async (shuttleId) => {
|
||||||
|
await this.repository.removeShuttleIfExists(shuttleId);
|
||||||
|
}));
|
||||||
} catch(e: any) {
|
} catch(e: any) {
|
||||||
throw new ApiResponseError(e.message);
|
throw new ApiResponseError(e.message);
|
||||||
}
|
}
|
||||||
@@ -240,7 +292,11 @@ export class ApiBasedRepositoryLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async updateStopDataForSystemAndApiResponse(systemId: string, json: any) {
|
protected async updateStopDataForSystemAndApiResponse(
|
||||||
|
systemId: string,
|
||||||
|
json: any,
|
||||||
|
setOfIdsToPrune: Set<string> = new Set(),
|
||||||
|
) {
|
||||||
if (json.stops) {
|
if (json.stops) {
|
||||||
const jsonStops = Object.values(json.stops);
|
const jsonStops = Object.values(json.stops);
|
||||||
|
|
||||||
@@ -256,6 +312,8 @@ export class ApiBasedRepositoryLoader {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await this.repository.addOrUpdateStop(constructedStop);
|
await this.repository.addOrUpdateStop(constructedStop);
|
||||||
|
|
||||||
|
setOfIdsToPrune.delete(constructedStop.id);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,15 +47,10 @@ export class TimedApiBasedRepositoryLoader extends ApiBasedRepositoryLoader {
|
|||||||
if (!this.shouldBeRunning) return;
|
if (!this.shouldBeRunning) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.repository.clearSystemData();
|
|
||||||
await this.fetchAndUpdateSystemData();
|
await this.fetchAndUpdateSystemData();
|
||||||
await this.repository.clearRouteData();
|
|
||||||
await this.fetchAndUpdateRouteDataForExistingSystemsInRepository();
|
await this.fetchAndUpdateRouteDataForExistingSystemsInRepository();
|
||||||
await this.repository.clearStopData();
|
|
||||||
await this.fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository();
|
await this.fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository();
|
||||||
await this.repository.clearShuttleData();
|
|
||||||
await this.fetchAndUpdateShuttleDataForExistingSystemsInRepository();
|
await this.fetchAndUpdateShuttleDataForExistingSystemsInRepository();
|
||||||
await this.repository.clearEtaData();
|
|
||||||
await this.fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository();
|
await this.fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ export interface GetterSetterRepository extends GetterRepository {
|
|||||||
addOrUpdateOrderedStop(orderedStop: IOrderedStop): Promise<void>;
|
addOrUpdateOrderedStop(orderedStop: IOrderedStop): Promise<void>;
|
||||||
addOrUpdateEta(eta: IEta): Promise<void>;
|
addOrUpdateEta(eta: IEta): Promise<void>;
|
||||||
|
|
||||||
|
removeSystemIfExists(systemId: string): Promise<ISystem | null>;
|
||||||
|
removeRouteIfExists(routeId: string): Promise<IRoute | null>;
|
||||||
|
removeShuttleIfExists(shuttleId: string): Promise<IShuttle | null>;
|
||||||
|
removeStopIfExists(stopId: string): Promise<IStop | null>;
|
||||||
|
removeOrderedStopIfExists(stopId: string, routeId: string): Promise<IOrderedStop | null>;
|
||||||
|
removeEtaIfExists(shuttleId: string, stopId: string): Promise<IEta | null>;
|
||||||
|
|
||||||
clearSystemData(): Promise<void>;
|
clearSystemData(): Promise<void>;
|
||||||
clearRouteData(): Promise<void>;
|
clearRouteData(): Promise<void>;
|
||||||
clearShuttleData(): Promise<void>;
|
clearShuttleData(): Promise<void>;
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
import { GetterSetterRepository } from "./GetterSetterRepository";
|
import { GetterSetterRepository } from "./GetterSetterRepository";
|
||||||
import {
|
import { IEntityWithId, IEta, IOrderedStop, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
|
||||||
IEntityWithId,
|
|
||||||
IEta,
|
|
||||||
IOrderedStop,
|
|
||||||
IRoute,
|
|
||||||
IShuttle,
|
|
||||||
IStop,
|
|
||||||
ISystem
|
|
||||||
} from "../entities/entities";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An unoptimized in memory repository.
|
* An unoptimized in memory repository.
|
||||||
@@ -148,6 +140,51 @@ export class UnoptimizedInMemoryRepository implements GetterSetterRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async removeEntityByMatcherIfExists<T>(callback: (value: T) => boolean, arrayToSearchIn: T[]) {
|
||||||
|
const index = arrayToSearchIn.findIndex(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
const entityToReturn = arrayToSearchIn[index];
|
||||||
|
arrayToSearchIn.splice(index, 1);
|
||||||
|
return entityToReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async removeEntityByIdIfExists<T extends IEntityWithId>(entityId: string, arrayToSearchIn: T[]) {
|
||||||
|
return await this.removeEntityByMatcherIfExists((value) => value.id === entityId, arrayToSearchIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeSystemIfExists(systemId: string): Promise<ISystem | null> {
|
||||||
|
return await this.removeEntityByIdIfExists(systemId, this.systems);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeRouteIfExists(routeId: string): Promise<IRoute | null> {
|
||||||
|
return await this.removeEntityByIdIfExists(routeId, this.routes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeShuttleIfExists(shuttleId: string): Promise<IShuttle | null> {
|
||||||
|
return await this.removeEntityByIdIfExists(shuttleId, this.shuttles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeStopIfExists(stopId: string): Promise<IStop | null> {
|
||||||
|
return await this.removeEntityByIdIfExists(stopId, this.stops);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeOrderedStopIfExists(stopId: string, routeId: string): Promise<IOrderedStop | null> {
|
||||||
|
return await this.removeEntityByMatcherIfExists((orderedStop) => {
|
||||||
|
return orderedStop.stopId === stopId
|
||||||
|
&& orderedStop.routeId === routeId
|
||||||
|
}, this.orderedStops);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeEtaIfExists(shuttleId: string, stopId: string): Promise<IEta | null> {
|
||||||
|
return await this.removeEntityByMatcherIfExists((eta) => {
|
||||||
|
return eta.stopId === stopId
|
||||||
|
&& eta.shuttleId === shuttleId
|
||||||
|
}, this.etas);
|
||||||
|
}
|
||||||
|
|
||||||
public async clearSystemData() {
|
public async clearSystemData() {
|
||||||
this.systems = [];
|
this.systems = [];
|
||||||
}
|
}
|
||||||
@@ -171,4 +208,5 @@ export class UnoptimizedInMemoryRepository implements GetterSetterRepository {
|
|||||||
public async clearStopData(): Promise<void> {
|
public async clearStopData(): Promise<void> {
|
||||||
this.stops = [];
|
this.stops = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,14 @@ import { fetchRouteDataSuccessfulResponse } from "../jsonSnapshots/fetchRouteDat
|
|||||||
import {
|
import {
|
||||||
fetchStopAndPolylineDataSuccessfulResponse
|
fetchStopAndPolylineDataSuccessfulResponse
|
||||||
} from "../jsonSnapshots/fetchStopAndPolylineData/fetchStopAndPolylineDataSuccessfulResponse";
|
} from "../jsonSnapshots/fetchStopAndPolylineData/fetchStopAndPolylineDataSuccessfulResponse";
|
||||||
import { generateMockStops, generateMockSystems } from "../generators";
|
import {
|
||||||
|
generateMockEtas,
|
||||||
|
generateMockOrderedStops,
|
||||||
|
generateMockRoutes,
|
||||||
|
generateMockShuttles,
|
||||||
|
generateMockStops,
|
||||||
|
generateMockSystems
|
||||||
|
} from "../generators";
|
||||||
import { IStop } from "../../src/entities/entities";
|
import { IStop } from "../../src/entities/entities";
|
||||||
import {
|
import {
|
||||||
fetchShuttleDataSuccessfulResponse
|
fetchShuttleDataSuccessfulResponse
|
||||||
@@ -33,11 +40,19 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
|
|
||||||
describe("fetchAndUpdateSystemData", () => {
|
describe("fetchAndUpdateSystemData", () => {
|
||||||
it("updates system data in repository if response received", async () => {
|
it("updates system data in repository if response received", async () => {
|
||||||
|
// Arrange
|
||||||
|
const systemsToPrune = generateMockSystems();
|
||||||
|
await Promise.all(systemsToPrune.map(async (system) => {
|
||||||
|
await loader.repository.addOrUpdateSystem(system);
|
||||||
|
}));
|
||||||
|
|
||||||
const numberOfSystemsInResponse = fetchSystemDataSuccessfulResponse.all.length;
|
const numberOfSystemsInResponse = fetchSystemDataSuccessfulResponse.all.length;
|
||||||
updateGlobalFetchMockJson(fetchSystemDataSuccessfulResponse);
|
updateGlobalFetchMockJson(fetchSystemDataSuccessfulResponse);
|
||||||
|
|
||||||
|
// Act
|
||||||
await loader.fetchAndUpdateSystemData();
|
await loader.fetchAndUpdateSystemData();
|
||||||
|
|
||||||
|
// Assert
|
||||||
const systems = await loader.repository.getSystems();
|
const systems = await loader.repository.getSystems();
|
||||||
if (loader.supportedSystemIds.length < numberOfSystemsInResponse) {
|
if (loader.supportedSystemIds.length < numberOfSystemsInResponse) {
|
||||||
expect(systems).toHaveLength(loader.supportedSystemIds.length);
|
expect(systems).toHaveLength(loader.supportedSystemIds.length);
|
||||||
@@ -81,12 +96,23 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("fetchAndUpdateRouteDataForSystemId", () => {
|
describe("fetchAndUpdateRouteDataForSystemId", () => {
|
||||||
|
const systemId = "263";
|
||||||
it("updates route data in repository if response received", async () => {
|
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);
|
updateGlobalFetchMockJson(fetchRouteDataSuccessfulResponse);
|
||||||
|
|
||||||
await loader.fetchAndUpdateRouteDataForSystemId("263");
|
// Act
|
||||||
|
await loader.fetchAndUpdateRouteDataForSystemId(systemId);
|
||||||
|
|
||||||
const routes = await loader.repository.getRoutesBySystemId("263");
|
// Assert
|
||||||
|
const routes = await loader.repository.getRoutesBySystemId(systemId);
|
||||||
|
|
||||||
expect(routes.length).toEqual(fetchRouteDataSuccessfulResponse.all.length)
|
expect(routes.length).toEqual(fetchRouteDataSuccessfulResponse.all.length)
|
||||||
});
|
});
|
||||||
@@ -98,7 +124,7 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
updateGlobalFetchMockJsonToThrowSyntaxError();
|
updateGlobalFetchMockJsonToThrowSyntaxError();
|
||||||
|
|
||||||
await assertAsyncCallbackThrowsApiResponseError(async () => {
|
await assertAsyncCallbackThrowsApiResponseError(async () => {
|
||||||
await loader.fetchAndUpdateRouteDataForSystemId("263");
|
await loader.fetchAndUpdateRouteDataForSystemId(systemId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -120,14 +146,23 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId", () => {
|
describe("fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId", () => {
|
||||||
|
const systemId = "263";
|
||||||
it("updates stop and polyline data if response received", async () => {
|
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);
|
updateGlobalFetchMockJson(fetchStopAndPolylineDataSuccessfulResponse);
|
||||||
|
|
||||||
const stopsArray = Object.values(fetchStopAndPolylineDataSuccessfulResponse.stops);
|
const stopsArray = Object.values(fetchStopAndPolylineDataSuccessfulResponse.stops);
|
||||||
|
|
||||||
await loader.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId("263");
|
await loader.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId(systemId);
|
||||||
|
|
||||||
const stops = await loader.repository.getStopsBySystemId("263");
|
const stops = await loader.repository.getStopsBySystemId(systemId);
|
||||||
expect(stops.length).toEqual(stopsArray.length);
|
expect(stops.length).toEqual(stopsArray.length);
|
||||||
|
|
||||||
await Promise.all(stops.map(async (stop) => {
|
await Promise.all(stops.map(async (stop) => {
|
||||||
@@ -135,7 +170,7 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
expect(orderedStops.length).toBeGreaterThan(0);
|
expect(orderedStops.length).toBeGreaterThan(0);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const routes = await loader.repository.getRoutesBySystemId("263");
|
const routes = await loader.repository.getRoutesBySystemId(systemId);
|
||||||
routes.forEach((route) => {
|
routes.forEach((route) => {
|
||||||
expect(route.polylineCoordinates.length).toBeGreaterThan(0);
|
expect(route.polylineCoordinates.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
@@ -145,7 +180,7 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
updateGlobalFetchMockJsonToThrowSyntaxError();
|
updateGlobalFetchMockJsonToThrowSyntaxError();
|
||||||
|
|
||||||
await assertAsyncCallbackThrowsApiResponseError(async () => {
|
await assertAsyncCallbackThrowsApiResponseError(async () => {
|
||||||
await loader.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId("263");
|
await loader.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId(systemId);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -166,13 +201,20 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("fetchAndUpdateShuttleDataForSystemId", () => {
|
describe("fetchAndUpdateShuttleDataForSystemId", () => {
|
||||||
|
const systemId = "263";
|
||||||
it("updates shuttle data in repository if response received", async () => {
|
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);
|
updateGlobalFetchMockJson(fetchShuttleDataSuccessfulResponse);
|
||||||
const busesInResponse = Object.values(fetchShuttleDataSuccessfulResponse.buses);
|
const busesInResponse = Object.values(fetchShuttleDataSuccessfulResponse.buses);
|
||||||
|
|
||||||
await loader.fetchAndUpdateShuttleDataForSystemId("263");
|
await loader.fetchAndUpdateShuttleDataForSystemId(systemId);
|
||||||
|
|
||||||
const shuttles = await loader.repository.getShuttlesBySystemId("263");
|
const shuttles = await loader.repository.getShuttlesBySystemId(systemId);
|
||||||
|
|
||||||
expect(shuttles.length).toEqual(busesInResponse.length);
|
expect(shuttles.length).toEqual(busesInResponse.length);
|
||||||
});
|
});
|
||||||
@@ -181,7 +223,7 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
updateGlobalFetchMockJsonToThrowSyntaxError();
|
updateGlobalFetchMockJsonToThrowSyntaxError();
|
||||||
|
|
||||||
await assertAsyncCallbackThrowsApiResponseError(async () => {
|
await assertAsyncCallbackThrowsApiResponseError(async () => {
|
||||||
await loader.fetchAndUpdateShuttleDataForSystemId("263");
|
await loader.fetchAndUpdateShuttleDataForSystemId(systemId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -221,9 +263,9 @@ describe("ApiBasedRepositoryLoader", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("fetchAndUpdateEtaDataForStopId", () => {
|
describe("fetchAndUpdateEtaDataForStopId", () => {
|
||||||
|
const stopId = "177666";
|
||||||
it("updates ETA data for stop id if response received", async () => {
|
it("updates ETA data for stop id if response received", async () => {
|
||||||
updateGlobalFetchMockJson(fetchEtaDataSuccessfulResponse);
|
updateGlobalFetchMockJson(fetchEtaDataSuccessfulResponse);
|
||||||
const stopId = "177666";
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const etasFromResponse = fetchEtaDataSuccessfulResponse.ETAs[stopId]
|
const etasFromResponse = fetchEtaDataSuccessfulResponse.ETAs[stopId]
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
|
||||||
import { TimedApiBasedRepositoryLoader } from "../../src/loaders/TimedApiBasedRepositoryLoader";
|
import { TimedApiBasedRepositoryLoader } from "../../src/loaders/TimedApiBasedRepositoryLoader";
|
||||||
import { resetGlobalFetchMockJson } from "../mockHelpers/fetchMockHelpers";
|
import { resetGlobalFetchMockJson } from "../mockHelpers/fetchMockHelpers";
|
||||||
import { GetterSetterRepository } from "../../src/repositories/GetterSetterRepository";
|
import { UnoptimizedInMemoryRepository } from "../../src/repositories/UnoptimizedInMemoryRepository";
|
||||||
|
|
||||||
describe("TimedApiBasedRepositoryLoader", () => {
|
describe("TimedApiBasedRepositoryLoader", () => {
|
||||||
let repositoryMock: GetterSetterRepository;
|
|
||||||
let loader: TimedApiBasedRepositoryLoader;
|
let loader: TimedApiBasedRepositoryLoader;
|
||||||
let spies: any;
|
let spies: any;
|
||||||
|
|
||||||
@@ -16,15 +15,7 @@ describe("TimedApiBasedRepositoryLoader", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
resetGlobalFetchMockJson();
|
resetGlobalFetchMockJson();
|
||||||
|
|
||||||
repositoryMock = {
|
loader = new TimedApiBasedRepositoryLoader(new UnoptimizedInMemoryRepository());
|
||||||
clearSystemData: jest.fn(),
|
|
||||||
clearRouteData: jest.fn(),
|
|
||||||
clearStopData: jest.fn(),
|
|
||||||
clearShuttleData: jest.fn(),
|
|
||||||
clearEtaData: jest.fn(),
|
|
||||||
} as unknown as GetterSetterRepository;
|
|
||||||
|
|
||||||
loader = new TimedApiBasedRepositoryLoader(repositoryMock);
|
|
||||||
|
|
||||||
spies = {
|
spies = {
|
||||||
fetchAndUpdateSystemData: jest.spyOn(loader, 'fetchAndUpdateSystemData'),
|
fetchAndUpdateSystemData: jest.spyOn(loader, 'fetchAndUpdateSystemData'),
|
||||||
@@ -49,9 +40,6 @@ describe("TimedApiBasedRepositoryLoader", () => {
|
|||||||
await loader.start();
|
await loader.start();
|
||||||
expect(loader["shouldBeRunning"]).toBe(true);
|
expect(loader["shouldBeRunning"]).toBe(true);
|
||||||
|
|
||||||
Object.values(repositoryMock).forEach((mockFn) => {
|
|
||||||
expect(mockFn).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
Object.values(spies).forEach((spy: any) => {
|
Object.values(spies).forEach((spy: any) => {
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -64,9 +52,6 @@ describe("TimedApiBasedRepositoryLoader", () => {
|
|||||||
await loader.start();
|
await loader.start();
|
||||||
await loader.start();
|
await loader.start();
|
||||||
|
|
||||||
Object.values(repositoryMock).forEach((mockFn) => {
|
|
||||||
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
Object.values(spies).forEach((spy: any) => {
|
Object.values(spies).forEach((spy: any) => {
|
||||||
expect(spy).toHaveBeenCalledTimes(1);
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -429,6 +429,194 @@ describe("UnoptimizedInMemoryRepository", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("removeSystemIfExists", () => {
|
||||||
|
test("removes system given ID", async () => {
|
||||||
|
const mockSystems = generateMockSystems();
|
||||||
|
await Promise.all(mockSystems.map(async (system) => {
|
||||||
|
await repository.addOrUpdateSystem(system);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const systemToRemove = mockSystems[0];
|
||||||
|
await repository.removeSystemIfExists(systemToRemove.id);
|
||||||
|
|
||||||
|
const remainingSystems = await repository.getSystems();
|
||||||
|
expect(remainingSystems).toHaveLength(mockSystems.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does nothing if system doesn't exist", async () => {
|
||||||
|
const mockSystems = generateMockSystems();
|
||||||
|
await Promise.all(mockSystems.map(async (system) => {
|
||||||
|
await repository.addOrUpdateSystem(system);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await repository.removeSystemIfExists("nonexistent-id");
|
||||||
|
|
||||||
|
const remainingSystems = await repository.getSystems();
|
||||||
|
expect(remainingSystems).toHaveLength(mockSystems.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("removeRouteIfExists", () => {
|
||||||
|
test("removes route given ID", async () => {
|
||||||
|
const systemId = "1";
|
||||||
|
const mockRoutes = generateMockRoutes();
|
||||||
|
await Promise.all(mockRoutes.map(async (route) => {
|
||||||
|
route.systemId = systemId;
|
||||||
|
await repository.addOrUpdateRoute(route);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const routeToRemove = mockRoutes[0];
|
||||||
|
await repository.removeRouteIfExists(routeToRemove.id);
|
||||||
|
|
||||||
|
const remainingRoutes = await repository.getRoutesBySystemId(systemId);
|
||||||
|
expect(remainingRoutes).toHaveLength(mockRoutes.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does nothing if route doesn't exist", async () => {
|
||||||
|
const systemId = "1";
|
||||||
|
const mockRoutes = generateMockRoutes();
|
||||||
|
await Promise.all(mockRoutes.map(async (route) => {
|
||||||
|
route.systemId = systemId;
|
||||||
|
await repository.addOrUpdateRoute(route);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await repository.removeRouteIfExists("nonexistent-id");
|
||||||
|
|
||||||
|
const remainingRoutes = await repository.getRoutesBySystemId(systemId);
|
||||||
|
expect(remainingRoutes).toHaveLength(mockRoutes.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("removeShuttleIfExists", () => {
|
||||||
|
test("removes shuttle given ID", async () => {
|
||||||
|
const systemId = "1";
|
||||||
|
const mockShuttles = generateMockShuttles();
|
||||||
|
await Promise.all(mockShuttles.map(async (shuttle) => {
|
||||||
|
shuttle.systemId = systemId;
|
||||||
|
await repository.addOrUpdateShuttle(shuttle);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const shuttleToRemove = mockShuttles[0];
|
||||||
|
await repository.removeShuttleIfExists(shuttleToRemove.id);
|
||||||
|
|
||||||
|
const remainingShuttles = await repository.getShuttlesBySystemId(systemId);
|
||||||
|
expect(remainingShuttles).toHaveLength(mockShuttles.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does nothing if shuttle doesn't exist", async () => {
|
||||||
|
const systemId = "1";
|
||||||
|
const mockShuttles = generateMockShuttles();
|
||||||
|
await Promise.all(mockShuttles.map(async (shuttle) => {
|
||||||
|
shuttle.systemId = systemId;
|
||||||
|
await repository.addOrUpdateShuttle(shuttle);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await repository.removeShuttleIfExists("nonexistent-id");
|
||||||
|
|
||||||
|
const remainingShuttles = await repository.getShuttlesBySystemId(systemId);
|
||||||
|
expect(remainingShuttles).toHaveLength(mockShuttles.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("removeStopIfExists", () => {
|
||||||
|
test("removes stop given ID", async () => {
|
||||||
|
const systemId = "1";
|
||||||
|
const mockStops = generateMockStops();
|
||||||
|
await Promise.all(mockStops.map(async (stop) => {
|
||||||
|
stop.systemId = systemId;
|
||||||
|
await repository.addOrUpdateStop(stop);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const stopToRemove = mockStops[0];
|
||||||
|
await repository.removeStopIfExists(stopToRemove.id);
|
||||||
|
|
||||||
|
const remainingStops = await repository.getStopsBySystemId(systemId);
|
||||||
|
expect(remainingStops).toHaveLength(mockStops.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does nothing if stop doesn't exist", async () => {
|
||||||
|
const systemId = "1";
|
||||||
|
const mockStops = generateMockStops();
|
||||||
|
await Promise.all(mockStops.map(async (stop) => {
|
||||||
|
stop.systemId = systemId;
|
||||||
|
await repository.addOrUpdateStop(stop);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await repository.removeStopIfExists("nonexistent-id");
|
||||||
|
|
||||||
|
const remainingStops = await repository.getStopsBySystemId(systemId);
|
||||||
|
expect(remainingStops).toHaveLength(mockStops.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("removeOrderedStopIfExists", () => {
|
||||||
|
test("removes ordered stop given stop ID and route ID", async () => {
|
||||||
|
let mockOrderedStops = generateMockOrderedStops();
|
||||||
|
const routeId = mockOrderedStops[0].routeId;
|
||||||
|
mockOrderedStops = mockOrderedStops.filter((orderedStop) => orderedStop.routeId === routeId);
|
||||||
|
await Promise.all(mockOrderedStops.map(async (stop) => {
|
||||||
|
stop.routeId = routeId;
|
||||||
|
await repository.addOrUpdateOrderedStop(stop);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const orderedStopToRemove = mockOrderedStops[0];
|
||||||
|
await repository.removeOrderedStopIfExists(orderedStopToRemove.stopId, orderedStopToRemove.routeId);
|
||||||
|
|
||||||
|
const remainingOrderedStops = await repository.getOrderedStopsByRouteId(routeId);
|
||||||
|
expect(remainingOrderedStops).toHaveLength(mockOrderedStops.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does nothing if ordered stop doesn't exist", async () => {
|
||||||
|
let mockOrderedStops = generateMockOrderedStops();
|
||||||
|
const routeId = mockOrderedStops[0].routeId;
|
||||||
|
mockOrderedStops = mockOrderedStops.filter((orderedStop) => orderedStop.routeId === routeId);
|
||||||
|
await Promise.all(mockOrderedStops.map(async (stop) => {
|
||||||
|
stop.routeId = routeId;
|
||||||
|
await repository.addOrUpdateOrderedStop(stop);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await repository.removeOrderedStopIfExists("nonexistent-stop-id", "nonexistent-route-id");
|
||||||
|
|
||||||
|
const remainingOrderedStops = await repository.getOrderedStopsByRouteId(routeId);
|
||||||
|
expect(remainingOrderedStops).toHaveLength(mockOrderedStops.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("removeEtaIfExists", () => {
|
||||||
|
test("removes eta given shuttle ID and stop ID", async () => {
|
||||||
|
let mockEtas = generateMockEtas();
|
||||||
|
const stopId = mockEtas[0].stopId;
|
||||||
|
mockEtas = mockEtas.filter((eta) => eta.stopId === stopId);
|
||||||
|
|
||||||
|
await Promise.all(mockEtas.map(async (eta) => {
|
||||||
|
eta.stopId = stopId;
|
||||||
|
await repository.addOrUpdateEta(eta);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const etaToRemove = mockEtas[0];
|
||||||
|
await repository.removeEtaIfExists(etaToRemove.shuttleId, etaToRemove.stopId);
|
||||||
|
|
||||||
|
const remainingEtas = await repository.getEtasForStopId(stopId);
|
||||||
|
expect(remainingEtas).toHaveLength(mockEtas.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does nothing if eta doesn't exist", async () => {
|
||||||
|
let mockEtas = generateMockEtas();
|
||||||
|
const stopId = mockEtas[0].stopId;
|
||||||
|
mockEtas = mockEtas.filter((eta) => eta.stopId === stopId);
|
||||||
|
|
||||||
|
await Promise.all(mockEtas.map(async (eta) => {
|
||||||
|
eta.stopId = stopId;
|
||||||
|
await repository.addOrUpdateEta(eta);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await repository.removeEtaIfExists("nonexistent-shuttle-id", "nonexistent-stop-id");
|
||||||
|
|
||||||
|
const remainingEtas = await repository.getEtasForStopId(stopId);
|
||||||
|
expect(remainingEtas).toHaveLength(mockEtas.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("clearSystemData", () => {
|
describe("clearSystemData", () => {
|
||||||
test("clears all systems from the repository", async () => {
|
test("clears all systems from the repository", async () => {
|
||||||
const mockSystems = generateMockSystems();
|
const mockSystems = generateMockSystems();
|
||||||
|
|||||||
Reference in New Issue
Block a user