Files
trade-message-center/apps/server/test/onetalk-rendered-card-flow.test.ts
T

183 lines
6.4 KiB
TypeScript

// 验证渲染卡片补全的 ACK 与实时发布终态
import assert from "node:assert/strict";
import test from "node:test";
import {
createOneTalkRenderedCardContentFingerprint,
createOneTalkRenderedCardObservedFrame,
type OneTalkFrame,
} from "@trade-message-center/onetalk-contract";
import { createOneTalkRenderedCardFlow } from "../src/websocket/plugin/flows/rendered-card-flow.ts";
const scope = { channelAccountId: "account-1", deviceId: "device-1" };
const context = {
channelAccountId: "account-1",
deviceId: "device-1",
binding: "binding-1",
mindUserId: "mind-user-1",
workspaceId: "workspace-1",
};
const content = {
version: 1 as const,
status: "Paid",
total: "US $10.00",
image: "https://img.alicdn.com/card.jpg",
};
const frame = createOneTalkRenderedCardObservedFrame(
{ connectionType: "plugin", requestId: "request-1", scope },
{
conversationId: "conversation-1",
messageId: "message-1",
content,
contentFingerprint: createOneTalkRenderedCardContentFingerprint(content),
observedAtMs: 100,
},
);
const message = {
messageId: "message-1",
conversationId: "conversation-1",
senderId: "sender-1",
participantIds: ["sender-1", "receiver-1"],
direction: "received" as const,
sentAtMs: 99,
readStatus: 1,
messageStatus: 2,
unreadCount: 0,
content: {
version: 1 as const,
kind: "order" as const,
orderId: "order-1",
bizCode: null,
contractId: null,
id: null,
tenant: null,
orderAmount: 1,
orderAmountCurrency: "USD",
paymentAmount: 1,
paymentAmountCurrency: "USD",
statusMessageKey: "paid",
actions: [],
},
};
test("ACKs an independently committed card when its base message has not arrived", async () => {
const sent: OneTalkFrame[] = [];
const published: unknown[] = [];
const canonical = {} as never;
const flow = createOneTalkRenderedCardFlow({
service: {
ingest: async () => ({
status: "accepted" as const,
renderedCardContent: content,
message: null,
}),
},
registry: {
getCanonicalConnection: () => canonical,
publishMessageUpdated: async (input: unknown) => published.push(input),
} as never,
sendFrame: (outbound) => {
sent.push(outbound);
return true;
},
isPolicyCurrent: () => true,
isCommitGuardFailure: () => false,
closeForPause: () => assert.fail("unexpected pause"),
closeForDatabaseFailure: () => assert.fail("unexpected database failure"),
closeForAcknowledgementFailure: () => assert.fail("unexpected ack failure"),
reauthorize: async () => ({ ok: true }),
handleAuthorizationFailure: () => assert.fail("unexpected authorization failure"),
});
await flow.handle({
socket: {} as never,
frame,
context,
guard: { assertValid: () => undefined },
policyEpoch: 1,
canonical,
});
assert.deepEqual(
sent.map((value) => value.type),
["rendered.card.ack"],
);
assert.equal((sent[0]?.payload as { status?: string } | undefined)?.status, "accepted");
assert.deepEqual(published, []);
});
for (const status of ["accepted", "duplicate", "conflict"] as const) {
test(`ACKs rendered-card ${status} and publishes only acceptance`, async () => {
const sent: OneTalkFrame[] = [];
const published: unknown[] = [];
const flow = createOneTalkRenderedCardFlow({
service: {
ingest: async () => {
if (status === "accepted")
return { status, renderedCardContent: content, message };
if (status === "duplicate")
return { status, renderedCardContent: content, message };
return { status, renderedCardContent: content, message };
},
},
registry: {
getCanonicalConnection: () => canonical,
publishMessageUpdated: async (input: unknown) => published.push(input),
} as never,
sendFrame: (outbound) => {
sent.push(outbound);
return true;
},
isPolicyCurrent: () => true,
isCommitGuardFailure: () => false,
closeForPause: () => assert.fail("unexpected pause"),
closeForDatabaseFailure: () => assert.fail("unexpected database failure"),
closeForAcknowledgementFailure: () => assert.fail("unexpected ack failure"),
reauthorize: async () => ({ ok: true }),
handleAuthorizationFailure: () => assert.fail("unexpected authorization failure"),
});
const canonical = {} as never;
await flow.handle({
socket: {} as never,
frame,
context,
guard: { assertValid: () => undefined },
policyEpoch: 1,
canonical,
});
assert.equal(sent.length, 1);
assert.equal(sent[0]?.type, "rendered.card.ack");
assert.deepEqual(sent[0]?.payload, {
conversationId: "conversation-1",
messageId: "message-1",
contentFingerprint: frame.payload.contentFingerprint,
observedAtMs: 100,
status,
...(status === "conflict" ? { rejectionCode: "content_conflict" } : {}),
});
assert.equal(published.length, status === "accepted" ? 1 : 0);
if (status === "accepted") {
assert.deepEqual(published[0], {
message: {
messageId: message.messageId,
conversationId: message.conversationId,
senderId: message.senderId,
participantIds: message.participantIds,
direction: message.direction,
sentAtMs: message.sentAtMs,
content: message.content,
renderedCard: content,
},
requestId: "request-1",
scope: {
mindUserId: "mind-user-1",
workspaceId: "workspace-1",
channelAccountId: "account-1",
},
policyEpoch: 1,
});
}
});
}