mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 04:31:10 +00:00
* feishu: add structured card actions and interactive approval flows * feishu: address review fixes and test-gate regressions * feishu: hold inflight card dedup until completion * feishu: restore fire-and-forget bot menu handling * feishu: format card interaction helpers * Feishu: add changelog entry for card interactions * Feishu: add changelog entry for ACP session binding
130 lines
3.3 KiB
TypeScript
130 lines
3.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
buildFeishuCardActionTextFallback,
|
|
createFeishuCardInteractionEnvelope,
|
|
decodeFeishuCardAction,
|
|
} from "./card-interaction.js";
|
|
|
|
describe("feishu card interaction decoder", () => {
|
|
it("decodes valid structured payloads", () => {
|
|
const result = decodeFeishuCardAction({
|
|
now: 1_700_000_000_000,
|
|
event: {
|
|
operator: { open_id: "u123" },
|
|
context: { chat_id: "chat1" },
|
|
action: {
|
|
value: createFeishuCardInteractionEnvelope({
|
|
k: "quick",
|
|
a: "feishu.quick_actions.help",
|
|
q: "/help",
|
|
c: { u: "u123", h: "chat1", t: "group", e: 1_700_000_060_000 },
|
|
}),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result).toEqual(
|
|
expect.objectContaining({
|
|
kind: "structured",
|
|
envelope: expect.objectContaining({
|
|
q: "/help",
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("falls back for legacy text-like payloads", () => {
|
|
const result = decodeFeishuCardAction({
|
|
event: {
|
|
operator: { open_id: "u123" },
|
|
context: { chat_id: "chat1" },
|
|
action: { value: { text: "/ping" } },
|
|
},
|
|
});
|
|
|
|
expect(result).toEqual({ kind: "legacy", text: "/ping" });
|
|
expect(
|
|
buildFeishuCardActionTextFallback({
|
|
operator: { open_id: "u123" },
|
|
context: { chat_id: "chat1" },
|
|
action: { value: { command: "/new" } },
|
|
}),
|
|
).toBe("/new");
|
|
});
|
|
|
|
it("rejects malformed structured payloads", () => {
|
|
const result = decodeFeishuCardAction({
|
|
event: {
|
|
operator: { open_id: "u123" },
|
|
context: { chat_id: "chat1" },
|
|
action: {
|
|
value: {
|
|
oc: "ocf1",
|
|
k: "quick",
|
|
a: "broken",
|
|
m: { bad: { nested: true } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result).toEqual({ kind: "invalid", reason: "malformed" });
|
|
});
|
|
|
|
it("rejects stale payloads", () => {
|
|
const result = decodeFeishuCardAction({
|
|
now: 100,
|
|
event: {
|
|
operator: { open_id: "u123" },
|
|
context: { chat_id: "chat1" },
|
|
action: {
|
|
value: createFeishuCardInteractionEnvelope({
|
|
k: "button",
|
|
a: "stale",
|
|
c: { e: 99, t: "group" },
|
|
}),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result).toEqual({ kind: "invalid", reason: "stale" });
|
|
});
|
|
|
|
it("rejects wrong-conversation payloads when chat context is enforced", () => {
|
|
const result = decodeFeishuCardAction({
|
|
event: {
|
|
operator: { open_id: "u123" },
|
|
context: { chat_id: "chat2" },
|
|
action: {
|
|
value: createFeishuCardInteractionEnvelope({
|
|
k: "button",
|
|
a: "scoped",
|
|
c: { u: "u123", h: "chat1", t: "group", e: Date.now() + 60_000 },
|
|
}),
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result).toEqual({ kind: "invalid", reason: "wrong_conversation" });
|
|
});
|
|
|
|
it("rejects malformed chat-type context", () => {
|
|
const result = decodeFeishuCardAction({
|
|
event: {
|
|
operator: { open_id: "u123" },
|
|
context: { chat_id: "chat1" },
|
|
action: {
|
|
value: {
|
|
oc: "ocf1",
|
|
k: "button",
|
|
a: "bad",
|
|
c: { t: "private" },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(result).toEqual({ kind: "invalid", reason: "malformed" });
|
|
});
|
|
});
|