mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-17 16:00:32 +00:00
155 lines
6.1 KiB
TypeScript
155 lines
6.1 KiB
TypeScript
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
import { ETANotificationScheduler } from "../../../src/notifications/schedulers/ETANotificationScheduler";
|
|
import { UnoptimizedInMemoryShuttleRepository } from "../../../src/repositories/UnoptimizedInMemoryShuttleRepository";
|
|
import http2 from "http2";
|
|
import { IEta, IShuttle, IStop } from "../../../src/entities/entities";
|
|
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<typeof AppleNotificationSender>;
|
|
|
|
function mockNotificationSenderMethods(shouldSimulateNotificationSend: boolean) {
|
|
MockAppleNotificationSender.prototype.sendNotificationImmediately = jest.fn(async () => shouldSimulateNotificationSend);
|
|
}
|
|
|
|
/**
|
|
* Wait for a condition to become true until the timeout
|
|
* is hit.
|
|
* @param condition
|
|
* @param timeoutMilliseconds
|
|
* @param intervalMilliseconds
|
|
*/
|
|
async function waitForCondition(condition: () => boolean, timeoutMilliseconds = 5000, intervalMilliseconds = 500) {
|
|
const startTime = Date.now();
|
|
while (!condition()) {
|
|
if (Date.now() - startTime > timeoutMilliseconds) {
|
|
throw new Error("Timeout waiting for condition");
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, intervalMilliseconds));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for a specified number of milliseconds.
|
|
* @param ms
|
|
*/
|
|
async function waitForMilliseconds(ms: number): Promise<void> {
|
|
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
|
|
);
|
|
});
|
|
|
|
function generateNotificationDataAndEta(shuttle: IShuttle, stop: IStop) {
|
|
const eta: IEta = {
|
|
shuttleId: shuttle.id,
|
|
stopId: stop.id,
|
|
secondsRemaining: 120,
|
|
};
|
|
|
|
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
|
|
// Because repository publisher calls subscriber without await
|
|
// wait for the change to occur first
|
|
await waitForCondition(() => !notificationRepository.isNotificationScheduled(notificationData1));
|
|
|
|
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)
|
|
|
|
mockNotificationSenderMethods(false);
|
|
notificationService = new ETANotificationScheduler(
|
|
shuttleRepository,
|
|
new InMemoryNotificationRepository(),
|
|
new MockAppleNotificationSender(),
|
|
)
|
|
|
|
// 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);
|
|
});
|
|
});
|
|
});
|