@openclaw/zalouser 2026.3.12 → 2026.5.1-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 (67) hide show
  1. package/README.md +4 -3
  2. package/api.ts +9 -0
  3. package/channel-plugin-api.ts +3 -0
  4. package/contract-api.ts +2 -0
  5. package/doctor-contract-api.ts +1 -0
  6. package/index.ts +29 -24
  7. package/openclaw.plugin.json +288 -1
  8. package/package.json +38 -11
  9. package/runtime-api.ts +67 -0
  10. package/secret-contract-api.ts +4 -0
  11. package/setup-entry.ts +9 -0
  12. package/setup-plugin-api.ts +2 -0
  13. package/src/accounts.runtime.ts +1 -0
  14. package/src/accounts.test-mocks.ts +14 -0
  15. package/src/accounts.test.ts +53 -1
  16. package/src/accounts.ts +52 -37
  17. package/src/channel-api.ts +20 -0
  18. package/src/channel.adapters.ts +390 -0
  19. package/src/channel.directory.test.ts +48 -61
  20. package/src/channel.runtime.ts +12 -0
  21. package/src/channel.sendpayload.test.ts +42 -37
  22. package/src/channel.setup.test.ts +33 -0
  23. package/src/channel.setup.ts +12 -0
  24. package/src/channel.test.ts +258 -56
  25. package/src/channel.ts +176 -692
  26. package/src/config-schema.ts +5 -5
  27. package/src/directory.ts +54 -0
  28. package/src/doctor-contract.ts +156 -0
  29. package/src/doctor.test.ts +77 -0
  30. package/src/doctor.ts +37 -0
  31. package/src/group-policy.test.ts +4 -4
  32. package/src/group-policy.ts +4 -2
  33. package/src/monitor.account-scope.test.ts +4 -10
  34. package/src/monitor.group-gating.test.ts +319 -190
  35. package/src/monitor.ts +233 -182
  36. package/src/probe.ts +3 -2
  37. package/src/qr-temp-file.ts +1 -1
  38. package/src/reaction.ts +5 -2
  39. package/src/runtime.ts +6 -3
  40. package/src/security-audit.test.ts +80 -0
  41. package/src/security-audit.ts +71 -0
  42. package/src/send.test.ts +2 -2
  43. package/src/send.ts +3 -3
  44. package/src/session-route.ts +121 -0
  45. package/src/setup-core.ts +33 -0
  46. package/src/setup-surface.test.ts +363 -0
  47. package/src/setup-surface.ts +470 -0
  48. package/src/setup-test-helpers.ts +42 -0
  49. package/src/shared.ts +92 -0
  50. package/src/status-issues.test.ts +5 -17
  51. package/src/status-issues.ts +18 -30
  52. package/src/test-helpers.ts +26 -0
  53. package/src/text-styles.test.ts +1 -1
  54. package/src/text-styles.ts +5 -2
  55. package/src/tool.test.ts +66 -3
  56. package/src/tool.ts +76 -14
  57. package/src/types.ts +3 -3
  58. package/src/zalo-js.credentials.test.ts +465 -0
  59. package/src/zalo-js.test-mocks.ts +89 -0
  60. package/src/zalo-js.ts +491 -274
  61. package/src/zca-client.test.ts +24 -0
  62. package/src/zca-client.ts +24 -58
  63. package/src/zca-constants.ts +55 -0
  64. package/test-api.ts +21 -0
  65. package/tsconfig.json +16 -0
  66. package/CHANGELOG.md +0 -101
  67. package/src/onboarding.ts +0 -340
@@ -1,31 +1,21 @@
1
- import type { ReplyPayload } from "openclaw/plugin-sdk/zalouser";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
1
  import {
4
- installSendPayloadContractSuite,
5
- primeSendMock,
6
- } from "../../../src/test-utils/send-payload-contract.js";
2
+ installChannelOutboundPayloadContractSuite,
3
+ primeChannelOutboundSendMock,
4
+ type OutboundPayloadHarnessParams,
5
+ } from "openclaw/plugin-sdk/channel-contract-testing";
6
+ import { beforeEach, describe, expect, it, vi } from "vitest";
7
+ import "./accounts.test-mocks.js";
8
+ import "./zalo-js.test-mocks.js";
9
+ import type { ReplyPayload } from "../runtime-api.js";
7
10
  import { zalouserPlugin } from "./channel.js";
