mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-17 07:50:31 +00:00
Merge pull request #9 from brendan-ch/chore/test-api-based-repository-loader
chore/test-api-based-repository-loader
This commit is contained in:
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ExceptionCaughtLocallyJS" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
@@ -1,26 +1,39 @@
|
|||||||
import { GetterSetterRepository } from "../repositories/GetterSetterRepository";
|
import { GetterSetterRepository } from "../repositories/GetterSetterRepository";
|
||||||
import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
|
import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities";
|
||||||
|
|
||||||
const systemIdsToSupport = ["263"];
|
export class ApiResponseError extends Error {
|
||||||
const baseUrl = "https://passiogo.com/mapGetData.php";
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "ApiResponseError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ApiBasedRepositoryLoader {
|
export class ApiBasedRepositoryLoader {
|
||||||
|
supportedSystemIds = ["263"];
|
||||||
|
baseUrl = "https://passiogo.com/mapGetData.php";
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected repository: GetterSetterRepository,
|
public repository: GetterSetterRepository,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected 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 response = await fetch(`${baseUrl}?${query}`);
|
|
||||||
const json = await response.json()
|
try {
|
||||||
|
const response = await fetch(`${this.baseUrl}?${query}`);
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error with status ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof json.all === "object") {
|
if (typeof json.all === "object") {
|
||||||
// filter down to supported systems
|
// filter down to supported systems
|
||||||
const filteredSystems = json.all.filter((jsonSystem: any) => systemIdsToSupport.includes(jsonSystem.id));
|
const filteredSystems = json.all.filter((jsonSystem: any) => this.supportedSystemIds.includes(jsonSystem.id));
|
||||||
await Promise.all(filteredSystems.map(async (system: any) => {
|
await Promise.all(filteredSystems.map(async (system: any) => {
|
||||||
const constructedSystem: ISystem = {
|
const constructedSystem: ISystem = {
|
||||||
id: system.id,
|
id: system.id,
|
||||||
@@ -29,25 +42,37 @@ export class ApiBasedRepositoryLoader {
|
|||||||
|
|
||||||
await this.repository.addOrUpdateSystem(constructedSystem);
|
await this.repository.addOrUpdateSystem(constructedSystem);
|
||||||
}));
|
}));
|
||||||
|
} else {
|
||||||
|
throw new Error("Received JSON object does not contain `all` field")
|
||||||
|
}
|
||||||
|
} catch(e: any) {
|
||||||
|
throw new ApiResponseError(e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async fetchAndUpdateRouteDataForExistingSystems() {
|
public async fetchAndUpdateRouteDataForExistingSystemsInRepository() {
|
||||||
const systems = await this.repository.getSystems();
|
const systems = await this.repository.getSystems();
|
||||||
await Promise.all(systems.map(async (system) => {
|
await Promise.all(systems.map(async (system) => {
|
||||||
|
await this.fetchAndUpdateRouteDataForSystemId(system.id);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchAndUpdateRouteDataForSystemId(systemId: string) {
|
||||||
const params = {
|
const params = {
|
||||||
getRoutes: "2",
|
getRoutes: "2",
|
||||||
};
|
};
|
||||||
|
|
||||||
const formDataJsonObject = {
|
const formDataJsonObject = {
|
||||||
"systemSelected0": system.id,
|
"systemSelected0": systemId,
|
||||||
"amount": "1",
|
"amount": "1",
|
||||||
}
|
}
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.set("json", JSON.stringify(formDataJsonObject));
|
formData.set("json", JSON.stringify(formDataJsonObject));
|
||||||
|
|
||||||
const query = new URLSearchParams(params).toString();
|
const query = new URLSearchParams(params).toString();
|
||||||
const response = await fetch(`${baseUrl}?${query}`, {
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.baseUrl}?${query}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
@@ -60,53 +85,70 @@ export class ApiBasedRepositoryLoader {
|
|||||||
color: jsonRoute.color,
|
color: jsonRoute.color,
|
||||||
id: jsonRoute.myid,
|
id: jsonRoute.myid,
|
||||||
polylineCoordinates: [],
|
polylineCoordinates: [],
|
||||||
systemId: system.id,
|
systemId: systemId,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.repository.addOrUpdateRoute(constructedRoute);
|
await this.repository.addOrUpdateRoute(constructedRoute);
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
} catch(e: any) {
|
||||||
|
throw new ApiResponseError(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository() {
|
||||||
|
const systems = await this.repository.getSystems();
|
||||||
|
await Promise.all(systems.map(async (system: ISystem) => {
|
||||||
|
await this.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId(system.id);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystems() {
|
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 systems = await this.repository.getSystems();
|
|
||||||
await Promise.all(systems.map(async (system: ISystem) => {
|
|
||||||
const params = {
|
const params = {
|
||||||
getStops: "2",
|
getStops: "2",
|
||||||
};
|
};
|
||||||
|
|
||||||
const formDataJsonObject = {
|
const formDataJsonObject = {
|
||||||
"s0": system.id,
|
"s0": systemId,
|
||||||
"sA": 1
|
"sA": 1
|
||||||
};
|
};
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.set("json", JSON.stringify(formDataJsonObject));
|
formData.set("json", JSON.stringify(formDataJsonObject));
|
||||||
|
|
||||||
const query = new URLSearchParams(params).toString();
|
const query = new URLSearchParams(params).toString();
|
||||||
const response = await fetch(`${baseUrl}?${query}`, {
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.baseUrl}?${query}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
await this.updateStopDataForSystemAndApiResponse(system, json);
|
await this.updateStopDataForSystemAndApiResponse(systemId, json);
|
||||||
await this.updateOrderedStopDataForExistingStops(json);
|
await this.updateOrderedStopDataForExistingStops(json);
|
||||||
await this.updatePolylineDataForExistingRoutesAndApiResponse(json);
|
await this.updatePolylineDataForExistingRoutesAndApiResponse(json);
|
||||||
|
} catch(e: any) {
|
||||||
|
throw new ApiResponseError(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchAndUpdateShuttleDataForExistingSystemsInRepository() {
|
||||||
|
const systems = await this.repository.getSystems();
|
||||||
|
await Promise.all(systems.map(async (system: ISystem) => {
|
||||||
|
const systemId = system.id;
|
||||||
|
await this.fetchAndUpdateShuttleDataForSystemId(systemId);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async fetchAndUpdateShuttleDataForExistingSystems() {
|
public async fetchAndUpdateShuttleDataForSystemId(systemId: string) {
|
||||||
const systems = await this.repository.getSystems();
|
|
||||||
await Promise.all(systems.map(async (system: ISystem) => {
|
|
||||||
const params = {
|
const params = {
|
||||||
getBuses: "2"
|
getBuses: "2"
|
||||||
};
|
};
|
||||||
|
|
||||||
const formDataJsonObject = {
|
const formDataJsonObject = {
|
||||||
"s0": system.id,
|
"s0": systemId,
|
||||||
"sA": "1"
|
"sA": "1"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,7 +156,9 @@ export class ApiBasedRepositoryLoader {
|
|||||||
formData.set("json", JSON.stringify(formDataJsonObject));
|
formData.set("json", JSON.stringify(formDataJsonObject));
|
||||||
|
|
||||||
const query = new URLSearchParams(params).toString();
|
const query = new URLSearchParams(params).toString();
|
||||||
const response = await fetch(`${baseUrl}?${query}`, {
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.baseUrl}?${query}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
@@ -133,55 +177,70 @@ export class ApiBasedRepositoryLoader {
|
|||||||
longitude: parseFloat(jsonBus.longitude),
|
longitude: parseFloat(jsonBus.longitude),
|
||||||
},
|
},
|
||||||
routeId: jsonBus.routeId,
|
routeId: jsonBus.routeId,
|
||||||
systemId: system.id,
|
systemId: systemId,
|
||||||
id: `${jsonBus.busId}`
|
id: `${jsonBus.busId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.repository.addOrUpdateShuttle(constructedShuttle);
|
await this.repository.addOrUpdateShuttle(constructedShuttle);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch(e: any) {
|
||||||
|
throw new ApiResponseError(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository() {
|
||||||
|
const systems = await this.repository.getSystems()
|
||||||
|
await Promise.all(systems.map(async (system: ISystem) => {
|
||||||
|
const systemId = system.id;
|
||||||
|
await this.fetchAndUpdateEtaDataForExistingStopsForSystemId(systemId);
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async fetchAndUpdateEtaDataForExistingStopsForSystemId(systemId: string) {
|
||||||
|
const stops = await this.repository.getStopsBySystemId(systemId);
|
||||||
|
await Promise.all(stops.map(async (stop) => {
|
||||||
|
let stopId = stop.id;
|
||||||
|
await this.fetchAndUpdateEtaDataForStopId(stopId);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async fetchAndUpdateEtaDataForExistingOrderedStops() {
|
public async fetchAndUpdateEtaDataForStopId(stopId: string) {
|
||||||
// TODO implement once I figure out how to associate ETA data with shuttles
|
|
||||||
|
|
||||||
const systems = await this.repository.getSystems()
|
|
||||||
await Promise.all(systems.map(async (system: ISystem) => {
|
|
||||||
const stops = await this.repository.getStopsBySystemId(system.id);
|
|
||||||
await Promise.all(stops.map(async (stop) => {
|
|
||||||
const params = {
|
const params = {
|
||||||
eta: "3",
|
eta: "3",
|
||||||
stopIds: stop.id,
|
stopIds: stopId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const query = new URLSearchParams(params).toString();
|
const query = new URLSearchParams(params).toString();
|
||||||
const response = await fetch(`${baseUrl}?${query}`, {
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.baseUrl}?${query}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
if (json.ETAs && json.ETAs[stop.id]) {
|
if (json.ETAs && json.ETAs[stopId]) {
|
||||||
// Continue with the parsing
|
// Continue with the parsing
|
||||||
json.ETAs[stop.id].forEach((jsonEta: any) => {
|
json.ETAs[stopId].forEach((jsonEta: any) => {
|
||||||
// Update cache
|
// Update cache
|
||||||
const shuttleId: string = jsonEta.busId;
|
const shuttleId: string = jsonEta.busId;
|
||||||
|
|
||||||
const eta: IEta = {
|
const eta: IEta = {
|
||||||
secondsRemaining: jsonEta.secondsSpent,
|
secondsRemaining: jsonEta.secondsSpent,
|
||||||
shuttleId: `${shuttleId}`,
|
shuttleId: `${shuttleId}`,
|
||||||
stopId: stop.id,
|
stopId: stopId,
|
||||||
millisecondsSinceEpoch: Date.now(),
|
millisecondsSinceEpoch: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.repository.addOrUpdateEta(eta);
|
this.repository.addOrUpdateEta(eta);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}));
|
} catch(e: any) {
|
||||||
}))
|
throw new ApiResponseError(e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async updateStopDataForSystemAndApiResponse(system: ISystem, json: any) {
|
protected async updateStopDataForSystemAndApiResponse(systemId: string, json: any) {
|
||||||
if (json.stops) {
|
if (json.stops) {
|
||||||
const jsonStops = Object.values(json.stops);
|
const jsonStops = Object.values(json.stops);
|
||||||
|
|
||||||
@@ -189,7 +248,7 @@ export class ApiBasedRepositoryLoader {
|
|||||||
const constructedStop: IStop = {
|
const constructedStop: IStop = {
|
||||||
name: stop.name,
|
name: stop.name,
|
||||||
id: stop.id,
|
id: stop.id,
|
||||||
systemId: system.id,
|
systemId,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
latitude: parseFloat(stop.latitude),
|
latitude: parseFloat(stop.latitude),
|
||||||
longitude: parseFloat(stop.longitude),
|
longitude: parseFloat(stop.longitude),
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ export class TimedApiBasedRepositoryLoader extends ApiBasedRepositoryLoader {
|
|||||||
await this.repository.clearSystemData();
|
await this.repository.clearSystemData();
|
||||||
await this.fetchAndUpdateSystemData();
|
await this.fetchAndUpdateSystemData();
|
||||||
await this.repository.clearRouteData();
|
await this.repository.clearRouteData();
|
||||||
await this.fetchAndUpdateRouteDataForExistingSystems();
|
await this.fetchAndUpdateRouteDataForExistingSystemsInRepository();
|
||||||
await this.repository.clearStopData();
|
await this.repository.clearStopData();
|
||||||
await this.fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystems();
|
await this.fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository();
|
||||||
await this.repository.clearShuttleData();
|
await this.repository.clearShuttleData();
|
||||||
await this.fetchAndUpdateShuttleDataForExistingSystems();
|
await this.fetchAndUpdateShuttleDataForExistingSystemsInRepository();
|
||||||
await this.repository.clearEtaData();
|
await this.repository.clearEtaData();
|
||||||
await this.fetchAndUpdateEtaDataForExistingOrderedStops();
|
await this.fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Snapshot taken from the Passio GO! API
|
// Snapshot taken from the Passio GO! API
|
||||||
export const genericEtaDataByStopId = {
|
export const fetchEtaDataSuccessfulResponse = {
|
||||||
"ETAs": {
|
"ETAs": {
|
||||||
"177666": [
|
"177666": [
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
export const fetchRouteDataSuccessfulResponse = {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"name": "Rinker Route",
|
||||||
|
"shortName": null,
|
||||||
|
"color": "#696969",
|
||||||
|
"userId": "263",
|
||||||
|
"myid": "53970",
|
||||||
|
"mapApp": "1",
|
||||||
|
"archive": "0",
|
||||||
|
"goPrefixRouteName": "1",
|
||||||
|
"goShowSchedule": 0,
|
||||||
|
"outdated": "0",
|
||||||
|
"id": "5902",
|
||||||
|
"distance": 1900,
|
||||||
|
"latitude": "33.656851000",
|
||||||
|
"longitude": "-117.733221000",
|
||||||
|
"timezone": "America/Los_Angeles",
|
||||||
|
"fullname": "Chapman University",
|
||||||
|
"nameOrig": "Rinker Route",
|
||||||
|
"groupId": "6706",
|
||||||
|
"groupColor": "#696969"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Red Route",
|
||||||
|
"shortName": null,
|
||||||
|
"color": "#d62728",
|
||||||
|
"userId": "263",
|
||||||
|
"myid": "53966",
|
||||||
|
"mapApp": "1",
|
||||||
|
"archive": "0",
|
||||||
|
"goPrefixRouteName": "1",
|
||||||
|
"goShowSchedule": 0,
|
||||||
|
"outdated": "0",
|
||||||
|
"id": "2032",
|
||||||
|
"distance": 1905,
|
||||||
|
"latitude": "33.793325000",
|
||||||
|
"longitude": "-117.852810000",
|
||||||
|
"timezone": "America/Los_Angeles",
|
||||||
|
"fullname": "Chapman University",
|
||||||
|
"nameOrig": "Red Route",
|
||||||
|
"groupId": "6703",
|
||||||
|
"groupColor": "#d62728"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Teal Route",
|
||||||
|
"shortName": null,
|
||||||
|
"color": "#096e90",
|
||||||
|
"userId": "263",
|
||||||
|
"myid": "54191",
|
||||||
|
"mapApp": "1",
|
||||||
|
"archive": "0",
|
||||||
|
"goPrefixRouteName": "1",
|
||||||
|
"goShowSchedule": 0,
|
||||||
|
"outdated": "0",
|
||||||
|
"id": "2032",
|
||||||
|
"distance": 1905,
|
||||||
|
"latitude": "33.793325000",
|
||||||
|
"longitude": "-117.852810000",
|
||||||
|
"timezone": "America/Los_Angeles",
|
||||||
|
"fullname": "Chapman University",
|
||||||
|
"nameOrig": "Teal Route",
|
||||||
|
"groupId": "6705",
|
||||||
|
"groupColor": "#096e90"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gold Route",
|
||||||
|
"shortName": null,
|
||||||
|
"color": "#bd9e39",
|
||||||
|
"userId": "263",
|
||||||
|
"myid": "54256",
|
||||||
|
"mapApp": "1",
|
||||||
|
"archive": "0",
|
||||||
|
"goPrefixRouteName": "1",
|
||||||
|
"goShowSchedule": 0,
|
||||||
|
"outdated": "0",
|
||||||
|
"id": "2032",
|
||||||
|
"distance": 1905,
|
||||||
|
"latitude": "33.793325000",
|
||||||
|
"longitude": "-117.852810000",
|
||||||
|
"timezone": "America/Los_Angeles",
|
||||||
|
"fullname": "Chapman University",
|
||||||
|
"nameOrig": "Gold Route",
|
||||||
|
"groupId": "6707",
|
||||||
|
"groupColor": "#bd9e39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Black Route",
|
||||||
|
"shortName": null,
|
||||||
|
"color": "#000000",
|
||||||
|
"userId": "263",
|
||||||
|
"myid": "54257",
|
||||||
|
"mapApp": "1",
|
||||||
|
"archive": "0",
|
||||||
|
"goPrefixRouteName": "1",
|
||||||
|
"goShowSchedule": 0,
|
||||||
|
"outdated": "0",
|
||||||
|
"id": "2032",
|
||||||
|
"distance": 1905,
|
||||||
|
"latitude": "33.793325000",
|
||||||
|
"longitude": "-117.852810000",
|
||||||
|
"timezone": "America/Los_Angeles",
|
||||||
|
"fullname": "Chapman University",
|
||||||
|
"nameOrig": "Black Route",
|
||||||
|
"groupId": "6704",
|
||||||
|
"groupColor": "#000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Weekend Route",
|
||||||
|
"shortName": null,
|
||||||
|
"color": "#510094",
|
||||||
|
"userId": "263",
|
||||||
|
"myid": "54282",
|
||||||
|
"mapApp": "1",
|
||||||
|
"archive": "0",
|
||||||
|
"goPrefixRouteName": "1",
|
||||||
|
"goShowSchedule": 0,
|
||||||
|
"outdated": "1",
|
||||||
|
"id": "2032",
|
||||||
|
"distance": 1905,
|
||||||
|
"latitude": "33.793325000",
|
||||||
|
"longitude": "-117.852810000",
|
||||||
|
"timezone": "America/Los_Angeles",
|
||||||
|
"fullname": "Chapman University",
|
||||||
|
"nameOrig": "Weekend Route",
|
||||||
|
"groupId": "6710",
|
||||||
|
"groupColor": "#510094",
|
||||||
|
"serviceTime": "is not provided: no bus on the route",
|
||||||
|
"serviceTimeShort": "No bus in service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Parking Lot",
|
||||||
|
"shortName": null,
|
||||||
|
"color": "#000000",
|
||||||
|
"userId": "263",
|
||||||
|
"myid": "54529",
|
||||||
|
"mapApp": "1",
|
||||||
|
"archive": "0",
|
||||||
|
"goPrefixRouteName": "1",
|
||||||
|
"goShowSchedule": 0,
|
||||||
|
"outdated": "0",
|
||||||
|
"id": "2032",
|
||||||
|
"distance": 1905,
|
||||||
|
"latitude": "33.793325000",
|
||||||
|
"longitude": "-117.852810000",
|
||||||
|
"timezone": "America/Los_Angeles",
|
||||||
|
"fullname": "Chapman University",
|
||||||
|
"nameOrig": "Parking Lot",
|
||||||
|
"groupId": "6731",
|
||||||
|
"groupColor": "#393838"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Snapshot taken from the Passio GO! API
|
// Snapshot taken from the Passio GO! API
|
||||||
export const genericShuttleDataBySystemId = {
|
export const fetchShuttleDataSuccessfulResponse = {
|
||||||
"alertCRC": "23c1b91c",
|
"alertCRC": "23c1b91c",
|
||||||
"buses": {
|
"buses": {
|
||||||
"402840": [
|
"402840": [
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
|||||||
|
// Not an actual response snapshot
|
||||||
|
// Simulate a case where `all` property of response is unavailable
|
||||||
|
export const fetchSystemDataFailedResponse = {
|
||||||
|
"error": "no systems",
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
279
test/loaders/ApiBasedRepositoryLoaderTests.test.ts
Normal file
279
test/loaders/ApiBasedRepositoryLoaderTests.test.ts
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
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 {
|
||||||
|
fetchStopAndPolylineDataSuccessfulResponse
|
||||||
|
} from "../jsonSnapshots/fetchStopAndPolylineData/fetchStopAndPolylineDataSuccessfulResponse";
|
||||||
|
import { generateMockStops, generateMockSystems } from "../generators";
|
||||||
|
import { IStop } from "../../src/entities/entities";
|
||||||
|
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({});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 = generateMockSystems();
|
||||||
|
|
||||||
|
await Promise.all(systems.map(async (system) => {
|
||||||
|
await loader.repository.addOrUpdateSystem(system);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await loader.fetchAndUpdateRouteDataForExistingSystemsInRepository();
|
||||||
|
|
||||||
|
expect(spy.mock.calls.length).toBe(systems.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchAndUpdateRouteDataForSystemId", () => {
|
||||||
|
it("updates route data in repository if 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
|
||||||
|
|
||||||
|
updateGlobalFetchMockJsonToThrowSyntaxError();
|
||||||
|
|
||||||
|
await assertAsyncCallbackThrowsApiResponseError(async () => {
|
||||||
|
await loader.fetchAndUpdateRouteDataForSystemId("263");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository", () => {
|
||||||
|
it("calls fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId for every system", async () => {
|
||||||
|
const spy = jest.spyOn(loader, "fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId");
|
||||||
|
|
||||||
|
const systems = generateMockSystems();
|
||||||
|
|
||||||
|
await Promise.all(systems.map(async (system) => {
|
||||||
|
await loader.repository.addOrUpdateSystem(system);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await loader.fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystemsInRepository();
|
||||||
|
|
||||||
|
expect(spy.mock.calls.length).toBe(systems.length);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId", () => {
|
||||||
|
it("updates stop and polyline data if 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 () => {
|
||||||
|
updateGlobalFetchMockJsonToThrowSyntaxError();
|
||||||
|
|
||||||
|
await assertAsyncCallbackThrowsApiResponseError(async () => {
|
||||||
|
await loader.fetchAndUpdateStopAndPolylineDataForRoutesWithSystemId("263");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchAndUpdateShuttleDataForExistingSystemsInRepository", () => {
|
||||||
|
it("calls fetchAndUpdateShuttleDataForSystemId for every system", async () => {
|
||||||
|
const spy = jest.spyOn(loader, "fetchAndUpdateShuttleDataForSystemId");
|
||||||
|
|
||||||
|
const systems = generateMockSystems();
|
||||||
|
await Promise.all(systems.map(async (system) => {
|
||||||
|
await loader.repository.addOrUpdateSystem(system);
|
||||||
|
}))
|
||||||
|
|
||||||
|
await loader.fetchAndUpdateShuttleDataForExistingSystemsInRepository();
|
||||||
|
|
||||||
|
expect(spy.mock.calls.length).toBe(systems.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchAndUpdateShuttleDataForSystemId", () => {
|
||||||
|
it("updates shuttle data in repository if response received", async () => {
|
||||||
|
updateGlobalFetchMockJson(fetchShuttleDataSuccessfulResponse);
|
||||||
|
const busesInResponse = Object.values(fetchShuttleDataSuccessfulResponse.buses);
|
||||||
|
|
||||||
|
await loader.fetchAndUpdateShuttleDataForSystemId("263");
|
||||||
|
|
||||||
|
const shuttles = await loader.repository.getShuttlesBySystemId("263");
|
||||||
|
|
||||||
|
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.fetchAndUpdateShuttleDataForSystemId("263");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository", () => {
|
||||||
|
it("calls fetchAndUpdateEtaDataFoExistingStopsForSystemId for every system in repository", async () => {
|
||||||
|
const spy = jest.spyOn(loader, "fetchAndUpdateEtaDataForExistingStopsForSystemId");
|
||||||
|
|
||||||
|
const systems = generateMockSystems();
|
||||||
|
await Promise.all(systems.map(async (system) => {
|
||||||
|
await loader.repository.addOrUpdateSystem(system);
|
||||||
|
}));
|
||||||
|
|
||||||
|
await loader.fetchAndUpdateEtaDataForExistingStopsForSystemsInRepository();
|
||||||
|
|
||||||
|
expect(spy.mock.calls.length).toBe(systems.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchAndUpdateEtaDataForExistingStopsForSystemId", () => {
|
||||||
|
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.fetchAndUpdateEtaDataForExistingStopsForSystemId("1");
|
||||||
|
|
||||||
|
expect(spy.mock.calls.length).toEqual(stops.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetchAndUpdateEtaDataForStopId", () => {
|
||||||
|
it("updates ETA data for stop id if response received", async () => {
|
||||||
|
updateGlobalFetchMockJson(fetchEtaDataSuccessfulResponse);
|
||||||
|
const stopId = "177666";
|
||||||
|
// @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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Reference in New Issue
Block a user