Files
project-inter-server/test/services/NotificationServiceTests.test.ts

164 lines
5.7 KiB
TypeScript

import { beforeEach, describe, expect, it, jest } from "@jest/globals";
import { NotificationService } from "../../src/services/NotificationService";
import { UnoptimizedInMemoryRepository } from "../../src/repositories/UnoptimizedInMemoryRepository";
import fs from "fs";
import { IEta, IShuttle, IStop } from "../../src/entities/entities";
import { updateGlobalFetchMockJson } from "../testHelpers/fetchMockHelpers";
import { addMockShuttleToRepository, addMockStopToRepository } from "../testHelpers/repositorySetupHelpers";
jest.mock("fs");
const sampleKey = `-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgsrmSAZIagOfCP8sB
Wi2CBXG1Oo7v1bispIZCwIr4RDegCgYIKoZIzj0DAQehRANCAATZHxV2wQJLMBq+
ya+yfGi3g2ZUv6hrfe+j08ytekPHjXS0qzJoVELzKHa6EL9YAoZDXBtB6h+fGhXe
SOcONbaf
-----END PRIVATE KEY-----`
/**
* Wait for a condition to become true until the timeout
* is hit.
* @param condition
* @param timeout
* @param interval
*/
async function waitForCondition(condition: () => boolean, timeout = 5000, interval = 500) {
const startTime = Date.now();
while (!condition()) {
if (Date.now() - startTime > timeout) {
throw new Error("Timeout waiting for condition");
}
await new Promise((resolve) => setTimeout(resolve, interval));
}
}
describe("NotificationService", () => {
let repository: UnoptimizedInMemoryRepository
let notificationService: NotificationService;
beforeEach(() => {
repository = new UnoptimizedInMemoryRepository();
notificationService = new NotificationService(repository);
// Ensure that tests don't hit the server
process.env = {
...process.env,
APNS_KEY_ID: "1",
APNS_TEAM_ID: "1",
APNS_KEY_PATH: "./dummy-path.p8",
APNS_BUNDLE_ID: "dev.bchen.ProjectInter"
};
(fs.readFileSync as jest.Mock).mockReturnValue(sampleKey);
})
describe("reloadAPNsTokenIfTimePassed", () => {
it("reloads the token if token hasn't been generated yet", async () => {
notificationService.reloadAPNsTokenIfTimePassed();
expect(notificationService.lastRefreshedTimeMs).toBeDefined();
});
it("doesn't reload the token if last refreshed time is recent", async () => {
notificationService.reloadAPNsTokenIfTimePassed();
const lastRefreshedTimeMs = notificationService.lastRefreshedTimeMs;
notificationService.reloadAPNsTokenIfTimePassed();
// Expect no change to have occurred
expect(lastRefreshedTimeMs).toEqual(notificationService.lastRefreshedTimeMs);
});
})
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,
}
const notificationData2 = {
...notificationData1,
deviceId: "2",
}
return { eta, notificationData1, notificationData2 };
}
describe("scheduleNotification", () => {
it("schedules the notification", async () => {
// arrange
const notificationData = {
deviceId: "1",
shuttleId: "1",
stopId: "1"
};
await notificationService.scheduleNotification(notificationData);
const isNotificationScheduled = notificationService.isNotificationScheduled(notificationData);
expect(isNotificationScheduled).toEqual(true);
});
it("sends and clears correct notification after ETA changed", async () => {
// Arrange
const shuttle = await addMockShuttleToRepository(repository, "1");
const stop = await addMockStopToRepository(repository, "1");
const { eta, notificationData1, notificationData2 } = generateNotificationDataAndEta(shuttle, stop);
// Simulate 200 + empty object for successful push notification
updateGlobalFetchMockJson({});
// Act
await notificationService.scheduleNotification(notificationData1);
await notificationService.scheduleNotification(notificationData2);
await repository.addOrUpdateEta(eta);
// Assert
// Because repository publisher calls subscriber without await
// wait for the change to occur first
await waitForCondition(() => !notificationService.isNotificationScheduled(notificationData1));
expect(fetch as jest.Mock).toHaveBeenCalledTimes(2);
const isFirstNotificationScheduled = notificationService.isNotificationScheduled(notificationData1);
const isSecondNotificationScheduled = notificationService.isNotificationScheduled(notificationData2);
// No longer scheduled after being sent
expect(isFirstNotificationScheduled).toBe(false);
expect(isSecondNotificationScheduled).toBe(false);
});
it("leaves notification in array if delivery unsuccessful", async () => {
// Arrange
const shuttle = await addMockShuttleToRepository(repository, "1");
const stop = await addMockStopToRepository(repository, "1");
const { eta, notificationData1, notificationData2 } = generateNotificationDataAndEta(shuttle, stop)
updateGlobalFetchMockJson({}, 400);
// Act
await notificationService.scheduleNotification(notificationData1);
await repository.addOrUpdateEta(eta);
// Assert
// The notification should stay scheduled to be retried once
// the ETA updates again
expect(notificationService.isNotificationScheduled(notificationData1)).toBe(true);
});
});
describe("cancelNotification", () => {
it("stops notification from sending to given shuttle/stop ID", async () => {
});
});
describe("cancelAllNotifications", () => {
it("clears all notifications scheduled to be sent", async () => {
});
})
});