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 { name = 'UnoptimizedInMemoryShuttleRepository'; factory = async () => { return new UnoptimizedInMemoryShuttleRepository(); }; teardown = async () => {}; } class RedisShuttleRepositoryHolder implements RepositoryHolder { 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()); }); }); });