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

推荐订阅源

有赞技术团队
有赞技术团队
G
Google Developers Blog
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
J
Java Code Geeks
P
Proofpoint News Feed
V
Visual Studio Blog
爱范儿
爱范儿
The Cloudflare Blog
博客园 - 叶小钗
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
M
MIT News - Artificial intelligence
Microsoft Security Blog
Microsoft Security Blog
博客园 - 聂微东
H
Help Net Security
B
Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
量子位
宝玉的分享
宝玉的分享
WordPress大学
WordPress大学
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

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
test: clear feishu card action broad matchers · openclaw/...
steipete · 2026-05-10 · via Recent Commits to openclaw:main

@@ -123,6 +123,51 @@ describe("Feishu Card Action Handler", () => {

123123

resetProcessedFeishuCardActionTokensForTests();

124124

});

125125126+

function mockCallArg(

127+

mock: { mock: { calls: unknown[][] } },

128+

index: number,

129+

label: string,

130+

): unknown {

131+

const call = mock.mock.calls[index];

132+

if (!call) {

133+

throw new Error(`Expected ${label} call ${index + 1}`);

134+

}

135+

return call[0];

136+

}

137+138+

function requireRecord(value: unknown, label: string): Record<string, unknown> {

139+

if (!value || typeof value !== "object") {

140+

throw new Error(`Expected ${label}`);

141+

}

142+

return value as Record<string, unknown>;

143+

}

144+145+

function handleMessageEvent(callIndex = 0) {

146+

const arg = requireRecord(

147+

mockCallArg(vi.mocked(handleFeishuMessage), callIndex, "handleFeishuMessage"),

148+

"handleFeishuMessage args",

149+

);

150+

return requireRecord(arg.event, "Feishu message event");

151+

}

152+153+

function handleMessage(callIndex = 0) {

154+

return requireRecord(handleMessageEvent(callIndex).message, "Feishu message");

155+

}

156+157+

function sendMessageCall(callIndex = 0) {

158+

return requireRecord(

159+

mockCallArg(sendMessageFeishuMock, callIndex, "sendMessageFeishu"),

160+

"sendMessageFeishu args",

161+

);

162+

}

163+164+

function sendCardCall(callIndex = 0) {

165+

return requireRecord(

166+

mockCallArg(sendCardFeishuMock, callIndex, "sendCardFeishu"),

167+

"sendCardFeishu args",

168+

);

169+

}

170+126171

