diff --git a/extensions/buzz/src/buzz-bus.lifecycle.test.ts b/extensions/buzz/src/buzz-bus.lifecycle.test.ts index 0b4e96c63de9..cbb3bc88382a 100644 --- a/extensions/buzz/src/buzz-bus.lifecycle.test.ts +++ b/extensions/buzz/src/buzz-bus.lifecycle.test.ts @@ -125,11 +125,41 @@ const CHANNEL_ID = "7c4a6d2a-2ed9-4b4e-a5e2-4d705ee9b34c"; const SECOND_CHANNEL_ID = "45cedd86-f853-45b7-8fea-812b7fe63d7a"; const BOT_PUBLIC_KEY = getPublicKey(Uint8Array.from(Buffer.from(PRIVATE_KEY, "hex"))); const SENDER_PUBLIC_KEY = getPublicKey(Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex"))); +const SENDER_SECRET_KEY = Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")); const RELAY_PUBLIC_KEY = "f".repeat(64); const tempDirs = new Set(); let previousStateDir: string | undefined; let stateDir: string; +function startTestBus( + overrides: Partial[0]> = {}, +): Promise { + return startBuzzBus({ + accountId: ACCOUNT_ID, + relayUrl: "wss://buzz.example.com", + privateKey: PRIVATE_KEY, + channelIds: [CHANNEL_ID], + onMessage: async () => {}, + ...overrides, + }); +} + +function sendTestTextOneShot( + overrides: Partial[0]> = {}, +): Promise { + return sendBuzzTextOneShot({ + relayUrl: "wss://buzz.example.com", + privateKey: PRIVATE_KEY, + channelId: CHANNEL_ID, + text: "hello", + ...overrides, + }); +} + +function signSenderEvent(template: Parameters[0]): Event { + return finalizeEvent(template, SENDER_SECRET_KEY); +} + function subscriptionIncludesKind( subscription: (typeof relayMocks.subscriptions)[number], kind: number, @@ -204,12 +234,8 @@ describe("Buzz bus lifecycle", () => { it("rejects an over-capacity room set before opening the relay", async () => { await expect( - startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, + startTestBus({ channelIds: Array.from({ length: 1_021 }, (_, index) => `room-${index}`), - onMessage: async () => {}, }), ).rejects.toThrow("Buzz supports at most 1020 configured rooms per account"); @@ -231,15 +257,7 @@ describe("Buzz bus lifecycle", () => { ), ); - await expect( - startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, - }), - ).rejects.toThrow("auth rejected"); + await expect(startTestBus()).rejects.toThrow("auth rejected"); expect(relayMocks.connect).toHaveBeenCalledOnce(); expect(relayMocks.close).toHaveBeenCalledOnce(); @@ -262,13 +280,7 @@ describe("Buzz bus lifecycle", () => { }), ), ); - const start = startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, - }); + const start = startTestBus(); const rejection = expect(start).rejects.toThrow("Timed out setting up Buzz relay session"); await vi.advanceTimersByTimeAsync(20_000); @@ -282,11 +294,7 @@ describe("Buzz bus lifecycle", () => { it("publishes and closes a standalone authenticated send", async () => { relayMocks.auth.mockResolvedValue("ok"); - const messageId = await sendBuzzTextOneShot({ - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelId: CHANNEL_ID, - text: "hello", + const messageId = await sendTestTextOneShot({ threadId: "root-id", replyToId: "parent-id", }); @@ -307,13 +315,7 @@ describe("Buzz bus lifecycle", () => { it("sends room and thread typing without waiting for a relay acknowledgement", async () => { relayMocks.auth.mockResolvedValue("ok"); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, - }); + const bus = await startTestBus(); await bus.sendTyping({ channelId: CHANNEL_ID, @@ -346,13 +348,7 @@ describe("Buzz bus lifecycle", () => { it("drops typing while the active relay is disconnected", async () => { relayMocks.auth.mockResolvedValue("ok"); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, - }); + const bus = await startTestBus(); relayMocks.connected = false; relayMocks.send.mockClear(); @@ -374,12 +370,8 @@ describe("Buzz bus lifecycle", () => { ], }); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, + const bus = await startTestBus({ channelIds: [CHANNEL_ID, SECOND_CHANNEL_ID], - onMessage: async () => {}, }); expect(relayMocks.subscriptions[0]?.filter.kinds).toEqual([39_000]); @@ -415,23 +407,17 @@ describe("Buzz bus lifecycle", () => { ], }); relayMocks.roomHistoryEvents = [ - finalizeEvent( - { - kind: 9, - created_at: 1_700_000_000, - content: "historical message", - tags: [["h", CHANNEL_ID]], - }, - Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")), - ), + signSenderEvent({ + kind: 9, + created_at: 1_700_000_000, + content: "historical message", + tags: [["h", CHANNEL_ID]], + }), ]; relayMocks.stallRoomEoseChannelId = SECOND_CHANNEL_ID; const onMessage = vi.fn(async (_message: BuzzInboundMessage) => {}); - const start = startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, + const start = startTestBus({ channelIds: [CHANNEL_ID, SECOND_CHANNEL_ID], onMessage, }); @@ -465,11 +451,7 @@ describe("Buzz bus lifecycle", () => { }); const onFatalError = vi.fn(); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], + const bus = await startTestBus({ onMessage, onFatalError, }); @@ -524,11 +506,7 @@ describe("Buzz bus lifecycle", () => { }); }, ); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], + const bus = await startTestBus({ onMessage, }); @@ -543,13 +521,7 @@ describe("Buzz bus lifecycle", () => { it("leaves room subscription shutdown to the relay", async () => { relayMocks.auth.mockResolvedValue("ok"); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, - }); + const bus = await startTestBus(); const roomSubscription = relayMocks.subscriptions.find((entry) => subscriptionIncludesKind(entry, 9), ); @@ -577,12 +549,7 @@ describe("Buzz bus lifecycle", () => { ], }, ]; - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, + const bus = await startTestBus({ onRoomDirectoryChanged, }); await vi.waitFor(() => expect(bus.directory.listGroups({})[0]?.name).toBe("Engineering")); @@ -664,12 +631,7 @@ describe("Buzz bus lifecycle", () => { }; const onFatalError = vi.fn(); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, + const bus = await startTestBus({ onFatalError, }); @@ -686,18 +648,15 @@ describe("Buzz bus lifecycle", () => { it("loads room metadata and current member profiles on the active bus", async () => { relayMocks.auth.mockResolvedValue("ok"); relayMocks.profileEvents = [ - finalizeEvent( - { - kind: 0, - created_at: 1_700_000_000, - content: JSON.stringify({ - display_name: "Alice", - picture: "https://example.com/alice.png", - }), - tags: [], - }, - Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")), - ), + signSenderEvent({ + kind: 0, + created_at: 1_700_000_000, + content: JSON.stringify({ + display_name: "Alice", + picture: "https://example.com/alice.png", + }), + tags: [], + }), ]; relayMocks.roomMetadataEvents = [ { @@ -714,13 +673,8 @@ describe("Buzz bus lifecycle", () => { }, ]; - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], + const bus = await startTestBus({ profileName: "OpenClaw", - onMessage: async () => {}, }); await vi.waitFor(() => @@ -764,13 +718,7 @@ describe("Buzz bus lifecycle", () => { Uint8Array.from(Buffer.from(joinedPrivateKey, "hex")), ), ]; - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, - }); + const bus = await startTestBus(); expect(bus.directory.listPeers({}).map((entry) => entry.id)).not.toContain(joinedPublicKey); relayMocks.membershipEvents = [ @@ -815,14 +763,7 @@ describe("Buzz bus lifecycle", () => { relayMocks.auth.mockResolvedValue("ok"); relayMocks.publish.mockRejectedValue(new Error("rejected")); - await expect( - sendBuzzTextOneShot({ - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelId: CHANNEL_ID, - text: "hello", - }), - ).rejects.toThrow("rejected"); + await expect(sendTestTextOneShot()).rejects.toThrow("rejected"); expect(relayMocks.close).toHaveBeenCalledOnce(); }); @@ -830,22 +771,13 @@ describe("Buzz bus lifecycle", () => { it("deduplicates replayed relay events by event id", async () => { relayMocks.auth.mockResolvedValue("ok"); const onMessage = vi.fn(async (_message: BuzzInboundMessage) => {}); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage, + const bus = await startTestBus({ onMessage }); + const event = signSenderEvent({ + kind: 9, + created_at: 1_700_000_000, + content: "hello", + tags: [["h", CHANNEL_ID]], }); - const event = finalizeEvent( - { - kind: 9, - created_at: 1_700_000_000, - content: "hello", - tags: [["h", CHANNEL_ID]], - }, - Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")), - ); const messageSubscription = relayMocks.subscriptions.find((entry) => subscriptionIncludesKind(entry, 9), @@ -863,13 +795,7 @@ describe("Buzz bus lifecycle", () => { const onMessage = vi.fn(async (message: BuzzInboundMessage) => { receivedKinds.push(message.kind); }); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage, - }); + const bus = await startTestBus({ onMessage }); const messageSubscription = relayMocks.subscriptions.find((entry) => subscriptionIncludesKind(entry, 9), ); @@ -877,28 +803,22 @@ describe("Buzz bus lifecycle", () => { [...BUZZ_INBOUND_MESSAGE_KINDS], ); - const richEvent = finalizeEvent( - { - kind: BUZZ_RICH_MESSAGE_KIND, - created_at: 1_700_000_000, - content: "**rich**", - tags: [["h", CHANNEL_ID]], - }, - Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")), - ); - const diffEvent = finalizeEvent( - { - kind: BUZZ_DIFF_MESSAGE_KIND, - created_at: 1_700_000_001, - content: "@@ -1 +1 @@\n-old\n+new", - tags: [ - ["h", CHANNEL_ID], - ["repo", "https://github.com/openclaw/openclaw"], - ["commit", "abcdef1"], - ], - }, - Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")), - ); + const richEvent = signSenderEvent({ + kind: BUZZ_RICH_MESSAGE_KIND, + created_at: 1_700_000_000, + content: "**rich**", + tags: [["h", CHANNEL_ID]], + }); + const diffEvent = signSenderEvent({ + kind: BUZZ_DIFF_MESSAGE_KIND, + created_at: 1_700_000_001, + content: "@@ -1 +1 @@\n-old\n+new", + tags: [ + ["h", CHANNEL_ID], + ["repo", "https://github.com/openclaw/openclaw"], + ["commit", "abcdef1"], + ], + }); messageSubscription?.handlers.onevent(richEvent); messageSubscription?.handlers.onevent(diffEvent); @@ -924,11 +844,7 @@ describe("Buzz bus lifecycle", () => { const onMessageError = vi.fn(); const onFatalError = vi.fn(); const onProfilePublished = vi.fn(); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], + const bus = await startTestBus({ onMessage: async () => { throw new Error("dispatch failed"); }, @@ -937,15 +853,12 @@ describe("Buzz bus lifecycle", () => { onFatalError, onProfilePublished, }); - const event = finalizeEvent( - { - kind: 9, - created_at: 1_700_000_000, - content: "hello", - tags: [["h", CHANNEL_ID]], - }, - Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")), - ); + const event = signSenderEvent({ + kind: 9, + created_at: 1_700_000_000, + content: "hello", + tags: [["h", CHANNEL_ID]], + }); relayMocks.subscriptions .find((entry) => subscriptionIncludesKind(entry, 9)) @@ -972,12 +885,7 @@ describe("Buzz bus lifecycle", () => { relayMocks.stallProfileQueryEose = true; const onFatalError = vi.fn(); const onProfileError = vi.fn(); - const bus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: async () => {}, + const bus = await startTestBus({ profileName: "Configured Agent Name", onFatalError, onProfileError, @@ -1005,23 +913,14 @@ describe("Buzz bus lifecycle", () => { it("deduplicates replayed events after the bus restarts", async () => { relayMocks.auth.mockResolvedValue("ok"); - const event = finalizeEvent( - { - kind: 9, - created_at: Math.floor(Date.now() / 1000), - content: "hello", - tags: [["h", CHANNEL_ID]], - }, - Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")), - ); - const firstOnMessage = vi.fn(async () => {}); - const firstBus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: firstOnMessage, + const event = signSenderEvent({ + kind: 9, + created_at: Math.floor(Date.now() / 1000), + content: "hello", + tags: [["h", CHANNEL_ID]], }); + const firstOnMessage = vi.fn(async () => {}); + const firstBus = await startTestBus({ onMessage: firstOnMessage }); relayMocks.subscriptions .find((entry) => subscriptionIncludesKind(entry, 9)) ?.handlers.onevent(event); @@ -1029,13 +928,7 @@ describe("Buzz bus lifecycle", () => { await firstBus.close(); const secondOnMessage = vi.fn(async () => {}); - const secondBus = await startBuzzBus({ - accountId: ACCOUNT_ID, - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - channelIds: [CHANNEL_ID], - onMessage: secondOnMessage, - }); + const secondBus = await startTestBus({ onMessage: secondOnMessage }); relayMocks.subscriptions .findLast((entry) => subscriptionIncludesKind(entry, 9)) ?.handlers.onevent(event); diff --git a/extensions/buzz/src/gateway.lifecycle.test.ts b/extensions/buzz/src/gateway.lifecycle.test.ts index 57dd61c175cb..1e3b6c66fea3 100644 --- a/extensions/buzz/src/gateway.lifecycle.test.ts +++ b/extensions/buzz/src/gateway.lifecycle.test.ts @@ -43,6 +43,49 @@ const CHANNEL_ID = "7c4a6d2a-2ed9-4b4e-a5e2-4d705ee9b34c"; const PRIVATE_KEY = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; const BOT_PUBLIC_KEY = "a".repeat(64); +function createBuzzConfig(name?: string): OpenClawConfig { + return { + channels: { + buzz: { + ...(name ? { name } : {}), + relayUrl: "wss://buzz.example.com", + privateKey: PRIVATE_KEY, + groups: { [CHANNEL_ID]: {} }, + }, + }, + } as OpenClawConfig; +} + +function startTestGateway( + options: { + profileName?: string; + setStatus?: ReturnType; + logInfo?: ReturnType; + logError?: ReturnType; + invalidateDirectoryCache?: ReturnType; + omitLog?: boolean; + } = {}, +) { + const abortController = new AbortController(); + const cfg = createBuzzConfig(options.profileName); + const account = resolveBuzzAccount({ cfg }); + const setStatus = options.setStatus ?? vi.fn(); + const lifecycle = startBuzzGatewayAccount({ + cfg, + accountId: account.accountId, + account, + runtime: {}, + abortSignal: abortController.signal, + ...(options.omitLog + ? {} + : { log: { info: options.logInfo ?? vi.fn(), error: options.logError ?? vi.fn() } }), + getStatus: vi.fn(), + setStatus, + invalidateDirectoryCache: options.invalidateDirectoryCache, + } as unknown as ChannelGatewayContext); + return { abortController, cfg, account, setStatus, lifecycle }; +} + function createMockBus(): BuzzBus { return { publicKey: BOT_PUBLIC_KEY, @@ -105,28 +148,11 @@ describe("Buzz gateway lifecycle", () => { }); it("invalidates cached room targets after initial discovery and newer room metadata", async () => { - const abortController = new AbortController(); - const cfg = { - channels: { - buzz: { - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; - const account = resolveBuzzAccount({ cfg }); const invalidateDirectoryCache = vi.fn(); - const lifecycle = startBuzzGatewayAccount({ - cfg, - accountId: account.accountId, - account, - runtime: {}, - abortSignal: abortController.signal, - getStatus: vi.fn(), - setStatus: vi.fn(), + const { abortController, lifecycle } = startTestGateway({ invalidateDirectoryCache, - } as unknown as ChannelGatewayContext); + omitLog: true, + }); await vi.waitFor(() => expect(gatewayMocks.startBuzzBus).toHaveBeenCalledOnce()); expect(invalidateDirectoryCache).toHaveBeenCalledOnce(); @@ -139,29 +165,8 @@ describe("Buzz gateway lifecycle", () => { it("restarts the account lifecycle when the bus reports a failure", async () => { gatewayMocks.resolveAgentIdentity.mockReturnValue({ name: "Molt" }); - const abortController = new AbortController(); - const cfg = { - channels: { - buzz: { - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; - const account = resolveBuzzAccount({ cfg }); const setStatus = vi.fn(); - const ctx = { - cfg, - accountId: account.accountId, - account, - runtime: {}, - abortSignal: abortController.signal, - log: { info: vi.fn(), error: vi.fn() }, - getStatus: vi.fn(), - setStatus, - } as unknown as ChannelGatewayContext; - const lifecycle = startBuzzGatewayAccount(ctx); + const { abortController, account, lifecycle } = startTestGateway({ setStatus }); await vi.waitFor(() => expect(gatewayMocks.startBuzzBus).toHaveBeenCalledOnce()); expect(gatewayMocks.startBuzzBus.mock.calls[0]?.[0].profileName).toBe("Molt"); @@ -194,15 +199,7 @@ describe("Buzz gateway lifecycle", () => { }); it("uses a one-shot authenticated connection when no gateway bus is running", async () => { - const cfg = { - channels: { - buzz: { - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; + const cfg = createBuzzConfig(); const result = await buzzOutboundAdapter.sendText({ cfg, @@ -230,15 +227,7 @@ describe("Buzz gateway lifecycle", () => { }); it("drops heartbeat typing when no gateway bus is running", async () => { - const cfg = { - channels: { - buzz: { - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; + const cfg = createBuzzConfig(); await sendBuzzTyping({ cfg, @@ -252,29 +241,7 @@ describe("Buzz gateway lifecycle", () => { }); it("reuses the gateway bus for sends in the running process", async () => { - const abortController = new AbortController(); - const cfg = { - channels: { - buzz: { - name: "BuzzClaw", - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; - const account = resolveBuzzAccount({ cfg }); - const ctx = { - cfg, - accountId: account.accountId, - account, - runtime: {}, - abortSignal: abortController.signal, - log: { info: vi.fn(), error: vi.fn() }, - getStatus: vi.fn(), - setStatus: vi.fn(), - } as unknown as ChannelGatewayContext; - const lifecycle = startBuzzGatewayAccount(ctx); + const { abortController, cfg, lifecycle } = startTestGateway({ profileName: "BuzzClaw" }); await vi.waitFor(() => expect(gatewayMocks.startBuzzBus).toHaveBeenCalledOnce()); expect(gatewayMocks.startBuzzBus.mock.calls[0]?.[0].profileName).toBe("BuzzClaw"); expect(gatewayMocks.resolveAgentRoute).not.toHaveBeenCalled(); @@ -299,28 +266,7 @@ describe("Buzz gateway lifecycle", () => { }); it("uses the active bus for heartbeat typing without destabilizing the account", async () => { - const abortController = new AbortController(); - const cfg = { - channels: { - buzz: { - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; - const account = resolveBuzzAccount({ cfg }); - const ctx = { - cfg, - accountId: account.accountId, - account, - runtime: {}, - abortSignal: abortController.signal, - log: { info: vi.fn(), error: vi.fn() }, - getStatus: vi.fn(), - setStatus: vi.fn(), - } as unknown as ChannelGatewayContext; - const lifecycle = startBuzzGatewayAccount(ctx); + const { abortController, cfg, lifecycle } = startTestGateway(); await vi.waitFor(() => expect(gatewayMocks.startBuzzBus).toHaveBeenCalledOnce()); await sendBuzzTyping({ @@ -351,28 +297,7 @@ describe("Buzz gateway lifecycle", () => { it("uses the rolling lookback after a failed initial session", async () => { gatewayMocks.startBuzzBus.mockRejectedValueOnce(new Error("connect failed")); - const abortController = new AbortController(); - const cfg = { - channels: { - buzz: { - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; - const account = resolveBuzzAccount({ cfg }); - const ctx = { - cfg, - accountId: account.accountId, - account, - runtime: {}, - abortSignal: abortController.signal, - log: { info: vi.fn(), error: vi.fn() }, - getStatus: vi.fn(), - setStatus: vi.fn(), - } as unknown as ChannelGatewayContext; - const lifecycle = startBuzzGatewayAccount(ctx); + const { abortController, lifecycle } = startTestGateway(); await vi.waitFor(() => expect(gatewayMocks.startBuzzBus).toHaveBeenCalledTimes(2), { timeout: 3_000, @@ -386,30 +311,9 @@ describe("Buzz gateway lifecycle", () => { }); it("keeps the account running when one message fails", async () => { - const abortController = new AbortController(); - const cfg = { - channels: { - buzz: { - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; - const account = resolveBuzzAccount({ cfg }); const setStatus = vi.fn(); const logError = vi.fn(); - const ctx = { - cfg, - accountId: account.accountId, - account, - runtime: {}, - abortSignal: abortController.signal, - log: { info: vi.fn(), error: logError }, - getStatus: vi.fn(), - setStatus, - } as unknown as ChannelGatewayContext; - const lifecycle = startBuzzGatewayAccount(ctx); + const { abortController, account, lifecycle } = startTestGateway({ setStatus, logError }); await vi.waitFor(() => expect(gatewayMocks.startBuzzBus).toHaveBeenCalledOnce()); gatewayMocks.onMessageError?.(new Error("dispatch failed")); @@ -426,30 +330,8 @@ describe("Buzz gateway lifecycle", () => { }); it("reconnects with a rolling lookback without trusting sender time", async () => { - const abortController = new AbortController(); - const cfg = { - channels: { - buzz: { - relayUrl: "wss://buzz.example.com", - privateKey: PRIVATE_KEY, - groups: { [CHANNEL_ID]: {} }, - }, - }, - } as OpenClawConfig; - const account = resolveBuzzAccount({ cfg }); const invalidateDirectoryCache = vi.fn(); - const ctx = { - cfg, - accountId: account.accountId, - account, - runtime: {}, - abortSignal: abortController.signal, - log: { info: vi.fn(), error: vi.fn() }, - getStatus: vi.fn(), - setStatus: vi.fn(), - invalidateDirectoryCache, - } as unknown as ChannelGatewayContext; - const lifecycle = startBuzzGatewayAccount(ctx); + const { abortController, lifecycle } = startTestGateway({ invalidateDirectoryCache }); await vi.waitFor(() => expect(gatewayMocks.startBuzzBus).toHaveBeenCalledOnce()); const createdAt = Math.floor(Date.now() / 1000) + 24 * 60 * 60;