Files
project-inter-server/src/repositories/shuttle/__tests__/ShuttleRepositorySharedTests.test.ts

800 lines
28 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, jest, test } from "@jest/globals";
import { createClient, RedisClientType } from "redis";
import { UnoptimizedInMemoryShuttleRepository } from "../UnoptimizedInMemoryShuttleRepository";
import { ShuttleGetterSetterRepository } from "../ShuttleGetterSetterRepository";
import { RedisShuttleRepository } from "../RedisShuttleRepository";
import {
generateMockOrderedStops,
generateMockRoutes,
generateMockShuttles,
generateMockStops,
} from "../../../../testHelpers/mockDataGenerators";
import { RepositoryHolder } from "../../../../testHelpers/RepositoryHolder";
import { setupRouteAndOrderedStopsForShuttleRepository } from "../../../../testHelpers/setupRouteAndOrderedStopsForShuttleRepository";
import { ShuttleRepositoryEvent } from "../ShuttleGetterRepository";
class UnoptimizedInMemoryShuttleRepositoryHolder implements RepositoryHolder<ShuttleGetterSetterRepository> {
name = 'UnoptimizedInMemoryShuttleRepository';
factory = async () => {
return new UnoptimizedInMemoryShuttleRepository();
};
teardown = async () => {};
}
class RedisShuttleRepositoryHolder implements RepositoryHolder<ShuttleGetterSetterRepository> {
repo: RedisShuttleRepository | undefined;
redisClient: RedisClientType | undefined;
name = 'RedisShuttleRepository';
factory = async () => {
this.redisClient = createClient({
url: process.env.REDIS_URL,
});
await this.redisClient.connect();
this.repo = new RedisShuttleRepository(this.redisClient);
return this.repo;
};
teardown = async () => {
if (this.redisClient) {
await this.redisClient.flushAll();
await this.redisClient.disconnect();
}
};
}
const repositoryImplementations = [
new UnoptimizedInMemoryShuttleRepositoryHolder(),
new RedisShuttleRepositoryHolder(),
];
describe.each(repositoryImplementations)('$name', (holder) => {
let repository: ShuttleGetterSetterRepository;
beforeEach(async () => {
repository = await holder.factory();
jest.useRealTimers();
});
afterEach(async () => {
await holder.teardown();
jest.useRealTimers();
});
describe("getStops", () => {
test("gets all stops in the repository", async () => {
const mockStops = generateMockStops();
for (const stop of mockStops) {
await repository.addOrUpdateStop(stop);
}
const result = await repository.getStops();
expect(result).toHaveLength(mockStops.length);
expect(result).toEqual(expect.arrayContaining(mockStops));
});
test("returns an empty list if there are no stops for the given system ID", async () => {
const result = await repository.getStops();
expect(result).toEqual([]);
});
});
describe("getStopById", () => {
test("gets a stop by ID if it exists", async () => {
const mockStops = generateMockStops();
const mockStop = mockStops[0];
await repository.addOrUpdateStop(mockStop);
const result = await repository.getStopById("st1");
expect(result).toEqual(mockStop);
});
test("returns null if the stop does not exist", async () => {
const result = await repository.getStopById("nonexistent-stop");
expect(result).toBeNull();
});
});
describe("getRoutes", () => {
test("gets all routes for a specific system ID", async () => {
const mockRoutes = generateMockRoutes();
for (const route of mockRoutes) {
await repository.addOrUpdateRoute(route);
}
const result = await repository.getRoutes();
expect(result).toHaveLength(mockRoutes.length);
expect(result).toEqual(expect.arrayContaining(mockRoutes));
});
test("returns an empty list if there are no routes for the system ID", async () => {
const result = await repository.getRoutes();
expect(result).toEqual([]);
});
});
describe("getRouteById", () => {
test("gets a route by ID if it exists", async () => {
const mockRoutes = generateMockRoutes();
const mockRoute = mockRoutes[0];
await repository.addOrUpdateRoute(mockRoute);
const result = await repository.getRouteById("r1");
expect(result).toEqual(mockRoute);
});
test("returns null if the route does not exist", async () => {
const result = await repository.getRouteById("nonexistent-route");
expect(result).toBeNull();
});
});
describe("getShuttles", () => {
test("gets all shuttles for a specific system ID", async () => {
const mockShuttles = generateMockShuttles();
for (const shuttle of mockShuttles) {
await repository.addOrUpdateShuttle(shuttle);
}
const result = await repository.getShuttles();
expect(result).toHaveLength(mockShuttles.length);
expect(result).toEqual(expect.arrayContaining(mockShuttles));
});
test("returns an empty list if there are no shuttles for the system ID", async () => {
const result = await repository.getShuttles();
expect(result).toEqual([]);
});
});
describe("getShuttlesByRouteId", () => {
test("gets all shuttles for a specific route ID", async () => {
const mockShuttles = generateMockShuttles();
for (const shuttle of mockShuttles) {
await repository.addOrUpdateShuttle(shuttle);
}
const result = await repository.getShuttlesByRouteId("r1");
expect(result).toEqual(mockShuttles.filter((sh) => sh.routeId === "r1"));
});
test("returns an empty list if there are no shuttles for the route ID", async () => {
const result = await repository.getShuttlesByRouteId("nonexistent-route");
expect(result).toEqual([]);
});
});
describe("getShuttleById", () => {
test("gets a shuttle by ID if it exists", async () => {
const mockShuttles = generateMockShuttles();
for (const shuttle of mockShuttles) {
await repository.addOrUpdateShuttle(shuttle);
}
const result = await repository.getShuttleById("sh2");
expect(result).toEqual(mockShuttles[1]);
});
test("returns null if the shuttle doesn't exist", async () => {
const result = await repository.getShuttleById("nonexistent-id");
expect(result).toBeNull();
});
});
describe("getOrderedStopByRouteAndStopId", () => {
test("gets an ordered stop by route ID and stop ID", async () => {
const mockOrderedStops = generateMockOrderedStops();
for (const orderedStop of mockOrderedStops) {
await repository.addOrUpdateOrderedStop(orderedStop);
}
const mockOrderedStop = mockOrderedStops[0];
const { routeId, stopId } = mockOrderedStop;
const result = await repository.getOrderedStopByRouteAndStopId(routeId, stopId);
expect(result).toEqual(mockOrderedStop);
});
test("returns null if no ordered stop matches the given route ID and stop ID", async () => {
const result = await repository.getOrderedStopByRouteAndStopId("nonexistent-route", "nonexistent-stop");
expect(result).toBeNull();
});
});
describe("getOrderedStopsByStopId", () => {
test("gets all ordered stops for a specific stop ID", async () => {
const mockOrderedStops = generateMockOrderedStops();
for (const orderedStop of mockOrderedStops) {
await repository.addOrUpdateOrderedStop(orderedStop);
}
const result = await repository.getOrderedStopsByStopId("st1");
const expected = mockOrderedStops.filter((os) => os.stopId === "st1");
expect(result).toHaveLength(expected.length);
expect(result).toEqual(expect.arrayContaining(expected));
});
test("returns an empty list if there are no ordered stops for the stop ID", async () => {
const result = await repository.getOrderedStopsByStopId("nonexistent-stop");
expect(result).toEqual([]);
});
});
describe("getOrderedStopsByRouteId", () => {
test("gets all ordered stops for a specific route ID", async () => {
const mockOrderedStops = generateMockOrderedStops();
for (const orderedStop of mockOrderedStops) {
await repository.addOrUpdateOrderedStop(orderedStop);
}
const result = await repository.getOrderedStopsByRouteId("r1");
const expected = mockOrderedStops.filter((os) => os.routeId === "r1");
expect(result).toHaveLength(expected.length);
expect(result).toEqual(expect.arrayContaining(expected));
});
test("returns an empty list if there are no ordered stops for the route ID", async () => {
const result = await repository.getOrderedStopsByRouteId("nonexistent-route");
expect(result).toEqual([]);
});
});
describe("addOrUpdateRoute", () => {
test("adds a new route if nonexistent", async () => {
const mockRoutes = generateMockRoutes();
const newRoute = mockRoutes[0];
await repository.addOrUpdateRoute(newRoute);
const result = await repository.getRoutes();
expect(result).toEqual([newRoute]);
});
test("updates an existing route if it exists", async () => {
const mockRoutes = generateMockRoutes();
const existingRoute = mockRoutes[0];
const updatedRoute = structuredClone(existingRoute);
updatedRoute.name = "Updated Route";
await repository.addOrUpdateRoute(existingRoute);
await repository.addOrUpdateRoute(updatedRoute);
const result = await repository.getRoutes();
expect(result).toEqual([updatedRoute]);
});
});
describe("addOrUpdateShuttle", () => {
test("adds a new shuttle if nonexistent", async () => {
const mockShuttles = generateMockShuttles();
const newShuttle = mockShuttles[0];
await repository.addOrUpdateShuttle(newShuttle);
const result = await repository.getShuttles();
expect(result).toEqual([newShuttle]);
});
test("updates an existing shuttle if it exists", async () => {
const mockShuttles = generateMockShuttles();
const existingShuttle = mockShuttles[0];
const updatedShuttle = structuredClone(existingShuttle);
updatedShuttle.name = "Updated Shuttle";
await repository.addOrUpdateShuttle(existingShuttle);
await repository.addOrUpdateShuttle(updatedShuttle);
const result = await repository.getShuttles();
expect(result).toEqual([updatedShuttle]);
});
});
describe("addOrUpdateStop", () => {
test("adds a new stop if nonexistent", async () => {
const mockStops = generateMockStops();
const newStop = mockStops[0];
await repository.addOrUpdateStop(newStop);
const result = await repository.getStops();
expect(result).toEqual([newStop]);
});
test("updates an existing stop if it exists", async () => {
const mockStops = generateMockStops();
const existingStop = mockStops[0];
const updatedStop = structuredClone(existingStop);
updatedStop.name = "Updated Stop";
await repository.addOrUpdateStop(existingStop);
await repository.addOrUpdateStop(updatedStop);
const result = await repository.getStops();
expect(result).toEqual([updatedStop]);
});
});
describe("addOrUpdateOrderedStop", () => {
test("adds a new ordered stop if nonexistent", async () => {
const mockOrderedStops = generateMockOrderedStops();
const newOrderedStop = mockOrderedStops[0];
await repository.addOrUpdateOrderedStop(newOrderedStop);
const result = await repository.getOrderedStopsByRouteId(newOrderedStop.routeId);
expect(result).toEqual([newOrderedStop]);
});
test("updates an existing ordered stop if it exists", async () => {
const mockOrderedStops = generateMockOrderedStops();
const existingOrderedStop = mockOrderedStops[0];
const updatedOrderedStop = structuredClone(existingOrderedStop);
updatedOrderedStop.position = 5;
await repository.addOrUpdateOrderedStop(existingOrderedStop);
await repository.addOrUpdateOrderedStop(updatedOrderedStop);
const result = await repository.getOrderedStopsByRouteId(existingOrderedStop.routeId);
expect(result).toEqual([updatedOrderedStop]);
});
});
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.getRoutes();
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.getRoutes();
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.getShuttles();
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.getShuttles();
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.getStops();
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.getStops();
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("clearShuttleData", () => {
test("clears all shuttles from the repository", async () => {
const mockShuttles = generateMockShuttles();
for (const shuttle of mockShuttles) {
await repository.addOrUpdateShuttle(shuttle);
}
await repository.clearShuttleData();
const result = await repository.getShuttles();
expect(result).toEqual([]);
});
});
describe("clearOrderedStopData", () => {
test("clears all ordered stops from the repository", async () => {
const mockOrderedStops = generateMockOrderedStops();
for (const system of mockOrderedStops) {
await repository.addOrUpdateOrderedStop(system);
}
await repository.clearOrderedStopData();
const result = await repository.getOrderedStopsByRouteId("route1");
expect(result).toEqual([]);
});
});
describe("clearRouteData", () => {
test("clears all routes from the repository", async () => {
const mockRoutes = generateMockRoutes();
for (const route of mockRoutes) {
await repository.addOrUpdateRoute(route);
}
await repository.clearRouteData();
const result = await repository.getRoutes();
expect(result).toEqual([]);
});
});
describe("clearStopData", () => {
test("clears all stops from the repository", async () => {
const mockStops = generateMockStops();
for (const stop of mockStops) {
await repository.addOrUpdateStop(stop);
}
await repository.clearStopData();
const result = await repository.getStops();
expect(result).toEqual([]);
});
});
// Helper function for setting up routes and ordered stops for shuttle tracking tests
async function setupRouteAndOrderedStops() {
return await setupRouteAndOrderedStopsForShuttleRepository(repository);
}
describe("addOrUpdateShuttle with shuttle tracking", () => {
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);
});
});
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("SHUTTLE_UPDATED event", () => {
test("emits SHUTTLE_UPDATED event when shuttles are added or updated", async () => {
const mockListener = jest.fn();
repository.on(ShuttleRepositoryEvent.SHUTTLE_UPDATED, mockListener);
const mockShuttles = generateMockShuttles();
for (const shuttle of mockShuttles) {
await repository.addOrUpdateShuttle(shuttle);
}
expect(mockListener).toHaveBeenCalledTimes(mockShuttles.length);
expect(mockListener).toHaveBeenCalledWith(mockShuttles[0]);
expect(mockListener).toHaveBeenCalledWith(mockShuttles[mockShuttles.length - 1]);
});
test("does not notify listener after it has been removed", async () => {
const mockListener = jest.fn();
repository.on(ShuttleRepositoryEvent.SHUTTLE_UPDATED, mockListener);
const mockShuttles = generateMockShuttles();
repository.off(ShuttleRepositoryEvent.SHUTTLE_UPDATED, mockListener);
await repository.addOrUpdateShuttle(mockShuttles[0]);
expect(mockListener).toHaveBeenCalledTimes(0);
});
test("stops notifying specific listener after removal but continues for others", async () => {
const mockListener1 = jest.fn();
const mockListener2 = jest.fn();
repository.on(ShuttleRepositoryEvent.SHUTTLE_UPDATED, mockListener1);
repository.on(ShuttleRepositoryEvent.SHUTTLE_UPDATED, mockListener2);
const mockShuttles = generateMockShuttles();
await repository.addOrUpdateShuttle(mockShuttles[0]);
repository.off(ShuttleRepositoryEvent.SHUTTLE_UPDATED, mockListener1);
await repository.addOrUpdateShuttle(mockShuttles[mockShuttles.length - 1]);
expect(mockListener1).toHaveBeenCalledTimes(1);
expect(mockListener1).toHaveBeenCalledWith(mockShuttles[0]);
expect(mockListener1).not.toHaveBeenCalledWith(mockShuttles[mockShuttles.length - 1]);
expect(mockListener2).toHaveBeenCalledTimes(2);
expect(mockListener2).toHaveBeenCalledWith(mockShuttles[0]);
expect(mockListener2).toHaveBeenCalledWith(mockShuttles[mockShuttles.length - 1]);
});
});
describe("SHUTTLE_REMOVED event", () => {
test("emits SHUTTLE_REMOVED event when a shuttle is removed", async () => {
const mockShuttles = generateMockShuttles();
const shuttleToRemove = mockShuttles[0];
const listener = jest.fn();
repository.on(ShuttleRepositoryEvent.SHUTTLE_REMOVED, listener);
await repository.addOrUpdateShuttle(shuttleToRemove);
await repository.removeShuttleIfExists(shuttleToRemove.id);
expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith(shuttleToRemove);
});
});
describe("SHUTTLE_WILL_ARRIVE_AT_STOP event", () => {
test("emits SHUTTLE_WILL_ARRIVE_AT_STOP event before shuttle arrives at a stop", async () => {
const { route, systemId, stop1 } = await setupRouteAndOrderedStops();
const listener = jest.fn();
repository.on(ShuttleRepositoryEvent.SHUTTLE_WILL_ARRIVE_AT_STOP, listener);
const shuttle = {
id: "sh1",
name: "Shuttle 1",
routeId: route.id,
systemId: systemId,
coordinates: stop1.coordinates,
orientationInDegrees: 0,
updatedTime: new Date(),
};
const arrivalTime = new Date("2024-01-15T10:30:00Z");
await repository.addOrUpdateShuttle(shuttle, arrivalTime.getTime());
expect(listener).toHaveBeenCalledTimes(1);
const emittedPayload = listener.mock.calls[0][0] as any;
expect(emittedPayload.shuttleId).toBe(shuttle.id);
expect(emittedPayload.stopId).toBe(stop1.id);
expect(emittedPayload.timestamp.getTime()).toBe(arrivalTime.getTime());
});
test("does not emit event when shuttle is not at a stop", async () => {
const { route, systemId } = await setupRouteAndOrderedStops();
const listener = jest.fn();
repository.on(ShuttleRepositoryEvent.SHUTTLE_WILL_ARRIVE_AT_STOP, listener);
const shuttle = {
id: "sh1",
name: "Shuttle 1",
routeId: route.id,
systemId: systemId,
coordinates: { latitude: 12.5, longitude: 22.5 }, // Not at any stop
orientationInDegrees: 0,
updatedTime: new Date(),
};
await repository.addOrUpdateShuttle(shuttle);
expect(listener).toHaveBeenCalledTimes(0);
});
test("emits multiple events as shuttle visits multiple stops", async () => {
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
const listener = jest.fn();
repository.on(ShuttleRepositoryEvent.SHUTTLE_WILL_ARRIVE_AT_STOP, listener);
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());
expect(listener).toHaveBeenCalledTimes(2);
const firstPayload = listener.mock.calls[0][0] as any;
expect(firstPayload.shuttleId).toBe(shuttle.id);
expect(firstPayload.stopId).toBe(stop1.id);
expect(firstPayload.timestamp.getTime()).toBe(firstArrivalTime.getTime());
const secondPayload = listener.mock.calls[1][0] as any;
expect(secondPayload.shuttleId).toBe(shuttle.id);
expect(secondPayload.stopId).toBe(stop2.id);
expect(secondPayload.timestamp.getTime()).toBe(secondArrivalTime.getTime());
});
});
});