diff --git a/src/auto-reply/reply/commands-setunset.test.ts b/src/auto-reply/reply/commands-setunset.test.ts index fd420fc2485..397c11b1cb7 100644 --- a/src/auto-reply/reply/commands-setunset.test.ts +++ b/src/auto-reply/reply/commands-setunset.test.ts @@ -11,6 +11,28 @@ type ParsedSetUnsetAction = | { action: "unset"; path: string } | { action: "error"; message: string }; +function createActionMappers() { + return { + onSet: (path: string, value: unknown): ParsedSetUnsetAction => ({ action: "set", path, value }), + onUnset: (path: string): ParsedSetUnsetAction => ({ action: "unset", path }), + onError: (message: string): ParsedSetUnsetAction => ({ action: "error", message }), + }; +} + +function createSlashParams(params: { + raw: string; + onKnownAction?: (action: string) => ParsedSetUnsetAction | undefined; +}) { + return { + raw: params.raw, + slash: "/config", + invalidMessage: "Invalid /config syntax.", + usageMessage: "Usage: /config show|set|unset", + onKnownAction: params.onKnownAction ?? (() => undefined), + ...createActionMappers(), + }; +} + describe("parseSetUnsetCommand", () => { it("parses unset values", () => { expect( @@ -35,25 +57,23 @@ describe("parseSetUnsetCommand", () => { describe("parseSetUnsetCommandAction", () => { it("returns null for non set/unset actions", () => { + const mappers = createActionMappers(); const result = parseSetUnsetCommandAction({ slash: "/config", action: "show", args: "", - onSet: (path, value) => ({ action: "set", path, value }), - onUnset: (path) => ({ action: "unset", path }), - onError: (message) => ({ action: "error", message }), + ...mappers, }); expect(result).toBeNull(); }); it("maps parse errors through onError", () => { + const mappers = createActionMappers(); const result = parseSetUnsetCommandAction({ slash: "/config", action: "set", args: "", - onSet: (path, value) => ({ action: "set", path, value }), - onUnset: (path) => ({ action: "unset", path }), - onError: (message) => ({ action: "error", message }), + ...mappers, }); expect(result).toEqual({ action: "error", message: "Usage: /config set path=value" }); }); @@ -61,57 +81,36 @@ describe("parseSetUnsetCommandAction", () => { describe("parseSlashCommandWithSetUnset", () => { it("returns null when the input does not match the slash command", () => { - const result = parseSlashCommandWithSetUnset({ - raw: "/debug show", - slash: "/config", - invalidMessage: "Invalid /config syntax.", - usageMessage: "Usage: /config show|set|unset", - onKnownAction: () => undefined, - onSet: (path, value) => ({ action: "set", path, value }), - onUnset: (path) => ({ action: "unset", path }), - onError: (message) => ({ action: "error", message }), - }); + const result = parseSlashCommandWithSetUnset( + createSlashParams({ raw: "/debug show" }), + ); expect(result).toBeNull(); }); it("prefers set/unset mapping and falls back to known actions", () => { - const setResult = parseSlashCommandWithSetUnset({ - raw: '/config set a.b={"ok":true}', - slash: "/config", - invalidMessage: "Invalid /config syntax.", - usageMessage: "Usage: /config show|set|unset", - onKnownAction: () => undefined, - onSet: (path, value) => ({ action: "set", path, value }), - onUnset: (path) => ({ action: "unset", path }), - onError: (message) => ({ action: "error", message }), - }); + const setResult = parseSlashCommandWithSetUnset( + createSlashParams({ + raw: '/config set a.b={"ok":true}', + }), + ); expect(setResult).toEqual({ action: "set", path: "a.b", value: { ok: true } }); - const showResult = parseSlashCommandWithSetUnset({ - raw: "/config show", - slash: "/config", - invalidMessage: "Invalid /config syntax.", - usageMessage: "Usage: /config show|set|unset", - onKnownAction: (action) => - action === "show" ? { action: "unset", path: "dummy" } : undefined, - onSet: (path, value) => ({ action: "set", path, value }), - onUnset: (path) => ({ action: "unset", path }), - onError: (message) => ({ action: "error", message }), - }); + const showResult = parseSlashCommandWithSetUnset( + createSlashParams({ + raw: "/config show", + onKnownAction: (action) => + action === "show" ? { action: "unset", path: "dummy" } : undefined, + }), + ); expect(showResult).toEqual({ action: "unset", path: "dummy" }); }); it("returns onError for unknown actions", () => { - const unknownAction = parseSlashCommandWithSetUnset({ - raw: "/config whoami", - slash: "/config", - invalidMessage: "Invalid /config syntax.", - usageMessage: "Usage: /config show|set|unset", - onKnownAction: () => undefined, - onSet: (path, value) => ({ action: "set", path, value }), - onUnset: (path) => ({ action: "unset", path }), - onError: (message) => ({ action: "error", message }), - }); + const unknownAction = parseSlashCommandWithSetUnset( + createSlashParams({ + raw: "/config whoami", + }), + ); expect(unknownAction).toEqual({ action: "error", message: "Usage: /config show|set|unset" }); }); }); diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index 4d2cf45d261..824e4d2fdf7 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -60,6 +60,26 @@ const baseQueuedRun = (messageProvider = "whatsapp"): FollowupRun => }, }) as FollowupRun; +function mockCompactionRun(params: { + willRetry: boolean; + result: { + payloads: Array<{ text: string }>; + meta: Record; + }; +}) { + runEmbeddedPiAgentMock.mockImplementationOnce( + async (args: { + onAgentEvent?: (evt: { stream: string; data: Record }) => void; + }) => { + args.onAgentEvent?.({ + stream: "compaction", + data: { phase: "end", willRetry: params.willRetry }, + }); + return params.result; + }, + ); +} + describe("createFollowupRunner compaction", () => { it("adds verbose auto-compaction notice and tracks count", async () => { const storePath = path.join( @@ -75,17 +95,10 @@ describe("createFollowupRunner compaction", () => { }; const onBlockReply = vi.fn(async () => {}); - runEmbeddedPiAgentMock.mockImplementationOnce( - async (params: { - onAgentEvent?: (evt: { stream: string; data: Record }) => void; - }) => { - params.onAgentEvent?.({ - stream: "compaction", - data: { phase: "end", willRetry: true }, - }); - return { payloads: [{ text: "final" }], meta: {} }; - }, - ); + mockCompactionRun({ + willRetry: true, + result: { payloads: [{ text: "final" }], meta: {} }, + }); const runner = createFollowupRunner({ opts: { onBlockReply }, @@ -149,29 +162,22 @@ describe("createFollowupRunner compaction", () => { await saveSessionStore(storePath, sessionStore); const onBlockReply = vi.fn(async () => {}); - runEmbeddedPiAgentMock.mockImplementationOnce( - async (params: { - onAgentEvent?: (evt: { stream: string; data: Record }) => void; - }) => { - params.onAgentEvent?.({ - stream: "compaction", - data: { phase: "end", willRetry: false }, - }); - return { - payloads: [{ text: "done" }], - meta: { - agentMeta: { - // Accumulated usage across pre+post compaction calls. - usage: { input: 190_000, output: 8_000, total: 198_000 }, - // Last call usage reflects post-compaction context. - lastCallUsage: { input: 11_000, output: 2_000, total: 13_000 }, - model: "claude-opus-4-5", - provider: "anthropic", - }, + mockCompactionRun({ + willRetry: false, + result: { + payloads: [{ text: "done" }], + meta: { + agentMeta: { + // Accumulated usage across pre+post compaction calls. + usage: { input: 190_000, output: 8_000, total: 198_000 }, + // Last call usage reflects post-compaction context. + lastCallUsage: { input: 11_000, output: 2_000, total: 13_000 }, + model: "claude-opus-4-5", + provider: "anthropic", }, - }; + }, }, - ); + }); const runner = createFollowupRunner({ opts: { onBlockReply }, diff --git a/src/telegram/send.proxy.test.ts b/src/telegram/send.proxy.test.ts index 39ef9e2d084..aa20cb72db7 100644 --- a/src/telegram/send.proxy.test.ts +++ b/src/telegram/send.proxy.test.ts @@ -56,6 +56,25 @@ import { deleteMessageTelegram, reactMessageTelegram, sendMessageTelegram } from describe("telegram proxy client", () => { const proxyUrl = "http://proxy.test:8080"; + const prepareProxyFetch = () => { + const proxyFetch = vi.fn(); + const fetchImpl = vi.fn(); + makeProxyFetch.mockReturnValue(proxyFetch as unknown as typeof fetch); + resolveTelegramFetch.mockReturnValue(fetchImpl as unknown as typeof fetch); + return { proxyFetch, fetchImpl }; + }; + + const expectProxyClient = (fetchImpl: ReturnType) => { + expect(makeProxyFetch).toHaveBeenCalledWith(proxyUrl); + expect(resolveTelegramFetch).toHaveBeenCalledWith(expect.any(Function), { network: undefined }); + expect(botCtorSpy).toHaveBeenCalledWith( + "tok", + expect.objectContaining({ + client: expect.objectContaining({ fetch: fetchImpl }), + }), + ); + }; + beforeEach(() => { botApi.sendMessage.mockResolvedValue({ message_id: 1, chat: { id: "123" } }); botApi.setMessageReaction.mockResolvedValue(undefined); @@ -69,56 +88,26 @@ describe("telegram proxy client", () => { }); it("uses proxy fetch for sendMessage", async () => { - const proxyFetch = vi.fn(); - const fetchImpl = vi.fn(); - makeProxyFetch.mockReturnValue(proxyFetch as unknown as typeof fetch); - resolveTelegramFetch.mockReturnValue(fetchImpl as unknown as typeof fetch); + const { fetchImpl } = prepareProxyFetch(); await sendMessageTelegram("123", "hi", { token: "tok", accountId: "foo" }); - expect(makeProxyFetch).toHaveBeenCalledWith(proxyUrl); - expect(resolveTelegramFetch).toHaveBeenCalledWith(proxyFetch, { network: undefined }); - expect(botCtorSpy).toHaveBeenCalledWith( - "tok", - expect.objectContaining({ - client: expect.objectContaining({ fetch: fetchImpl }), - }), - ); + expectProxyClient(fetchImpl); }); it("uses proxy fetch for reactions", async () => { - const proxyFetch = vi.fn(); - const fetchImpl = vi.fn(); - makeProxyFetch.mockReturnValue(proxyFetch as unknown as typeof fetch); - resolveTelegramFetch.mockReturnValue(fetchImpl as unknown as typeof fetch); + const { fetchImpl } = prepareProxyFetch(); await reactMessageTelegram("123", "456", "✅", { token: "tok", accountId: "foo" }); - expect(makeProxyFetch).toHaveBeenCalledWith(proxyUrl); - expect(resolveTelegramFetch).toHaveBeenCalledWith(proxyFetch, { network: undefined }); - expect(botCtorSpy).toHaveBeenCalledWith( - "tok", - expect.objectContaining({ - client: expect.objectContaining({ fetch: fetchImpl }), - }), - ); + expectProxyClient(fetchImpl); }); it("uses proxy fetch for deleteMessage", async () => { - const proxyFetch = vi.fn(); - const fetchImpl = vi.fn(); - makeProxyFetch.mockReturnValue(proxyFetch as unknown as typeof fetch); - resolveTelegramFetch.mockReturnValue(fetchImpl as unknown as typeof fetch); + const { fetchImpl } = prepareProxyFetch(); await deleteMessageTelegram("123", "456", { token: "tok", accountId: "foo" }); - expect(makeProxyFetch).toHaveBeenCalledWith(proxyUrl); - expect(resolveTelegramFetch).toHaveBeenCalledWith(proxyFetch, { network: undefined }); - expect(botCtorSpy).toHaveBeenCalledWith( - "tok", - expect.objectContaining({ - client: expect.objectContaining({ fetch: fetchImpl }), - }), - ); + expectProxyClient(fetchImpl); }); });