Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
175 lines
6.1 KiB
TypeScript
175 lines
6.1 KiB
TypeScript
import type { AdapterContext, MessagingInboundEvent } from "@rakazo/adapter-kit";
|
|
import { describe, expect, it } from "vitest";
|
|
import { ChatSdkMessagingSurface } from "./chat-sdk-surface.js";
|
|
import { createEmulatedSendbluePlatform, SendBlueEmulator } from "./sendblue-emulator.js";
|
|
|
|
const context: AdapterContext = {
|
|
operationId: "op-1",
|
|
traceId: "trace-1",
|
|
spaceId: "ws-1",
|
|
userId: "user-1",
|
|
signal: new AbortController().signal,
|
|
};
|
|
|
|
/** The production sendblue pipeline (Chat SDK + official SDK) over the emulator. */
|
|
function createHarness() {
|
|
const emulator = new SendBlueEmulator();
|
|
const surface = new ChatSdkMessagingSurface([createEmulatedSendbluePlatform(emulator)]);
|
|
const events: MessagingInboundEvent[] = [];
|
|
surface.onInbound(async (event) => {
|
|
events.push(event);
|
|
});
|
|
return { emulator, surface, events };
|
|
}
|
|
|
|
describe("emulated sendblue platform inbound", () => {
|
|
it("normalizes a DM webhook into a direct inbound message", async () => {
|
|
const { emulator, surface, events } = createHarness();
|
|
const response = await surface.handleWebhook(
|
|
"sendblue",
|
|
emulator.buildInboundRequest({
|
|
fromNumber: "+15551234567",
|
|
content: "hi there",
|
|
handle: "handle-dm-1",
|
|
}),
|
|
);
|
|
|
|
expect(response?.status).toBe(200);
|
|
// handleWebhook drains waitUntil, so the sink has finished by ACK.
|
|
expect(events).toHaveLength(1);
|
|
expect(events[0]).toEqual({
|
|
type: "message",
|
|
provider: "sendblue",
|
|
handle: "handle-dm-1",
|
|
threadId: expect.stringMatching(/^sendblue:/),
|
|
isDirect: true,
|
|
from: "+15551234567",
|
|
fromLabel: null,
|
|
channelName: null,
|
|
participants: [],
|
|
content: "hi there",
|
|
mediaUrl: null,
|
|
});
|
|
// The DM thread id matches what outbound resolution would open.
|
|
const dmThreadId = await surface.openDirectThread("sendblue", "+15551234567", context);
|
|
expect((events[0] as { threadId: string }).threadId).toBe(dmThreadId);
|
|
});
|
|
|
|
it("normalizes a group webhook with roster and display name", async () => {
|
|
const { emulator, surface, events } = createHarness();
|
|
const response = await surface.handleWebhook(
|
|
"sendblue",
|
|
emulator.buildInboundRequest({
|
|
fromNumber: "+15551111111",
|
|
content: "hello group",
|
|
groupId: "grp-1",
|
|
groupName: "Family",
|
|
participants: ["+15551111111", "+15552222222", emulator.phoneNumber],
|
|
}),
|
|
);
|
|
|
|
expect(response?.status).toBe(200);
|
|
expect(events).toHaveLength(1);
|
|
expect(events[0]).toEqual(
|
|
expect.objectContaining({
|
|
type: "message",
|
|
provider: "sendblue",
|
|
threadId: expect.stringMatching(/^sendblue:/),
|
|
isDirect: false,
|
|
from: "+15551111111",
|
|
channelName: "Family",
|
|
// The deployment's own line number never appears in the roster.
|
|
participants: ["+15551111111", "+15552222222"],
|
|
content: "hello group",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("emulated sendblue platform outbound", () => {
|
|
it("sends a DM through openDirectThread + sendToThread", async () => {
|
|
const { emulator, surface } = createHarness();
|
|
const threadId = await surface.openDirectThread("sendblue", "+15557654321", context);
|
|
const sent = await surface.sendToThread({ threadId, body: "hello you" }, context);
|
|
|
|
expect(sent.handle).toMatch(/^emulated-handle-/);
|
|
expect(emulator.sent).toEqual([
|
|
{ kind: "dm", to: "+15557654321", body: "hello you", handle: sent.handle },
|
|
]);
|
|
});
|
|
|
|
it("posts to a group via the thread id captured from an inbound group event", async () => {
|
|
const { emulator, surface, events } = createHarness();
|
|
await surface.handleWebhook(
|
|
"sendblue",
|
|
emulator.buildInboundRequest({
|
|
fromNumber: "+15551111111",
|
|
content: "hello group",
|
|
groupId: "grp-1",
|
|
participants: ["+15551111111", emulator.phoneNumber],
|
|
}),
|
|
);
|
|
expect(events).toHaveLength(1);
|
|
const threadId = (events[0] as { threadId: string }).threadId;
|
|
|
|
const sent = await surface.sendToThread({ threadId, body: "hi all" }, context);
|
|
|
|
expect(emulator.sent).toEqual([
|
|
{ kind: "group", groupId: "grp-1", body: "hi all", handle: sent.handle },
|
|
]);
|
|
});
|
|
|
|
it("rejects the send when the vendor API fails", async () => {
|
|
const { emulator, surface } = createHarness();
|
|
const threadId = await surface.openDirectThread("sendblue", "+15557654321", context);
|
|
// The official SDK retries 5xx twice before surfacing the failure.
|
|
emulator.failNextSends(3);
|
|
|
|
await expect(surface.sendToThread({ threadId, body: "will fail" }, context)).rejects.toThrow();
|
|
expect(emulator.sent).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("emulated sendblue platform delivery status", () => {
|
|
it("surfaces a signed status webhook to the sink", async () => {
|
|
const { emulator, surface, events } = createHarness();
|
|
const response = await surface.handleWebhook(
|
|
"sendblue",
|
|
emulator.buildStatusRequest({ handle: "handle-9", status: "DELIVERED" }),
|
|
);
|
|
|
|
expect(response?.status).toBe(200);
|
|
expect(events).toEqual([
|
|
{ type: "status", provider: "sendblue", handle: "handle-9", status: "DELIVERED" },
|
|
]);
|
|
});
|
|
|
|
it("rejects a wrong signing secret with 401 and no sink event", async () => {
|
|
const { emulator, surface, events } = createHarness();
|
|
const good = emulator.buildStatusRequest({ handle: "handle-9", status: "DELIVERED" });
|
|
const forged = new Request(good.url, {
|
|
method: "POST",
|
|
headers: {
|
|
"content-type": "application/json",
|
|
"sb-signing-secret": "not-the-secret",
|
|
},
|
|
body: await good.text(),
|
|
});
|
|
|
|
const response = await surface.handleWebhook("sendblue", forged);
|
|
|
|
expect(response?.status).toBe(401);
|
|
expect(events).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("emulated sendblue platform typing", () => {
|
|
it("records DM typing indicators", async () => {
|
|
const { emulator, surface } = createHarness();
|
|
const threadId = await surface.openDirectThread("sendblue", "+15557654321", context);
|
|
await surface.sendTyping(threadId, context);
|
|
|
|
expect(emulator.typingIndicators).toEqual(["+15557654321"]);
|
|
});
|
|
});
|