diff --git a/packages/server/src/server/push/push-service.test.ts b/packages/server/src/server/push/push-service.test.ts index 3616f5dc19..da6887941c 100644 --- a/packages/server/src/server/push/push-service.test.ts +++ b/packages/server/src/server/push/push-service.test.ts @@ -1,42 +1,37 @@ -import type pino from "pino"; -import { afterEach, describe, expect, test, vi } from "vitest"; +import pino from "pino"; +import { describe, expect, test } from "vitest"; import { PushService } from "./push-service.js"; -function createLogger(): pino.Logger { - const logger = { - child: () => logger, - debug: () => undefined, - info: () => undefined, - warn: () => undefined, - error: () => undefined, - }; - return logger as unknown as pino.Logger; -} - describe("PushService", () => { - afterEach(() => { - vi.unstubAllGlobals(); - }); + test("requests high priority for every notification across batches", async () => { + const requests: Record[][] = []; + const sendRequest: typeof fetch = async (_url, init) => { + const messages = JSON.parse(String(init?.body)) as Record[]; + requests.push(messages); + return new Response( + JSON.stringify({ data: messages.map(() => ({ status: "ok", id: "ticket" })) }), + ); + }; + const service = new PushService(pino({ level: "silent" }), () => undefined, sendRequest); + const tokens = Array.from({ length: 101 }, (_, index) => `ExponentPushToken[device-${index}]`); - test("sends high priority so Android delivers while the device dozes", async () => { - const requests: unknown[] = []; - vi.stubGlobal( - "fetch", - vi.fn(async (_url: string, init: RequestInit) => { - requests.push(JSON.parse(String(init.body))); - return new Response(JSON.stringify({ data: [{ status: "ok", id: "ticket" }] })); - }), - ); - const service = new PushService(createLogger(), () => undefined); - - await service.sendPush(["ExponentPushToken[android-device]"], { + await service.sendPush(tokens, { title: "Agent finished", body: "Done", + data: { agentId: "agent" }, }); - expect(requests).toEqual([ - [expect.objectContaining({ to: "ExponentPushToken[android-device]", priority: "high" })], - ]); + expect(requests.map((batch) => batch.length)).toEqual([100, 1]); + expect(requests.flat()).toEqual( + tokens.map((token) => ({ + to: token, + priority: "high", + title: "Agent finished", + body: "Done", + data: { agentId: "agent" }, + sound: "default", + })), + ); }); }); diff --git a/packages/server/src/server/push/push-service.ts b/packages/server/src/server/push/push-service.ts index 628b3a7f77..6002807210 100644 --- a/packages/server/src/server/push/push-service.ts +++ b/packages/server/src/server/push/push-service.ts @@ -33,7 +33,11 @@ export class PushService { private readonly logger: pino.Logger; private readonly revokeToken: (token: string) => void; - constructor(logger: pino.Logger, revokeToken: (token: string) => void) { + constructor( + logger: pino.Logger, + revokeToken: (token: string) => void, + private readonly sendRequest: typeof fetch = fetch, + ) { this.logger = logger.child({ component: "push-service" }); this.revokeToken = revokeToken; } @@ -66,7 +70,7 @@ export class PushService { private async sendBatch(messages: ExpoPushMessage[]): Promise { try { - const response = await fetch(EXPO_PUSH_URL, { + const response = await this.sendRequest(EXPO_PUSH_URL, { method: "POST", headers: { "Content-Type": "application/json",