it("handles card action with text payload", async () => {

127172

const event: FeishuCardActionEvent = {

128173

operator: { open_id: "u123", user_id: "uid1", union_id: "un1" },

@@ -133,16 +178,9 @@ describe("Feishu Card Action Handler", () => {

133178134179

await handleFeishuCardAction({ cfg, event, runtime });

135180136-

expect(handleFeishuMessage).toHaveBeenCalledWith(

137-

expect.objectContaining({

138-

event: expect.objectContaining({

139-

message: expect.objectContaining({

140-

content: '{"text":"/ping"}',

141-

chat_id: "chat1",

142-

}),

143-

}),

144-

}),

145-

);

181+

const message = handleMessage();

182+

expect(message.content).toBe('{"text":"/ping"}');

183+

expect(message.chat_id).toBe("chat1");

146184

});

147185148186

it("handles card action with JSON object payload", async () => {

@@ -155,16 +193,9 @@ describe("Feishu Card Action Handler", () => {

155193156194

await handleFeishuCardAction({ cfg, event, runtime });

157195158-

expect(handleFeishuMessage).toHaveBeenCalledWith(

159-

expect.objectContaining({

160-

event: expect.objectContaining({

161-

message: expect.objectContaining({

162-

content: '{"text":"{\\"key\\":\\"val\\"}"}',

163-

chat_id: "u123", // Fallback to open_id

164-

}),

165-

}),

166-

}),

167-

);

196+

const message = handleMessage();

197+

expect(message.content).toBe('{"text":"{\\"key\\":\\"val\\"}"}');

198+

expect(message.chat_id).toBe("u123"); // Fallback to open_id

168199

});

169200170201

it("routes quick command actions with operator and conversation context", async () => {

@@ -176,23 +207,15 @@ describe("Feishu Card Action Handler", () => {

176207177208

await handleFeishuCardAction({ cfg, event, runtime });

178209179-

expect(handleFeishuMessage).toHaveBeenCalledWith(

180-

expect.objectContaining({

181-

event: expect.objectContaining({

182-

sender: expect.objectContaining({

183-

sender_id: expect.objectContaining({

184-

open_id: "u123",

185-

user_id: "uid1",

186-

union_id: "un1",

187-

}),

188-

}),

189-

message: expect.objectContaining({

190-

chat_id: "chat1",

191-

content: '{"text":"/help"}',

192-

}),

193-

}),

194-

}),

195-

);

210+

const eventArg = handleMessageEvent();

211+

const sender = requireRecord(eventArg.sender, "Feishu sender");

212+

const senderId = requireRecord(sender.sender_id, "Feishu sender id");

213+

expect(senderId.open_id).toBe("u123");

214+

expect(senderId.user_id).toBe("uid1");

215+

expect(senderId.union_id).toBe("un1");

216+

const message = requireRecord(eventArg.message, "Feishu message");

217+

expect(message.chat_id).toBe("chat1");

218+

expect(message.content).toBe('{"text":"/help"}');

196219

});

197220198221

it("opens an approval card for metadata actions", async () => {

@@ -222,39 +245,27 @@ describe("Feishu Card Action Handler", () => {

222245223246

await handleFeishuCardAction({ cfg, event, runtime, accountId: "main" });

224247225-

expect(sendCardFeishuMock).toHaveBeenCalledWith(

226-

expect.objectContaining({

227-

to: "chat:chat1",

228-

accountId: "main",

229-

card: expect.objectContaining({

230-

config: expect.objectContaining({

231-

width_mode: "fill",

232-

}),

233-

header: expect.objectContaining({

234-

title: expect.objectContaining({ content: "Confirm action" }),

235-

}),

236-

body: expect.objectContaining({

237-

elements: expect.arrayContaining([

238-

expect.objectContaining({

239-

tag: "action",

240-

actions: expect.arrayContaining([

241-

expect.objectContaining({

242-

value: expect.objectContaining({

243-

c: expect.objectContaining({

244-

u: "u123",

245-

h: "chat1",

246-

t: "group",

247-

s: "agent:codex:feishu:chat:chat1",

248-

}),

249-

}),

250-

}),

251-

]),

252-

}),

253-

]),

254-

}),

255-

}),

256-

}),

257-

);

248+

const cardCall = sendCardCall();

249+

expect(cardCall.to).toBe("chat:chat1");

250+

expect(cardCall.accountId).toBe("main");

251+

const card = requireRecord(cardCall.card, "Feishu card");

252+

expect(requireRecord(card.config, "Feishu card config").width_mode).toBe("fill");

253+

const header = requireRecord(card.header, "Feishu card header");

254+

expect(requireRecord(header.title, "Feishu card title").content).toBe("Confirm action");

255+

const body = requireRecord(card.body, "Feishu card body");

256+

const elements = body.elements as Array<Record<string, unknown>>;

257+

const actionElement = elements.find((element) => element.tag === "action");

258+

if (!actionElement) {

259+

throw new Error("Expected action element");

260+

}

261+

const actions = actionElement.actions as Array<Record<string, unknown>>;

262+

const actionValue = requireRecord(actions[0]?.value, "Feishu approval action value");

263+

const approvalContext = requireRecord(actionValue.c, "Feishu approval context");

264+

expect(approvalContext.u).toBe("u123");

265+

expect(approvalContext.h).toBe("chat1");

266+

expect(approvalContext.t).toBe("group");

267+

expect(approvalContext.s).toBe("agent:codex:feishu:chat:chat1");

268+

expect(typeof approvalContext.e).toBe("number");

258269

expectFirstSentCardUsesFillWidthOnly(sendCardFeishuMock);

259270

expect(handleFeishuMessage).not.toHaveBeenCalled();

260271

});

@@ -268,15 +279,7 @@ describe("Feishu Card Action Handler", () => {

268279269280

await handleFeishuCardAction({ cfg, event, runtime });

270281271-

expect(handleFeishuMessage).toHaveBeenCalledWith(

272-

expect.objectContaining({

273-

event: expect.objectContaining({

274-

message: expect.objectContaining({

275-

content: '{"text":"/new"}',

276-

}),

277-

}),

278-

}),

279-

);

282+

expect(handleMessage().content).toBe('{"text":"/new"}');

280283

});

281284282285

it("safely rejects stale structured actions", async () => {

@@ -292,12 +295,9 @@ describe("Feishu Card Action Handler", () => {

292295293296

await handleFeishuCardAction({ cfg, event, runtime });

294297295-

expect(sendMessageFeishuMock).toHaveBeenCalledWith(

296-

expect.objectContaining({

297-

to: "chat:chat1",

298-

text: expect.stringContaining("expired"),

299-

}),

300-

);

298+

const sendMessage = sendMessageCall();

299+

expect(sendMessage.to).toBe("chat:chat1");

300+

expect(String(sendMessage.text)).toContain("expired");

301301

expect(handleFeishuMessage).not.toHaveBeenCalled();

302302

});

303303

@@ -312,11 +312,7 @@ describe("Feishu Card Action Handler", () => {

312312313313

await handleFeishuCardAction({ cfg, event, runtime });

314314315-

expect(sendMessageFeishuMock).toHaveBeenCalledWith(

316-

expect.objectContaining({

317-

text: expect.stringContaining("different user"),

318-

}),

319-

);

315+

expect(String(sendMessageCall().text)).toContain("different user");

320316

expect(handleFeishuMessage).not.toHaveBeenCalled();

321317

});

322318

@@ -337,12 +333,9 @@ describe("Feishu Card Action Handler", () => {

337333338334

await handleFeishuCardAction({ cfg, event, runtime });

339335340-

expect(sendMessageFeishuMock).toHaveBeenCalledWith(

341-

expect.objectContaining({

342-

to: "chat:chat1",

343-

text: "Cancelled.",

344-

}),

345-

);

336+

const sendMessage = sendMessageCall();

337+

expect(sendMessage.to).toBe("chat:chat1");

338+

expect(sendMessage.text).toBe("Cancelled.");

346339

});

347340348341

it("preserves p2p callbacks for DM quick actions", async () => {

@@ -356,16 +349,9 @@ describe("Feishu Card Action Handler", () => {

356349357350

await handleFeishuCardAction({ cfg, event, runtime });

358351359-

expect(handleFeishuMessage).toHaveBeenCalledWith(

360-

expect.objectContaining({

361-

event: expect.objectContaining({

362-

message: expect.objectContaining({

363-

chat_id: "p2p-chat-1",

364-

chat_type: "p2p",

365-

}),

366-

}),

367-

}),

368-

);

352+

const message = handleMessage();

353+

expect(message.chat_id).toBe("p2p-chat-1");

354+

expect(message.chat_type).toBe("p2p");

369355

});

370356371357

it("resolves DM chat type from the Feishu chat API when card context omits it", async () => {

@@ -384,16 +370,9 @@ describe("Feishu Card Action Handler", () => {

384370385371

await handleFeishuCardAction({ cfg, event, runtime });

386372387-

expect(handleFeishuMessage).toHaveBeenCalledWith(

388-

expect.objectContaining({

389-

event: expect.objectContaining({

390-

message: expect.objectContaining({

391-

chat_id: "oc_dm_chat_123",

392-

chat_type: "p2p",

393-

}),

394-

}),

395-

}),

396-

);

373+

const message = handleMessage();

374+

expect(message.chat_id).toBe("oc_dm_chat_123");

375+

expect(message.chat_type).toBe("p2p");

397376

expect(createFeishuClientMock).toHaveBeenCalledTimes(1);

398377

});

399378

@@ -445,15 +424,7 @@ describe("Feishu Card Action Handler", () => {

445424446425

await handleFeishuCardAction({ cfg, event, runtime });

447426448-

expect(handleFeishuMessage).toHaveBeenCalledWith(

449-

expect.objectContaining({

450-

event: expect.objectContaining({

451-

message: expect.objectContaining({

452-

chat_type: "p2p",

453-

}),

454-

}),

455-

}),

456-

);

427+

expect(handleMessage().chat_type).toBe("p2p");

457428

});

458429459430

it("falls back to p2p when Feishu chat API throws", async () => {

@@ -472,15 +443,7 @@ describe("Feishu Card Action Handler", () => {

472443473444

await handleFeishuCardAction({ cfg, event, runtime });

474445475-

expect(handleFeishuMessage).toHaveBeenCalledWith(

476-

expect.objectContaining({

477-

event: expect.objectContaining({

478-

message: expect.objectContaining({

479-

chat_type: "p2p",

480-

}),

481-

}),

482-

}),

483-

);

446+

expect(handleMessage().chat_type).toBe("p2p");

484447

});

485448486449

it("drops duplicate structured callback tokens", async () => {