@openclaw-china/shared 2026.3.12 → 2026.3.16
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/package.json +1 -1
- package/src/cli/china-setup.test.ts +341 -270
- package/src/cli/china-setup.ts +159 -95
- package/src/cli/index.ts +2 -2
- package/src/cli/install-hint.ts +1 -0
- package/src/cron/index.ts +16 -16
- package/src/file/file-utils.test.ts +141 -141
- package/src/file/file-utils.ts +284 -284
- package/src/file/index.ts +10 -10
- package/src/index.ts +3 -3
- package/src/logger/index.ts +1 -1
- package/src/logger/logger.ts +51 -51
- package/src/media/index.ts +22 -22
- package/vitest.config.ts +8 -8
package/package.json
CHANGED
|
@@ -1,270 +1,341 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
const cancelMock = vi.fn();
|
|
4
|
-
const confirmMock = vi.fn();
|
|
5
|
-
const introMock = vi.fn();
|
|
6
|
-
const noteMock = vi.fn();
|
|
7
|
-
const outroMock = vi.fn();
|
|
8
|
-
const selectMock = vi.fn();
|
|
9
|
-
const textMock = vi.fn();
|
|
10
|
-
|
|
11
|
-
vi.mock("@clack/prompts", () => ({
|
|
12
|
-
cancel: (...args: unknown[]) => cancelMock(...args),
|
|
13
|
-
confirm: (...args: unknown[]) => confirmMock(...args),
|
|
14
|
-
intro: (...args: unknown[]) => introMock(...args),
|
|
15
|
-
isCancel: () => false,
|
|
16
|
-
note: (...args: unknown[]) => noteMock(...args),
|
|
17
|
-
outro: (...args: unknown[]) => outroMock(...args),
|
|
18
|
-
select: (...args: unknown[]) => selectMock(...args),
|
|
19
|
-
text: (...args: unknown[]) => textMock(...args),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
import { registerChinaSetupCli } from "./china-setup.js";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
Object.defineProperty(process.
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
expect(wecomConfig?.
|
|
151
|
-
expect(wecomConfig?.
|
|
152
|
-
expect(wecomConfig?.
|
|
153
|
-
expect(wecomConfig?.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (
|
|
205
|
-
Object.defineProperty(process.
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
);
|
|
269
|
-
});
|
|
270
|
-
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const cancelMock = vi.fn();
|
|
4
|
+
const confirmMock = vi.fn();
|
|
5
|
+
const introMock = vi.fn();
|
|
6
|
+
const noteMock = vi.fn();
|
|
7
|
+
const outroMock = vi.fn();
|
|
8
|
+
const selectMock = vi.fn();
|
|
9
|
+
const textMock = vi.fn();
|
|
10
|
+
|
|
11
|
+
vi.mock("@clack/prompts", () => ({
|
|
12
|
+
cancel: (...args: unknown[]) => cancelMock(...args),
|
|
13
|
+
confirm: (...args: unknown[]) => confirmMock(...args),
|
|
14
|
+
intro: (...args: unknown[]) => introMock(...args),
|
|
15
|
+
isCancel: () => false,
|
|
16
|
+
note: (...args: unknown[]) => noteMock(...args),
|
|
17
|
+
outro: (...args: unknown[]) => outroMock(...args),
|
|
18
|
+
select: (...args: unknown[]) => selectMock(...args),
|
|
19
|
+
text: (...args: unknown[]) => textMock(...args),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import { registerChinaSetupCli } from "./china-setup.js";
|
|
23
|
+
import type { ChannelId } from "./china-setup.js";
|
|
24
|
+
|
|
25
|
+
type ActionHandler = () => void | Promise<void>;
|
|
26
|
+
|
|
27
|
+
type LoggerLike = {
|
|
28
|
+
info?: (message: string) => void;
|
|
29
|
+
warn?: (message: string) => void;
|
|
30
|
+
error?: (message: string) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type CommandNode = {
|
|
34
|
+
children: Map<string, CommandNode>;
|
|
35
|
+
actionHandler?: ActionHandler;
|
|
36
|
+
command: (name: string) => CommandNode;
|
|
37
|
+
description: (text: string) => CommandNode;
|
|
38
|
+
action: (handler: ActionHandler) => CommandNode;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type ConfigRoot = {
|
|
42
|
+
channels?: Record<string, Record<string, unknown>>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const CLI_STATE_KEY = Symbol.for("@openclaw-china/china-cli-state");
|
|
46
|
+
|
|
47
|
+
function createCommandNode(): CommandNode {
|
|
48
|
+
const node: CommandNode = {
|
|
49
|
+
children: new Map<string, CommandNode>(),
|
|
50
|
+
command(name: string): CommandNode {
|
|
51
|
+
const child = createCommandNode();
|
|
52
|
+
node.children.set(name, child);
|
|
53
|
+
return child;
|
|
54
|
+
},
|
|
55
|
+
description(): CommandNode {
|
|
56
|
+
return node;
|
|
57
|
+
},
|
|
58
|
+
action(handler: ActionHandler): CommandNode {
|
|
59
|
+
node.actionHandler = handler;
|
|
60
|
+
return node;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
return node;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runSetup(
|
|
67
|
+
initialConfig: ConfigRoot,
|
|
68
|
+
channels: readonly ChannelId[] = ["wecom"]
|
|
69
|
+
): Promise<{
|
|
70
|
+
writeConfigFile: ReturnType<typeof vi.fn>;
|
|
71
|
+
}> {
|
|
72
|
+
let registrar:
|
|
73
|
+
| ((ctx: { program: unknown; config?: unknown; logger?: LoggerLike }) => void | Promise<void>)
|
|
74
|
+
| undefined;
|
|
75
|
+
const writeConfigFile = vi.fn(async (_cfg: ConfigRoot) => {});
|
|
76
|
+
|
|
77
|
+
registerChinaSetupCli(
|
|
78
|
+
{
|
|
79
|
+
runtime: {
|
|
80
|
+
config: {
|
|
81
|
+
writeConfigFile,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
registerCli: (nextRegistrar) => {
|
|
85
|
+
registrar = nextRegistrar;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{ channels }
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const program = createCommandNode();
|
|
92
|
+
await registrar?.({
|
|
93
|
+
program,
|
|
94
|
+
config: initialConfig,
|
|
95
|
+
logger: {},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const setupCommand = program.children.get("china")?.children.get("setup");
|
|
99
|
+
expect(setupCommand?.actionHandler).toBeTypeOf("function");
|
|
100
|
+
await setupCommand?.actionHandler?.();
|
|
101
|
+
|
|
102
|
+
return { writeConfigFile };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe("china setup wecom", () => {
|
|
106
|
+
const stdinDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
|
|
107
|
+
const stdoutDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
|
|
108
|
+
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
vi.clearAllMocks();
|
|
111
|
+
delete (globalThis as Record<PropertyKey, unknown>)[CLI_STATE_KEY];
|
|
112
|
+
Object.defineProperty(process.stdin, "isTTY", {
|
|
113
|
+
configurable: true,
|
|
114
|
+
value: true,
|
|
115
|
+
});
|
|
116
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
117
|
+
configurable: true,
|
|
118
|
+
value: true,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
afterEach(() => {
|
|
123
|
+
if (stdinDescriptor) {
|
|
124
|
+
Object.defineProperty(process.stdin, "isTTY", stdinDescriptor);
|
|
125
|
+
}
|
|
126
|
+
if (stdoutDescriptor) {
|
|
127
|
+
Object.defineProperty(process.stdout, "isTTY", stdoutDescriptor);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("stores ws-only credentials for wecom setup", async () => {
|
|
132
|
+
selectMock.mockResolvedValueOnce("wecom");
|
|
133
|
+
textMock.mockResolvedValueOnce("bot-123").mockResolvedValueOnce("secret-456");
|
|
134
|
+
confirmMock.mockResolvedValueOnce(false);
|
|
135
|
+
|
|
136
|
+
const { writeConfigFile } = await runSetup({
|
|
137
|
+
channels: {
|
|
138
|
+
wecom: {
|
|
139
|
+
webhookPath: "/legacy-wecom",
|
|
140
|
+
token: "legacy-token",
|
|
141
|
+
encodingAESKey: "legacy-aes-key",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(writeConfigFile).toHaveBeenCalledTimes(1);
|
|
147
|
+
const savedConfig = writeConfigFile.mock.calls[0]?.[0] as ConfigRoot;
|
|
148
|
+
const wecomConfig = savedConfig.channels?.wecom;
|
|
149
|
+
|
|
150
|
+
expect(wecomConfig?.enabled).toBe(true);
|
|
151
|
+
expect(wecomConfig?.mode).toBe("ws");
|
|
152
|
+
expect(wecomConfig?.botId).toBe("bot-123");
|
|
153
|
+
expect(wecomConfig?.secret).toBe("secret-456");
|
|
154
|
+
expect(wecomConfig?.webhookPath).toBeUndefined();
|
|
155
|
+
expect(wecomConfig?.token).toBeUndefined();
|
|
156
|
+
expect(wecomConfig?.encodingAESKey).toBeUndefined();
|
|
157
|
+
|
|
158
|
+
const promptMessages = textMock.mock.calls.map((call) => {
|
|
159
|
+
const firstArg = call[0] as { message?: string } | undefined;
|
|
160
|
+
return firstArg?.message ?? "";
|
|
161
|
+
});
|
|
162
|
+
expect(promptMessages).toEqual(["WeCom botId(ws 长连接)", "WeCom secret(ws 长连接)"]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("marks wecom as configured when botId and secret already exist", async () => {
|
|
166
|
+
let selectOptions: Array<{ label?: string; value?: string }> = [];
|
|
167
|
+
selectMock.mockImplementationOnce(async (params: { options?: Array<{ label?: string; value?: string }> }) => {
|
|
168
|
+
selectOptions = params.options ?? [];
|
|
169
|
+
return "cancel";
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const { writeConfigFile } = await runSetup({
|
|
173
|
+
channels: {
|
|
174
|
+
wecom: {
|
|
175
|
+
botId: "existing-bot",
|
|
176
|
+
secret: "existing-secret",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(writeConfigFile).not.toHaveBeenCalled();
|
|
182
|
+
expect(selectOptions.some((option) => option.label === "WeCom(企业微信-智能机器人)(已配置)")).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("china setup wecom-kf", () => {
|
|
187
|
+
const stdinDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
|
|
188
|
+
const stdoutDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
|
|
189
|
+
|
|
190
|
+
beforeEach(() => {
|
|
191
|
+
vi.clearAllMocks();
|
|
192
|
+
delete (globalThis as Record<PropertyKey, unknown>)[CLI_STATE_KEY];
|
|
193
|
+
Object.defineProperty(process.stdin, "isTTY", {
|
|
194
|
+
configurable: true,
|
|
195
|
+
value: true,
|
|
196
|
+
});
|
|
197
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
198
|
+
configurable: true,
|
|
199
|
+
value: true,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
afterEach(() => {
|
|
204
|
+
if (stdinDescriptor) {
|
|
205
|
+
Object.defineProperty(process.stdin, "isTTY", stdinDescriptor);
|
|
206
|
+
}
|
|
207
|
+
if (stdoutDescriptor) {
|
|
208
|
+
Object.defineProperty(process.stdout, "isTTY", stdoutDescriptor);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("stores wecom-kf callback and api credentials", async () => {
|
|
213
|
+
selectMock.mockResolvedValueOnce("wecom-kf");
|
|
214
|
+
textMock
|
|
215
|
+
.mockResolvedValueOnce("/kf-hook")
|
|
216
|
+
.mockResolvedValueOnce("callback-token")
|
|
217
|
+
.mockResolvedValueOnce("encoding-aes-key")
|
|
218
|
+
.mockResolvedValueOnce("ww-test-corp")
|
|
219
|
+
.mockResolvedValueOnce("kf-secret")
|
|
220
|
+
.mockResolvedValueOnce("wk-test")
|
|
221
|
+
.mockResolvedValueOnce("你好,这里是 AI 客服");
|
|
222
|
+
|
|
223
|
+
const { writeConfigFile } = await runSetup({}, ["wecom-kf"]);
|
|
224
|
+
|
|
225
|
+
expect(writeConfigFile).toHaveBeenCalledTimes(1);
|
|
226
|
+
const savedConfig = writeConfigFile.mock.calls[0]?.[0] as ConfigRoot;
|
|
227
|
+
const wecomKfConfig = savedConfig.channels?.["wecom-kf"];
|
|
228
|
+
|
|
229
|
+
expect(wecomKfConfig?.enabled).toBe(true);
|
|
230
|
+
expect(wecomKfConfig?.webhookPath).toBe("/kf-hook");
|
|
231
|
+
expect(wecomKfConfig?.token).toBe("callback-token");
|
|
232
|
+
expect(wecomKfConfig?.encodingAESKey).toBe("encoding-aes-key");
|
|
233
|
+
expect(wecomKfConfig?.corpId).toBe("ww-test-corp");
|
|
234
|
+
expect(wecomKfConfig?.corpSecret).toBe("kf-secret");
|
|
235
|
+
expect(wecomKfConfig?.openKfId).toBe("wk-test");
|
|
236
|
+
expect(wecomKfConfig?.welcomeText).toBe("你好,这里是 AI 客服");
|
|
237
|
+
|
|
238
|
+
const promptMessages = textMock.mock.calls.map((call) => {
|
|
239
|
+
const firstArg = call[0] as { message?: string } | undefined;
|
|
240
|
+
return firstArg?.message ?? "";
|
|
241
|
+
});
|
|
242
|
+
expect(promptMessages).toEqual([
|
|
243
|
+
"Webhook 路径(默认 /wecom-kf)",
|
|
244
|
+
"微信客服回调 Token",
|
|
245
|
+
"微信客服回调 EncodingAESKey",
|
|
246
|
+
"corpId",
|
|
247
|
+
"微信客服 Secret",
|
|
248
|
+
"open_kfid",
|
|
249
|
+
"欢迎语(可选)",
|
|
250
|
+
]);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("china setup dingtalk", () => {
|
|
255
|
+
const stdinDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
|
|
256
|
+
const stdoutDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
|
|
257
|
+
|
|
258
|
+
beforeEach(() => {
|
|
259
|
+
vi.clearAllMocks();
|
|
260
|
+
delete (globalThis as Record<PropertyKey, unknown>)[CLI_STATE_KEY];
|
|
261
|
+
Object.defineProperty(process.stdin, "isTTY", {
|
|
262
|
+
configurable: true,
|
|
263
|
+
value: true,
|
|
264
|
+
});
|
|
265
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
266
|
+
configurable: true,
|
|
267
|
+
value: true,
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
afterEach(() => {
|
|
272
|
+
if (stdinDescriptor) {
|
|
273
|
+
Object.defineProperty(process.stdin, "isTTY", stdinDescriptor);
|
|
274
|
+
}
|
|
275
|
+
if (stdoutDescriptor) {
|
|
276
|
+
Object.defineProperty(process.stdout, "isTTY", stdoutDescriptor);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("stores gateway token when dingtalk AI Card streaming is enabled", async () => {
|
|
281
|
+
let registrar:
|
|
282
|
+
| ((ctx: { program: unknown; config?: unknown; logger?: LoggerLike }) => void | Promise<void>)
|
|
283
|
+
| undefined;
|
|
284
|
+
const writeConfigFile = vi.fn(async (_cfg: ConfigRoot) => {});
|
|
285
|
+
|
|
286
|
+
registerChinaSetupCli(
|
|
287
|
+
{
|
|
288
|
+
runtime: {
|
|
289
|
+
config: {
|
|
290
|
+
writeConfigFile,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
registerCli: (nextRegistrar) => {
|
|
294
|
+
registrar = nextRegistrar;
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{ channels: ["dingtalk"] }
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
selectMock.mockResolvedValueOnce("dingtalk");
|
|
301
|
+
textMock.mockResolvedValueOnce("ding-app-key");
|
|
302
|
+
textMock.mockResolvedValueOnce("ding-app-secret");
|
|
303
|
+
confirmMock.mockResolvedValueOnce(true);
|
|
304
|
+
textMock.mockResolvedValueOnce("gateway-token-123");
|
|
305
|
+
|
|
306
|
+
const program = createCommandNode();
|
|
307
|
+
await registrar?.({
|
|
308
|
+
program,
|
|
309
|
+
config: {
|
|
310
|
+
gateway: {
|
|
311
|
+
auth: {
|
|
312
|
+
token: "global-token",
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
logger: {},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const setupCommand = program.children.get("china")?.children.get("setup");
|
|
320
|
+
expect(setupCommand?.actionHandler).toBeTypeOf("function");
|
|
321
|
+
await setupCommand?.actionHandler?.();
|
|
322
|
+
|
|
323
|
+
expect(writeConfigFile).toHaveBeenCalledTimes(1);
|
|
324
|
+
const savedConfig = writeConfigFile.mock.calls[0]?.[0] as ConfigRoot;
|
|
325
|
+
const dingtalkConfig = savedConfig.channels?.dingtalk;
|
|
326
|
+
|
|
327
|
+
expect(dingtalkConfig?.enabled).toBe(true);
|
|
328
|
+
expect(dingtalkConfig?.clientId).toBe("ding-app-key");
|
|
329
|
+
expect(dingtalkConfig?.clientSecret).toBe("ding-app-secret");
|
|
330
|
+
expect(dingtalkConfig?.enableAICard).toBe(true);
|
|
331
|
+
expect(dingtalkConfig?.gatewayToken).toBe("gateway-token-123");
|
|
332
|
+
|
|
333
|
+
const promptMessages = textMock.mock.calls.map((call) => {
|
|
334
|
+
const firstArg = call[0] as { message?: string } | undefined;
|
|
335
|
+
return firstArg?.message ?? "";
|
|
336
|
+
});
|
|
337
|
+
expect(promptMessages).toContain(
|
|
338
|
+
"OpenClaw Gateway Token(流式输出必需;留空则使用全局 gateway.auth.token)"
|
|
339
|
+
);
|
|
340
|
+
});
|
|
341
|
+
});
|