



























@@ -99,6 +99,46 @@ type ExecuteSendContext = ExecuteSendInput["ctx"];
9999let executePollAction: OutboundSendServiceModule["executePollAction"];
100100let executeSendAction: OutboundSendServiceModule["executeSendAction"];
101101102+type MockCalls = {
103+mock: { calls: unknown[][] };
104+};
105+106+function requireRecord(value: unknown, label: string): Record<string, unknown> {
107+expect(typeof value, label).toBe("object");
108+expect(value, label).not.toBeNull();
109+return value as Record<string, unknown>;
110+}
111+112+function requireArray(value: unknown, label: string): unknown[] {
113+expect(Array.isArray(value), label).toBe(true);
114+return value as unknown[];
115+}
116+117+function expectFields(record: Record<string, unknown>, expected: Record<string, unknown>) {
118+for (const [key, value] of Object.entries(expected)) {
119+expect(record[key], key).toEqual(value);
120+}
121+}
122+123+function expectSingleCallFirstArg(
124+mock: MockCalls,
125+label = "mock first argument",
126+): Record<string, unknown> {
127+expect(mock.mock.calls).toHaveLength(1);
128+const [firstArg] = mock.mock.calls[0] ?? [];
129+return requireRecord(firstArg, label);
130+}
131+132+function expectSingleCallFields(
133+mock: MockCalls,
134+expected: Record<string, unknown>,
135+label?: string,
136+): Record<string, unknown> {
137+const firstArg = expectSingleCallFirstArg(mock, label);
138+expectFields(firstArg, expected);
139+return firstArg;
140+}
141+102142describe("executeSendAction", () => {
103143function pluginActionResult(messageId: string) {
104144return {
@@ -121,9 +161,10 @@ describe("executeSendAction", () => {
121161mediaUrls: string[];
122162}>,
123163) {
124-expect(mocks.appendAssistantMessageToSessionTranscript).toHaveBeenCalledWith(
125-expect.objectContaining({ ...expected, config: {} }),
126-);
164+expectSingleCallFields(mocks.appendAssistantMessageToSessionTranscript, {
165+ ...expected,
166+config: {},
167+});
127168}
128169129170async function executePluginMirroredSend(params: {
@@ -213,14 +254,12 @@ describe("executeSendAction", () => {
213254message: "hello",
214255});
215256216-expect(mocks.sendMessage).toHaveBeenCalledWith(
217-expect.objectContaining({
218-agentId: "work",
219-channel: "demo-outbound",
220-to: "channel:123",
221-content: "hello",
222-}),
223-);
257+expectSingleCallFields(mocks.sendMessage, {
258+agentId: "work",
259+channel: "demo-outbound",
260+to: "channel:123",
261+content: "hello",
262+});
224263});
225264226265it("forwards requesterSenderId to sendMessage on core outbound path", async () => {
@@ -245,11 +284,9 @@ describe("executeSendAction", () => {
245284message: "hello",
246285});
247286248-expect(mocks.sendMessage).toHaveBeenCalledWith(
249-expect.objectContaining({
250-requesterSenderId: "attacker",
251-}),
252-);
287+expectSingleCallFields(mocks.sendMessage, {
288+requesterSenderId: "attacker",
289+});
253290});
254291255292it("forwards non-id requester sender fields to sendMessage on core outbound path", async () => {
@@ -276,13 +313,11 @@ describe("executeSendAction", () => {
276313message: "hello",
277314});
278315279-expect(mocks.sendMessage).toHaveBeenCalledWith(
280-expect.objectContaining({
281-requesterSenderName: "Alice",
282-requesterSenderUsername: "alice_u",
283-requesterSenderE164: "+15551234567",
284-}),
285-);
316+expectSingleCallFields(mocks.sendMessage, {
317+requesterSenderName: "Alice",
318+requesterSenderUsername: "alice_u",
319+requesterSenderE164: "+15551234567",
320+});
286321});
287322288323it("forwards requester session context to sendMessage on core outbound path", async () => {
@@ -309,26 +344,22 @@ describe("executeSendAction", () => {
309344message: "hello",
310345});
311346312-expect(mocks.sendMessage).toHaveBeenCalledWith(
313-expect.objectContaining({
314-requesterSessionKey: "agent:main:directchat:group:ops",
315-requesterAccountId: "source-account",
316-requesterSenderId: "attacker",
317-accountId: "destination-account",
318-}),
319-);
347+expectSingleCallFields(mocks.sendMessage, {
348+requesterSessionKey: "agent:main:directchat:group:ops",
349+requesterAccountId: "source-account",
350+requesterSenderId: "attacker",
351+accountId: "destination-account",
352+});
320353});
321354322355it("forwards requesterSenderId into outbound media access resolution", async () => {
323356await executePluginMediaSend({
324357requesterSenderId: "attacker",
325358});
326359327-expect(mocks.resolveAgentScopedOutboundMediaAccess).toHaveBeenCalledWith(
328-expect.objectContaining({
329-requesterSenderId: "attacker",
330-}),
331-);
360+expectSingleCallFields(mocks.resolveAgentScopedOutboundMediaAccess, {
361+requesterSenderId: "attacker",
362+});
332363});
333364334365it("forwards non-id requester sender fields into outbound media access resolution", async () => {
@@ -338,26 +369,22 @@ describe("executeSendAction", () => {
338369requesterSenderE164: "+15551234567",
339370});
340371341-expect(mocks.resolveAgentScopedOutboundMediaAccess).toHaveBeenCalledWith(
342-expect.objectContaining({
343-requesterSenderName: "Alice",
344-requesterSenderUsername: "alice_u",
345-requesterSenderE164: "+15551234567",
346-}),
347-);
372+expectSingleCallFields(mocks.resolveAgentScopedOutboundMediaAccess, {
373+requesterSenderName: "Alice",
374+requesterSenderUsername: "alice_u",
375+requesterSenderE164: "+15551234567",
376+});
348377});
349378350379it("keeps requester session channel authoritative for media policy", async () => {
351380await executePluginMediaSend({
352381requesterSenderId: "attacker",
353382});
354383355-expect(mocks.resolveAgentScopedOutboundMediaAccess).toHaveBeenCalledWith(
356-expect.objectContaining({
357-sessionKey: "agent:main:directchat:group:ops",
358-messageProvider: undefined,
359-}),
360-);
384+expectSingleCallFields(mocks.resolveAgentScopedOutboundMediaAccess, {
385+sessionKey: "agent:main:directchat:group:ops",
386+messageProvider: undefined,
387+});
361388});
362389363390it("uses requester account for media policy when session context is present", async () => {
@@ -367,12 +394,10 @@ describe("executeSendAction", () => {
367394accountId: "destination-account",
368395});
369396370-expect(mocks.resolveAgentScopedOutboundMediaAccess).toHaveBeenCalledWith(
371-expect.objectContaining({
372-sessionKey: "agent:main:directchat:group:ops",
373-accountId: "source-account",
374-}),
375-);
397+expectSingleCallFields(mocks.resolveAgentScopedOutboundMediaAccess, {
398+sessionKey: "agent:main:directchat:group:ops",
399+accountId: "source-account",
400+});
376401});
377402378403it("falls back to destination account for media policy when requester account is missing", async () => {
@@ -381,12 +406,10 @@ describe("executeSendAction", () => {
381406accountId: "destination-account",
382407});
383408384-expect(mocks.resolveAgentScopedOutboundMediaAccess).toHaveBeenCalledWith(
385-expect.objectContaining({
386-sessionKey: "agent:main:directchat:group:ops",
387-accountId: "destination-account",
388-}),
389-);
409+expectSingleCallFields(mocks.resolveAgentScopedOutboundMediaAccess, {
410+sessionKey: "agent:main:directchat:group:ops",
411+accountId: "destination-account",
412+});
390413});
391414392415it("falls back to destination account when forwarding requester context to sendMessage", async () => {
@@ -412,12 +435,10 @@ describe("executeSendAction", () => {
412435message: "hello",
413436});
414437415-expect(mocks.sendMessage).toHaveBeenCalledWith(
416-expect.objectContaining({
417-requesterSessionKey: "agent:main:directchat:group:ops",
418-requesterAccountId: "destination-account",
419-}),
420-);
438+expectSingleCallFields(mocks.sendMessage, {
439+requesterSessionKey: "agent:main:directchat:group:ops",
440+requesterAccountId: "destination-account",
441+});
421442});
422443423444it("uses plugin poll action when available", async () => {
@@ -488,12 +509,10 @@ describe("executeSendAction", () => {
488509agentId: "agent-1",
489510mediaSources: [],
490511});
491-expect(mocks.dispatchChannelMessageAction).toHaveBeenCalledWith(
492-expect.objectContaining({
493-mediaLocalRoots: ["/tmp/agent-roots"],
494-mediaReadFile: mocks.createAgentScopedHostMediaReadFile.mock.results[0]?.value,
495-}),
496-);
512+expectSingleCallFields(mocks.dispatchChannelMessageAction, {
513+mediaLocalRoots: ["/tmp/agent-roots"],
514+mediaReadFile: mocks.createAgentScopedHostMediaReadFile.mock.results[0]?.value,
515+});
497516});
498517499518it("passes concrete media sources when widening plugin dispatch roots", async () => {
@@ -581,19 +600,17 @@ describe("executeSendAction", () => {
581600});
582601583602expect(mocks.dispatchChannelMessageAction).not.toHaveBeenCalled();
584-expect(mocks.sendMessage).toHaveBeenCalledWith(
585-expect.objectContaining({
586-to: "channel:123",
587-content: "hello",
588-dryRun: true,
589-silent: true,
590-gateway: expect.objectContaining({
591-url: "http://127.0.0.1:18789",
592-token: "tok",
593-timeoutMs: 5000,
594-}),
595-}),
596-);
603+const sendArgs = expectSingleCallFields(mocks.sendMessage, {
604+to: "channel:123",
605+content: "hello",
606+dryRun: true,
607+silent: true,
608+});
609+expectFields(requireRecord(sendArgs.gateway, "send gateway"), {
610+url: "http://127.0.0.1:18789",
611+token: "tok",
612+timeoutMs: 5000,
613+});
597614});
598615599616it("routes prepared plugin send payloads through core best-effort delivery by default", async () => {
@@ -631,13 +648,14 @@ describe("executeSendAction", () => {
631648632649expect(prepareSendPayload).toHaveBeenCalled();
633650expect(mocks.dispatchChannelMessageAction).not.toHaveBeenCalled();
634-expect(mocks.sendMessage).toHaveBeenCalledWith(
635-expect.objectContaining({
636-channel: "discord",
637-queuePolicy: "best_effort",
638-payloads: [expect.objectContaining({ channelData: { prepared: true } })],
639-}),
640-);
651+const sendArgs = expectSingleCallFields(mocks.sendMessage, {
652+channel: "discord",
653+queuePolicy: "best_effort",
654+});
655+const [payload] = requireArray(sendArgs.payloads, "send payloads");
656+expectFields(requireRecord(payload, "prepared payload"), {
657+channelData: { prepared: true },
658+});
641659});
642660643661it("uses required core delivery only when the send action opts out of best-effort", async () => {
@@ -674,12 +692,10 @@ describe("executeSendAction", () => {
674692bestEffort: false,
675693});
676694677-expect(mocks.sendMessage).toHaveBeenCalledWith(
678-expect.objectContaining({
679-channel: "discord",
680-queuePolicy: "required",
681-}),
682-);
695+expectSingleCallFields(mocks.sendMessage, {
696+channel: "discord",
697+queuePolicy: "required",
698+});
683699});
684700685701it("forwards poll args to sendPoll on core outbound path", async () => {
@@ -714,19 +730,17 @@ describe("executeSendAction", () => {
714730}),
715731});
716732717-expect(mocks.sendPoll).toHaveBeenCalledWith(
718-expect.objectContaining({
719-channel: "demo-outbound",
720-accountId: "acc-1",
721-to: "channel:123",
722-question: "Lunch?",
723-options: ["Pizza", "Sushi"],
724-maxSelections: 1,
725-durationSeconds: 300,
726-threadId: "thread-1",
727-isAnonymous: true,
728-}),
729-);
733+expectSingleCallFields(mocks.sendPoll, {
734+channel: "demo-outbound",
735+accountId: "acc-1",
736+to: "channel:123",
737+question: "Lunch?",
738+options: ["Pizza", "Sushi"],
739+maxSelections: 1,
740+durationSeconds: 300,
741+threadId: "thread-1",
742+isAnonymous: true,
743+});
730744});
731745732746it("skips plugin dispatch during dry-run polls and forwards durationHours + silent", async () => {
@@ -766,19 +780,17 @@ describe("executeSendAction", () => {
766780});
767781768782expect(mocks.dispatchChannelMessageAction).not.toHaveBeenCalled();
769-expect(mocks.sendPoll).toHaveBeenCalledWith(
770-expect.objectContaining({
771-to: "channel:123",
772-question: "Lunch?",
773-durationHours: 6,
774-dryRun: true,
775-silent: true,
776-gateway: expect.objectContaining({
777-url: "http://127.0.0.1:18789",
778-token: "tok",
779-timeoutMs: 5000,
780-}),
781-}),
782-);
783+const pollArgs = expectSingleCallFields(mocks.sendPoll, {
784+to: "channel:123",
785+question: "Lunch?",
786+durationHours: 6,
787+dryRun: true,
788+silent: true,
789+});
790+expectFields(requireRecord(pollArgs.gateway, "poll gateway"), {
791+url: "http://127.0.0.1:18789",
792+token: "tok",
793+timeoutMs: 5000,
794+});
783795});
784796});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。