@moltzap/client 2026.503.3 → 2026.504.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.d.ts +10 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +8 -5
- package/dist/auth.js.map +1 -1
- package/dist/channel-core.d.ts +1 -4
- package/dist/channel-core.d.ts.map +1 -1
- package/dist/channel-core.js +0 -9
- package/dist/channel-core.js.map +1 -1
- package/dist/channel-core.test.js +90 -71
- package/dist/channel-core.test.js.map +1 -1
- package/dist/cli/commands/agents.d.ts.map +1 -1
- package/dist/cli/commands/agents.js +5 -3
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/apps.d.ts +9 -27
- package/dist/cli/commands/apps.d.ts.map +1 -1
- package/dist/cli/commands/apps.js +36 -57
- package/dist/cli/commands/apps.js.map +1 -1
- package/dist/cli/commands/apps.test.js +63 -75
- package/dist/cli/commands/apps.test.js.map +1 -1
- package/dist/cli/commands/contacts.d.ts +1 -5
- package/dist/cli/commands/contacts.d.ts.map +1 -1
- package/dist/cli/commands/contacts.js +14 -27
- package/dist/cli/commands/contacts.js.map +1 -1
- package/dist/cli/commands/conversations-v2.test.js +34 -29
- package/dist/cli/commands/conversations-v2.test.js.map +1 -1
- package/dist/cli/commands/conversations.d.ts +7 -3
- package/dist/cli/commands/conversations.d.ts.map +1 -1
- package/dist/cli/commands/conversations.js +63 -48
- package/dist/cli/commands/conversations.js.map +1 -1
- package/dist/cli/commands/messages.d.ts +7 -3
- package/dist/cli/commands/messages.d.ts.map +1 -1
- package/dist/cli/commands/messages.js +13 -15
- package/dist/cli/commands/messages.js.map +1 -1
- package/dist/cli/commands/messages.test.js +30 -31
- package/dist/cli/commands/messages.test.js.map +1 -1
- package/dist/cli/commands/ping.d.ts +9 -0
- package/dist/cli/commands/ping.d.ts.map +1 -1
- package/dist/cli/commands/ping.js +17 -4
- package/dist/cli/commands/ping.js.map +1 -1
- package/dist/cli/commands/presence.d.ts.map +1 -1
- package/dist/cli/commands/presence.js +6 -4
- package/dist/cli/commands/presence.js.map +1 -1
- package/dist/cli/commands/register.d.ts.map +1 -1
- package/dist/cli/commands/register.js +2 -1
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +22 -11
- package/dist/cli/commands/send.js.map +1 -1
- package/dist/cli/commands/send.test.js +12 -9
- package/dist/cli/commands/send.test.js.map +1 -1
- package/dist/cli/commands/status.js +2 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/test-transport.d.ts +10 -0
- package/dist/cli/commands/test-transport.d.ts.map +1 -0
- package/dist/cli/commands/test-transport.js +29 -0
- package/dist/cli/commands/test-transport.js.map +1 -0
- package/dist/cli/commands/whoami.d.ts.map +1 -1
- package/dist/cli/commands/whoami.js +5 -1
- package/dist/cli/commands/whoami.js.map +1 -1
- package/dist/cli/config.d.ts +42 -6
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +91 -27
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/http-client.d.ts +11 -1
- package/dist/cli/http-client.d.ts.map +1 -1
- package/dist/cli/http-client.js +13 -4
- package/dist/cli/http-client.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/profile.d.ts +3 -4
- package/dist/cli/profile.d.ts.map +1 -1
- package/dist/cli/profile.js +42 -29
- package/dist/cli/profile.js.map +1 -1
- package/dist/cli/runtime.d.ts.map +1 -1
- package/dist/cli/runtime.js +9 -4
- package/dist/cli/runtime.js.map +1 -1
- package/dist/cli/socket-client.d.ts +29 -4
- package/dist/cli/socket-client.d.ts.map +1 -1
- package/dist/cli/socket-client.js +52 -21
- package/dist/cli/socket-client.js.map +1 -1
- package/dist/cli/transport.d.ts +6 -9
- package/dist/cli/transport.d.ts.map +1 -1
- package/dist/cli/transport.js +47 -37
- package/dist/cli/transport.js.map +1 -1
- package/dist/cli/transport.test.js +19 -16
- package/dist/cli/transport.test.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/__tests__/app-callback-partition-key.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.d.ts.map +1 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.js +33 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partition-worker.test.js → app-callback-partition-worker.test.js} +6 -8
- package/dist/internal/__tests__/app-callback-partition-worker.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partitioned-dispatcher-real-ws.test.js → app-callback-partitioned-dispatcher-real-ws.test.js} +69 -93
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partitioned-dispatcher.test.js → app-callback-partitioned-dispatcher.test.js} +36 -69
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-test-requests.d.ts +58 -0
- package/dist/internal/__tests__/app-callback-test-requests.d.ts.map +1 -0
- package/dist/internal/__tests__/app-callback-test-requests.js +124 -0
- package/dist/internal/__tests__/app-callback-test-requests.js.map +1 -0
- package/dist/internal/{s2c-dispatcher-errors.d.ts → app-callback-dispatcher-errors.d.ts} +5 -21
- package/dist/internal/app-callback-dispatcher-errors.d.ts.map +1 -0
- package/dist/internal/{s2c-dispatcher-errors.js → app-callback-dispatcher-errors.js} +3 -13
- package/dist/internal/app-callback-dispatcher-errors.js.map +1 -0
- package/dist/internal/app-callback-partition-key.d.ts +68 -0
- package/dist/internal/app-callback-partition-key.d.ts.map +1 -0
- package/dist/internal/app-callback-partition-key.js +90 -0
- package/dist/internal/app-callback-partition-key.js.map +1 -0
- package/dist/internal/{s2c-partition-worker.d.ts → app-callback-partition-worker.d.ts} +5 -5
- package/dist/internal/app-callback-partition-worker.d.ts.map +1 -0
- package/dist/internal/{s2c-partition-worker.js → app-callback-partition-worker.js} +5 -5
- package/dist/internal/app-callback-partition-worker.js.map +1 -0
- package/dist/internal/{s2c-partitioned-dispatcher.d.ts → app-callback-partitioned-dispatcher.d.ts} +9 -10
- package/dist/internal/app-callback-partitioned-dispatcher.d.ts.map +1 -0
- package/dist/internal/{s2c-partitioned-dispatcher.js → app-callback-partitioned-dispatcher.js} +13 -21
- package/dist/internal/app-callback-partitioned-dispatcher.js.map +1 -0
- package/dist/runtime/close-info.d.ts +15 -16
- package/dist/runtime/close-info.d.ts.map +1 -1
- package/dist/runtime/close-info.js +12 -8
- package/dist/runtime/close-info.js.map +1 -1
- package/dist/runtime/errors.d.ts +2 -28
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +0 -9
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/errors.test.js +4 -3
- package/dist/runtime/errors.test.js.map +1 -1
- package/dist/runtime/frame.d.ts +17 -18
- package/dist/runtime/frame.d.ts.map +1 -1
- package/dist/runtime/frame.js +86 -31
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/frame.test.js +15 -11
- package/dist/runtime/frame.test.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/local-service-commands.d.ts +7 -0
- package/dist/runtime/local-service-commands.d.ts.map +1 -0
- package/dist/runtime/local-service-commands.js +6 -0
- package/dist/runtime/local-service-commands.js.map +1 -0
- package/dist/runtime/runtime.test.js +3 -3
- package/dist/runtime/runtime.test.js.map +1 -1
- package/dist/runtime/subscribers.d.ts +37 -33
- package/dist/runtime/subscribers.d.ts.map +1 -1
- package/dist/runtime/subscribers.js +22 -21
- package/dist/runtime/subscribers.js.map +1 -1
- package/dist/runtime/subscribers.test.js +50 -31
- package/dist/runtime/subscribers.test.js.map +1 -1
- package/dist/service.d.ts +24 -39
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +190 -171
- package/dist/service.js.map +1 -1
- package/dist/service.test.js +117 -175
- package/dist/service.test.js.map +1 -1
- package/dist/test/index.d.ts +9 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +14 -3
- package/dist/test/index.js.map +1 -1
- package/dist/test-utils/channel-service-fixture.d.ts +1 -2
- package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
- package/dist/test-utils/channel-service-fixture.js +28 -20
- package/dist/test-utils/channel-service-fixture.js.map +1 -1
- package/dist/test-utils/conformance-adapter.d.ts +2 -2
- package/dist/test-utils/conformance-adapter.d.ts.map +1 -1
- package/dist/test-utils/conformance-adapter.js +78 -54
- package/dist/test-utils/conformance-adapter.js.map +1 -1
- package/dist/test-utils/fake-service.d.ts +14 -39
- package/dist/test-utils/fake-service.d.ts.map +1 -1
- package/dist/test-utils/fake-service.js +33 -39
- package/dist/test-utils/fake-service.js.map +1 -1
- package/dist/test-utils/ids.d.ts +9 -0
- package/dist/test-utils/ids.d.ts.map +1 -0
- package/dist/test-utils/ids.js +52 -0
- package/dist/test-utils/ids.js.map +1 -0
- package/dist/test-utils/index.d.ts +10 -2
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +25 -9
- package/dist/test-utils/index.js.map +1 -1
- package/dist/ws-client.d.ts +59 -64
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +228 -175
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-client.test.js +190 -176
- package/dist/ws-client.test.js.map +1 -1
- package/package.json +3 -3
- package/dist/cli/commands/permissions.d.ts +0 -66
- package/dist/cli/commands/permissions.d.ts.map +0 -1
- package/dist/cli/commands/permissions.js +0 -101
- package/dist/cli/commands/permissions.js.map +0 -1
- package/dist/cli/commands/permissions.test.d.ts +0 -2
- package/dist/cli/commands/permissions.test.d.ts.map +0 -1
- package/dist/cli/commands/permissions.test.js +0 -126
- package/dist/cli/commands/permissions.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partition-key.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partition-key.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partition-key.test.js +0 -230
- package/dist/internal/__tests__/s2c-partition-key.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partition-worker.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partition-worker.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partition-worker.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.js.map +0 -1
- package/dist/internal/s2c-dispatcher-errors.d.ts.map +0 -1
- package/dist/internal/s2c-dispatcher-errors.js.map +0 -1
- package/dist/internal/s2c-partition-key.d.ts +0 -71
- package/dist/internal/s2c-partition-key.d.ts.map +0 -1
- package/dist/internal/s2c-partition-key.js +0 -140
- package/dist/internal/s2c-partition-key.js.map +0 -1
- package/dist/internal/s2c-partition-worker.d.ts.map +0 -1
- package/dist/internal/s2c-partition-worker.js.map +0 -1
- package/dist/internal/s2c-partitioned-dispatcher.d.ts.map +0 -1
- package/dist/internal/s2c-partitioned-dispatcher.js.map +0 -1
package/dist/service.test.js
CHANGED
|
@@ -2,61 +2,69 @@ import * as path from "node:path";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { Effect, Either } from "effect";
|
|
5
|
-
import { AppsAuthorizeDispatch, ConversationsGet, ErrorCodes,
|
|
5
|
+
import { AppsAuthorizeDispatch, ConversationsGet, ErrorCodes, ConversationArchivedNotificationDefinition, ConversationCreatedNotificationDefinition, ConversationUnarchivedNotificationDefinition, MessageReceivedNotificationDefinition, MessagesSend, notificationFrame, } from "@moltzap/protocol";
|
|
6
6
|
import { sanitizeForSystemReminder } from "./service.js";
|
|
7
7
|
import { FakeMoltZapService } from "./test-utils/fake-service.js";
|
|
8
|
+
import { buildMessage, testAgentId, testConversationId, testMessageId, } from "./test-utils/index.js";
|
|
9
|
+
import { AgentsLookupByName, ConversationsCreate } from "@moltzap/protocol";
|
|
8
10
|
/** Run a service Effect to a Promise for test assertions. */
|
|
9
11
|
const run = (e) => Effect.runPromise(e);
|
|
12
|
+
const AGENT_ALICE_ID = testAgentId("agent-alice-id");
|
|
13
|
+
const AGENT_SELF_ID = testAgentId("agent-self");
|
|
14
|
+
const AGENT_GM_ID = testAgentId("agent-gm");
|
|
15
|
+
const CONVERSATION_ALICE_ID = testConversationId("conv-alice");
|
|
16
|
+
const CONVERSATION_ARCHIVED_ID = testConversationId("conv-archived");
|
|
17
|
+
const DISPATCH_MESSAGE_ID = testMessageId("msg-dispatch");
|
|
10
18
|
describe("MoltZapService.sendToAgent", () => {
|
|
11
19
|
let service;
|
|
12
20
|
beforeEach(() => {
|
|
13
21
|
service = new FakeMoltZapService();
|
|
14
22
|
// `setResponse` is typed: the wire name must be a `RpcMethodName` literal
|
|
15
|
-
// and the value must match `
|
|
23
|
+
// and the value must match `RpcResult<M>`. Both guard against the
|
|
16
24
|
// contract-drift bug (A7) that motivated this fake.
|
|
17
|
-
service.setResponse(
|
|
25
|
+
service.setResponse(AgentsLookupByName, {
|
|
18
26
|
agents: [
|
|
19
27
|
{
|
|
20
|
-
id:
|
|
28
|
+
id: AGENT_ALICE_ID,
|
|
21
29
|
name: "alice",
|
|
22
30
|
status: "active",
|
|
23
31
|
},
|
|
24
32
|
],
|
|
25
33
|
});
|
|
26
|
-
service.setResponse(
|
|
34
|
+
service.setResponse(ConversationsCreate, {
|
|
27
35
|
conversation: {
|
|
28
|
-
id:
|
|
36
|
+
id: CONVERSATION_ALICE_ID,
|
|
29
37
|
type: "dm",
|
|
30
|
-
createdBy:
|
|
38
|
+
createdBy: AGENT_SELF_ID,
|
|
31
39
|
createdAt: "2026-04-16T00:00:00Z",
|
|
32
40
|
updatedAt: "2026-04-16T00:00:00Z",
|
|
33
41
|
},
|
|
34
42
|
});
|
|
35
|
-
service.setResponse(
|
|
36
|
-
message: {
|
|
43
|
+
service.setResponse(MessagesSend, {
|
|
44
|
+
message: buildMessage({
|
|
37
45
|
id: "msg-1",
|
|
38
46
|
conversationId: "conv-alice",
|
|
39
47
|
senderId: "agent-self",
|
|
40
48
|
parts: [{ type: "text", text: "placeholder" }],
|
|
41
49
|
createdAt: "2026-04-16T00:00:00Z",
|
|
42
|
-
},
|
|
50
|
+
}),
|
|
43
51
|
});
|
|
44
52
|
});
|
|
45
53
|
it("resolves agent name, creates a DM, and sends the message on first call", async () => {
|
|
46
54
|
await run(service.sendToAgent("alice", "hello"));
|
|
47
55
|
expect(service.calls).toEqual([
|
|
48
|
-
{ method:
|
|
56
|
+
{ method: AgentsLookupByName.name, params: { names: ["alice"] } },
|
|
49
57
|
{
|
|
50
|
-
method:
|
|
58
|
+
method: ConversationsCreate.name,
|
|
51
59
|
params: {
|
|
52
60
|
type: "dm",
|
|
53
|
-
participants: [{ type: "agent", id:
|
|
61
|
+
participants: [{ type: "agent", id: AGENT_ALICE_ID }],
|
|
54
62
|
},
|
|
55
63
|
},
|
|
56
64
|
{
|
|
57
|
-
method:
|
|
65
|
+
method: MessagesSend.name,
|
|
58
66
|
params: {
|
|
59
|
-
conversationId:
|
|
67
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
60
68
|
parts: [{ type: "text", text: "hello" }],
|
|
61
69
|
},
|
|
62
70
|
},
|
|
@@ -68,36 +76,39 @@ describe("MoltZapService.sendToAgent", () => {
|
|
|
68
76
|
await run(service.sendToAgent("alice", "second"));
|
|
69
77
|
expect(service.calls).toEqual([
|
|
70
78
|
{
|
|
71
|
-
method:
|
|
79
|
+
method: MessagesSend.name,
|
|
72
80
|
params: {
|
|
73
|
-
conversationId:
|
|
81
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
74
82
|
parts: [{ type: "text", text: "second" }],
|
|
75
83
|
},
|
|
76
84
|
},
|
|
77
85
|
]);
|
|
78
86
|
});
|
|
79
87
|
it("forwards replyTo to messages/send as replyToId", async () => {
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
const replyToId = testMessageId("msg-123");
|
|
89
|
+
await run(service.sendToAgent("alice", "reply text", { replyTo: replyToId }));
|
|
90
|
+
const sendCall = service.calls.find((c) => c.method === MessagesSend.name);
|
|
82
91
|
expect(sendCall?.params).toEqual({
|
|
83
|
-
conversationId:
|
|
92
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
84
93
|
parts: [{ type: "text", text: "reply text" }],
|
|
85
|
-
replyToId
|
|
94
|
+
replyToId,
|
|
86
95
|
});
|
|
87
96
|
});
|
|
88
97
|
it("maintains separate cache entries per agent name", async () => {
|
|
89
|
-
service.setResponse(
|
|
90
|
-
agents: [{ id:
|
|
98
|
+
service.setResponse(AgentsLookupByName, {
|
|
99
|
+
agents: [{ id: AGENT_ALICE_ID, name: "alice", status: "active" }],
|
|
91
100
|
});
|
|
92
101
|
await run(service.sendToAgent("alice", "hello alice"));
|
|
93
|
-
service.setResponse(
|
|
94
|
-
agents: [
|
|
102
|
+
service.setResponse(AgentsLookupByName, {
|
|
103
|
+
agents: [
|
|
104
|
+
{ id: testAgentId("agent-bob-id"), name: "bob", status: "active" },
|
|
105
|
+
],
|
|
95
106
|
});
|
|
96
|
-
service.setResponse(
|
|
107
|
+
service.setResponse(ConversationsCreate, {
|
|
97
108
|
conversation: {
|
|
98
|
-
id: "conv-bob",
|
|
109
|
+
id: testConversationId("conv-bob"),
|
|
99
110
|
type: "dm",
|
|
100
|
-
createdBy:
|
|
111
|
+
createdBy: AGENT_SELF_ID,
|
|
101
112
|
createdAt: "2026-04-16T00:00:00Z",
|
|
102
113
|
updatedAt: "2026-04-16T00:00:00Z",
|
|
103
114
|
},
|
|
@@ -106,25 +117,25 @@ describe("MoltZapService.sendToAgent", () => {
|
|
|
106
117
|
service.calls = [];
|
|
107
118
|
await run(service.sendToAgent("alice", "alice again"));
|
|
108
119
|
await run(service.sendToAgent("bob", "bob again"));
|
|
109
|
-
const sendCalls = service.calls.filter((c) => c.method ===
|
|
120
|
+
const sendCalls = service.calls.filter((c) => c.method === MessagesSend.name);
|
|
110
121
|
expect(sendCalls).toHaveLength(2);
|
|
111
|
-
expect(sendCalls[0].params.conversationId).toBe(
|
|
112
|
-
expect(sendCalls[1].params.conversationId).toBe("conv-bob");
|
|
122
|
+
expect(sendCalls[0].params.conversationId).toBe(CONVERSATION_ALICE_ID);
|
|
123
|
+
expect(sendCalls[1].params.conversationId).toBe(testConversationId("conv-bob"));
|
|
113
124
|
});
|
|
114
125
|
it("throws a clear error when no agent is found for the given name", async () => {
|
|
115
|
-
service.setResponse(
|
|
126
|
+
service.setResponse(AgentsLookupByName, { agents: [] });
|
|
116
127
|
await expect(run(service.sendToAgent("nobody", "hi"))).rejects.toThrow(/Agent not found: nobody/);
|
|
117
128
|
});
|
|
118
129
|
it("propagates errors from agents/lookupByName", async () => {
|
|
119
|
-
service.deleteResponse(
|
|
130
|
+
service.deleteResponse(AgentsLookupByName);
|
|
120
131
|
await expect(run(service.sendToAgent("alice", "hi"))).rejects.toThrow(/no canned response for agents\/lookupByName/);
|
|
121
132
|
});
|
|
122
133
|
it("propagates errors from conversations/create", async () => {
|
|
123
|
-
service.deleteResponse(
|
|
134
|
+
service.deleteResponse(ConversationsCreate);
|
|
124
135
|
await expect(run(service.sendToAgent("alice", "hi"))).rejects.toThrow(/no canned response for conversations\/create/);
|
|
125
136
|
});
|
|
126
137
|
it("propagates errors from messages/send", async () => {
|
|
127
|
-
service.deleteResponse(
|
|
138
|
+
service.deleteResponse(MessagesSend);
|
|
128
139
|
await expect(run(service.sendToAgent("alice", "hi"))).rejects.toThrow(/no canned response for messages\/send/);
|
|
129
140
|
});
|
|
130
141
|
});
|
|
@@ -154,7 +165,7 @@ describe("sanitizeForSystemReminder", () => {
|
|
|
154
165
|
describe("MoltZapService.authorizeDispatch", () => {
|
|
155
166
|
it("uses the long app-dispatch RPC timeout instead of the generic RPC timeout", async () => {
|
|
156
167
|
const service = new FakeMoltZapService();
|
|
157
|
-
service.setResponse(AppsAuthorizeDispatch
|
|
168
|
+
service.setResponse(AppsAuthorizeDispatch, {
|
|
158
169
|
admission: {
|
|
159
170
|
decision: "grant",
|
|
160
171
|
leaseId: "lease-1",
|
|
@@ -162,23 +173,23 @@ describe("MoltZapService.authorizeDispatch", () => {
|
|
|
162
173
|
},
|
|
163
174
|
});
|
|
164
175
|
const result = await run(service.authorizeDispatch({
|
|
165
|
-
conversationId:
|
|
166
|
-
senderAgentId:
|
|
176
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
177
|
+
senderAgentId: AGENT_GM_ID,
|
|
167
178
|
attempt: 0,
|
|
168
179
|
receivedAt: "2026-04-29T22:00:00.000Z",
|
|
169
180
|
clock: {
|
|
170
|
-
domainId:
|
|
181
|
+
domainId: CONVERSATION_ALICE_ID,
|
|
171
182
|
epoch: 1,
|
|
172
|
-
vector: {
|
|
183
|
+
vector: { [AGENT_GM_ID]: 1 },
|
|
173
184
|
},
|
|
174
185
|
pending: [],
|
|
175
|
-
message: {
|
|
176
|
-
id:
|
|
177
|
-
conversationId:
|
|
178
|
-
senderId:
|
|
186
|
+
message: buildMessage({
|
|
187
|
+
id: DISPATCH_MESSAGE_ID,
|
|
188
|
+
conversationId: CONVERSATION_ALICE_ID,
|
|
189
|
+
senderId: AGENT_GM_ID,
|
|
179
190
|
parts: [{ type: "text", text: "Time to vote!" }],
|
|
180
191
|
createdAt: "2026-04-29T22:00:00.000Z",
|
|
181
|
-
},
|
|
192
|
+
}),
|
|
182
193
|
}));
|
|
183
194
|
expect(result).toEqual({
|
|
184
195
|
_tag: "grant",
|
|
@@ -195,14 +206,14 @@ describe("MoltZapService.authorizeDispatch", () => {
|
|
|
195
206
|
describe("MoltZapService.getContext — XML injection hardening", () => {
|
|
196
207
|
/** Build a message that lands in `messages` via addMessage(). */
|
|
197
208
|
function msg(overrides) {
|
|
198
|
-
return {
|
|
199
|
-
id:
|
|
200
|
-
conversationId:
|
|
201
|
-
senderId:
|
|
202
|
-
parts:
|
|
203
|
-
createdAt:
|
|
209
|
+
return buildMessage({
|
|
210
|
+
id: "msg-1",
|
|
211
|
+
conversationId: "conv-other",
|
|
212
|
+
senderId: "agent-attacker",
|
|
213
|
+
parts: [{ type: "text", text: "hello" }],
|
|
214
|
+
createdAt: new Date().toISOString(),
|
|
204
215
|
...overrides,
|
|
205
|
-
};
|
|
216
|
+
});
|
|
206
217
|
}
|
|
207
218
|
it("escapes senderName with </system-reminder> injection attempt", () => {
|
|
208
219
|
const service = new FakeMoltZapService();
|
|
@@ -274,13 +285,13 @@ describe("MoltZapService.getContext — XML injection hardening", () => {
|
|
|
274
285
|
});
|
|
275
286
|
describe("MoltZapService.peekContextEntries", () => {
|
|
276
287
|
function addSimpleMessage(service, convId, seq, text = "hi") {
|
|
277
|
-
service.addMessage(convId, {
|
|
288
|
+
service.addMessage(convId, buildMessage({
|
|
278
289
|
id: `m-${seq}`,
|
|
279
290
|
conversationId: convId,
|
|
280
291
|
senderId: "agent-bob",
|
|
281
292
|
parts: [{ type: "text", text }],
|
|
282
293
|
createdAt: new Date().toISOString(),
|
|
283
|
-
});
|
|
294
|
+
}));
|
|
284
295
|
}
|
|
285
296
|
it("returns structured entries without advancing markers", () => {
|
|
286
297
|
const service = new FakeMoltZapService();
|
|
@@ -369,68 +380,68 @@ describe("MoltZapService.peekFullMessages", () => {
|
|
|
369
380
|
const service = new FakeMoltZapService();
|
|
370
381
|
service.setAgentNameDirect("agent-bob", "Bob");
|
|
371
382
|
service.setAgentNameDirect("agent-alice", "Alice");
|
|
372
|
-
service.addMessage("conv-a", {
|
|
383
|
+
service.addMessage("conv-a", buildMessage({
|
|
373
384
|
id: "m-1",
|
|
374
385
|
conversationId: "conv-a",
|
|
375
386
|
senderId: "agent-bob",
|
|
376
387
|
parts: [{ type: "text", text: "first" }],
|
|
377
388
|
createdAt: "2026-04-13T22:00:00Z",
|
|
378
|
-
});
|
|
379
|
-
service.addMessage("conv-b", {
|
|
389
|
+
}));
|
|
390
|
+
service.addMessage("conv-b", buildMessage({
|
|
380
391
|
id: "m-2",
|
|
381
392
|
conversationId: "conv-b",
|
|
382
393
|
senderId: "agent-alice",
|
|
383
394
|
parts: [{ type: "text", text: "second" }],
|
|
384
395
|
createdAt: "2026-04-13T22:00:01Z",
|
|
385
|
-
});
|
|
386
|
-
service.addMessage("conv-a", {
|
|
396
|
+
}));
|
|
397
|
+
service.addMessage("conv-a", buildMessage({
|
|
387
398
|
id: "m-3",
|
|
388
399
|
conversationId: "conv-a",
|
|
389
400
|
senderId: "agent-bob",
|
|
390
401
|
parts: [{ type: "text", text: "third" }],
|
|
391
402
|
createdAt: "2026-04-13T22:00:02Z",
|
|
392
|
-
});
|
|
403
|
+
}));
|
|
393
404
|
const { messages } = service.peekFullMessages("conv-self");
|
|
394
405
|
expect(messages).toHaveLength(3);
|
|
395
406
|
expect(messages.map((m) => m.text)).toEqual(["first", "second", "third"]);
|
|
396
407
|
expect(messages[0].conversationId).toBe("conv-a");
|
|
397
408
|
expect(messages[0].senderName).toBe("Bob");
|
|
398
|
-
expect(messages[0].senderId).toBe("agent-bob");
|
|
409
|
+
expect(messages[0].senderId).toBe(testAgentId("agent-bob"));
|
|
399
410
|
expect(messages[1].conversationId).toBe("conv-b");
|
|
400
411
|
expect(messages[1].senderName).toBe("Alice");
|
|
401
|
-
expect(messages[1].senderId).toBe("agent-alice");
|
|
412
|
+
expect(messages[1].senderId).toBe(testAgentId("agent-alice"));
|
|
402
413
|
});
|
|
403
414
|
it("excludes messages from the current conversation", () => {
|
|
404
415
|
const service = new FakeMoltZapService();
|
|
405
|
-
service.addMessage("conv-self", {
|
|
416
|
+
service.addMessage("conv-self", buildMessage({
|
|
406
417
|
id: "m-1",
|
|
407
418
|
conversationId: "conv-self",
|
|
408
419
|
senderId: "agent-bob",
|
|
409
420
|
parts: [{ type: "text", text: "own conv" }],
|
|
410
421
|
createdAt: "2026-04-13T22:00:00Z",
|
|
411
|
-
});
|
|
422
|
+
}));
|
|
412
423
|
const { messages } = service.peekFullMessages("conv-self");
|
|
413
424
|
expect(messages).toHaveLength(0);
|
|
414
425
|
});
|
|
415
426
|
it("commit advances markers; subsequent peek returns only new messages", () => {
|
|
416
427
|
const service = new FakeMoltZapService();
|
|
417
|
-
service.addMessage("conv-a", {
|
|
428
|
+
service.addMessage("conv-a", buildMessage({
|
|
418
429
|
id: "m-1",
|
|
419
430
|
conversationId: "conv-a",
|
|
420
431
|
senderId: "agent-bob",
|
|
421
432
|
parts: [{ type: "text", text: "old" }],
|
|
422
433
|
createdAt: "2026-04-13T22:00:00Z",
|
|
423
|
-
});
|
|
434
|
+
}));
|
|
424
435
|
const first = service.peekFullMessages("conv-self");
|
|
425
436
|
first.commit();
|
|
426
437
|
expect(first.messages).toHaveLength(1);
|
|
427
|
-
service.addMessage("conv-a", {
|
|
438
|
+
service.addMessage("conv-a", buildMessage({
|
|
428
439
|
id: "m-2",
|
|
429
440
|
conversationId: "conv-a",
|
|
430
441
|
senderId: "agent-bob",
|
|
431
442
|
parts: [{ type: "text", text: "new" }],
|
|
432
443
|
createdAt: "2026-04-13T22:01:00Z",
|
|
433
|
-
});
|
|
444
|
+
}));
|
|
434
445
|
const second = service.peekFullMessages("conv-self");
|
|
435
446
|
expect(second.messages).toHaveLength(1);
|
|
436
447
|
expect(second.messages[0].text).toBe("new");
|
|
@@ -439,14 +450,13 @@ describe("MoltZapService.peekFullMessages", () => {
|
|
|
439
450
|
const service = new FakeMoltZapService();
|
|
440
451
|
for (let c = 0; c < 10; c++) {
|
|
441
452
|
for (let m = 0; m < 5; m++) {
|
|
442
|
-
service.addMessage(`conv-${c}`, {
|
|
453
|
+
service.addMessage(`conv-${c}`, buildMessage({
|
|
443
454
|
id: `m-${c}-${m}`,
|
|
444
455
|
conversationId: `conv-${c}`,
|
|
445
456
|
senderId: "agent-bob",
|
|
446
|
-
seq: m + 1,
|
|
447
457
|
parts: [{ type: "text", text: `c${c}-m${m}` }],
|
|
448
458
|
createdAt: new Date(Date.now() + c * 10000 + m * 1000).toISOString(),
|
|
449
|
-
});
|
|
459
|
+
}));
|
|
450
460
|
}
|
|
451
461
|
}
|
|
452
462
|
const { messages } = service.peekFullMessages("conv-self");
|
|
@@ -454,13 +464,13 @@ describe("MoltZapService.peekFullMessages", () => {
|
|
|
454
464
|
});
|
|
455
465
|
it("peek without commit is idempotent", () => {
|
|
456
466
|
const service = new FakeMoltZapService();
|
|
457
|
-
service.addMessage("conv-a", {
|
|
467
|
+
service.addMessage("conv-a", buildMessage({
|
|
458
468
|
id: "m-1",
|
|
459
469
|
conversationId: "conv-a",
|
|
460
470
|
senderId: "agent-bob",
|
|
461
471
|
parts: [{ type: "text", text: "hi" }],
|
|
462
472
|
createdAt: "2026-04-13T22:00:00Z",
|
|
463
|
-
});
|
|
473
|
+
}));
|
|
464
474
|
const a = service.peekFullMessages("conv-self").messages;
|
|
465
475
|
const b = service.peekFullMessages("conv-self").messages;
|
|
466
476
|
expect(a).toHaveLength(1);
|
|
@@ -468,128 +478,86 @@ describe("MoltZapService.peekFullMessages", () => {
|
|
|
468
478
|
});
|
|
469
479
|
it("commit for one viewing conv does not affect another", () => {
|
|
470
480
|
const service = new FakeMoltZapService();
|
|
471
|
-
service.addMessage("conv-a", {
|
|
481
|
+
service.addMessage("conv-a", buildMessage({
|
|
472
482
|
id: "m-1",
|
|
473
483
|
conversationId: "conv-a",
|
|
474
484
|
senderId: "agent-bob",
|
|
475
485
|
parts: [{ type: "text", text: "hi" }],
|
|
476
486
|
createdAt: "2026-04-13T22:00:00Z",
|
|
477
|
-
});
|
|
487
|
+
}));
|
|
478
488
|
service.peekFullMessages("viewer-1").commit();
|
|
479
489
|
expect(service.peekFullMessages("viewer-2").messages).toHaveLength(1);
|
|
480
490
|
});
|
|
481
491
|
it("stores more than 20 messages per conversation without eviction", () => {
|
|
482
492
|
const service = new FakeMoltZapService();
|
|
483
493
|
for (let i = 1; i <= 30; i++) {
|
|
484
|
-
service.addMessage("conv-a", {
|
|
494
|
+
service.addMessage("conv-a", buildMessage({
|
|
485
495
|
id: `m-${i}`,
|
|
486
496
|
conversationId: "conv-a",
|
|
487
497
|
senderId: "agent-bob",
|
|
488
|
-
seq: i,
|
|
489
498
|
parts: [{ type: "text", text: `msg-${i}` }],
|
|
490
499
|
createdAt: new Date(Date.now() + i * 1000).toISOString(),
|
|
491
|
-
});
|
|
500
|
+
}));
|
|
492
501
|
}
|
|
493
502
|
const { messages } = service.peekFullMessages("conv-self");
|
|
494
503
|
expect(messages).toHaveLength(30);
|
|
495
504
|
});
|
|
496
505
|
});
|
|
497
|
-
describe("MoltZapService.on('permissionRequired')", () => {
|
|
498
|
-
it("fires handler when permissions/required event arrives", () => {
|
|
499
|
-
const service = new FakeMoltZapService();
|
|
500
|
-
const received = [];
|
|
501
|
-
service.on("permissionRequired", (data) => received.push(data));
|
|
502
|
-
const event = {
|
|
503
|
-
jsonrpc: "2.0",
|
|
504
|
-
type: "event",
|
|
505
|
-
event: EventNames.PermissionsRequired,
|
|
506
|
-
data: {
|
|
507
|
-
sessionId: "sess-1",
|
|
508
|
-
appId: "test-app",
|
|
509
|
-
resource: "contacts",
|
|
510
|
-
access: ["read"],
|
|
511
|
-
requestId: crypto.randomUUID(),
|
|
512
|
-
targetUserId: crypto.randomUUID(),
|
|
513
|
-
},
|
|
514
|
-
};
|
|
515
|
-
Reflect.get(service, "handleEvent").call(service, event);
|
|
516
|
-
expect(received).toHaveLength(1);
|
|
517
|
-
expect(received[0]).toMatchObject({
|
|
518
|
-
sessionId: "sess-1",
|
|
519
|
-
appId: "test-app",
|
|
520
|
-
resource: "contacts",
|
|
521
|
-
access: ["read"],
|
|
522
|
-
});
|
|
523
|
-
});
|
|
524
|
-
it("does not fire for unrelated events", () => {
|
|
525
|
-
const service = new FakeMoltZapService();
|
|
526
|
-
const received = [];
|
|
527
|
-
service.on("permissionRequired", (data) => received.push(data));
|
|
528
|
-
const event = {
|
|
529
|
-
jsonrpc: "2.0",
|
|
530
|
-
type: "event",
|
|
531
|
-
event: EventNames.PresenceChanged,
|
|
532
|
-
data: { agentId: "agent-1", status: "online" },
|
|
533
|
-
};
|
|
534
|
-
Reflect.get(service, "handleEvent").call(service, event);
|
|
535
|
-
expect(received).toHaveLength(0);
|
|
536
|
-
});
|
|
537
|
-
});
|
|
538
506
|
describe("MoltZapService conversation archive lifecycle", () => {
|
|
539
507
|
it("purges local state, fires conversationArchived, and locally rejects sends", async () => {
|
|
540
508
|
const service = new FakeMoltZapService();
|
|
541
|
-
service.setResponse(ConversationsGet
|
|
509
|
+
service.setResponse(ConversationsGet, {
|
|
542
510
|
conversation: {
|
|
543
|
-
id:
|
|
511
|
+
id: CONVERSATION_ARCHIVED_ID,
|
|
544
512
|
type: "group",
|
|
545
513
|
name: "Archived",
|
|
546
|
-
createdBy:
|
|
514
|
+
createdBy: AGENT_SELF_ID,
|
|
547
515
|
createdAt: "2026-05-01T00:00:00.000Z",
|
|
548
516
|
updatedAt: "2026-05-01T00:00:00.000Z",
|
|
549
517
|
},
|
|
550
518
|
participants: [],
|
|
551
519
|
});
|
|
552
|
-
service.setResponse(MessagesSend
|
|
553
|
-
message: {
|
|
520
|
+
service.setResponse(MessagesSend, {
|
|
521
|
+
message: buildMessage({
|
|
554
522
|
id: "msg-unreachable",
|
|
555
523
|
conversationId: "conv-archived",
|
|
556
524
|
senderId: "agent-self",
|
|
557
525
|
parts: [{ type: "text", text: "unreachable" }],
|
|
558
526
|
createdAt: "2026-05-01T00:00:00.000Z",
|
|
559
|
-
},
|
|
527
|
+
}),
|
|
560
528
|
});
|
|
561
|
-
service.emitEvent(
|
|
529
|
+
service.emitEvent(notificationFrame(ConversationCreatedNotificationDefinition, {
|
|
562
530
|
conversation: {
|
|
563
|
-
id:
|
|
531
|
+
id: CONVERSATION_ARCHIVED_ID,
|
|
564
532
|
type: "group",
|
|
565
533
|
name: "Archived",
|
|
566
|
-
createdBy:
|
|
534
|
+
createdBy: AGENT_SELF_ID,
|
|
567
535
|
createdAt: "2026-05-01T00:00:00.000Z",
|
|
568
536
|
updatedAt: "2026-05-01T00:00:00.000Z",
|
|
569
537
|
},
|
|
570
538
|
}));
|
|
571
|
-
service.addMessage(
|
|
539
|
+
service.addMessage(CONVERSATION_ARCHIVED_ID, buildMessage({
|
|
572
540
|
id: "msg-1",
|
|
573
541
|
conversationId: "conv-archived",
|
|
574
542
|
senderId: "agent-other",
|
|
575
543
|
parts: [{ type: "text", text: "old" }],
|
|
576
544
|
createdAt: "2026-05-01T00:00:00.000Z",
|
|
577
|
-
});
|
|
545
|
+
}));
|
|
578
546
|
const archivedEvents = [];
|
|
579
547
|
const unarchivedEvents = [];
|
|
580
548
|
service.on("conversationArchived", (data) => archivedEvents.push(data));
|
|
581
549
|
service.on("conversationUnarchived", (data) => unarchivedEvents.push(data));
|
|
582
|
-
const archivedEvent =
|
|
583
|
-
conversationId:
|
|
550
|
+
const archivedEvent = notificationFrame(ConversationArchivedNotificationDefinition, {
|
|
551
|
+
conversationId: CONVERSATION_ARCHIVED_ID,
|
|
584
552
|
archivedAt: "2026-05-01T00:01:00.000Z",
|
|
585
|
-
by:
|
|
553
|
+
by: AGENT_GM_ID,
|
|
586
554
|
});
|
|
587
555
|
service.emitEvent(archivedEvent);
|
|
588
|
-
expect(service.isConversationArchived(
|
|
589
|
-
expect(service.getConversation(
|
|
590
|
-
expect(service.getHistory(
|
|
591
|
-
expect(archivedEvents).toEqual([archivedEvent.
|
|
592
|
-
const lateSend = await run(Effect.either(service.send(
|
|
556
|
+
expect(service.isConversationArchived(CONVERSATION_ARCHIVED_ID)).toBe(true);
|
|
557
|
+
expect(service.getConversation(CONVERSATION_ARCHIVED_ID)).toBeUndefined();
|
|
558
|
+
expect(service.getHistory(CONVERSATION_ARCHIVED_ID)).toEqual([]);
|
|
559
|
+
expect(archivedEvents).toEqual([archivedEvent.params]);
|
|
560
|
+
const lateSend = await run(Effect.either(service.send(CONVERSATION_ARCHIVED_ID, "should not hit rpc")));
|
|
593
561
|
expect(Either.isLeft(lateSend)).toBe(true);
|
|
594
562
|
if (Either.isLeft(lateSend)) {
|
|
595
563
|
expect(lateSend.left).toMatchObject({
|
|
@@ -598,34 +566,13 @@ describe("MoltZapService conversation archive lifecycle", () => {
|
|
|
598
566
|
});
|
|
599
567
|
}
|
|
600
568
|
expect(service.calls.filter((c) => c.method === MessagesSend.name)).toEqual([]);
|
|
601
|
-
const unarchivedEvent =
|
|
602
|
-
conversationId:
|
|
603
|
-
by:
|
|
569
|
+
const unarchivedEvent = notificationFrame(ConversationUnarchivedNotificationDefinition, {
|
|
570
|
+
conversationId: CONVERSATION_ARCHIVED_ID,
|
|
571
|
+
by: AGENT_GM_ID,
|
|
604
572
|
});
|
|
605
573
|
service.emitEvent(unarchivedEvent);
|
|
606
|
-
expect(service.isConversationArchived(
|
|
607
|
-
expect(unarchivedEvents).toEqual([unarchivedEvent.
|
|
608
|
-
});
|
|
609
|
-
});
|
|
610
|
-
describe("MoltZapService.grantPermission", () => {
|
|
611
|
-
it("sends permissions/grant RPC", async () => {
|
|
612
|
-
const service = new FakeMoltZapService();
|
|
613
|
-
service.setResponse("permissions/grant", {});
|
|
614
|
-
await run(service.grantPermission({
|
|
615
|
-
sessionId: "sess-1",
|
|
616
|
-
agentId: "agent-2",
|
|
617
|
-
resource: "contacts",
|
|
618
|
-
access: ["read"],
|
|
619
|
-
}));
|
|
620
|
-
expect(service.calls).toContainEqual({
|
|
621
|
-
method: "permissions/grant",
|
|
622
|
-
params: {
|
|
623
|
-
sessionId: "sess-1",
|
|
624
|
-
agentId: "agent-2",
|
|
625
|
-
resource: "contacts",
|
|
626
|
-
access: ["read"],
|
|
627
|
-
},
|
|
628
|
-
});
|
|
574
|
+
expect(service.isConversationArchived(CONVERSATION_ARCHIVED_ID)).toBe(false);
|
|
575
|
+
expect(unarchivedEvents).toEqual([unarchivedEvent.params]);
|
|
629
576
|
});
|
|
630
577
|
});
|
|
631
578
|
describe("MoltZapService.socketPath — agentId sanitization", () => {
|
|
@@ -725,22 +672,17 @@ describe("MoltZapService.fanout — message handlers", () => {
|
|
|
725
672
|
service.on("message", (m) => {
|
|
726
673
|
seen.push(m);
|
|
727
674
|
});
|
|
728
|
-
const msg = {
|
|
675
|
+
const msg = buildMessage({
|
|
729
676
|
id: "m-1",
|
|
730
677
|
conversationId: "conv-1",
|
|
731
678
|
senderId: "agent-other",
|
|
732
679
|
parts: [{ type: "text", text: "hi" }],
|
|
733
680
|
createdAt: "2026-04-16T00:00:00.000Z",
|
|
734
|
-
};
|
|
735
|
-
const event = {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
data: { message: msg },
|
|
740
|
-
};
|
|
741
|
-
// handleEvent is private; reach into it the same way the existing
|
|
742
|
-
// permissions test does.
|
|
743
|
-
Reflect.get(service, "handleEvent").call(service, event);
|
|
681
|
+
});
|
|
682
|
+
const event = notificationFrame(MessageReceivedNotificationDefinition, {
|
|
683
|
+
message: msg,
|
|
684
|
+
});
|
|
685
|
+
service.emitEvent(event);
|
|
744
686
|
// Second handler still fired despite first handler throwing.
|
|
745
687
|
expect(seen).toEqual([msg]);
|
|
746
688
|
expect(logger.error).toHaveBeenCalledOnce();
|