From 2d864df02ec1581a6efa1f07de9acbb36f05b041 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2026 00:00:57 +0000 Subject: [PATCH] test(push): cover urgent notifications without global stubs Keep the push transport explicit so the priority regression exercises every batch without replacing global fetch. Production callers retain the native transport by default. Co-authored-by: Andrii Panasiuk --- .../src/server/push/push-service.test.ts | 57 +++++++++---------- .../server/src/server/push/push-service.ts | 8 ++- 2 files changed, 32 insertions(+), 33 deletions(-) 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",