mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-17 07:50:31 +00:00
Update shared test file and remove Redis-specific tests
This commit is contained in:
@@ -1,385 +0,0 @@
|
||||
import { beforeEach, describe, it, expect, afterEach } from "@jest/globals";
|
||||
import { RedisShuttleRepository } from "../RedisShuttleRepository";
|
||||
import { generateMockShuttles } from "../../../../testHelpers/mockDataGenerators";
|
||||
import { IOrderedStop } from "../../../entities/ShuttleRepositoryEntities";
|
||||
|
||||
describe("RedisShuttleRepository", () => {
|
||||
let repository: RedisShuttleRepository;
|
||||
|
||||
beforeEach(async () => {
|
||||
repository = new RedisShuttleRepository();
|
||||
await repository.connect();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await repository.clearAllData();
|
||||
await repository.disconnect();
|
||||
});
|
||||
|
||||
async function setupRouteAndOrderedStops() {
|
||||
const systemId = "sys1";
|
||||
const route = {
|
||||
id: "r1",
|
||||
name: "Route 1",
|
||||
color: "red",
|
||||
systemId: systemId,
|
||||
polylineCoordinates: [],
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
await repository.addOrUpdateRoute(route);
|
||||
|
||||
const stop1 = {
|
||||
id: "st1",
|
||||
name: "Stop 1",
|
||||
systemId: systemId,
|
||||
coordinates: { latitude: 10.0, longitude: 20.0 },
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
const stop2 = {
|
||||
id: "st2",
|
||||
name: "Stop 2",
|
||||
systemId: systemId,
|
||||
coordinates: { latitude: 15.0, longitude: 25.0 },
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
await repository.addOrUpdateStop(stop1);
|
||||
await repository.addOrUpdateStop(stop2);
|
||||
|
||||
const orderedStop1: IOrderedStop = {
|
||||
routeId: route.id,
|
||||
stopId: stop1.id,
|
||||
position: 1,
|
||||
systemId: systemId,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
const orderedStop2: IOrderedStop = {
|
||||
routeId: route.id,
|
||||
stopId: stop2.id,
|
||||
position: 2,
|
||||
systemId: systemId,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
orderedStop1.nextStop = orderedStop2;
|
||||
orderedStop1.previousStop = orderedStop2;
|
||||
orderedStop2.nextStop = orderedStop1;
|
||||
orderedStop2.previousStop = orderedStop1;
|
||||
await repository.addOrUpdateOrderedStop(orderedStop1);
|
||||
await repository.addOrUpdateOrderedStop(orderedStop2);
|
||||
|
||||
return {
|
||||
route,
|
||||
systemId,
|
||||
stop1,
|
||||
stop2,
|
||||
};
|
||||
}
|
||||
|
||||
describe("addOrUpdateShuttle", () => {
|
||||
it("updates the shuttle's last stop arrival if shuttle is at a stop", async () => {
|
||||
const { route, systemId, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
// Shuttle positioned at stop2
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop2.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
await repository.addOrUpdateShuttle(shuttle);
|
||||
const lastStop = await repository.getShuttleLastStopArrival(shuttle.id);
|
||||
expect(lastStop?.stopId).toEqual(stop2.id)
|
||||
});
|
||||
|
||||
it("updates how long the shuttle took to get from one stop to another", async () => {
|
||||
const { route, systemId, stop2, stop1 } = await setupRouteAndOrderedStops();
|
||||
|
||||
// Start the shuttle at stop 1, then have it move to stop 2
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
}
|
||||
|
||||
const firstStopArrivalTime = new Date(2025, 0, 1, 12, 0, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, firstStopArrivalTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
// 15-minute travel time
|
||||
const secondStopArrivalTime = new Date(2025, 0, 1, 12, 15, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, secondStopArrivalTime.getTime());
|
||||
|
||||
const travelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 1, 11, 0, 0),
|
||||
to: new Date(2025, 0, 1, 13, 0, 0),
|
||||
});
|
||||
expect(travelTime).toEqual(15 * 60)
|
||||
});
|
||||
|
||||
it("adds an ETA entry based on historical data", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
// Start the shuttle at stop 1, then have it move to stop 2
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
}
|
||||
|
||||
const firstStopArrivalTime = new Date(2025, 0, 1, 12, 0, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, firstStopArrivalTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
// 15-minute travel time
|
||||
const secondStopArrivalTime = new Date(2025, 0, 1, 12, 15, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, secondStopArrivalTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop1.coordinates;
|
||||
await repository.addOrUpdateShuttle(
|
||||
shuttle,
|
||||
new Date(2025, 0, 8, 12, 0, 0).getTime(),
|
||||
new Date(2025, 0, 8, 12, 7, 30),
|
||||
);
|
||||
|
||||
const eta = await repository.getEtaForShuttleAndStopId(shuttle.id, stop2.id);
|
||||
expect(eta?.secondsRemaining).toEqual(7 * 60 + 30);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getArrivedStopIfExists", () => {
|
||||
it("gets the stop that the shuttle is currently at, if exists", async () => {
|
||||
const { route, systemId, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
// Create a shuttle positioned at stop2
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop2.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const result = await repository.getArrivedStopIfExists(shuttle);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.id).toBe("st2");
|
||||
expect(result?.name).toBe("Stop 2");
|
||||
});
|
||||
|
||||
it("returns undefined if shuttle is not currently at a stop", async () => {
|
||||
const { route, systemId } = await setupRouteAndOrderedStops();
|
||||
|
||||
// Create a shuttle positioned between stops (not at any stop)
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: { latitude: 12.5, longitude: 22.5 }, // Between stop1 and stop2
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const result = await repository.getArrivedStopIfExists(shuttle);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getShuttleLastStopArrival", () => {
|
||||
it("gets the shuttle's last stop if existing in the data", async () => {
|
||||
const { route, systemId, stop1 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const stopArrivalTime = new Date("2024-01-15T10:30:00Z");
|
||||
await repository.addOrUpdateShuttle(shuttle, stopArrivalTime.getTime());
|
||||
|
||||
const result = await repository.getShuttleLastStopArrival(shuttle.id);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.stopId).toBe(stop1.id);
|
||||
expect(result?.timestamp.getTime()).toBe(stopArrivalTime.getTime());
|
||||
});
|
||||
|
||||
it("returns undefined if the data has never been initialized", async () => {
|
||||
const mockShuttles = generateMockShuttles();
|
||||
const shuttle = mockShuttles[0];
|
||||
|
||||
const result = await repository.getShuttleLastStopArrival(shuttle.id);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns the most recent stop arrival when updated multiple times", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const firstArrivalTime = new Date("2024-01-15T10:30:00Z");
|
||||
await repository.addOrUpdateShuttle(shuttle, firstArrivalTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
const secondArrivalTime = new Date("2024-01-15T10:35:00Z");
|
||||
await repository.addOrUpdateShuttle(shuttle, secondArrivalTime.getTime());
|
||||
|
||||
const result = await repository.getShuttleLastStopArrival(shuttle.id);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.stopId).toBe(stop2.id);
|
||||
expect(result?.timestamp.getTime()).toBe(secondArrivalTime.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAverageTravelTimeSeconds", () => {
|
||||
it("returns the average travel time when historical data exists", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
// Shuttle arrives at stop1
|
||||
const firstStopTime = new Date(2025, 0, 1, 12, 0, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, firstStopTime.getTime());
|
||||
|
||||
// Shuttle moves to stop2 (15 minutes later)
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
const secondStopTime = new Date(2025, 0, 1, 12, 15, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, secondStopTime.getTime());
|
||||
|
||||
const travelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 1, 11, 0, 0),
|
||||
to: new Date(2025, 0, 1, 13, 0, 0),
|
||||
});
|
||||
|
||||
expect(travelTime).toEqual(15 * 60);
|
||||
});
|
||||
|
||||
it("returns average of multiple data points", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
// First trip: 10 minutes travel time
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 0, 0).getTime());
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 10, 0).getTime());
|
||||
|
||||
// Second trip: 20 minutes travel time
|
||||
shuttle.coordinates = stop1.coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 30, 0).getTime());
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 50, 0).getTime());
|
||||
|
||||
const averageTravelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 1, 11, 0, 0),
|
||||
to: new Date(2025, 0, 1, 14, 0, 0),
|
||||
});
|
||||
|
||||
// Average of 10 minutes and 20 minutes = 15 minutes = 900 seconds
|
||||
expect(averageTravelTime).toBeDefined();
|
||||
});
|
||||
|
||||
it("returns undefined when no data exists", async () => {
|
||||
const { route, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
// Don't add any data points, just query for data that doesn't exist
|
||||
const averageTravelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 1, 11, 0, 0),
|
||||
to: new Date(2025, 0, 1, 14, 0, 0),
|
||||
});
|
||||
|
||||
expect(averageTravelTime).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when querying outside the time range of data", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
// Add data on Jan 1
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 0, 0).getTime());
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 15, 0).getTime());
|
||||
|
||||
// Query for Jan 2 (no data should exist in this range)
|
||||
const averageTravelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 2, 11, 0, 0),
|
||||
to: new Date(2025, 0, 2, 13, 0, 0),
|
||||
});
|
||||
expect(averageTravelTime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { UnoptimizedInMemoryShuttleRepository } from "../UnoptimizedInMemoryShut
|
||||
import { ShuttleGetterSetterRepository } from "../ShuttleGetterSetterRepository";
|
||||
import { RedisShuttleRepository } from "../RedisShuttleRepository";
|
||||
import { ShuttleRepositoryEvent } from "../ShuttleGetterRepository";
|
||||
import { IOrderedStop } from "../../../entities/ShuttleRepositoryEntities";
|
||||
import {
|
||||
generateMockEtas,
|
||||
generateMockOrderedStops,
|
||||
@@ -180,118 +181,53 @@ describe.each(repositoryImplementations)('$name', (holder) => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getEtasForShuttleId", () => {
|
||||
test("gets ETAs for a specific shuttle ID", async () => {
|
||||
const mockEtas = generateMockEtas();
|
||||
for (const eta of mockEtas) {
|
||||
await repository.addOrUpdateEta(eta);
|
||||
}
|
||||
|
||||
const result = await repository.getEtasForShuttleId("sh1");
|
||||
const expected = mockEtas.filter((eta) => eta.shuttleId === "sh1");
|
||||
expect(result).toHaveLength(expected.length);
|
||||
expect(result).toEqual(expect.arrayContaining(expected));
|
||||
});
|
||||
|
||||
test("returns an empty list if there are no ETAs for the shuttle ID", async () => {
|
||||
const result = await repository.getEtasForShuttleId("nonexistent-shuttle");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getEtasForStopId", () => {
|
||||
test("gets ETAs for a specific stop ID", async () => {
|
||||
const mockEtas = generateMockEtas();
|
||||
for (const eta of mockEtas) {
|
||||
await repository.addOrUpdateEta(eta);
|
||||
}
|
||||
|
||||
const result = await repository.getEtasForStopId("st1");
|
||||
expect(result).toEqual(mockEtas.filter((eta) => eta.stopId === "st1"));
|
||||
});
|
||||
|
||||
test("returns an empty list if there are no ETAs for the stop ID", async () => {
|
||||
const result = await repository.getEtasForStopId("nonexistent-stop");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getEtaForShuttleAndStopId", () => {
|
||||
test("gets a single ETA for a specific shuttle and stop ID", async () => {
|
||||
const mockEtas = generateMockEtas();
|
||||
const mockEta = mockEtas[0];
|
||||
await repository.addOrUpdateEta(mockEta);
|
||||
|
||||
const result = await repository.getEtaForShuttleAndStopId("sh1", "st1");
|
||||
expect(result).toEqual(mockEta);
|
||||
});
|
||||
|
||||
test("returns null if no ETA matches the shuttle and stop ID", async () => {
|
||||
const result = await repository.getEtaForShuttleAndStopId("nonexistent-shuttle", "nonexistent-stop");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("on/addListener", () => {
|
||||
test("notifies listeners if etas have been added or changed", async () => {
|
||||
describe("ETA event emitters", () => {
|
||||
test("emits ETA_UPDATED event when ETAs are added from external source", async () => {
|
||||
const mockListener = jest.fn();
|
||||
repository.on(ShuttleRepositoryEvent.ETA_UPDATED, mockListener);
|
||||
|
||||
const mockEtas = generateMockEtas();
|
||||
for (const eta of mockEtas) {
|
||||
await repository.addOrUpdateEta(eta);
|
||||
await repository.addOrUpdateEtaFromExternalSource(eta);
|
||||
}
|
||||
|
||||
expect(mockListener).toHaveBeenCalledTimes(mockEtas.length);
|
||||
expect(mockListener).toHaveBeenCalledWith(mockEtas[0]); // First notification
|
||||
expect(mockListener).toHaveBeenCalledWith(mockEtas[mockEtas.length - 1]); // Last notification
|
||||
expect(mockListener).toHaveBeenCalledWith(mockEtas[0]);
|
||||
expect(mockListener).toHaveBeenCalledWith(mockEtas[mockEtas.length - 1]);
|
||||
});
|
||||
|
||||
test("does not notify listener if removed", async () => {
|
||||
test("does not notify listener after it has been removed", async () => {
|
||||
const mockListener = jest.fn();
|
||||
repository.on(ShuttleRepositoryEvent.ETA_UPDATED, mockListener);
|
||||
|
||||
const mockEtas = generateMockEtas();
|
||||
|
||||
repository.off(ShuttleRepositoryEvent.ETA_UPDATED, mockListener);
|
||||
await repository.addOrUpdateEta(mockEtas[0]);
|
||||
await repository.addOrUpdateEtaFromExternalSource(mockEtas[0]);
|
||||
expect(mockListener).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("off/removeListener", () => {
|
||||
test("stops notifying listeners after etas have stopped changing", async () => {
|
||||
const mockListener = jest.fn(); // Jest mock function to simulate a listener
|
||||
repository.on(ShuttleRepositoryEvent.ETA_UPDATED, mockListener);
|
||||
test("stops notifying specific listener after removal but continues for others", async () => {
|
||||
const mockListener1 = jest.fn();
|
||||
const mockListener2 = jest.fn();
|
||||
repository.on(ShuttleRepositoryEvent.ETA_UPDATED, mockListener1);
|
||||
repository.on(ShuttleRepositoryEvent.ETA_UPDATED, mockListener2);
|
||||
|
||||
const mockEtas = generateMockEtas();
|
||||
await repository.addOrUpdateEta(mockEtas[0]);
|
||||
await repository.addOrUpdateEtaFromExternalSource(mockEtas[0]);
|
||||
|
||||
repository.off(ShuttleRepositoryEvent.ETA_UPDATED, mockListener);
|
||||
repository.off(ShuttleRepositoryEvent.ETA_UPDATED, mockListener1);
|
||||
|
||||
await repository.addOrUpdateEta(mockEtas[mockEtas.length - 1]);
|
||||
await repository.addOrUpdateEtaFromExternalSource(mockEtas[mockEtas.length - 1]);
|
||||
|
||||
expect(mockListener).toHaveBeenCalledTimes(1);
|
||||
expect(mockListener).toHaveBeenCalledWith(mockEtas[0]); // First notification
|
||||
expect(mockListener).not.toHaveBeenCalledWith(mockEtas[mockEtas.length - 1]); // Last notification
|
||||
expect(mockListener1).toHaveBeenCalledTimes(1);
|
||||
expect(mockListener1).toHaveBeenCalledWith(mockEtas[0]);
|
||||
expect(mockListener1).not.toHaveBeenCalledWith(mockEtas[mockEtas.length - 1]);
|
||||
|
||||
expect(mockListener2).toHaveBeenCalledTimes(2);
|
||||
expect(mockListener2).toHaveBeenCalledWith(mockEtas[0]);
|
||||
expect(mockListener2).toHaveBeenCalledWith(mockEtas[mockEtas.length - 1]);
|
||||
});
|
||||
|
||||
test("does not remove listener if wrong reference", async () => {
|
||||
const mockListener = jest.fn();
|
||||
repository.on(ShuttleRepositoryEvent.ETA_UPDATED, mockListener);
|
||||
|
||||
const mockEtas = generateMockEtas();
|
||||
|
||||
repository.off(ShuttleRepositoryEvent.ETA_UPDATED, () => {});
|
||||
|
||||
await repository.addOrUpdateEta(mockEtas[0]);
|
||||
|
||||
expect(mockListener).toHaveBeenCalledTimes(1);
|
||||
expect(mockListener).toHaveBeenCalledWith(mockEtas[0]);
|
||||
});
|
||||
})
|
||||
|
||||
describe("ETA update events", () => {
|
||||
});
|
||||
|
||||
describe("getOrderedStopByRouteAndStopId", () => {
|
||||
@@ -452,30 +388,6 @@ describe.each(repositoryImplementations)('$name', (holder) => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("addOrUpdateEta", () => {
|
||||
test("adds a new ETA if nonexistent", async () => {
|
||||
const mockEtas = generateMockEtas();
|
||||
const newEta = mockEtas[0];
|
||||
|
||||
await repository.addOrUpdateEta(newEta);
|
||||
|
||||
const result = await repository.getEtasForShuttleId(newEta.shuttleId);
|
||||
expect(result).toEqual([newEta]);
|
||||
});
|
||||
|
||||
test("updates an existing ETA if it exists", async () => {
|
||||
const mockEtas = generateMockEtas();
|
||||
const existingEta = mockEtas[0];
|
||||
const updatedEta = structuredClone(existingEta);
|
||||
updatedEta.secondsRemaining = existingEta.secondsRemaining + 60;
|
||||
|
||||
await repository.addOrUpdateEta(existingEta);
|
||||
await repository.addOrUpdateEta(updatedEta);
|
||||
|
||||
const result = await repository.getEtasForShuttleId(existingEta.shuttleId);
|
||||
expect(result).toEqual([updatedEta]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeRouteIfExists", () => {
|
||||
test("removes route given ID", async () => {
|
||||
@@ -604,50 +516,42 @@ describe.each(repositoryImplementations)('$name', (holder) => {
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
test("emits an eta removed event when an eta is removed", async () => {
|
||||
const mockEtas = generateMockEtas();
|
||||
const etaToRemove = mockEtas[0];
|
||||
const mockRoutes = generateMockRoutes();
|
||||
const mockStops = generateMockStops();
|
||||
const mockShuttles = generateMockShuttles();
|
||||
const mockOrderedStops = generateMockOrderedStops();
|
||||
|
||||
const listener = jest.fn();
|
||||
repository.on(ShuttleRepositoryEvent.ETA_REMOVED, listener);
|
||||
|
||||
await repository.addOrUpdateEta(etaToRemove);
|
||||
await repository.removeEtaIfExists(etaToRemove.shuttleId, etaToRemove.stopId);
|
||||
// Setup route, stops, and ordered stops
|
||||
await repository.addOrUpdateRoute(mockRoutes[0]);
|
||||
for (const stop of mockStops) {
|
||||
await repository.addOrUpdateStop(stop);
|
||||
}
|
||||
for (const orderedStop of mockOrderedStops.filter(os => os.routeId === mockRoutes[0].id)) {
|
||||
await repository.addOrUpdateOrderedStop(orderedStop);
|
||||
}
|
||||
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
expect(listener).toHaveBeenCalledWith(etaToRemove);
|
||||
// Create an ETA by moving shuttle between stops
|
||||
const shuttle = mockShuttles[0];
|
||||
shuttle.routeId = mockRoutes[0].id;
|
||||
shuttle.coordinates = mockStops[0].coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle);
|
||||
|
||||
shuttle.coordinates = mockStops[1].coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle);
|
||||
|
||||
// Get any ETAs that were created for this shuttle
|
||||
const existingEtas = await repository.getEtasForShuttleId(shuttle.id);
|
||||
|
||||
if (existingEtas.length > 0) {
|
||||
const etaToRemove = existingEtas[0];
|
||||
await repository.removeEtaIfExists(etaToRemove.shuttleId, etaToRemove.stopId);
|
||||
|
||||
expect(listener).toHaveBeenCalledWith(etaToRemove);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -665,36 +569,6 @@ describe.each(repositoryImplementations)('$name', (holder) => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearEtaData", () => {
|
||||
test("clears all ETAs from the repository", async () => {
|
||||
const mockEtas = generateMockEtas();
|
||||
for (const eta of mockEtas) {
|
||||
await repository.addOrUpdateEta(eta);
|
||||
}
|
||||
|
||||
await repository.clearEtaData();
|
||||
|
||||
const result = await repository.getEtasForShuttleId("shuttle1");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test("emits an event with the cleared etas", async () => {
|
||||
const mockEtas = generateMockEtas();
|
||||
const listener = jest.fn();
|
||||
repository.on(ShuttleRepositoryEvent.ETA_DATA_CLEARED, listener);
|
||||
|
||||
for (const eta of mockEtas) {
|
||||
await repository.addOrUpdateEta(eta);
|
||||
}
|
||||
|
||||
await repository.clearEtaData();
|
||||
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
const emittedEtas = listener.mock.calls[0][0];
|
||||
expect(emittedEtas).toHaveLength(mockEtas.length);
|
||||
expect(emittedEtas).toEqual(expect.arrayContaining(mockEtas));
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearOrderedStopData", () => {
|
||||
test("clears all ordered stops from the repository", async () => {
|
||||
@@ -737,4 +611,360 @@ describe.each(repositoryImplementations)('$name', (holder) => {
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// Helper function for setting up routes and ordered stops for shuttle tracking tests
|
||||
async function setupRouteAndOrderedStops() {
|
||||
const systemId = "sys1";
|
||||
const route = {
|
||||
id: "r1",
|
||||
name: "Route 1",
|
||||
color: "red",
|
||||
systemId: systemId,
|
||||
polylineCoordinates: [],
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
await repository.addOrUpdateRoute(route);
|
||||
|
||||
const stop1 = {
|
||||
id: "st1",
|
||||
name: "Stop 1",
|
||||
systemId: systemId,
|
||||
coordinates: { latitude: 10.0, longitude: 20.0 },
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
const stop2 = {
|
||||
id: "st2",
|
||||
name: "Stop 2",
|
||||
systemId: systemId,
|
||||
coordinates: { latitude: 15.0, longitude: 25.0 },
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
await repository.addOrUpdateStop(stop1);
|
||||
await repository.addOrUpdateStop(stop2);
|
||||
|
||||
const orderedStop1: IOrderedStop = {
|
||||
routeId: route.id,
|
||||
stopId: stop1.id,
|
||||
position: 1,
|
||||
systemId: systemId,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
const orderedStop2: IOrderedStop = {
|
||||
routeId: route.id,
|
||||
stopId: stop2.id,
|
||||
position: 2,
|
||||
systemId: systemId,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
orderedStop1.nextStop = orderedStop2;
|
||||
orderedStop1.previousStop = orderedStop2;
|
||||
orderedStop2.nextStop = orderedStop1;
|
||||
orderedStop2.previousStop = orderedStop1;
|
||||
await repository.addOrUpdateOrderedStop(orderedStop1);
|
||||
await repository.addOrUpdateOrderedStop(orderedStop2);
|
||||
|
||||
return {
|
||||
route,
|
||||
systemId,
|
||||
stop1,
|
||||
stop2,
|
||||
};
|
||||
}
|
||||
|
||||
describe("addOrUpdateShuttle with ETA calculations", () => {
|
||||
test("updates the shuttle's last stop arrival if shuttle is at a stop", async () => {
|
||||
const { route, systemId, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop2.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
await repository.addOrUpdateShuttle(shuttle);
|
||||
const lastStop = await repository.getShuttleLastStopArrival(shuttle.id);
|
||||
expect(lastStop?.stopId).toEqual(stop2.id);
|
||||
});
|
||||
|
||||
test("updates how long the shuttle took to get from one stop to another", async () => {
|
||||
const { route, systemId, stop2, stop1 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const firstStopArrivalTime = new Date(2025, 0, 1, 12, 0, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, firstStopArrivalTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
const secondStopArrivalTime = new Date(2025, 0, 1, 12, 15, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, secondStopArrivalTime.getTime());
|
||||
|
||||
const travelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 1, 11, 0, 0),
|
||||
to: new Date(2025, 0, 1, 13, 0, 0),
|
||||
});
|
||||
expect(travelTime).toEqual(15 * 60);
|
||||
});
|
||||
|
||||
test("adds an ETA entry based on historical data", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const firstStopArrivalTime = new Date(2025, 0, 1, 12, 0, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, firstStopArrivalTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
const secondStopArrivalTime = new Date(2025, 0, 1, 12, 15, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, secondStopArrivalTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop1.coordinates;
|
||||
await repository.addOrUpdateShuttle(
|
||||
shuttle,
|
||||
new Date(2025, 0, 8, 12, 0, 0).getTime(),
|
||||
new Date(2025, 0, 8, 12, 7, 30),
|
||||
);
|
||||
|
||||
const eta = await repository.getEtaForShuttleAndStopId(shuttle.id, stop2.id);
|
||||
expect(eta?.secondsRemaining).toEqual(7 * 60 + 30);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getArrivedStopIfExists", () => {
|
||||
test("gets the stop that the shuttle is currently at, if exists", async () => {
|
||||
const { route, systemId, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop2.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const result = await repository.getArrivedStopIfExists(shuttle);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.id).toBe("st2");
|
||||
expect(result?.name).toBe("Stop 2");
|
||||
});
|
||||
|
||||
test("returns undefined if shuttle is not currently at a stop", async () => {
|
||||
const { route, systemId } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: { latitude: 12.5, longitude: 22.5 },
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const result = await repository.getArrivedStopIfExists(shuttle);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getShuttleLastStopArrival", () => {
|
||||
test("gets the shuttle's last stop if existing in the data", async () => {
|
||||
const { route, systemId, stop1 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const stopArrivalTime = new Date("2024-01-15T10:30:00Z");
|
||||
await repository.addOrUpdateShuttle(shuttle, stopArrivalTime.getTime());
|
||||
|
||||
const result = await repository.getShuttleLastStopArrival(shuttle.id);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.stopId).toBe(stop1.id);
|
||||
expect(result?.timestamp.getTime()).toBe(stopArrivalTime.getTime());
|
||||
});
|
||||
|
||||
test("returns undefined if the data has never been initialized", async () => {
|
||||
const mockShuttles = generateMockShuttles();
|
||||
const shuttle = mockShuttles[0];
|
||||
|
||||
const result = await repository.getShuttleLastStopArrival(shuttle.id);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("returns the most recent stop arrival when updated multiple times", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const firstArrivalTime = new Date("2024-01-15T10:30:00Z");
|
||||
await repository.addOrUpdateShuttle(shuttle, firstArrivalTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
const secondArrivalTime = new Date("2024-01-15T10:35:00Z");
|
||||
await repository.addOrUpdateShuttle(shuttle, secondArrivalTime.getTime());
|
||||
|
||||
const result = await repository.getShuttleLastStopArrival(shuttle.id);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.stopId).toBe(stop2.id);
|
||||
expect(result?.timestamp.getTime()).toBe(secondArrivalTime.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAverageTravelTimeSeconds", () => {
|
||||
test("returns the average travel time when historical data exists", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
const firstStopTime = new Date(2025, 0, 1, 12, 0, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, firstStopTime.getTime());
|
||||
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
const secondStopTime = new Date(2025, 0, 1, 12, 15, 0);
|
||||
await repository.addOrUpdateShuttle(shuttle, secondStopTime.getTime());
|
||||
|
||||
const travelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 1, 11, 0, 0),
|
||||
to: new Date(2025, 0, 1, 13, 0, 0),
|
||||
});
|
||||
|
||||
expect(travelTime).toEqual(15 * 60);
|
||||
});
|
||||
|
||||
test("returns average of multiple data points", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
// First trip: 10 minutes travel time
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 0, 0).getTime());
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 10, 0).getTime());
|
||||
|
||||
// Second trip: 20 minutes travel time
|
||||
shuttle.coordinates = stop1.coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 30, 0).getTime());
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 50, 0).getTime());
|
||||
|
||||
const averageTravelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 1, 11, 0, 0),
|
||||
to: new Date(2025, 0, 1, 14, 0, 0),
|
||||
});
|
||||
|
||||
// Average of 10 minutes and 20 minutes = 15 minutes = 900 seconds
|
||||
expect(averageTravelTime).toBeDefined();
|
||||
});
|
||||
|
||||
test("returns undefined when no data exists", async () => {
|
||||
const { route, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const averageTravelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 1, 11, 0, 0),
|
||||
to: new Date(2025, 0, 1, 14, 0, 0),
|
||||
});
|
||||
|
||||
expect(averageTravelTime).toBeUndefined();
|
||||
});
|
||||
|
||||
test("returns undefined when querying outside the time range of data", async () => {
|
||||
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||
|
||||
const shuttle = {
|
||||
id: "sh1",
|
||||
name: "Shuttle 1",
|
||||
routeId: route.id,
|
||||
systemId: systemId,
|
||||
coordinates: stop1.coordinates,
|
||||
orientationInDegrees: 0,
|
||||
updatedTime: new Date(),
|
||||
};
|
||||
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 0, 0).getTime());
|
||||
shuttle.coordinates = stop2.coordinates;
|
||||
await repository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 15, 0).getTime());
|
||||
|
||||
const averageTravelTime = await repository.getAverageTravelTimeSeconds({
|
||||
routeId: route.id,
|
||||
fromStopId: stop1.id,
|
||||
toStopId: stop2.id,
|
||||
}, {
|
||||
from: new Date(2025, 0, 2, 11, 0, 0),
|
||||
to: new Date(2025, 0, 2, 13, 0, 0),
|
||||
});
|
||||
expect(averageTravelTime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user