import { beforeEach, describe, expect, it, jest } from "@jest/globals"; import { ETANotificationScheduler } from "../../../src/notifications/schedulers/ETANotificationScheduler"; import { UnoptimizedInMemoryShuttleRepository } from "../../../src/repositories/UnoptimizedInMemoryShuttleRepository"; import { IEta, IShuttle, IStop } from "../../../src/entities/ShuttleRepositoryEntities"; import { addMockShuttleToRepository, addMockStopToRepository } from "../../testHelpers/repositorySetupHelpers"; import { AppleNotificationSender } from "../../../src/notifications/senders/AppleNotificationSender"; import { InMemoryNotificationRepository } from "../../../src/repositories/InMemoryNotificationRepository"; import { NotificationRepository } from "../../../src/repositories/NotificationRepository"; jest.mock("http2"); jest.mock("../../../src/notifications/senders/AppleNotificationSender"); const MockAppleNotificationSender = AppleNotificationSender as jest.MockedClass; function mockNotificationSenderMethods(shouldSimulateNotificationSend: boolean) { MockAppleNotificationSender.prototype.sendNotificationImmediately = jest.fn(async () => shouldSimulateNotificationSend); } /** * Wait for a specified number of milliseconds. * @param ms */ async function waitForMilliseconds(ms: number): Promise { await new Promise((resolve) => setTimeout(resolve, ms)); } describe("ETANotificationScheduler", () => { let shuttleRepository: UnoptimizedInMemoryShuttleRepository let notificationService: ETANotificationScheduler; let notificationRepository: NotificationRepository; beforeEach(() => { shuttleRepository = new UnoptimizedInMemoryShuttleRepository(); notificationRepository = new InMemoryNotificationRepository(); mockNotificationSenderMethods(true); const appleNotificationSender = new MockAppleNotificationSender(false); notificationService = new ETANotificationScheduler( shuttleRepository, notificationRepository, appleNotificationSender, "1", ); notificationService.startListeningForUpdates(); }); function generateNotificationDataAndEta(shuttle: IShuttle, stop: IStop) { const eta: IEta = { shuttleId: shuttle.id, stopId: stop.id, secondsRemaining: 120, systemId: "1", updatedTime: Date.now(), }; const notificationData1 = { deviceId: "1", shuttleId: eta.shuttleId, stopId: eta.stopId, secondsThreshold: 240, } const notificationData2 = { ...notificationData1, deviceId: "2", secondsThreshold: 180, } return { eta, notificationData1, notificationData2 }; } describe("etaSubscriberCallback", () => { it("sends and clears correct notification after ETA changed", async () => { // Arrange const shuttle = await addMockShuttleToRepository(shuttleRepository, "1"); const stop = await addMockStopToRepository(shuttleRepository, "1"); const { eta, notificationData1, notificationData2 } = generateNotificationDataAndEta(shuttle, stop); // Act await notificationRepository.addOrUpdateNotification(notificationData1); await notificationRepository.addOrUpdateNotification(notificationData2); await shuttleRepository.addOrUpdateEta(eta); // Assert // Wait for the callback to actually be called await waitForMilliseconds(1000); const isFirstNotificationScheduled = await notificationRepository.isNotificationScheduled(notificationData1); const isSecondNotificationScheduled = await notificationRepository.isNotificationScheduled(notificationData2); // No longer scheduled after being sent expect(isFirstNotificationScheduled).toBe(false); expect(isSecondNotificationScheduled).toBe(false); }); it("doesn't send notification if seconds threshold not exceeded", async () => { // Arrange const shuttle = await addMockShuttleToRepository(shuttleRepository, "1"); const stop = await addMockStopToRepository(shuttleRepository, "1"); const { eta, notificationData1 } = generateNotificationDataAndEta(shuttle, stop); notificationData1.secondsThreshold = eta.secondsRemaining - 10; // Act await notificationRepository.addOrUpdateNotification(notificationData1); await shuttleRepository.addOrUpdateEta(eta); // Assert await waitForMilliseconds(500); const isNotificationScheduled = await notificationRepository.isNotificationScheduled(notificationData1); expect(isNotificationScheduled).toBe(true); }); it("leaves notification in array if delivery unsuccessful", async () => { // Arrange const shuttle = await addMockShuttleToRepository(shuttleRepository, "1"); const stop = await addMockStopToRepository(shuttleRepository, "1"); const { eta, notificationData1 } = generateNotificationDataAndEta(shuttle, stop) // replace the old notification scheduler with a new one // detach the old callback method from the shuttle repo notificationService.stopListeningForUpdates(); // replace the notification repository with a fresh one too const notificationRepository = new InMemoryNotificationRepository(); mockNotificationSenderMethods(false); const updatedNotificationSender = new MockAppleNotificationSender(false); notificationService = new ETANotificationScheduler( shuttleRepository, notificationRepository, updatedNotificationSender, "1", ); notificationService.startListeningForUpdates(); // Act await notificationRepository.addOrUpdateNotification(notificationData1); await shuttleRepository.addOrUpdateEta(eta); // Assert // The notification should stay scheduled to be retried once // the ETA updates again await waitForMilliseconds(500); const isNotificationScheduled = await notificationRepository.isNotificationScheduled(notificationData1); expect(isNotificationScheduled).toBe(true); }); }); });