@openclaw/zalo 2026.5.2 → 2026.5.3-beta.2

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.
Files changed (87) hide show
  1. package/dist/accounts-9NLDDlZ8.js +118 -0
  2. package/dist/actions.runtime-kJ65ZxW7.js +5 -0
  3. package/dist/api.js +5 -0
  4. package/dist/channel-VPbtV3Oq.js +343 -0
  5. package/dist/channel-plugin-api.js +2 -0
  6. package/dist/channel.runtime-BnTAWQx5.js +106 -0
  7. package/dist/contract-api.js +3 -0
  8. package/dist/group-access-DZR43lOR.js +30 -0
  9. package/dist/index.js +22 -0
  10. package/dist/monitor-DMysJBWa.js +823 -0
  11. package/dist/monitor.webhook-DqnuvgjV.js +175 -0
  12. package/dist/proxy-CY8VuC6H.js +135 -0
  13. package/dist/runtime-BRFxnYQx.js +8 -0
  14. package/dist/runtime-api-MOTmRW4F.js +19 -0
  15. package/dist/runtime-api.js +3 -0
  16. package/dist/secret-contract-Dw93tGo2.js +87 -0
  17. package/dist/secret-contract-api.js +2 -0
  18. package/dist/send-Gv3l5EGI.js +101 -0
  19. package/dist/setup-api.js +30 -0
  20. package/dist/setup-core-DigRD3j1.js +166 -0
  21. package/dist/setup-entry.js +15 -0
  22. package/dist/setup-surface-2Up3yWov.js +216 -0
  23. package/dist/test-api.js +2 -0
  24. package/package.json +15 -6
  25. package/api.ts +0 -9
  26. package/channel-plugin-api.ts +0 -1
  27. package/contract-api.ts +0 -5
  28. package/index.test.ts +0 -15
  29. package/index.ts +0 -20
  30. package/runtime-api.test.ts +0 -17
  31. package/runtime-api.ts +0 -75
  32. package/secret-contract-api.ts +0 -5
  33. package/setup-api.ts +0 -34
  34. package/setup-entry.ts +0 -13
  35. package/src/accounts.test.ts +0 -70
  36. package/src/accounts.ts +0 -60
  37. package/src/actions.runtime.ts +0 -5
  38. package/src/actions.test.ts +0 -32
  39. package/src/actions.ts +0 -62
  40. package/src/api.test.ts +0 -149
  41. package/src/api.ts +0 -265
  42. package/src/approval-auth.test.ts +0 -17
  43. package/src/approval-auth.ts +0 -25
  44. package/src/channel.directory.test.ts +0 -59
  45. package/src/channel.runtime.ts +0 -93
  46. package/src/channel.startup.test.ts +0 -101
  47. package/src/channel.ts +0 -275
  48. package/src/config-schema.test.ts +0 -30
  49. package/src/config-schema.ts +0 -29
  50. package/src/group-access.ts +0 -49
  51. package/src/monitor.group-policy.test.ts +0 -94
  52. package/src/monitor.image.polling.test.ts +0 -110
  53. package/src/monitor.lifecycle.test.ts +0 -198
  54. package/src/monitor.pairing.lifecycle.test.ts +0 -141
  55. package/src/monitor.polling.media-reply.test.ts +0 -425
  56. package/src/monitor.reply-once.lifecycle.test.ts +0 -171
  57. package/src/monitor.ts +0 -1028
  58. package/src/monitor.types.ts +0 -4
  59. package/src/monitor.webhook.test.ts +0 -806
  60. package/src/monitor.webhook.ts +0 -278
  61. package/src/outbound-media.test.ts +0 -182
  62. package/src/outbound-media.ts +0 -241
  63. package/src/outbound-payload.contract.test.ts +0 -45
  64. package/src/probe.ts +0 -45
  65. package/src/proxy.ts +0 -24
  66. package/src/runtime-api.ts +0 -75
  67. package/src/runtime-support.ts +0 -91
  68. package/src/runtime.ts +0 -9
  69. package/src/secret-contract.ts +0 -109
  70. package/src/secret-input.ts +0 -5
  71. package/src/send.test.ts +0 -120
  72. package/src/send.ts +0 -153
  73. package/src/session-route.ts +0 -32
  74. package/src/setup-allow-from.ts +0 -94
  75. package/src/setup-core.ts +0 -149
  76. package/src/setup-status.test.ts +0 -33
  77. package/src/setup-surface.test.ts +0 -175
  78. package/src/setup-surface.ts +0 -291
  79. package/src/status-issues.test.ts +0 -17
  80. package/src/status-issues.ts +0 -37
  81. package/src/test-support/lifecycle-test-support.ts +0 -413
  82. package/src/test-support/monitor-mocks-test-support.ts +0 -209
  83. package/src/token.test.ts +0 -92
  84. package/src/token.ts +0 -79
  85. package/src/types.ts +0 -50
  86. package/test-api.ts +0 -1
  87. package/tsconfig.json +0 -16
