From b6b79e13457c0f663eafcc53f833cac19b3d01a7 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Tue, 11 Nov 2025 12:27:10 -0800 Subject: [PATCH] Migrate ETA-related tests over to the ETA repository tests --- .../ShuttleRepositorySharedTests.test.ts | 178 +------------ ...lfUpdatingETARepositorySharedTests.test.ts | 245 ++++++++++++++++++ 2 files changed, 246 insertions(+), 177 deletions(-) create mode 100644 src/repositories/shuttle/eta/__tests__/SelfUpdatingETARepositorySharedTests.test.ts diff --git a/src/repositories/shuttle/__tests__/ShuttleRepositorySharedTests.test.ts b/src/repositories/shuttle/__tests__/ShuttleRepositorySharedTests.test.ts index d9edec1..bb99165 100644 --- a/src/repositories/shuttle/__tests__/ShuttleRepositorySharedTests.test.ts +++ b/src/repositories/shuttle/__tests__/ShuttleRepositorySharedTests.test.ts @@ -3,7 +3,6 @@ import { UnoptimizedInMemoryShuttleRepository } from "../UnoptimizedInMemoryShut import { ShuttleGetterSetterRepository } from "../ShuttleGetterSetterRepository"; import { RedisShuttleRepository } from "../RedisShuttleRepository"; import { ShuttleRepositoryEvent } from "../ShuttleGetterRepository"; -import { IOrderedStop } from "../../../entities/ShuttleRepositoryEntities"; import { generateMockEtas, generateMockOrderedStops, @@ -613,7 +612,7 @@ describe.each(repositoryImplementations)('$name', (holder) => { return await setupRouteAndOrderedStopsForShuttleRepository(repository); } - describe("addOrUpdateShuttle with ETA calculations", () => { + 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(); @@ -631,68 +630,6 @@ describe.each(repositoryImplementations)('$name', (holder) => { 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", () => { @@ -796,117 +733,4 @@ describe.each(repositoryImplementations)('$name', (holder) => { }); }); - 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(); - }); - }); }); diff --git a/src/repositories/shuttle/eta/__tests__/SelfUpdatingETARepositorySharedTests.test.ts b/src/repositories/shuttle/eta/__tests__/SelfUpdatingETARepositorySharedTests.test.ts new file mode 100644 index 0000000..ac61cf1 --- /dev/null +++ b/src/repositories/shuttle/eta/__tests__/SelfUpdatingETARepositorySharedTests.test.ts @@ -0,0 +1,245 @@ +import { afterEach, beforeEach, describe, expect, test } from "@jest/globals"; +import { RepositoryHolder } from "../../../../../testHelpers/RepositoryHolder"; +import { SelfUpdatingETARepository } from "../SelfUpdatingETARepository"; +import { RedisSelfUpdatingETARepository } from "../RedisSelfUpdatingETARepository"; +import { RedisShuttleRepository } from "../../RedisShuttleRepository"; +import { setupRouteAndOrderedStopsForShuttleRepository } from "../../../../../testHelpers/setupRouteAndOrderedStopsForShuttleRepository"; + +class RedisSelfUpdatingETARepositoryHolder implements RepositoryHolder { + repo: RedisSelfUpdatingETARepository | undefined; + shuttleRepo: RedisShuttleRepository | undefined; + + name = "RedisSelfUpdatingETARepository" + factory = async () => { + this.shuttleRepo = new RedisShuttleRepository(); + await this.shuttleRepo.connect(); + this.repo = new RedisSelfUpdatingETARepository( + this.shuttleRepo, + ); + await this.repo.connect(); + return this.repo; + } + teardown = async () => { + if (this.shuttleRepo) { + await this.shuttleRepo.clearAllData(); + await this.shuttleRepo.disconnect(); + } + if (this.repo) { + await this.repo.clearAllData(); + await this.repo.disconnect(); + } + } +} + +const repositoryImplementations = [ + new RedisSelfUpdatingETARepositoryHolder() +]; + +describe.each(repositoryImplementations)('$name', (holder) => { + let repository: SelfUpdatingETARepository; + let shuttleRepository: RedisShuttleRepository; + + beforeEach(async () => { + repository = await holder.factory(); + shuttleRepository = holder.shuttleRepo!; + }); + + afterEach(async () => { + await holder.teardown(); + }); + + // Helper function for setting up routes and ordered stops + async function setupRouteAndOrderedStops() { + return await setupRouteAndOrderedStopsForShuttleRepository(shuttleRepository); + } + + describe("addOrUpdateShuttle triggers ETA calculations", () => { + test("updates how long the shuttle took to get from one stop to another", async () => { + const { route, systemId, stop2, stop1 } = await setupRouteAndOrderedStops(); + + repository.startListeningForUpdates(); + + 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 shuttleRepository.addOrUpdateShuttle(shuttle, firstStopArrivalTime.getTime()); + + shuttle.coordinates = stop2.coordinates; + const secondStopArrivalTime = new Date(2025, 0, 1, 12, 15, 0); + await shuttleRepository.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(); + + repository.startListeningForUpdates(); + + 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 shuttleRepository.addOrUpdateShuttle(shuttle, firstStopArrivalTime.getTime()); + + shuttle.coordinates = stop2.coordinates; + const secondStopArrivalTime = new Date(2025, 0, 1, 12, 15, 0); + await shuttleRepository.addOrUpdateShuttle(shuttle, secondStopArrivalTime.getTime()); + + shuttle.coordinates = stop1.coordinates; + await shuttleRepository.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("getAverageTravelTimeSeconds", () => { + test("returns the average travel time when historical data exists", async () => { + const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops(); + + repository.startListeningForUpdates(); + + 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 shuttleRepository.addOrUpdateShuttle(shuttle, firstStopTime.getTime()); + + shuttle.coordinates = stop2.coordinates; + const secondStopTime = new Date(2025, 0, 1, 12, 15, 0); + await shuttleRepository.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(); + + repository.startListeningForUpdates(); + + 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 shuttleRepository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 0, 0).getTime()); + shuttle.coordinates = stop2.coordinates; + await shuttleRepository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 10, 0).getTime()); + + // Second trip: 20 minutes travel time + shuttle.coordinates = stop1.coordinates; + await shuttleRepository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 30, 0).getTime()); + shuttle.coordinates = stop2.coordinates; + await shuttleRepository.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(); + + repository.startListeningForUpdates(); + + 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(); + + repository.startListeningForUpdates(); + + const shuttle = { + id: "sh1", + name: "Shuttle 1", + routeId: route.id, + systemId: systemId, + coordinates: stop1.coordinates, + orientationInDegrees: 0, + updatedTime: new Date(), + }; + + await shuttleRepository.addOrUpdateShuttle(shuttle, new Date(2025, 0, 1, 12, 0, 0).getTime()); + shuttle.coordinates = stop2.coordinates; + await shuttleRepository.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(); + }); + }); +})