mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
183 lines
6.4 KiB
TypeScript
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,
|
|
});
|
|
}
|
|
});
|
|
}
|