Files
trade-message-center/apps/chrome-extension/test/onetalk-send-observation.test.js
T

303 lines
11 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import { createSendObservationCorrelator } from "../src/onetalk/main-page/message-observer/send-observation.ts";
const completeSent = (overrides = {}) => ({
messageType: "new",
upstreamType: 1,
messageId: "message-1",
conversationId: "conversation-1",
senderId: "seller@icbu",
direction: "sent",
sentAtMs: Date.now(),
content: { version: 1, kind: "text", text: "hello" },
participantIds: ["buyer@icbu", "seller@icbu"],
readStatus: 0,
messageStatus: 1,
unreadCount: 0,
...overrides,
});
test("confirms only a complete sent message from the parsed observer batch", async () => {
const correlator = createSendObservationCorrelator(100);
const calls = [];
const resultPromise = correlator.execute(
"conversation-1",
"hello",
{ source: "test" },
(input) => {
calls.push(input);
return { opId: "accepted-locally" };
},
);
await new Promise((resolve) => setImmediate(resolve));
const observed = completeSent();
correlator.observe([observed]);
const { messageType: _messageType, upstreamType: _upstreamType, ...confirmed } = observed;
assert.deepEqual(await resultPromise, { status: "confirmed_sent", message: confirmed });
assert.deepEqual(calls, [
{
cid: "conversation-1",
conversationCode: "conversation-1",
content: "hello",
ext: { source: "test" },
},
]);
});
test("maps SDK exceptions and observer timeout to delivery_unknown without retry", async () => {
const failed = createSendObservationCorrelator(100);
assert.deepEqual(
await failed.execute("conversation-1", "hello", undefined, () =>
Promise.reject(new Error("offline")),
),
{ status: "delivery_unknown", reason: "send_connection_lost" },
);
const timedOut = createSendObservationCorrelator(1);
let sendCount = 0;
assert.deepEqual(
await timedOut.execute("conversation-1", "hello", undefined, () => {
sendCount += 1;
return undefined;
}),
{ status: "delivery_unknown", reason: "send_state_lost" },
);
assert.equal(sendCount, 1);
});
test("uses a returned message ID before applying the timestamp window", async () => {
const correlator = createSendObservationCorrelator(30);
const resultPromise = correlator.execute("conversation-1", "different", undefined, () => ({
messageId: "message-1",
}));
await new Promise((resolve) => setImmediate(resolve));
const observed = completeSent({
content: { version: 1, kind: "text", text: "different" },
sentAtMs: Date.now() + 60_000,
});
correlator.observe([observed]);
const { messageType: _messageType, upstreamType: _upstreamType, ...confirmed } = observed;
assert.deepEqual(await resultPromise, { status: "confirmed_sent", message: confirmed });
const mismatch = createSendObservationCorrelator(5);
const mismatchPromise = mismatch.execute("conversation-1", "hello", undefined, () => ({
messageId: "not-message-1",
}));
await new Promise((resolve) => setImmediate(resolve));
mismatch.observe([completeSent()]);
assert.deepEqual(await mismatchPromise, {
status: "delivery_unknown",
reason: "send_state_lost",
});
});
test("confirms a normalized text message after a candidate ID match", async () => {
const correlator = createSendObservationCorrelator(100);
const resultPromise = correlator.execute("conversation-1", "hello", undefined, () => ({
id: "message-1",
}));
await new Promise((resolve) => setImmediate(resolve));
const observed = completeSent({ sentAtMs: Date.now() - 60_000 });
correlator.observe([observed]);
const result = await resultPromise;
assert.equal(result.status, "confirmed_sent");
assert.equal(result.message.sentAtMs, observed.sentAtMs);
});
test("matches a .PNM SDK send result to its canonical observed message ID", async () => {
const correlator = createSendObservationCorrelator(100);
const canonicalId = "4012345670429";
const resultPromise = correlator.execute("conversation-1", "hello", undefined, () => ({
messageId: `${canonicalId}.PNM`,
}));
await new Promise((resolve) => setImmediate(resolve));
const observed = completeSent({ messageId: canonicalId });
correlator.observe([observed]);
assert.equal((await resultPromise).status, "confirmed_sent");
});
test("uses a five-second window and never confirms a pre-send observation", async () => {
const beforeSend = createSendObservationCorrelator(20);
const beforeSendResult = beforeSend.execute(
"conversation-1",
"hello",
undefined,
() => undefined,
);
await new Promise((resolve) => setImmediate(resolve));
beforeSend.observe([completeSent({ sentAtMs: Date.now() - 1_000 })]);
assert.deepEqual(await beforeSendResult, {
status: "delivery_unknown",
reason: "send_state_lost",
});
const outsideWindow = createSendObservationCorrelator(20);
const outsideWindowResult = outsideWindow.execute(
"conversation-1",
"hello",
undefined,
() => undefined,
);
await new Promise((resolve) => setImmediate(resolve));
outsideWindow.observe([completeSent({ sentAtMs: Date.now() + 6_000 })]);
assert.deepEqual(await outsideWindowResult, {
status: "delivery_unknown",
reason: "send_state_lost",
});
});
test("does not confirm received, wrong-conversation, incomplete, or ambiguous observations", async () => {
const correlator = createSendObservationCorrelator(10);
const first = correlator.execute("conversation-1", "hello", undefined, () => undefined);
const second = correlator.execute("conversation-1", "hello", undefined, () => undefined);
await new Promise((resolve) => setImmediate(resolve));
correlator.observe([
completeSent({ direction: "received" }),
completeSent({ conversationId: "conversation-2" }),
completeSent({ messageStatus: undefined }),
completeSent(),
]);
assert.deepEqual(await first, { status: "delivery_unknown", reason: "send_ambiguous" });
assert.deepEqual(await second, { status: "delivery_unknown", reason: "send_ambiguous" });
});
test("confirms no-dimension live images from relation metadata and fails closed on same-image ambiguity", async () => {
const expected = { sizeBytes: 123, md5: "hash-1" };
const image = () =>
completeSent({
sentAtMs: Date.now() + 1,
content: {
version: 1,
kind: "image",
fileId: "file-1",
extension: "jpg",
sizeBytes: 123,
isOriginal: true,
md5: "hash-1",
previewUrl: null,
urlScope: "onetalk_session",
},
});
const candidate = createSendObservationCorrelator(100);
const byCandidate = candidate.executeImage(
"conversation-1",
expected,
() => ({ id: "message-1" }),
{ timeoutMs: 100 },
);
await new Promise((resolve) => setImmediate(resolve));
candidate.observe([image()]);
assert.equal((await byCandidate).status, "confirmed_sent");
const wrongDigest = createSendObservationCorrelator(10);
const byWrongDigest = wrongDigest.executeImage("conversation-1", expected, () => undefined, {
timeoutMs: 10,
});
await new Promise((resolve) => setImmediate(resolve));
const mismatchedImage = image();
wrongDigest.observe([
{
...mismatchedImage,
content: { ...mismatchedImage.content, md5: "other-hash" },
},
]);
assert.deepEqual(await byWrongDigest, { status: "delivery_unknown", reason: "send_timeout" });
const ambiguous = createSendObservationCorrelator(100);
const first = ambiguous.executeImage("conversation-1", expected, () => undefined, {
timeoutMs: 100,
});
const second = ambiguous.executeImage("conversation-1", expected, () => undefined, {
timeoutMs: 100,
});
await new Promise((resolve) => setImmediate(resolve));
ambiguous.observe([image()]);
assert.deepEqual(await first, { status: "delivery_unknown", reason: "send_ambiguous" });
assert.deepEqual(await second, { status: "delivery_unknown", reason: "send_ambiguous" });
});
test("confirms files only by a unique relation fingerprint or candidate message ID", async () => {
const expected = {
fileName: "quotation.zip",
extension: "zip",
sizeBytes: 456,
md5: "hash-1",
fileId: "file-1",
parentId: "parent-1",
};
const file = (overrides = {}) =>
completeSent({
sentAtMs: Date.now() + 1,
content: {
version: 1,
kind: "file",
fileId: "file-1",
parentId: "parent-1",
fileName: "quotation.zip",
extension: "zip",
sizeBytes: 456,
md5: "hash-1",
previewUrl: null,
thumbnailUrl: null,
downloadUrl: null,
downloadState: "not_provided",
urlScope: "onetalk_session",
},
...overrides,
});
const candidate = createSendObservationCorrelator(100);
const candidateResult = candidate.executeFile(
"conversation-1",
expected,
() => ({ id: "message-1" }),
{ timeoutMs: 100 },
);
await new Promise((resolve) => setImmediate(resolve));
candidate.observe([
file({
messageId: "message-1",
content: { ...file().content, fileName: "different-name.zip", sizeBytes: 999 },
}),
]);
assert.equal((await candidateResult).status, "confirmed_sent");
const fingerprint = createSendObservationCorrelator(100);
const fingerprintResult = fingerprint.executeFile("conversation-1", expected, () => undefined, {
timeoutMs: 100,
});
await new Promise((resolve) => setImmediate(resolve));
fingerprint.observe([file()]);
assert.equal((await fingerprintResult).status, "confirmed_sent");
const wrongConversation = createSendObservationCorrelator(10);
const wrongConversationResult = wrongConversation.executeFile(
"conversation-1",
expected,
() => undefined,
{ timeoutMs: 10 },
);
await new Promise((resolve) => setImmediate(resolve));
wrongConversation.observe([{ ...file(), conversationId: "conversation-2" }]);
assert.deepEqual(await wrongConversationResult, {
status: "delivery_unknown",
reason: "send_timeout",
});
const ambiguous = createSendObservationCorrelator(100);
const first = ambiguous.executeFile("conversation-1", expected, () => undefined, {
timeoutMs: 100,
});
const second = ambiguous.executeFile("conversation-1", expected, () => undefined, {
timeoutMs: 100,
});
await new Promise((resolve) => setImmediate(resolve));
ambiguous.observe([file()]);
assert.deepEqual(await first, { status: "delivery_unknown", reason: "send_ambiguous" });
assert.deepEqual(await second, { status: "delivery_unknown", reason: "send_ambiguous" });
});