diff --git a/extensions/mattermost/src/mattermost/draft-stream.test.ts b/extensions/mattermost/src/mattermost/draft-stream.test.ts index 69adde3d696b..d469dbf632fa 100644 --- a/extensions/mattermost/src/mattermost/draft-stream.test.ts +++ b/extensions/mattermost/src/mattermost/draft-stream.test.ts @@ -12,11 +12,20 @@ type RequestRecord = { init?: RequestInit; }; -function createMockClient(): { +type DraftStreamOptions = Omit< + Parameters[0], + "client" | "channelId" +> & { + request?: MattermostClient["request"]; +}; + +function createDraftStreamFixture(options: DraftStreamOptions = {}): { client: MattermostClient; calls: RequestRecord[]; requestMock: ReturnType>; + stream: ReturnType; } { + const { request, ...streamOptions } = options; const calls: RequestRecord[] = []; let nextId = 1; const requestImpl: MattermostClient["request"] = async ( @@ -32,7 +41,7 @@ function createMockClient(): { } return {} as T; }; - const requestMock = vi.fn(requestImpl); + const requestMock = vi.fn(request ?? requestImpl); const client: MattermostClient = { baseUrl: "https://chat.example.com", apiBaseUrl: "https://chat.example.com/api/v4", @@ -40,7 +49,13 @@ function createMockClient(): { request: requestMock as MattermostClient["request"], fetchImpl: vi.fn() as MattermostClient["fetchImpl"], }; - return { client, calls, requestMock }; + const stream = createMattermostDraftStream({ + client, + channelId: "channel-1", + throttleMs: 0, + ...streamOptions, + }); + return { client, calls, requestMock, stream }; } function parseRequestJson(init: RequestInit | undefined): Record { @@ -56,13 +71,7 @@ function parseRequestJson(init: RequestInit | undefined): Record { it("creates a preview post and updates it on later changes", async () => { - const { client, calls } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - rootId: "root-1", - throttleMs: 0, - }); + const { calls, stream } = createDraftStreamFixture({ rootId: "root-1" }); stream.update("Running `read`…"); await stream.flush(); @@ -81,12 +90,7 @@ describe("createMattermostDraftStream", () => { }); it("does not resend identical updates", async () => { - const { client, calls } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); + const { calls, stream } = createDraftStreamFixture(); stream.update("Working..."); await stream.flush(); @@ -97,13 +101,7 @@ describe("createMattermostDraftStream", () => { }); it("clears the preview post when no final reply is delivered", async () => { - const { client, calls } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - rootId: "root-1", - throttleMs: 0, - }); + const { calls, stream } = createDraftStreamFixture({ rootId: "root-1" }); stream.update("Working..."); await stream.flush(); @@ -116,13 +114,7 @@ describe("createMattermostDraftStream", () => { }); it("discardPending keeps the preview post but ignores later updates", async () => { - const { client, calls } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - rootId: "root-1", - throttleMs: 0, - }); + const { calls, stream } = createDraftStreamFixture({ rootId: "root-1" }); stream.update("Working..."); await stream.flush(); @@ -136,13 +128,7 @@ describe("createMattermostDraftStream", () => { }); it("seal keeps the preview post and cancels pending final overwrites", async () => { - const { client, calls } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - rootId: "root-1", - throttleMs: 0, - }); + const { calls, stream } = createDraftStreamFixture({ rootId: "root-1" }); stream.update("Working..."); await stream.flush(); @@ -156,13 +142,7 @@ describe("createMattermostDraftStream", () => { }); it("stop flushes the last pending update and ignores later ones", async () => { - const { client, calls } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - rootId: "root-1", - throttleMs: 1000, - }); + const { calls, stream } = createDraftStreamFixture({ rootId: "root-1", throttleMs: 1000 }); stream.update("Working..."); await stream.flush(); @@ -185,20 +165,7 @@ describe("createMattermostDraftStream", () => { const requestImpl: MattermostClient["request"] = async () => { throw new Error("boom"); }; - const requestMock = vi.fn(requestImpl); - const client: MattermostClient = { - baseUrl: "https://chat.example.com", - apiBaseUrl: "https://chat.example.com/api/v4", - token: "token", - request: requestMock as MattermostClient["request"], - fetchImpl: vi.fn() as MattermostClient["fetchImpl"], - }; - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - warn, - }); + const { requestMock, stream } = createDraftStreamFixture({ request: requestImpl, warn }); stream.update("Working..."); await stream.flush(); @@ -212,14 +179,8 @@ describe("createMattermostDraftStream", () => { it("retains an accepted preview failure after its background flush has settled", async () => { const warn = vi.fn(); - const { client, requestMock } = createMockClient(); + const { requestMock, stream } = createDraftStreamFixture({ warn }); requestMock.mockResolvedValueOnce({ message: "already visible" }); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - warn, - }); stream.update("Already delivered"); await vi.waitFor(() => expect(warn).toHaveBeenCalledOnce()); @@ -250,16 +211,10 @@ describe("createMattermostDraftStream", () => { }); it("truncates on a code-point boundary so a straddling emoji is dropped whole", async () => { - const { client, calls } = createMockClient(); // maxChars=12 => cut point is maxChars-3=9. The emoji 😀 occupies UTF-16 // indices 8-9, so a raw slice(0,9) would keep the lone high surrogate at // index 8 and drop its low surrogate at index 9, leaking a dangling half. - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - maxChars: 12, - }); + const { calls, stream } = createDraftStreamFixture({ maxChars: 12 }); const input = `${"a".repeat(8)}\u{1F600}${"b".repeat(5)}`; stream.update(input); @@ -296,20 +251,7 @@ describe("createMattermostDraftStream", () => { } return {} as T; }; - const requestMock = vi.fn(requestImpl); - const client: MattermostClient = { - baseUrl: "https://chat.example.com", - apiBaseUrl: "https://chat.example.com/api/v4", - token: "token", - request: requestMock as MattermostClient["request"], - fetchImpl: vi.fn() as MattermostClient["fetchImpl"], - }; - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 1000, - warn, - }); + const { stream } = createDraftStreamFixture({ request: requestImpl, throttleMs: 1000, warn }); stream.update("Working..."); await stream.flush(); @@ -326,11 +268,7 @@ describe("createMattermostDraftStream", () => { describe("createMattermostDraftStream forceNewMessage", () => { it("propagates a provider-accepted boundary post without an identity", async () => { - const { client, requestMock } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, + const { requestMock, stream } = createDraftStreamFixture({ maxChars: 10, chunkText: () => ["aaaaaaaaaa", "bbbbbbbbbb"], }); @@ -347,17 +285,13 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("retains accepted boundary failures before synchronous warning callbacks re-enter", async () => { - const { client, requestMock } = createMockClient(); let reenteredBoundary: Promise | undefined; const warn = vi.fn(() => { stream.update("must not publish twice"); reenteredBoundary = stream.forceNewMessage(); void reenteredBoundary.catch(() => {}); }); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, + const { requestMock, stream } = createDraftStreamFixture({ maxChars: 10, chunkText: () => ["aaaaaaaaaa", "bbbbbbbbbb"], warn, @@ -376,7 +310,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("propagates an accepted background failure that settles during boundary rotation", async () => { - const { client, requestMock } = createMockClient(); + const { requestMock, stream } = createDraftStreamFixture(); let releaseCreate: (() => void) | undefined; const createReady = new Promise((resolve) => { releaseCreate = resolve; @@ -385,11 +319,6 @@ describe("createMattermostDraftStream forceNewMessage", () => { await createReady; return { message: "already visible" }; }); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); stream.update("Accepted background preview"); await vi.waitFor(() => expect(requestMock).toHaveBeenCalledOnce()); @@ -402,13 +331,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("creates a new post on the next update after forceNewMessage", async () => { - const { client, calls } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - rootId: "root-1", - throttleMs: 0, - }); + const { calls, stream } = createDraftStreamFixture({ rootId: "root-1" }); stream.update("Running `read`…"); await stream.flush(); @@ -436,14 +359,10 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("restores and chunks an already-flushed over-limit block before rotating", async () => { - const { client, calls } = createMockClient(); const firstChunk = "a".repeat(10); const secondChunk = "b".repeat(10); const chunkText = vi.fn(() => [firstChunk, secondChunk]); - const configuredStream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, + const { calls, stream: configuredStream } = createDraftStreamFixture({ maxChars: 10, chunkText, }); @@ -507,19 +426,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { } return {} as T; }; - const requestMock = vi.fn(requestImpl); - const client: MattermostClient = { - baseUrl: "https://chat.example.com", - apiBaseUrl: "https://chat.example.com/api/v4", - token: "token", - request: requestMock as MattermostClient["request"], - fetchImpl: vi.fn() as MattermostClient["fetchImpl"], - }; - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); + const { stream } = createDraftStreamFixture({ request: requestImpl }); stream.update("tool start"); stream.update("tool complete"); @@ -573,19 +480,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { } return {} as T; }; - const requestMock = vi.fn(requestImpl); - const client: MattermostClient = { - baseUrl: "https://chat.example.com", - apiBaseUrl: "https://chat.example.com/api/v4", - token: "token", - request: requestMock as MattermostClient["request"], - fetchImpl: vi.fn() as MattermostClient["fetchImpl"], - }; - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); + const { stream } = createDraftStreamFixture({ request: requestImpl }); stream.update("Looking into the logs"); stream.update("Looking into the logs now"); @@ -600,12 +495,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("opens a fresh post for a partial that arrives before a fire-and-forget boundary settles", async () => { - const { client, calls } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); + const { calls, stream } = createDraftStreamFixture(); stream.update("block A"); await stream.flush(); @@ -624,12 +514,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("resolves a cumulative terminal reply to the current confirmed generation", async () => { - const { client } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); + const { stream } = createDraftStreamFixture(); stream.updateAssistantText("First block"); await stream.flush(); @@ -655,12 +540,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("strips confirmed assistant blocks but not transient progress generations", async () => { - const { client } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); + const { stream } = createDraftStreamFixture(); stream.updateAssistantText("First block"); await stream.flush(); @@ -692,12 +572,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("uses provider-finalized content from a boundary edit", async () => { - const { client, requestMock } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); + const { requestMock, stream } = createDraftStreamFixture(); stream.updateAssistantText("Draft block"); await stream.flush(); @@ -712,12 +587,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("keeps the canonical final when an assistant boundary fails to publish", async () => { - const { client, requestMock } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, - }); + const { requestMock, stream } = createDraftStreamFixture(); stream.updateAssistantText("First block"); await stream.flush(); @@ -734,11 +604,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("retains posts published before a later boundary chunk fails", async () => { - const { client, requestMock } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, + const { requestMock, stream } = createDraftStreamFixture({ chunkText: () => ["First half", "Second half"], }); @@ -758,11 +624,7 @@ describe("createMattermostDraftStream forceNewMessage", () => { }); it("does not strip a requested prefix rewritten by the provider", async () => { - const { client, requestMock } = createMockClient(); - const stream = createMattermostDraftStream({ - client, - channelId: "channel-1", - throttleMs: 0, + const { requestMock, stream } = createDraftStreamFixture({ chunkText: () => ["First half", "Second half"], }); diff --git a/extensions/mattermost/src/mattermost/monitor-draft-delivery.test.ts b/extensions/mattermost/src/mattermost/monitor-draft-delivery.test.ts index ea71bf010cf1..f62214038fc9 100644 --- a/extensions/mattermost/src/mattermost/monitor-draft-delivery.test.ts +++ b/extensions/mattermost/src/mattermost/monitor-draft-delivery.test.ts @@ -23,6 +23,23 @@ function createMattermostClientMock(): MattermostClient { }; } +function createMattermostReceipt(messageId: string, kind: "text" | "preview" | "media") { + return createMessageReceiptFromOutboundResults({ + results: [{ channel: "mattermost", messageId }], + kind, + }); +} + +function createConfirmedPreviewDelivery(messageId: string, content: string) { + return { + outcome: "text" as const, + messageIds: [messageId], + receipt: createMattermostReceipt(messageId, "preview"), + visibleReplySent: true, + content, + }; +} + function createDraftStreamMock(postId: string | null | undefined = "preview-post-1") { return { flush: vi.fn(async () => {}), @@ -37,10 +54,7 @@ function createDeliverFinalMock() { return vi.fn(async (payload: { text?: string }) => ({ outcome: "text" as const, messageIds: ["delivered-post-1"], - receipt: createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "delivered-post-1" }], - kind: "text", - }), + receipt: createMattermostReceipt("delivered-post-1", "text"), visibleReplySent: true, content: payload.text ?? "", })); @@ -51,6 +65,23 @@ function resolvePreviewFinalText(text?: string) { return editText ? { editText, alreadyDelivered: false } : undefined; } +type DraftDeliveryParams = Parameters[0]; + +function deliverDraftPreview( + params: Pick & + Partial>, +) { + return deliverMattermostReplyWithDraftPreview({ + info: { kind: "final" }, + kind: "channel", + client: createMattermostClientMock(), + resolvePreviewFinalText, + previewState: { finalizedViaPreviewPost: false }, + logVerboseMessage: vi.fn(), + ...params, + }); +} + function mockCall(mock: { mock: { calls: unknown[][] } }, index: number, label: string): unknown[] { const resolvedIndex = index < 0 ? mock.mock.calls.length + index : index; const call = mock.mock.calls[resolvedIndex]; @@ -71,16 +102,10 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const deliverFinal = createDeliverFinalMock(); const recordThreadParticipation = vi.fn(); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { text: " \n > Reasoning:\n> _hidden_" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), recordThreadParticipation, deliverPayload: deliverFinal, }); @@ -99,16 +124,10 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const deliverFinal = createDeliverFinalMock(); const recordThreadParticipation = vi.fn(); - const result = await deliverMattermostReplyWithDraftPreview({ + const result = await deliverDraftPreview({ payload: { text: "All good" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), recordThreadParticipation, deliverPayload: deliverFinal, }); @@ -131,30 +150,16 @@ describe("deliverMattermostReplyWithDraftPreview", () => { it("reports a final already published in a sealed preview generation", async () => { const draftStream = createDraftStreamMock(null); const deliverFinal = createDeliverFinalMock(); - const confirmedReceipt = createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "sealed-post-1" }], - kind: "preview", - }); + const confirmedDelivery = createConfirmedPreviewDelivery("sealed-post-1", "Already visible"); - const result = await deliverMattermostReplyWithDraftPreview({ + const result = await deliverDraftPreview({ payload: { text: "Already visible" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", resolvePreviewFinalText: () => ({ alreadyDelivered: true, - confirmedDelivery: { - outcome: "text", - messageIds: ["sealed-post-1"], - receipt: confirmedReceipt, - visibleReplySent: true, - content: "Already visible", - }, + confirmedDelivery, }), - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -171,14 +176,8 @@ describe("deliverMattermostReplyWithDraftPreview", () => { it("still delivers media when the text is already published", async () => { const draftStream = createDraftStreamMock(null); - const confirmedReceipt = createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "sealed-post-1" }], - kind: "preview", - }); - const mediaReceipt = createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "media-post-1" }], - kind: "media", - }); + const confirmedDelivery = createConfirmedPreviewDelivery("sealed-post-1", "Already visible"); + const mediaReceipt = createMattermostReceipt("media-post-1", "media"); const deliverFinal = vi.fn(async () => ({ outcome: "media" as const, messageIds: ["media-post-1"], @@ -187,25 +186,14 @@ describe("deliverMattermostReplyWithDraftPreview", () => { content: "", })); - const result = await deliverMattermostReplyWithDraftPreview({ + const result = await deliverDraftPreview({ payload: { text: "Already visible", mediaUrl: "https://example.com/image.png" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", resolvePreviewFinalText: () => ({ alreadyDelivered: true, - confirmedDelivery: { - outcome: "text", - messageIds: ["sealed-post-1"], - receipt: confirmedReceipt, - visibleReplySent: true, - content: "Already visible", - }, + confirmedDelivery, }), - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -224,31 +212,17 @@ describe("deliverMattermostReplyWithDraftPreview", () => { it("aggregates sealed and current preview posts into one terminal result", async () => { const draftStream = createDraftStreamMock("current-preview"); const deliverFinal = createDeliverFinalMock(); - const confirmedReceipt = createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "sealed-post-1" }], - kind: "preview", - }); + const confirmedDelivery = createConfirmedPreviewDelivery("sealed-post-1", "First block"); - const result = await deliverMattermostReplyWithDraftPreview({ + const result = await deliverDraftPreview({ payload: { text: "First block\n\nSecond block" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", resolvePreviewFinalText: () => ({ editText: "Second block", alreadyDelivered: false, - confirmedDelivery: { - outcome: "text", - messageIds: ["sealed-post-1"], - receipt: confirmedReceipt, - visibleReplySent: true, - content: "First block", - }, + confirmedDelivery, }), - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -268,32 +242,18 @@ describe("deliverMattermostReplyWithDraftPreview", () => { it("sends only the remaining suffix after a partial boundary publish", async () => { const draftStream = createDraftStreamMock(null); const deliverFinal = createDeliverFinalMock(); - const confirmedReceipt = createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "partial-post-1" }], - kind: "preview", - }); + const confirmedDelivery = createConfirmedPreviewDelivery("partial-post-1", "First half"); - const result = await deliverMattermostReplyWithDraftPreview({ + const result = await deliverDraftPreview({ payload: { text: "First half Second half" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", resolvePreviewFinalText: () => ({ editText: "Second half", deliveryText: "Second half", alreadyDelivered: false, - confirmedDelivery: { - outcome: "text", - messageIds: ["partial-post-1"], - receipt: confirmedReceipt, - visibleReplySent: true, - content: "First half", - }, + confirmedDelivery, }), - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -343,15 +303,9 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const draftStream = createDraftStreamMock(); const deliverFinal = createDeliverFinalMock(); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { text: "All good", replyToId: "reply-1" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -369,15 +323,9 @@ describe("deliverMattermostReplyWithDraftPreview", () => { let caught: unknown; try { - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { text: "Already visible", replyToId: "reply-1" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); } catch (error: unknown) { @@ -401,20 +349,14 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const draftStream = createDraftStreamMock(); const deliverFinal = createDeliverFinalMock(); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { text: "Photo", replyToId: "reply-1", mediaUrl: "https://example.com/a.png", } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -428,21 +370,15 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const draftStream = createDraftStreamMock(); const deliverFinal = createDeliverFinalMock(); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { mediaUrl: "https://example.com/tts.mp3", audioAsVoice: true, spokenText: "Spoken answer", ttsSupplement: { spokenText: "Spoken answer" }, } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -474,30 +410,21 @@ describe("deliverMattermostReplyWithDraftPreview", () => { return { outcome: "text" as const, messageIds: ["supplement-post-1"], - receipt: createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "supplement-post-1" }], - kind: "media", - }), + receipt: createMattermostReceipt("supplement-post-1", "media"), visibleReplySent: true, content: payload.text ?? "", }; }); - const result = await deliverMattermostReplyWithDraftPreview({ + const result = await deliverDraftPreview({ payload: { mediaUrl: "https://example.com/tts.mp3", audioAsVoice: true, spokenText: "Spoken answer", ttsSupplement: { spokenText: "Spoken answer" }, } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -512,10 +439,7 @@ describe("deliverMattermostReplyWithDraftPreview", () => { it("preserves the finalized preview receipt when its supplement fails after sending", async () => { const draftStream = createDraftStreamMock(); - const mediaReceipt = createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "media-post-1" }], - kind: "media", - }); + const mediaReceipt = createMattermostReceipt("media-post-1", "media"); const deliverFinal = vi.fn(async () => { throw createChannelPartialDeliveryError(new Error("supplement bookkeeping failed"), { messageIds: ["media-post-1"], @@ -527,21 +451,15 @@ describe("deliverMattermostReplyWithDraftPreview", () => { let caught: unknown; try { - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { mediaUrl: "https://example.com/tts.mp3", audioAsVoice: true, spokenText: "Spoken answer", ttsSupplement: { spokenText: "Spoken answer" }, } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); } catch (error: unknown) { @@ -564,16 +482,13 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const deliverFinal = createDeliverFinalMock(); updateMattermostPostSpy.mockRejectedValueOnce(new Error("edit failed")); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { mediaUrl: "https://example.com/tts.mp3", audioAsVoice: true, spokenText: "Spoken answer", ttsSupplement: { spokenText: "Spoken answer" }, } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", resolvePreviewFinalText: (text) => ({ @@ -581,8 +496,6 @@ describe("deliverMattermostReplyWithDraftPreview", () => { deliveryText: "", alreadyDelivered: false, }), - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -603,7 +516,7 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const deliverFinal = createDeliverFinalMock(); updateMattermostPostSpy.mockRejectedValueOnce(new Error("edit failed")); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { mediaUrl: "https://example.com/tts.mp3", audioAsVoice: true, @@ -613,9 +526,6 @@ describe("deliverMattermostReplyWithDraftPreview", () => { visibleTextAlreadyDelivered: true, }, } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", resolvePreviewFinalText: (text) => ({ @@ -623,8 +533,6 @@ describe("deliverMattermostReplyWithDraftPreview", () => { deliveryText: text?.trim(), alreadyDelivered: false, }), - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -643,34 +551,20 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const draftStream = createDraftStreamMock(); const deliverFinal = createDeliverFinalMock(); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { mediaUrl: "https://example.com/tts.mp3", audioAsVoice: true, spokenText: "Spoken answer", ttsSupplement: { spokenText: "Spoken answer" }, } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", resolvePreviewFinalText: () => ({ deliveryText: "", - confirmedDelivery: { - outcome: "text", - messageIds: ["preview-post-1"], - receipt: createMessageReceiptFromOutboundResults({ - results: [{ channel: "mattermost", messageId: "preview-post-1" }], - kind: "preview", - }), - visibleReplySent: true, - content: "Spoken answer", - }, + confirmedDelivery: createConfirmedPreviewDelivery("preview-post-1", "Spoken answer"), alreadyDelivered: true, }), - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -686,16 +580,10 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const draftStream = createDraftStreamMock(); const deliverFinal = createDeliverFinalMock(); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { text: "Error", isError: true } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, effectiveReplyToId: "thread-root-1", - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -709,16 +597,11 @@ describe("deliverMattermostReplyWithDraftPreview", () => { const deliverFinal = createDeliverFinalMock(); const client = createMattermostClientMock(); - await deliverMattermostReplyWithDraftPreview({ + await deliverDraftPreview({ payload: { text: "Final answer", replyToId: "child-post-789" } as never, - info: { kind: "final" }, - kind: "channel", client, draftStream, effectiveReplyToId: "thread-root-456", - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }); @@ -747,15 +630,9 @@ describe("deliverMattermostReplyWithDraftPreview", () => { }); await expect( - deliverMattermostReplyWithDraftPreview({ + deliverDraftPreview({ payload: { text: "Broken", replyToId: "reply-1" } as never, - info: { kind: "final" }, - kind: "channel", - client: createMattermostClientMock(), draftStream, - resolvePreviewFinalText, - previewState: { finalizedViaPreviewPost: false }, - logVerboseMessage: vi.fn(), deliverPayload: deliverFinal, }), ).rejects.toThrow("send failed");