import { beforeEach, describe, expect, it, jest } from "@jest/globals"; import http2 from "http2"; import { EventEmitter } from "node:events"; import { AppleNotificationSender, NotificationAlertArguments } from "../../../src/notifications/senders/AppleNotificationSender"; jest.mock("http2"); const sampleKeyBase64 = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZ3NybVNBWklhZ09mQ1A4c0IKV2kyQ0JYRzFPbzd2MWJpc3BJWkN3SXI0UkRlZ0NnWUlLb1pJemowREFRZWhSQU5DQUFUWkh4VjJ3UUpMTUJxKwp5YSt5ZkdpM2cyWlV2NmhyZmUrajA4eXRla1BIalhTMHF6Sm9WRUx6S0hhNkVMOVlBb1pEWEJ0QjZoK2ZHaFhlClNPY09OYmFmCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"; function mockHttp2Connect(status: number) { class MockClient extends EventEmitter { request = jest.fn((_) => { 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("AppleNotificationSender", () => { let notificationSender: AppleNotificationSender; beforeEach(() => { notificationSender = new AppleNotificationSender(); // 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 () => { notificationSender.reloadAPNsTokenIfTimePassed(); expect(notificationSender.lastRefreshedTimeMs).toBeDefined(); }); it("doesn't reload the token if last refreshed time is recent", async () => { notificationSender.reloadAPNsTokenIfTimePassed(); const lastRefreshedTimeMs = notificationSender.lastRefreshedTimeMs; notificationSender.reloadAPNsTokenIfTimePassed(); // Expect no change to have occurred expect(lastRefreshedTimeMs).toEqual(notificationSender.lastRefreshedTimeMs); }); }); 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 = AppleNotificationSender.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 = AppleNotificationSender.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("sendNotificationImmediately", () => { it('makes the connection to the http server if the notification should be sent', async () => { const notificationArguments: NotificationAlertArguments = { title: 'Test notification', body: 'This notification will send', } const result = await notificationSender.sendNotificationImmediately('1', notificationArguments); expect(http2.connect).toHaveBeenCalled(); expect(result).toBe(true); }); it('throws an error if the bundle ID is not set correctly', async () => { process.env = { ...process.env, APNS_BUNDLE_ID: undefined, } const notificationArguments: NotificationAlertArguments = { title: 'Test notification', body: 'This notification will not send', } await expect(async () => { await notificationSender.sendNotificationImmediately('1', notificationArguments); }).rejects.toThrow(); }); it('returns false if there is an error sending the notification', async () => { mockHttp2Connect(403); const notificationArguments: NotificationAlertArguments = { title: 'Test notification', body: 'This notification will not send', } const result = await notificationSender.sendNotificationImmediately('1', notificationArguments); expect(http2.connect).toHaveBeenCalled(); expect(result).toBe(false); }); it('does not send notification if shouldActuallySendNotifications is false', async () => { notificationSender = new AppleNotificationSender(false); const notificationArguments: NotificationAlertArguments = { title: 'Test notification', body: 'This notification should not send', } const result = await notificationSender.sendNotificationImmediately('1', notificationArguments); expect(http2.connect).not.toHaveBeenCalled(); expect(result).toBe(true); }); }); });