@@ -1,94 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { __testing } from "./monitor.js";
3
-
4
- describe("zalo group policy access", () => {
5
- it("blocks all group messages when policy is disabled", () => {
6
- const decision = __testing.evaluateZaloGroupAccess({
7
- providerConfigPresent: true,
8
- configuredGroupPolicy: "disabled",
9
- defaultGroupPolicy: "open",
10
- groupAllowFrom: ["zalo:123"],
11
- senderId: "123",
12
- });
13
- expect(decision).toMatchObject({
14
- allowed: false,
15
- groupPolicy: "disabled",
16
- reason: "disabled",
17
- });
18
- });
19
-
20
- it("blocks group messages on allowlist policy with empty allowlist", () => {
21
- const decision = __testing.evaluateZaloGroupAccess({
22
- providerConfigPresent: true,
23
- configuredGroupPolicy: "allowlist",
24
- defaultGroupPolicy: "open",
25
- groupAllowFrom: [],
26
- senderId: "attacker",
27
- });
28
- expect(decision).toMatchObject({
29
- allowed: false,
30
- groupPolicy: "allowlist",
31
- reason: "empty_allowlist",
32
- });
33
- });
34
-
35
- it("blocks sender not in group allowlist", () => {
36
- const decision = __testing.evaluateZaloGroupAccess({
37
- providerConfigPresent: true,
38
- configuredGroupPolicy: "allowlist",
39
- defaultGroupPolicy: "open",
40
- groupAllowFrom: ["zalo:victim-user-001"],
41
- senderId: "attacker-user-999",
42
- });
43
- expect(decision).toMatchObject({
44
- allowed: false,
45
- groupPolicy: "allowlist",
46
- reason: "sender_not_allowlisted",
47
- });
48
- });
49
-
50
- it("allows sender in group allowlist", () => {
51
- const decision = __testing.evaluateZaloGroupAccess({
52
- providerConfigPresent: true,
53
- configuredGroupPolicy: "allowlist",
54
- defaultGroupPolicy: "open",
55
- groupAllowFrom: ["zl:12345"],
56
- senderId: "12345",
57
- });
58
- expect(decision).toMatchObject({
59
- allowed: true,
60
- groupPolicy: "allowlist",
61
- reason: "allowed",
62
- });
63
- });
64
-
65
- it("allows any sender with wildcard allowlist", () => {
66
- const decision = __testing.evaluateZaloGroupAccess({
67
- providerConfigPresent: true,
68
- configuredGroupPolicy: "allowlist",
69
- defaultGroupPolicy: "open",
70
- groupAllowFrom: ["*"],
71
- senderId: "random-user",
72
- });
73
- expect(decision).toMatchObject({
74
- allowed: true,
75
- groupPolicy: "allowlist",
76
- reason: "allowed",
77
- });
78
- });
79
-
80
- it("allows all group senders on open policy", () => {
81
- const decision = __testing.evaluateZaloGroupAccess({
82
- providerConfigPresent: true,
83
- configuredGroupPolicy: "open",
84
- defaultGroupPolicy: "allowlist",
85
- groupAllowFrom: [],
86
- senderId: "attacker-user-999",
87
- });
88
- expect(decision).toMatchObject({
89
- allowed: true,
90
- groupPolicy: "open",
91
- reason: "allowed",
92
- });
93
- });
94
- });
@@ -1,110 +0,0 @@
1
- import { createRuntimeEnv } from "openclaw/plugin-sdk/plugin-test-runtime";
2
- import { afterAll, beforeEach, describe, expect, it } from "vitest";
3
- import {
4
- createImageLifecycleCore,
5
- createImageUpdate,
6
- createLifecycleMonitorSetup,
7
- expectImageLifecycleDelivery,
8
- settleAsyncWork,
9
- } from "./test-support/lifecycle-test-support.js";
10
- import {
11
- getUpdatesMock,
12
- getZaloRuntimeMock,
13
- loadCachedLifecycleMonitorModule,
14
- resetLifecycleTestState,
15
- sendMessageMock,
16
- } from "./test-support/monitor-mocks-test-support.js";
17
-
18
- describe("Zalo polling image handling", () => {
19
- const {
20
- core,
21
- finalizeInboundContextMock,
22
- recordInboundSessionMock,
23
- fetchRemoteMediaMock,
24
- saveMediaBufferMock,
25
- } = createImageLifecycleCore();
26
-
27
- beforeEach(async () => {
28
- await resetLifecycleTestState();
29
- getZaloRuntimeMock.mockReturnValue(core);
30
- });
31
-
32
- afterAll(async () => {
33
- await resetLifecycleTestState();
34
- });
35
-
36
- it("downloads inbound image media from photo_url and preserves display_name", async () => {
37
- getUpdatesMock
38
- .mockResolvedValueOnce({
39
- ok: true,
40
- result: createImageUpdate({ date: 1774084566880 }),
41
- })
42
- .mockImplementation(() => new Promise(() => {}));
43
-
44
- const { monitorZaloProvider } = await loadCachedLifecycleMonitorModule("zalo-image-polling");
45
- const abort = new AbortController();
46
- const runtime = createRuntimeEnv();
47
- const { account, config } = createLifecycleMonitorSetup({
48
- accountId: "default",
49
- dmPolicy: "open",
50
- });
51
- const run = monitorZaloProvider({
52
- token: "zalo-token", // pragma: allowlist secret
53
- account,
54
- config,
55
- runtime,
56
- abortSignal: abort.signal,
57
- });
58
-
59
- await settleAsyncWork();
60
- expect(fetchRemoteMediaMock).toHaveBeenCalledTimes(1);
61
- expectImageLifecycleDelivery({
62
- fetchRemoteMediaMock,
63
- saveMediaBufferMock,
64
- finalizeInboundContextMock,
65
- recordInboundSessionMock,
66
- });
67
-
68
- abort.abort();
69
- await run;
70
- });
71
-
72
- it("rejects unauthorized DM images before downloading media", async () => {
73
- getUpdatesMock
74
- .mockResolvedValueOnce({
75
- ok: true,
76
- result: createImageUpdate({
77
- messageId: "msg-unauthorized-1",
78
- userId: "user-unauthorized-1",
79
- chatId: "chat-unauthorized-1",
80
- }),
81
- })
82
- .mockImplementation(() => new Promise(() => {}));
83
-
84
- const { monitorZaloProvider } = await loadCachedLifecycleMonitorModule("zalo-image-polling");
85
- const abort = new AbortController();
86
- const runtime = createRuntimeEnv();
87
- const { account, config } = createLifecycleMonitorSetup({
88
- accountId: "default",
89
- dmPolicy: "pairing",
90
- allowFrom: ["allowed-user"],
91
- });
92
- const run = monitorZaloProvider({
93
- token: "zalo-token", // pragma: allowlist secret
94
- account,
95
- config,
96
- runtime,
97
- abortSignal: abort.signal,
98
- });
99
-
100
- await settleAsyncWork();
101
- expect(sendMessageMock).toHaveBeenCalledTimes(1);
102
- expect(fetchRemoteMediaMock).not.toHaveBeenCalled();
103
- expect(saveMediaBufferMock).not.toHaveBeenCalled();
104
- expect(finalizeInboundContextMock).not.toHaveBeenCalled();
105
- expect(recordInboundSessionMock).not.toHaveBeenCalled();
106
-
107
- abort.abort();
108
- await run;
109
- });
110
- });
@@ -1,198 +0,0 @@
1
- import {
2
- createEmptyPluginRegistry,
3
- createRuntimeEnv,
4
- setActivePluginRegistry,
5
- } from "openclaw/plugin-sdk/plugin-test-runtime";
6
- import { afterEach, describe, expect, it, vi } from "vitest";
7
- import type { OpenClawConfig } from "../runtime-api.js";
8
- import type { ResolvedZaloAccount } from "./accounts.js";
9
-
10
- const getWebhookInfoMock = vi.fn(async () => ({ ok: true, result: { url: "" } }));
11
- const deleteWebhookMock = vi.fn(async () => ({ ok: true, result: { url: "" } }));
12
- const getUpdatesMock = vi.fn(() => new Promise(() => {}));
13
- const setWebhookMock = vi.fn(async () => ({ ok: true, result: { url: "" } }));
14
-
15
- vi.mock("./api.js", async () => {
16
- const actual = await vi.importActual<typeof import("./api.js")>("./api.js");
17
- return {
18
- ...actual,
19
- deleteWebhook: deleteWebhookMock,
20
- getWebhookInfo: getWebhookInfoMock,
21
- getUpdates: getUpdatesMock,
22
- setWebhook: setWebhookMock,
23
- };
24
- });
25
-
26
- vi.mock("./runtime.js", () => ({
27
- getZaloRuntime: () => ({
28
- logging: {
29
- shouldLogVerbose: () => false,
30
- },
31
- }),
32
- }));
33
-
34
- const TEST_ACCOUNT = {
35
- accountId: "default",
36
- config: {},
37
- } as unknown as ResolvedZaloAccount;
38
-
39
- const TEST_CONFIG = {} as OpenClawConfig;
40
-
41
- async function settleLifecycleWork(): Promise<void> {
42
- for (let i = 0; i < 6; i += 1) {
43
- await Promise.resolve();
44
- await new Promise((resolve) => setImmediate(resolve));
45
- }
46
- }
47
-
48
- async function startLifecycleMonitor(
49
- options: {
50
- useWebhook?: boolean;
51
- webhookSecret?: string;
52
- webhookUrl?: string;
53
- } = {},
54
- ) {
55
- const { monitorZaloProvider } = await import("./monitor.js");
56
- const abort = new AbortController();
57
- const runtime = createRuntimeEnv();
58
- const run = monitorZaloProvider({
59
- token: "test-token",
60
- account: TEST_ACCOUNT,
61
- config: TEST_CONFIG,
62
- runtime,
63
- abortSignal: abort.signal,
64
- ...options,
65
- });
66
- return { abort, runtime, run };
67
- }
68
-
69
- describe("monitorZaloProvider lifecycle", () => {
70
- afterEach(() => {
71
- vi.clearAllMocks();
72
- setActivePluginRegistry(createEmptyPluginRegistry());
73
- });
74
-
75
- it("stays alive in polling mode until abort", async () => {
76
- let settled = false;
77
- const { abort, runtime, run } = await startLifecycleMonitor();
78
- const monitoredRun = run.then(() => {
79
- settled = true;
80
- });
81
-
82
- await settleLifecycleWork();
83
- expect(getUpdatesMock).toHaveBeenCalledTimes(1);
84
-
85
- expect(getWebhookInfoMock).toHaveBeenCalledTimes(1);
86
- expect(deleteWebhookMock).not.toHaveBeenCalled();
87
- expect(getUpdatesMock).toHaveBeenCalledTimes(1);
88
- expect(settled).toBe(false);
89
-
90
- abort.abort();
91
- await monitoredRun;
92
-
93
- expect(settled).toBe(true);
94
- expect(runtime.log).toHaveBeenCalledWith(
95
- expect.stringContaining("Zalo provider stopped mode=polling"),
96
- );
97
- });
98
-
99
- it("deletes an existing webhook before polling", async () => {
100
- getWebhookInfoMock.mockResolvedValueOnce({
101
- ok: true,
102
- result: { url: "https://example.com/hooks/zalo" },
103
- });
104
-
105
- const { abort, runtime, run } = await startLifecycleMonitor();
106
-
107
- await settleLifecycleWork();
108
- expect(getUpdatesMock).toHaveBeenCalledTimes(1);
109
-
110
- expect(getWebhookInfoMock).toHaveBeenCalledTimes(1);
111
- expect(deleteWebhookMock).toHaveBeenCalledTimes(1);
112
- expect(runtime.log).toHaveBeenCalledWith(
113
- expect.stringContaining("Zalo polling mode ready (webhook disabled)"),
114
- );
115
-
116
- abort.abort();
117
- await run;
118
- });
119
-
120
- it("continues polling when webhook inspection returns 404", async () => {
121
- const { ZaloApiError } = await import("./api.js");
122
- getWebhookInfoMock.mockRejectedValueOnce(new ZaloApiError("Not Found", 404, "Not Found"));
123
-
124
- const { abort, runtime, run } = await startLifecycleMonitor();
125
-
126
- await settleLifecycleWork();
127
- expect(getUpdatesMock).toHaveBeenCalledTimes(1);
128
-
129
- expect(getWebhookInfoMock).toHaveBeenCalledTimes(1);
130
- expect(deleteWebhookMock).not.toHaveBeenCalled();
131
- expect(runtime.log).toHaveBeenCalledWith(
132
- expect.stringContaining("webhook inspection unavailable; continuing without webhook cleanup"),
133
- );
134
- expect(runtime.error).not.toHaveBeenCalled();
135
-
136
- abort.abort();
137
- await run;
138
- });
139
-
140
- it("waits for webhook deletion before finishing webhook shutdown", async () => {
141
- const registry = createEmptyPluginRegistry();
142
- setActivePluginRegistry(registry);
143
-
144
- let resolveSetWebhookCalled: (() => void) | undefined;
145
- const setWebhookCalled = new Promise<void>((resolve) => {
146
- resolveSetWebhookCalled = resolve;
147
- });
148
- setWebhookMock.mockImplementationOnce(async () => {
149
- resolveSetWebhookCalled?.();
150
- return { ok: true, result: { url: "" } };
151
- });
152
-
153
- let resolveDeleteWebhookCalled: (() => void) | undefined;
154
- const deleteWebhookCalled = new Promise<void>((resolve) => {
155
- resolveDeleteWebhookCalled = resolve;
156
- });
157
- let resolveDeleteWebhook: (() => void) | undefined;
158
- deleteWebhookMock.mockImplementationOnce(
159
- () =>
160
- new Promise((resolve) => {
161
- resolveDeleteWebhookCalled?.();
162
- resolveDeleteWebhook = () => resolve({ ok: true, result: { url: "" } });
163
- }),
164
- );
165
-
166
- let settled = false;
167
- const { abort, runtime, run } = await startLifecycleMonitor({
168
- useWebhook: true,
169
- webhookUrl: "https://example.com/hooks/zalo",
170
- webhookSecret: "supersecret", // pragma: allowlist secret
171
- });
172
- const monitoredRun = run.then(() => {
173
- settled = true;
174
- });
175
-
176
- await setWebhookCalled;
177
- await settleLifecycleWork();
178
- expect(setWebhookMock).toHaveBeenCalledTimes(1);
179
- expect(registry.httpRoutes).toHaveLength(2);
180
-
181
- abort.abort();
182
-
183
- await deleteWebhookCalled;
184
- expect(deleteWebhookMock).toHaveBeenCalledTimes(1);
185
- expect(deleteWebhookMock).toHaveBeenCalledWith("test-token", undefined, 5000);
186
- expect(settled).toBe(false);
187
- expect(registry.httpRoutes).toHaveLength(2);
188
-
189
- resolveDeleteWebhook?.();
190
- await monitoredRun;
191
-
192
- expect(settled).toBe(true);
193
- expect(registry.httpRoutes).toHaveLength(0);
194
- expect(runtime.log).toHaveBeenCalledWith(
195
- expect.stringContaining("Zalo provider stopped mode=webhook"),
196
- );
197
- });
198
- });
@@ -1,141 +0,0 @@
1
- import { withServer } from "openclaw/plugin-sdk/test-env";
2
- import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
3
- import {
4
- createLifecycleMonitorSetup,
5
- createTextUpdate,
6
- postWebhookReplay,
7
- settleAsyncWork,
8
- } from "./test-support/lifecycle-test-support.js";
9
- import {
10
- resetLifecycleTestState,
11
- sendMessageMock,
12
- setLifecycleRuntimeCore,
13
- startWebhookLifecycleMonitor,
14
- } from "./test-support/monitor-mocks-test-support.js";
15
-
16
- describe("Zalo pairing lifecycle", () => {
17
- const readAllowFromStoreMock = vi.fn(async () => [] as string[]);
18
- const upsertPairingRequestMock = vi.fn(async () => ({ code: "PAIRCODE", created: true }));
19
-
20
- beforeEach(async () => {
21
- await resetLifecycleTestState();
22
- setLifecycleRuntimeCore({
23
- pairing: {
24
- readAllowFromStore: readAllowFromStoreMock,
25
- upsertPairingRequest: upsertPairingRequestMock,
26
- },
27
- commands: {
28
- shouldComputeCommandAuthorized: vi.fn(() => false),
29
- resolveCommandAuthorizedFromAuthorizers: vi.fn(() => false),
30
- },
31
- });
32
- });
33
-
34
- afterAll(async () => {
35
- await resetLifecycleTestState();
36
- });
37
-
38
- function createPairingMonitorSetup() {
39
- return createLifecycleMonitorSetup({
40
- accountId: "acct-zalo-pairing",
41
- dmPolicy: "pairing",
42
- allowFrom: [],
43
- });
44
- }
45
-
46
- it("emits one pairing reply across duplicate webhook replay and scopes reads and writes to accountId", async () => {
47
- const monitor = await startWebhookLifecycleMonitor({
48
- ...createPairingMonitorSetup(),
49
- cacheKey: "zalo-pairing-lifecycle",
50
- });
51
-
52
- try {
53
- await withServer(
54
- (req, res) => monitor.route.handler(req, res),
55
- async (baseUrl) => {
56
- const { first, replay } = await postWebhookReplay({
57
- baseUrl,
58
- path: "/hooks/zalo",
59
- secret: "supersecret",
60
- payload: createTextUpdate({
61
- messageId: `zalo-pairing-${Date.now()}`,
62
- userId: "user-unauthorized",
63
- userName: "Unauthorized User",
64
- chatId: "dm-pairing-1",
65
- }),
66
- });
67
-
68
- expect(first.status).toBe(200);
69
- expect(replay.status).toBe(200);
70
- await settleAsyncWork();
71
- },
72
- );
73
-
74
- expect(readAllowFromStoreMock).toHaveBeenCalledTimes(1);
75
- expect(readAllowFromStoreMock).toHaveBeenCalledWith(
76
- expect.objectContaining({
77
- channel: "zalo",
78
- accountId: "acct-zalo-pairing",
79
- }),
80
- );
81
- expect(upsertPairingRequestMock).toHaveBeenCalledTimes(1);
82
- expect(upsertPairingRequestMock).toHaveBeenCalledWith(
83
- expect.objectContaining({
84
- channel: "zalo",
85
- accountId: "acct-zalo-pairing",
86
- id: "user-unauthorized",
87
- }),
88
- );
89
- expect(sendMessageMock).toHaveBeenCalledTimes(1);
90
- expect(sendMessageMock).toHaveBeenCalledWith(
91
- "zalo-token",
92
- expect.objectContaining({
93
- chat_id: "dm-pairing-1",
94
- text: expect.stringContaining("PAIRCODE"),
95
- }),
96
- undefined,
97
- );
98
- } finally {
99
- await monitor.stop();
100
- }
101
- });
102
-
103
- it("does not emit a second pairing reply when replay arrives after the first send fails", async () => {
104
- sendMessageMock.mockRejectedValueOnce(new Error("pairing send failed"));
105
-
106
- const monitor = await startWebhookLifecycleMonitor({
107
- ...createPairingMonitorSetup(),
108
- cacheKey: "zalo-pairing-lifecycle",
109
- });
110
-
111
- try {
112
- await withServer(
113
- (req, res) => monitor.route.handler(req, res),
114
- async (baseUrl) => {
115
- const { first, replay } = await postWebhookReplay({
116
- baseUrl,
117
- path: "/hooks/zalo",
118
- secret: "supersecret",
119
- payload: createTextUpdate({
120
- messageId: `zalo-pairing-retry-${Date.now()}`,
121
- userId: "user-unauthorized",
122
- userName: "Unauthorized User",
123
- chatId: "dm-pairing-1",
124
- }),
125
- settleBeforeReplay: true,
126
- });
127
-
128
- expect(first.status).toBe(200);
129
- expect(replay.status).toBe(200);
130
- await settleAsyncWork();
131
- },
132
- );
133
-
134
- expect(upsertPairingRequestMock).toHaveBeenCalledTimes(1);
135
- expect(sendMessageMock).toHaveBeenCalledTimes(1);
136
- expect(monitor.runtime.error).not.toHaveBeenCalled();
137
- } finally {
138
- await monitor.stop();
139
- }
140
- });
141
- });