diff --git a/src/gateway/server-methods/send.test.ts b/src/gateway/server-methods/send.test.ts index be4dc69003a..2964e9e952c 100644 --- a/src/gateway/server-methods/send.test.ts +++ b/src/gateway/server-methods/send.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { resolveOutboundTarget } from "../../infra/outbound/targets.js"; import { sendHandlers } from "./send.js"; import type { GatewayRequestContext } from "./types.js"; @@ -6,6 +7,7 @@ const mocks = vi.hoisted(() => ({ deliverOutboundPayloads: vi.fn(), appendAssistantMessageToSessionTranscript: vi.fn(async () => ({ ok: true, sessionFile: "x" })), recordSessionMetaFromInbound: vi.fn(async () => ({ ok: true })), + resolveOutboundTarget: vi.fn(() => ({ ok: true, to: "resolved" })), })); vi.mock("../../config/config.js", async () => { @@ -23,7 +25,7 @@ vi.mock("../../channels/plugins/index.js", () => ({ })); vi.mock("../../infra/outbound/targets.js", () => ({ - resolveOutboundTarget: () => ({ ok: true, to: "resolved" }), + resolveOutboundTarget: mocks.resolveOutboundTarget, })); vi.mock("../../infra/outbound/deliver.js", () => ({ @@ -59,13 +61,18 @@ async function runSend(params: Record) { return { respond }; } +function mockDeliverySuccess(messageId: string) { + mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId, channel: "slack" }]); +} + describe("gateway send mirroring", () => { beforeEach(() => { vi.clearAllMocks(); + mocks.resolveOutboundTarget.mockReturnValue({ ok: true, to: "resolved" }); }); it("accepts media-only sends without message", async () => { - mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId: "m-media", channel: "slack" }]); + mockDeliverySuccess("m-media"); const { respond } = await runSend({ to: "channel:C1", @@ -151,7 +158,7 @@ describe("gateway send mirroring", () => { }); it("mirrors media filenames when delivery succeeds", async () => { - mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId: "m1", channel: "slack" }]); + mockDeliverySuccess("m1"); await runSend({ to: "channel:C1", @@ -174,7 +181,7 @@ describe("gateway send mirroring", () => { }); it("mirrors MEDIA tags as attachments", async () => { - mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId: "m2", channel: "slack" }]); + mockDeliverySuccess("m2"); await runSend({ to: "channel:C1", @@ -196,7 +203,7 @@ describe("gateway send mirroring", () => { }); it("lowercases provided session keys for mirroring", async () => { - mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId: "m-lower", channel: "slack" }]); + mockDeliverySuccess("m-lower"); await runSend({ to: "channel:C1", @@ -216,7 +223,7 @@ describe("gateway send mirroring", () => { }); it("derives a target session key when none is provided", async () => { - mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId: "m3", channel: "slack" }]); + mockDeliverySuccess("m3"); await runSend({ to: "channel:C1", @@ -237,7 +244,7 @@ describe("gateway send mirroring", () => { }); it("forwards threadId to outbound delivery when provided", async () => { - mocks.deliverOutboundPayloads.mockResolvedValue([{ messageId: "m-thread", channel: "slack" }]); + mockDeliverySuccess("m-thread"); await runSend({ to: "channel:C1", @@ -253,4 +260,27 @@ describe("gateway send mirroring", () => { }), ); }); + + it("returns invalid request when outbound target resolution fails", async () => { + vi.mocked(resolveOutboundTarget).mockReturnValue({ ok: false, error: "target not found" }); + + const { respond } = await runSend({ + to: "channel:C1", + message: "hi", + channel: "slack", + idempotencyKey: "idem-target-fail", + }); + + expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled(); + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ + message: expect.stringContaining("target not found"), + }), + expect.objectContaining({ + channel: "slack", + }), + ); + }); });