@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.
- package/README.md +4 -3
- package/api.ts +9 -0
- package/channel-plugin-api.ts +3 -0
- package/contract-api.ts +2 -0
- package/doctor-contract-api.ts +1 -0
- package/index.ts +29 -24
- package/openclaw.plugin.json +288 -1
- package/package.json +38 -11
- package/runtime-api.ts +67 -0
- package/secret-contract-api.ts +4 -0
- package/setup-entry.ts +9 -0
- package/setup-plugin-api.ts +2 -0
- package/src/accounts.runtime.ts +1 -0
- package/src/accounts.test-mocks.ts +14 -0
- package/src/accounts.test.ts +53 -1
- package/src/accounts.ts +52 -37
- package/src/channel-api.ts +20 -0
- package/src/channel.adapters.ts +390 -0
- package/src/channel.directory.test.ts +48 -61
- package/src/channel.runtime.ts +12 -0
- package/src/channel.sendpayload.test.ts +42 -37
- package/src/channel.setup.test.ts +33 -0
- package/src/channel.setup.ts +12 -0
- package/src/channel.test.ts +258 -56
- package/src/channel.ts +176 -692
- package/src/config-schema.ts +5 -5
- package/src/directory.ts +54 -0
- package/src/doctor-contract.ts +156 -0
- package/src/doctor.test.ts +77 -0
- package/src/doctor.ts +37 -0
- package/src/group-policy.test.ts +4 -4
- package/src/group-policy.ts +4 -2
- package/src/monitor.account-scope.test.ts +4 -10
- package/src/monitor.group-gating.test.ts +319 -190
- package/src/monitor.ts +233 -182
- package/src/probe.ts +3 -2
- package/src/qr-temp-file.ts +1 -1
- package/src/reaction.ts +5 -2
- package/src/runtime.ts +6 -3
- package/src/security-audit.test.ts +80 -0
- package/src/security-audit.ts +71 -0
- package/src/send.test.ts +2 -2
- package/src/send.ts +3 -3
- package/src/session-route.ts +121 -0
- package/src/setup-core.ts +33 -0
- package/src/setup-surface.test.ts +363 -0
- package/src/setup-surface.ts +470 -0
- package/src/setup-test-helpers.ts +42 -0
- package/src/shared.ts +92 -0
- package/src/status-issues.test.ts +5 -17
- package/src/status-issues.ts +18 -30
- package/src/test-helpers.ts +26 -0
- package/src/text-styles.test.ts +1 -1
- package/src/text-styles.ts +5 -2
- package/src/tool.test.ts +66 -3
- package/src/tool.ts +76 -14
- package/src/types.ts +3 -3
- package/src/zalo-js.credentials.test.ts +465 -0
- package/src/zalo-js.test-mocks.ts +89 -0
- package/src/zalo-js.ts +491 -274
- package/src/zca-client.test.ts +24 -0
- package/src/zca-client.ts +24 -58
- package/src/zca-constants.ts +55 -0
- package/test-api.ts +21 -0
- package/tsconfig.json +16 -0
- package/CHANGELOG.md +0 -101
- 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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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(
|
|
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
|
-
|
|
51
|
-
mockedSend
|
|
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
|
-
|
|
141
|
+
installChannelOutboundPayloadContractSuite({
|
|
128
142
|
channel: "zalouser",
|
|
129
143
|
chunking: { mode: "passthrough", longTextLength: 3000 },
|
|
130
|
-
createHarness:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/src/channel.test.ts
CHANGED
|
@@ -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
|
|
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("./
|
|
7
|
-
|
|
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 =
|
|
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("
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
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 =
|
|
143
|
-
expect(
|
|
144
|
-
|
|
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).
|
|
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
|
});
|