mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
refactor(mattermost): deduplicate draft stream tests (#118215)
This commit is contained in:
committed by
GitHub
parent
c54f7e9733
commit
761c3e33be
@@ -12,11 +12,20 @@ type RequestRecord = {
|
||||
init?: RequestInit;
|
||||
};
|
||||
|
||||
function createMockClient(): {
|
||||
type DraftStreamOptions = Omit<
|
||||
Parameters<typeof createMattermostDraftStream>[0],
|
||||
"client" | "channelId"
|
||||
> & {
|
||||
request?: MattermostClient["request"];
|
||||
};
|
||||
|
||||
function createDraftStreamFixture(options: DraftStreamOptions = {}): {
|
||||
client: MattermostClient;
|
||||
calls: RequestRecord[];
|
||||
requestMock: ReturnType<typeof vi.fn<MattermostClient["request"]>>;
|
||||
stream: ReturnType<typeof createMattermostDraftStream>;
|
||||
} {
|
||||
const { request, ...streamOptions } = options;
|
||||
const calls: RequestRecord[] = [];
|
||||
let nextId = 1;
|
||||
const requestImpl: MattermostClient["request"] = async <T>(
|
||||
@@ -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<string, unknown> {
|
||||
@@ -56,13 +71,7 @@ function parseRequestJson(init: RequestInit | undefined): Record<string, unknown
|
||||
|
||||
describe("createMattermostDraftStream", () => {
|
||||
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<void> | 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<void>((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"],
|
||||
});
|
||||
|
||||
|
||||
@@ -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<typeof deliverMattermostReplyWithDraftPreview>[0];
|
||||
|
||||
function deliverDraftPreview(
|
||||
params: Pick<DraftDeliveryParams, "payload" | "draftStream" | "deliverPayload"> &
|
||||
Partial<Omit<DraftDeliveryParams, "payload" | "draftStream" | "deliverPayload">>,
|
||||
) {
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user