update naming of classes and tests

This commit is contained in:
2025-03-24 09:20:10 -07:00
parent 135a294e5d
commit 619ef9a27f
8 changed files with 26 additions and 26 deletions

View File

@@ -0,0 +1,267 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
import { ETANotificationScheduler } from "../../../src/notifications/schedulers/ETANotificationScheduler";
import { UnoptimizedInMemoryRepository } from "../../../src/repositories/UnoptimizedInMemoryRepository";
import http2 from "http2";
import { IEta, IShuttle, IStop } from "../../../src/entities/entities";
import { addMockShuttleToRepository, addMockStopToRepository } from "../../testHelpers/repositorySetupHelpers";
import EventEmitter = require("node:events");
jest.mock("http2");
const sampleKeyBase64 = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZ3NybVNBWklhZ09mQ1A4c0IKV2kyQ0JYRzFPbzd2MWJpc3BJWkN3SXI0UkRlZ0NnWUlLb1pJemowREFRZWhSQU5DQUFUWkh4VjJ3UUpMTUJxKwp5YSt5ZkdpM2cyWlV2NmhyZmUrajA4eXRla1BIalhTMHF6Sm9WRUx6S0hhNkVMOVlBb1pEWEJ0QjZoK2ZHaFhlClNPY09OYmFmCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K";
/**
* 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));
}
function mockHttp2Connect(status: number) {
class MockClient extends EventEmitter {
request = jest.fn((headers: any) => {
const mockRequest: any = new EventEmitter();
mockRequest.setEncoding = jest.fn();
mockRequest.write = jest.fn();
mockRequest.end = jest.fn(() => {
setTimeout(() => {
mockRequest.emit('response', { ':status': status });
}, 10);
});
return mockRequest;
});
close() {};
}
(http2.connect as jest.Mock) = jest.fn(() => new MockClient());
}
describe("ETANotificationScheduler", () => {
let repository: UnoptimizedInMemoryRepository
let notificationService: ETANotificationScheduler;
beforeEach(() => {
repository = new UnoptimizedInMemoryRepository();
notificationService = new ETANotificationScheduler(repository);
// Ensure that tests don't hit the server
process.env = {
...process.env,
APNS_KEY_ID: "1",
APNS_TEAM_ID: "1",
APNS_BUNDLE_ID: "dev.bchen.ProjectInter",
APNS_PRIVATE_KEY: sampleKeyBase64,
};
});
beforeEach(() => {
mockHttp2Connect(200);
});
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);
// 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));
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("doesn't send notification if seconds threshold not exceeded", async () => {
// Arrange
const shuttle = await addMockShuttleToRepository(repository, "1");
const stop = await addMockStopToRepository(repository, "1");
const { eta, notificationData1 } = generateNotificationDataAndEta(shuttle, stop);
eta.secondsRemaining = notificationService.secondsThresholdForNotificationToFire + 100;
// Act
await notificationService.scheduleNotification(notificationData1);
await repository.addOrUpdateEta(eta);
// Assert
await waitForMilliseconds(500);
const isNotificationScheduled = notificationService.isNotificationScheduled(notificationData1);
expect(isNotificationScheduled).toBe(true);
});
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 } = generateNotificationDataAndEta(shuttle, stop)
mockHttp2Connect(403);
// Act
await notificationService.scheduleNotification(notificationData1);
await repository.addOrUpdateEta(eta);
// Assert
// The notification should stay scheduled to be retried once
// the ETA updates again
await waitForMilliseconds(500);
const isNotificationScheduled = notificationService.isNotificationScheduled(notificationData1);
expect(isNotificationScheduled).toBe(true);
});
});
describe('getAPNsFullUrlToUse', () => {
it('should return the production URL when APNS_IS_PRODUCTION is set to "1"', () => {
process.env.APNS_IS_PRODUCTION = "1";
const deviceId = 'testDeviceId';
const result = ETANotificationScheduler.getAPNsFullUrlToUse(deviceId);
const { fullUrl, host, path } = result;
expect(fullUrl).toBe(`https://api.push.apple.com/3/device/${deviceId}`);
expect(host).toBe("https://api.push.apple.com");
expect(path).toBe(`/3/device/${deviceId}`);
});
it('should return the sandbox URL when APNS_IS_PRODUCTION is set to something other than 1', () => {
process.env.APNS_IS_PRODUCTION = "0";
const deviceId = 'testDeviceId';
const result = ETANotificationScheduler.getAPNsFullUrlToUse(deviceId);
const { fullUrl, host, path } = result;
expect(fullUrl).toBe(`https://api.development.push.apple.com/3/device/${deviceId}`);
expect(host).toBe("https://api.development.push.apple.com");
expect(path).toBe(`/3/device/${deviceId}`);
});
});
describe("cancelNotification", () => {
it("stops notification from sending to given shuttle/stop ID", async () => {
// Arrange
const shuttle = await addMockShuttleToRepository(repository, "1");
const stop = await addMockStopToRepository(repository, "1");
const { eta, notificationData1 } = generateNotificationDataAndEta(shuttle, stop);
await notificationService.scheduleNotification(notificationData1);
// Act
await notificationService.cancelNotificationIfExists(notificationData1);
await repository.addOrUpdateEta(eta);
// Assert
await waitForMilliseconds(500);
expect(http2.connect as jest.Mock).toHaveBeenCalledTimes(0);
});
});
describe("getAllScheduledNotificationsForDevice", () => {
it("returns scheduled notifications for the device ID", async () => {
// Arrange
const shuttle1 = await addMockShuttleToRepository(repository, "1");
const stop = await addMockStopToRepository(repository, "1");
const { eta, notificationData1 } = generateNotificationDataAndEta(shuttle1, stop);
await notificationService.scheduleNotification(notificationData1);
const shuttle2 = {
...shuttle1,
id: "2",
}
await repository.addOrUpdateShuttle(shuttle2);
const notificationData2 = {
...notificationData1,
shuttleId: shuttle2.id,
}
await notificationService.scheduleNotification(notificationData2);
// Act
const notifications = await notificationService.getAllScheduledNotificationsForDevice(notificationData1.deviceId);
// Assert
expect(notifications.length).toBe(2);
});
it("returns an empty array if there are no notifications", async () => {
// Act
const notifications = await notificationService.getAllScheduledNotificationsForDevice("1");
expect(notifications.length).toBe(0);
});
});
});