mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-19 08:50:29 +00:00
386 lines
12 KiB
TypeScript
386 lines
12 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|