惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
Microsoft Security Blog
Microsoft Security Blog
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
V2EX
酷 壳 – CoolShell
酷 壳 – CoolShell
量子位
博客园_首页
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
A
About on SuperTechFans
T
The Blog of Author Tim Ferriss
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
P
Proofpoint News Feed
博客园 - 司徒正美
有赞技术团队
有赞技术团队
Engineering at Meta
Engineering at Meta
Last Week in AI
Last Week in AI
MongoDB | Blog
MongoDB | Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
Configure: defer channel status until selection (#68007) ...
gumadeiras · 2026-04-18 · via Recent Commits to openclaw:main

@@ -0,0 +1,267 @@

1+

import { beforeEach, describe, expect, it, vi } from "vitest";

2+3+

const select = vi.hoisted(() => vi.fn());

4+

const confirm = vi.hoisted(() => vi.fn());

5+

const note = vi.hoisted(() => vi.fn());

6+

const chatChannels = vi.hoisted(() =>

7+

vi.fn(() => [

8+

{ id: "telegram", label: "Telegram" },

9+

{ id: "twitch", label: "Twitch" },

10+

]),

11+

);

12+13+

vi.mock("../channels/chat-meta.js", () => ({

14+

listChatChannels: () => chatChannels(),

15+

}));

16+17+

vi.mock("../terminal/note.js", () => ({

18+

note: (...args: unknown[]) => note(...args),

19+

}));

20+21+

vi.mock("./configure.shared.js", () => ({

22+

select: (params: unknown) => select(params),

23+

confirm: (params: unknown) => confirm(params),

24+

}));

25+26+

import { removeChannelConfigWizard } from "./configure.channels.js";

27+28+

const channelChoice = (id: string) => ({ kind: "channel" as const, id });

29+

const doneChoice = { kind: "done" as const };

30+31+

describe("removeChannelConfigWizard", () => {

32+

beforeEach(() => {

33+

vi.resetAllMocks();

34+

chatChannels.mockReturnValue([

35+

{ id: "telegram", label: "Telegram" },

36+

{ id: "twitch", label: "Twitch" },

37+

]);

38+

confirm.mockResolvedValue(true);

39+

});

40+41+

it("lists configured channels from openclaw.json even when no plugins are loaded", async () => {

42+

select.mockResolvedValue(doneChoice);

43+44+

await removeChannelConfigWizard(

45+

{

46+

channels: {

47+

defaults: { groupPolicy: "open" },

48+

modelByChannel: { openai: { telegram: "gpt-5.4" } },

49+

twitch: {},

50+

unknown: {},

51+

telegram: {},

52+

},

53+

} as never,

54+

{} as never,

55+

);

56+57+

expect(select).toHaveBeenCalledWith(

58+

expect.objectContaining({

59+

message: "Remove which channel config?",

60+

options: [

61+

expect.objectContaining({ value: channelChoice("telegram"), label: "Telegram" }),

62+

expect.objectContaining({ value: channelChoice("twitch"), label: "Twitch" }),

63+

expect.objectContaining({ value: channelChoice("unknown"), label: "unknown" }),

64+

{ value: doneChoice, label: "Done" },

65+

],

66+

}),

67+

);

68+

});

69+70+

it("deletes the selected channel block from openclaw.json", async () => {

71+

select.mockResolvedValueOnce(channelChoice("telegram")).mockResolvedValueOnce(doneChoice);

72+73+

const next = await removeChannelConfigWizard(

74+

{

75+

channels: {

76+

telegram: { token: "secret" },

77+

twitch: { token: "secret" },

78+

},

79+

} as never,

80+

{} as never,

81+

);

82+83+

expect(confirm).toHaveBeenCalledWith(

84+

expect.objectContaining({

85+

message: "Delete Telegram configuration from ~/.openclaw/openclaw.json?",

86+

}),

87+

);

88+

expect(next.channels).toEqual({ twitch: { token: "secret" } });

89+

expect(note).toHaveBeenCalledWith(

90+

"Telegram removed from config.\nNote: credentials/sessions on disk are unchanged.",

91+

"Channel removed",

92+

);

93+

});

94+95+

it("deletes a real channel block named done", async () => {

96+

select.mockResolvedValueOnce(channelChoice("done")).mockResolvedValueOnce(doneChoice);

97+98+

const next = await removeChannelConfigWizard(

99+

{

100+

channels: {

101+

done: { token: "secret" },

102+

telegram: { token: "secret" },

103+

},

104+

} as never,

105+

{} as never,

106+

);

107+108+

expect(confirm).toHaveBeenCalledWith(

109+

expect.objectContaining({

110+

message: "Delete done configuration from ~/.openclaw/openclaw.json?",

111+

}),

112+

);

113+

expect(next.channels).toEqual({ telegram: { token: "secret" } });

114+

expect(note).toHaveBeenCalledWith(

115+

"done removed from config.\nNote: credentials/sessions on disk are unchanged.",

116+

"Channel removed",

117+

);

118+

});

119+120+

it("preserves channel-wide defaults when deleting the last channel block", async () => {

121+

select.mockResolvedValueOnce(channelChoice("telegram")).mockResolvedValueOnce(doneChoice);

122+123+

const next = await removeChannelConfigWizard(

124+

{

125+

channels: {

126+

defaults: { groupPolicy: "open" },

127+

modelByChannel: { openai: { telegram: "gpt-5.4" } },

128+

telegram: { token: "secret" },

129+

},

130+

} as never,

131+

{} as never,

132+

);

133+134+

expect(next.channels).toEqual({

135+

defaults: { groupPolicy: "open" },

136+

modelByChannel: { openai: { telegram: "gpt-5.4" } },

137+

});

138+

});

139+140+

it("does not list blocked object keys as removable channels", async () => {

141+

select.mockResolvedValue(doneChoice);

142+143+

await removeChannelConfigWizard(

144+

{

145+

channels: {

146+

__proto__: { token: "secret" },

147+

constructor: { token: "secret" },

148+

prototype: { token: "secret" },

149+

telegram: { token: "secret" },

150+

},

151+

} as never,

152+

{} as never,

153+

);

154+155+

expect(select).toHaveBeenCalledWith(

156+

expect.objectContaining({

157+

options: [

158+

expect.objectContaining({ value: channelChoice("telegram"), label: "Telegram" }),

159+

{ value: doneChoice, label: "Done" },

160+

],

161+

}),

162+

);

163+

});

164+165+

it("sanitizes known channel labels before rendering prompts", async () => {

166+

chatChannels.mockReturnValue([

167+

{ id: "telegram", label: "Telegram\u001B[31m\nBot\u0007" },

168+

{ id: "twitch", label: "Twitch" },

169+

]);

170+

select.mockResolvedValueOnce(channelChoice("telegram")).mockResolvedValueOnce(doneChoice);

171+172+

await removeChannelConfigWizard(

173+

{

174+

channels: {

175+

telegram: { token: "secret" },

176+

},

177+

} as never,

178+

{} as never,

179+

);

180+181+

expect(select).toHaveBeenCalledWith(

182+

expect.objectContaining({

183+

options: expect.arrayContaining([

184+

expect.objectContaining({ value: channelChoice("telegram"), label: "Telegram\\nBot" }),

185+

]),

186+

}),

187+

);

188+

expect(confirm).toHaveBeenCalledWith(

189+

expect.objectContaining({

190+

message: "Delete Telegram\\nBot configuration from ~/.openclaw/openclaw.json?",

191+

}),

192+

);

193+

expect(note).toHaveBeenCalledWith(

194+

"Telegram\\nBot removed from config.\nNote: credentials/sessions on disk are unchanged.",

195+

"Channel removed",

196+

);

197+

});

198+199+

it("sanitizes unknown channel keys before rendering prompts", async () => {

200+

const unsafeChannel = "bad\u001B[31m\nkey\u0007";

201+

select.mockResolvedValueOnce(channelChoice(unsafeChannel)).mockResolvedValueOnce(doneChoice);

202+203+

const next = await removeChannelConfigWizard(

204+

{

205+

channels: {

206+

[unsafeChannel]: { token: "secret" },

207+

telegram: { token: "secret" },

208+

},

209+

} as never,

210+

{} as never,

211+

);

212+213+

expect(select).toHaveBeenCalledWith(

214+

expect.objectContaining({

215+

options: expect.arrayContaining([

216+

expect.objectContaining({ value: channelChoice(unsafeChannel), label: "bad\\nkey" }),

217+

]),

218+

}),

219+

);

220+

expect(confirm).toHaveBeenCalledWith(

221+

expect.objectContaining({

222+

message: "Delete bad\\nkey configuration from ~/.openclaw/openclaw.json?",

223+

}),

224+

);

225+

expect(next.channels).toEqual({ telegram: { token: "secret" } });

226+

expect(note).toHaveBeenCalledWith(

227+

"bad\\nkey removed from config.\nNote: credentials/sessions on disk are unchanged.",

228+

"Channel removed",

229+

);

230+

});

231+232+

it("uses a placeholder when an unknown channel key sanitizes to empty", async () => {

233+

const unsafeChannel = "\u001B[31m\u0007";

234+

select.mockResolvedValueOnce(channelChoice(unsafeChannel)).mockResolvedValueOnce(doneChoice);

235+236+

const next = await removeChannelConfigWizard(

237+

{

238+

channels: {

239+

[unsafeChannel]: { token: "secret" },

240+

telegram: { token: "secret" },

241+

},

242+

} as never,

243+

{} as never,

244+

);

245+246+

expect(select).toHaveBeenCalledWith(

247+

expect.objectContaining({

248+

options: expect.arrayContaining([

249+

expect.objectContaining({

250+

value: channelChoice(unsafeChannel),

251+

label: "<invalid channel key>",

252+

}),

253+

]),

254+

}),

255+

);

256+

expect(confirm).toHaveBeenCalledWith(

257+

expect.objectContaining({

258+

message: "Delete <invalid channel key> configuration from ~/.openclaw/openclaw.json?",

259+

}),

260+

);

261+

expect(next.channels).toEqual({ telegram: { token: "secret" } });

262+

expect(note).toHaveBeenCalledWith(

263+

"<invalid channel key> removed from config.\nNote: credentials/sessions on disk are unchanged.",

264+

"Channel removed",

265+

);

266+

});

267+

});