8
11
  import { setZalouserRuntime } from "./runtime.js";
12
+ import * as sendModule from "./send.js";
9
13
 
10
14
  vi.mock("./send.js", () => ({
11
15
  sendMessageZalouser: vi.fn().mockResolvedValue({ ok: true, messageId: "zlu-1" }),
12
16
  sendReactionZalouser: vi.fn().mockResolvedValue({ ok: true }),
13
17
  }));
14
18
 
15
- vi.mock("./accounts.js", async (importOriginal) => {
16
- const actual = (await importOriginal()) as Record<string, unknown>;
17
- return {
18
- ...actual,
19
- resolveZalouserAccountSync: () => ({
20
- accountId: "default",
21
- profile: "default",
22
- name: "test",
23
- enabled: true,
24
- config: {},
25
- }),
26
- };
27
- });
28
-
29
19
  function baseCtx(payload: ReplyPayload) {
30
20
  return {
31
21
  cfg: {},
@@ -38,7 +28,7 @@ function baseCtx(payload: ReplyPayload) {
38
28
  describe("zalouserPlugin outbound sendPayload", () => {
39
29
  let mockedSend: ReturnType<typeof vi.mocked<(typeof import("./send.js"))["sendMessageZalouser"]>>;
40
30
 
41
- beforeEach(async () => {
31
+ beforeEach(() => {
42
32
  setZalouserRuntime({
43
33
  channel: {
44
34
  text: {
@@ -47,10 +37,8 @@ describe("zalouserPlugin outbound sendPayload", () => {
47
37
  },
48
38
  },
49
39
  } as never);
50
- const mod = await import("./send.js");
51
- mockedSend = vi.mocked(mod.sendMessageZalouser);
52
- mockedSend.mockClear();
53
- mockedSend.mockResolvedValue({ ok: true, messageId: "zlu-1" });
40
+ mockedSend = vi.mocked(sendModule.sendMessageZalouser);
41
+ primeChannelOutboundSendMock(mockedSend, { ok: true, messageId: "zlu-1" });
54
42
  });
55
43
 
56
44
  it("group target delegates with isGroup=true and stripped threadId", async () => {
@@ -123,27 +111,45 @@ describe("zalouserPlugin outbound sendPayload", () => {
123
111
  );
124
112
  expect(result).toMatchObject({ channel: "zalouser", messageId: "zlu-code" });
125
113
  });
114
+ });
115
+
116
+ describe("zalouserPlugin outbound payload contract", () => {
117
+ function createZalouserHarness(params: OutboundPayloadHarnessParams) {
118
+ const mockedSend = vi.mocked(sendModule.sendMessageZalouser);
119
+ setZalouserRuntime({
120
+ channel: {
121
+ text: {
122
+ resolveChunkMode: vi.fn(() => "length"),
123
+ resolveTextChunkLimit: vi.fn(() => 1200),
124
+ },
125
+ },
126
+ } as never);
127
+ primeChannelOutboundSendMock(mockedSend, { ok: true, messageId: "zlu-1" }, params.sendResults);
128
+ const ctx = {
129
+ cfg: {},
130
+ to: "user:987654321",
131
+ text: "",
132
+ payload: params.payload,
133
+ };
134
+ return {
135
+ run: async () => await zalouserPlugin.outbound!.sendPayload!(ctx),
136
+ sendMock: mockedSend,
137
+ to: "987654321",
138
+ };
139
+ }
126
140
 
127
- installSendPayloadContractSuite({
141
+ installChannelOutboundPayloadContractSuite({
128
142
  channel: "zalouser",
129
143
  chunking: { mode: "passthrough", longTextLength: 3000 },
130
- createHarness: ({ payload, sendResults }) => {
131
- primeSendMock(mockedSend, { ok: true, messageId: "zlu-1" }, sendResults);
132
- return {
133
- run: async () => await zalouserPlugin.outbound!.sendPayload!(baseCtx(payload)),
134
- sendMock: mockedSend,
135
- to: "987654321",
136
- };
137
- },
144
+ createHarness: createZalouserHarness,
138
145
  });
139
146
  });
140
147
 
141
148
  describe("zalouserPlugin messaging target normalization", () => {
142
149
  it("normalizes user/group aliases to canonical targets", () => {
143
150
  const normalize = zalouserPlugin.messaging?.normalizeTarget;
144
- expect(normalize).toBeTypeOf("function");
145
151
  if (!normalize) {
146
- return;
152
+ throw new Error("normalizeTarget unavailable");
147
153
  }
148
154
  expect(normalize("zlu:g:30003")).toBe("group:30003");
149
155
  expect(normalize("zalouser:u:20002")).toBe("user:20002");
@@ -154,9 +160,8 @@ describe("zalouserPlugin messaging target normalization", () => {
154
160
 
155
161
  it("treats canonical and provider-native user/group targets as ids", () => {
156
162
  const looksLikeId = zalouserPlugin.messaging?.targetResolver?.looksLikeId;
157
- expect(looksLikeId).toBeTypeOf("function");
158
163
  if (!looksLikeId) {
159
- return;
164
+ throw new Error("looksLikeId unavailable");
160
165
  }
161
166
  expect(looksLikeId("user:20002")).toBe(true);
162
167
  expect(looksLikeId("group:30003")).toBe(true);
@@ -0,0 +1,33 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createPluginSetupWizardStatus } from "openclaw/plugin-sdk/plugin-test-runtime";
5
+ import { withEnvAsync } from "openclaw/plugin-sdk/test-env";
6
+ import { describe, expect, it } from "vitest";
7
+ import "./zalo-js.test-mocks.js";
8
+ import { zalouserSetupPlugin } from "./setup-test-helpers.js";
9
+
10
+ const zalouserSetupGetStatus = createPluginSetupWizardStatus(zalouserSetupPlugin);
11
+
12
+ describe("zalouser setup plugin", () => {
13
+ it("builds setup status without an initialized runtime", async () => {
14
+ const stateDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-zalouser-setup-"));
15
+
16
+ try {
17
+ await withEnvAsync({ OPENCLAW_STATE_DIR: stateDir }, async () => {
18
+ await expect(
19
+ zalouserSetupGetStatus({
20
+ cfg: {},
21
+ accountOverrides: {},
22
+ }),
23
+ ).resolves.toMatchObject({
24
+ channel: "zalouser",
25
+ configured: false,
26
+ statusLines: ["Zalo Personal: needs QR login"],
27
+ });
28
+ });
29
+ } finally {
30
+ await rm(stateDir, { recursive: true, force: true });
31
+ }
32
+ });
33
+ });
@@ -0,0 +1,12 @@
1
+ import type { ResolvedZalouserAccount } from "./accounts.js";
2
+ import type { ChannelPlugin } from "./channel-api.js";
3
+ import { zalouserSetupAdapter } from "./setup-core.js";
4
+ import { zalouserSetupWizard } from "./setup-surface.js";
5
+ import { createZalouserPluginBase } from "./shared.js";
6
+
7
+ export const zalouserSetupPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
8
+ ...createZalouserPluginBase({
9
+ setupWizard: zalouserSetupWizard,
10
+ setup: zalouserSetupAdapter,
11
+ }),
12
+ };
@@ -1,10 +1,29 @@
1
+ import { createNonExitingRuntimeEnv } from "openclaw/plugin-sdk/plugin-test-runtime";
1
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { zalouserPlugin } from "./channel.js";
3
+ import "./zalo-js.test-mocks.js";
4
+ import {
5
+ zalouserAuthAdapter,
6
+ zalouserGroupsAdapter,
7
+ zalouserMessageActions,
8
+ zalouserOutboundAdapter,
9
+ zalouserPairingTextAdapter,
10
+ zalouserResolverAdapter,
11
+ zalouserSecurityAdapter,
12
+ } from "./channel.adapters.js";
3
13
  import { setZalouserRuntime } from "./runtime.js";
4
14
  import { sendMessageZalouser, sendReactionZalouser } from "./send.js";
15
+ import {
16
+ listZaloFriendsMatchingMock,
17
+ startZaloQrLoginMock,
18
+ waitForZaloQrLoginMock,
19
+ } from "./zalo-js.test-mocks.js";
5
20
 
6
- vi.mock("./send.js", async (importOriginal) => {
7
- const actual = (await importOriginal()) as Record<string, unknown>;
21
+ vi.mock("./qr-temp-file.js", () => ({
22
+ writeQrDataUrlToTempFile: vi.fn(async () => null),
23
+ }));
24
+
25
+ vi.mock("./send.js", async () => {
26
+ const actual = (await vi.importActual("./send.js")) as Record<string, unknown>;
8
27
  return {
9
28
  ...actual,
10
29
  sendMessageZalouser: vi.fn(async () => ({ ok: true, messageId: "mid-1" })),
@@ -15,6 +34,56 @@ vi.mock("./send.js", async (importOriginal) => {
15
34
  const mockSendMessage = vi.mocked(sendMessageZalouser);
16
35
  const mockSendReaction = vi.mocked(sendReactionZalouser);
17
36
 
37
+ function requireZalouserSendText() {
38
+ const sendText = zalouserOutboundAdapter.sendText;
39
+ if (!sendText) {
40
+ throw new Error("zalouser outbound.sendText unavailable");
41
+ }
42
+ return sendText;
43
+ }
44
+
45
+ function getResolveToolPolicy() {
46
+ const resolveToolPolicy = zalouserGroupsAdapter.resolveToolPolicy;
47
+ if (!resolveToolPolicy) {
48
+ throw new Error("resolveToolPolicy unavailable");
49
+ }
50
+ return resolveToolPolicy;
51
+ }
52
+
53
+ function requireZalouserResolveRequireMention() {
54
+ const resolveRequireMention = zalouserGroupsAdapter.resolveRequireMention;
55
+ if (!resolveRequireMention) {
56
+ throw new Error("resolveRequireMention unavailable");
57
+ }
58
+ return resolveRequireMention;
59
+ }
60
+
61
+ function requireZalouserPairingNormalizer() {
62
+ const normalizeAllowEntry = zalouserPairingTextAdapter.normalizeAllowEntry;
63
+ if (!normalizeAllowEntry) {
64
+ throw new Error("pairing.normalizeAllowEntry unavailable");
65
+ }
66
+ return normalizeAllowEntry;
67
+ }
68
+
69
+ function resolveGroupToolPolicy(
70
+ groups: Record<string, { tools: { allow?: string[]; deny?: string[] } }>,
71
+ groupId: string,
72
+ ) {
73
+ return getResolveToolPolicy()({
74
+ cfg: {
75
+ channels: {
76
+ zalouser: {
77
+ groups,
78
+ },
79
+ },
80
+ },
81
+ accountId: "default",
82
+ groupId,
83
+ groupChannel: groupId,
84
+ });
85
+ }
86
+
18
87
  describe("zalouser outbound", () => {
19
88
  beforeEach(() => {
20
89
  mockSendMessage.mockClear();
@@ -29,11 +98,7 @@ describe("zalouser outbound", () => {
29
98
  });
30
99
 
31
100
  it("passes markdown chunk settings through sendText", async () => {
32
- const sendText = zalouserPlugin.outbound?.sendText;
33
- expect(sendText).toBeTypeOf("function");
34
- if (!sendText) {
35
- return;
36
- }
101
+ const sendText = requireZalouserSendText();
37
102
 
38
103
  const result = await sendText({
39
104
  cfg: { channels: { zalouser: { enabled: true } } } as never,
@@ -63,18 +128,67 @@ describe("zalouser outbound", () => {
63
128
  });
64
129
  });
65
130
 
131
+ describe("zalouser outbound chunking", () => {
132
+ it("chunks outbound text without requiring Zalouser runtime initialization", () => {
133
+ const chunker = zalouserOutboundAdapter.chunker;
134
+ if (!chunker) {
135
+ throw new Error("zalouser outbound.chunker unavailable");
136
+ }
137
+
138
+ expect(chunker("alpha beta", 5)).toEqual(["alpha", "beta"]);
139
+ });
140
+ });
141
+
66
142
  describe("zalouser channel policies", () => {
67
143
  beforeEach(() => {
68
144
  mockSendReaction.mockClear();
69
145
  mockSendReaction.mockResolvedValue({ ok: true });
70
146
  });
71
147
 
72
- it("resolves requireMention from group config", () => {
73
- const resolveRequireMention = zalouserPlugin.groups?.resolveRequireMention;
74
- expect(resolveRequireMention).toBeTypeOf("function");
75
- if (!resolveRequireMention) {
76
- return;
148
+ it("normalizes dm allowlist entries after trimming channel prefixes", () => {
149
+ const resolveDmPolicy = zalouserSecurityAdapter.resolveDmPolicy;
150
+ if (!resolveDmPolicy) {
151
+ throw new Error("resolveDmPolicy unavailable");
77
152
  }
153
+
154
+ const cfg = {
155
+ channels: {
156
+ zalouser: {
157
+ dmPolicy: "allowlist",
158
+ allowFrom: [" zlu:123456 "],
159
+ },
160
+ },
161
+ } as never;
162
+ const account = {
163
+ accountId: "default",
164
+ enabled: true,
165
+ authenticated: false,
166
+ profile: "default",
167
+ config: {
168
+ dmPolicy: "allowlist",
169
+ allowFrom: [" zlu:123456 "],
170
+ },
171
+ } as never;
172
+
173
+ const result = resolveDmPolicy({ cfg, account });
174
+ if (!result) {
175
+ throw new Error("zalouser resolveDmPolicy returned null");
176
+ }
177
+
178
+ expect(result.policy).toBe("allowlist");
179
+ expect(result.allowFrom).toEqual([" zlu:123456 "]);
180
+ expect(result.normalizeEntry?.(" zlu:123456 ")).toBe("123456");
181
+ });
182
+
183
+ it("normalizes pairing allowlist entries after trimming channel prefixes", () => {
184
+ const normalizeAllowEntry = requireZalouserPairingNormalizer();
185
+
186
+ expect(normalizeAllowEntry(" zlu:123456 ")).toBe("123456");
187
+ expect(normalizeAllowEntry(" zalouser:654321 ")).toBe("654321");
188
+ });
189
+
190
+ it("resolves requireMention from group config", () => {
191
+ const resolveRequireMention = requireZalouserResolveRequireMention();
78
192
  const requireMention = resolveRequireMention({
79
193
  cfg: {
80
194
  channels: {
@@ -93,56 +207,21 @@ describe("zalouser channel policies", () => {
93
207
  });
94
208
 
95
209
  it("resolves group tool policy by explicit group id", () => {
96
- const resolveToolPolicy = zalouserPlugin.groups?.resolveToolPolicy;
97
- expect(resolveToolPolicy).toBeTypeOf("function");
98
- if (!resolveToolPolicy) {
99
- return;
100
- }
101
- const policy = resolveToolPolicy({
102
- cfg: {
103
- channels: {
104
- zalouser: {
105
- groups: {
106
- "123": { tools: { allow: ["search"] } },
107
- },
108
- },
109
- },
110
- },
111
- accountId: "default",
112
- groupId: "123",
113
- groupChannel: "123",
114
- });
210
+ const policy = resolveGroupToolPolicy({ "123": { tools: { allow: ["search"] } } }, "123");
115
211
  expect(policy).toEqual({ allow: ["search"] });
116
212
  });
117
213
 
118
214
  it("falls back to wildcard group policy", () => {
119
- const resolveToolPolicy = zalouserPlugin.groups?.resolveToolPolicy;
120
- expect(resolveToolPolicy).toBeTypeOf("function");
121
- if (!resolveToolPolicy) {
122
- return;
123
- }
124
- const policy = resolveToolPolicy({
125
- cfg: {
126
- channels: {
127
- zalouser: {
128
- groups: {
129
- "*": { tools: { deny: ["system.run"] } },
130
- },
131
- },
132
- },
133
- },
134
- accountId: "default",
135
- groupId: "missing",
136
- groupChannel: "missing",
137
- });
215
+ const policy = resolveGroupToolPolicy({ "*": { tools: { deny: ["system.run"] } } }, "missing");
138
216
  expect(policy).toEqual({ deny: ["system.run"] });
139
217
  });
140
218
 
141
219
  it("handles react action", async () => {
142
- const actions = zalouserPlugin.actions;
143
- expect(actions?.listActions?.({ cfg: { channels: { zalouser: { enabled: true } } } })).toEqual([
144
- "react",
145
- ]);
220
+ const actions = zalouserMessageActions;
221
+ expect(
222
+ actions?.describeMessageTool?.({ cfg: { channels: { zalouser: { enabled: true } } } })
223
+ ?.actions,
224
+ ).toEqual(["react"]);
146
225
  const result = await actions?.handleAction?.({
147
226
  channel: "zalouser",
148
227
  action: "react",
@@ -170,6 +249,129 @@ describe("zalouser channel policies", () => {
170
249
  emoji: "👍",
171
250
  remove: false,
172
251
  });
173
- expect(result).toBeDefined();
252
+ expect(result).toMatchObject({
253
+ content: [{ type: "text", text: "Reacted 👍 on 111" }],
254
+ details: {
255
+ messageId: "111",
256
+ cliMsgId: "222",
257
+ threadId: "123456",
258
+ },
259
+ });
260
+ });
261
+
262
+ it("honors the selected Zalouser account during discovery", () => {
263
+ const actions = zalouserMessageActions;
264
+ const cfg = {
265
+ channels: {
266
+ zalouser: {
267
+ enabled: true,
268
+ profile: "default",
269
+ accounts: {
270
+ default: {
271
+ enabled: false,
272
+ profile: "default",
273
+ },
274
+ work: {
275
+ enabled: true,
276
+ profile: "work",
277
+ },
278
+ },
279
+ },
280
+ },
281
+ };
282
+
283
+ expect(actions?.describeMessageTool?.({ cfg, accountId: "default" })).toBeNull();
284
+ expect(actions?.describeMessageTool?.({ cfg, accountId: "work" })?.actions).toEqual(["react"]);
285
+ });
286
+ });
287
+
288
+ describe("zalouser account resolution", () => {
289
+ beforeEach(() => {
290
+ listZaloFriendsMatchingMock.mockReset();
291
+ startZaloQrLoginMock.mockReset();
292
+ waitForZaloQrLoginMock.mockReset();
293
+ });
294
+
295
+ it("uses the configured default account for omitted target lookup", async () => {
296
+ const resolveTargets = zalouserResolverAdapter.resolveTargets;
297
+ if (!resolveTargets) {
298
+ throw new Error("zalouser resolver.resolveTargets unavailable");
299
+ }
300
+
301
+ listZaloFriendsMatchingMock.mockResolvedValue([
302
+ { userId: "42", displayName: "Work User" } as never,
303
+ ]);
304
+
305
+ const result = await resolveTargets({
306
+ cfg: {
307
+ channels: {
308
+ zalouser: {
309
+ defaultAccount: "work",
310
+ accounts: {
311
+ work: {
312
+ profile: "work-profile",
313
+ },
314
+ },
315
+ },
316
+ },
317
+ } as never,
318
+ inputs: ["Work User"],
319
+ kind: "user",
320
+ runtime: createNonExitingRuntimeEnv(),
321
+ });
322
+
323
+ expect(listZaloFriendsMatchingMock).toHaveBeenCalledWith("work-profile", "Work User");
324
+ expect(result).toEqual([
325
+ expect.objectContaining({
326
+ input: "Work User",
327
+ resolved: true,
328
+ id: "42",
329
+ name: "Work User",
330
+ }),
331
+ ]);
332
+ });
333
+
334
+ it("uses the configured default account for omitted qr login", async () => {
335
+ const login = zalouserAuthAdapter.login;
336
+ if (!login) {
337
+ throw new Error("zalouser auth.login unavailable");
338
+ }
339
+
340
+ startZaloQrLoginMock.mockResolvedValue({
341
+ message: "qr ready",
342
+ qrDataUrl: "data:image/png;base64,abc",
343
+ } as never);
344
+ waitForZaloQrLoginMock.mockResolvedValue({
345
+ connected: true,
346
+ userId: "u-1",
347
+ displayName: "Work User",
348
+ } as never);
349
+
350
+ const runtime = createNonExitingRuntimeEnv();
351
+
352
+ await login({
353
+ cfg: {
354
+ channels: {
355
+ zalouser: {
356
+ defaultAccount: "work",
357
+ accounts: {
358
+ work: {
359
+ profile: "work-profile",
360
+ },
361
+ },
362
+ },
363
+ },
364
+ } as never,
365
+ runtime,
366
+ });
367
+
368
+ expect(startZaloQrLoginMock).toHaveBeenCalledWith({
369
+ profile: "work-profile",
370
+ timeoutMs: 35_000,
371
+ });
372
+ expect(waitForZaloQrLoginMock).toHaveBeenCalledWith({
373
+ profile: "work-profile",
374
+ timeoutMs: 180_000,
375
+ });
174
376
  });
175
377
  });