diff --git a/extensions/device-pair/notify.test.ts b/extensions/device-pair/notify.test.ts index 91bca82a0aab..9ceff22a7a99 100644 --- a/extensions/device-pair/notify.test.ts +++ b/extensions/device-pair/notify.test.ts @@ -96,12 +96,11 @@ describe("device-pair notify persistence", () => { }); } - it("keeps one notify poll in flight across service recreation", async () => { + it("defers the first notify poll and keeps one in flight across service recreation", async () => { vi.useFakeTimers(); const firstPoll = createDeferred>>(); const failedPoll = createDeferred>>(); listDevicePairingMock - .mockResolvedValueOnce({ pending: [], paired: [] }) .mockImplementationOnce(() => firstPoll.promise) .mockImplementationOnce(() => failedPoll.promise) .mockResolvedValue({ pending: [], paired: [] }); @@ -109,29 +108,30 @@ describe("device-pair notify persistence", () => { let service = createPairingNotifierService(api); await service.start({} as never); - expect(listDevicePairingMock).toHaveBeenCalledTimes(1); + expect(listDevicePairingMock).not.toHaveBeenCalled(); await vi.advanceTimersByTimeAsync(10_000); - expect(listDevicePairingMock).toHaveBeenCalledTimes(2); + expect(listDevicePairingMock).toHaveBeenCalledTimes(1); await vi.advanceTimersByTimeAsync(20_000); - expect(listDevicePairingMock).toHaveBeenCalledTimes(2); + expect(listDevicePairingMock).toHaveBeenCalledTimes(1); await service.stop?.({} as never); service = createPairingNotifierService(createApi()); await service.start({} as never); - expect(listDevicePairingMock).toHaveBeenCalledTimes(2); + await vi.advanceTimersByTimeAsync(10_000); + expect(listDevicePairingMock).toHaveBeenCalledTimes(1); firstPoll.resolve({ pending: [], paired: [] }); await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(10_000); - expect(listDevicePairingMock).toHaveBeenCalledTimes(3); + expect(listDevicePairingMock).toHaveBeenCalledTimes(2); await vi.advanceTimersByTimeAsync(20_000); - expect(listDevicePairingMock).toHaveBeenCalledTimes(3); + expect(listDevicePairingMock).toHaveBeenCalledTimes(2); failedPoll.reject(new Error("poll failed")); await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(10_000); - expect(listDevicePairingMock).toHaveBeenCalledTimes(4); + expect(listDevicePairingMock).toHaveBeenCalledTimes(3); await service.stop?.({} as never); }); @@ -166,9 +166,7 @@ describe("device-pair notify persistence", () => { }, action: "on", }); - listDevicePairingMock - .mockResolvedValueOnce({ pending: [], paired: [] }) - .mockResolvedValue({ pending: [firstRequest], paired: [] }); + listDevicePairingMock.mockResolvedValue({ pending: [firstRequest], paired: [] }); let service = createPairingNotifierService(api); await service.start({} as never); @@ -211,7 +209,7 @@ describe("device-pair notify persistence", () => { ctx: { channel: "telegram", senderId: "old-chat" }, action: "on", }); - listDevicePairingMock.mockResolvedValueOnce({ pending: [], paired: [] }).mockResolvedValue({ + listDevicePairingMock.mockResolvedValue({ pending: [ { requestId: "request-1", @@ -261,7 +259,7 @@ describe("device-pair notify persistence", () => { ctx: { channel: "telegram", senderId: "chat-123" }, action: "once", }); - listDevicePairingMock.mockResolvedValueOnce({ pending: [], paired: [] }).mockResolvedValue({ + listDevicePairingMock.mockResolvedValue({ pending: [ { requestId: "request-1", @@ -325,6 +323,7 @@ describe("device-pair notify persistence", () => { const service = createPairingNotifierService(api); await service.start({} as never); + await vi.advanceTimersByTimeAsync(10_000); expect(sendText).not.toHaveBeenCalled(); await expect( @@ -367,6 +366,7 @@ describe("device-pair notify persistence", () => { }); const service = createPairingNotifierService(api); await service.start({} as never); + await vi.advanceTimersByTimeAsync(10_000); expect(sendText).toHaveBeenCalledWith( expect.objectContaining({ text: expect.stringContaining("ID: request-same-ms") }), @@ -404,6 +404,7 @@ describe("device-pair notify persistence", () => { const service = createPairingNotifierService(api); await service.start({} as never); + await vi.advanceTimersByTimeAsync(10_000); expect(sendText).toHaveBeenCalledTimes(1); expect(sendText).toHaveBeenCalledWith( diff --git a/extensions/device-pair/notify.ts b/extensions/device-pair/notify.ts index f7c7ea61b734..e64a9fe7db78 100644 --- a/extensions/device-pair/notify.ts +++ b/extensions/device-pair/notify.ts @@ -454,14 +454,13 @@ export function createPairingNotifierService(api: OpenClawPluginApi): OpenClawPl return { id: "device-pair-notifier", - start: async () => { + start: () => { const tick = async () => { await runNotifyPoll(api); }; - await tick().catch((err: unknown) => { - api.logger.warn(`device-pair: initial notify poll failed: ${formatErrorMessage(err)}`); - }); + // Pairing notifications are eventual background work. Starting on the + // existing interval keeps SQLite pairing scans out of Gateway readiness. notifyInterval = setInterval(() => { tick().catch((err: unknown) => { api.logger.warn(`device-pair: notify poll failed: ${formatErrorMessage(err)}`);