



















@@ -18,6 +18,34 @@ import {
1818verifyInteractionToken,
1919} from "./interactions.js";
202021+type ButtonAttachments = ReturnType<typeof buildButtonAttachments>;
22+type ButtonAttachment = ButtonAttachments[number];
23+type ButtonAction = NonNullable<ButtonAttachment["actions"]>[number];
24+25+function requireFirstAttachment(attachments: ButtonAttachments): ButtonAttachment {
26+const [attachment] = attachments;
27+if (!attachment) {
28+throw new Error("Expected button attachment fixture");
29+}
30+return attachment;
31+}
32+33+function requireActions(attachments: ButtonAttachments): ButtonAction[] {
34+const attachment = requireFirstAttachment(attachments);
35+if (!attachment.actions) {
36+throw new Error("Expected button attachment fixture actions");
37+}
38+return attachment.actions;
39+}
40+41+function requireAction(attachments: ButtonAttachments, index = 0): ButtonAction {
42+const action = requireActions(attachments).at(index);
43+if (!action) {
44+throw new Error(`Expected button attachment action at index ${index}`);
45+}
46+return action;
47+}
48+2149// ── HMAC token management ────────────────────────────────────────────
22502351describe("setInteractionSecret / getInteractionSecret", () => {
@@ -308,7 +336,7 @@ describe("buildButtonAttachments", () => {
308336});
309337310338expect(result).toHaveLength(1);
311-expect(result[0].actions).toHaveLength(2);
339+expect(requireActions(result)).toHaveLength(2);
312340});
313341314342it("sets type to 'button' on every action", () => {
@@ -317,7 +345,7 @@ describe("buildButtonAttachments", () => {
317345buttons: [{ id: "a", name: "A" }],
318346});
319347320-expect(result[0].actions![0].type).toBe("button");
348+expect(requireAction(result).type).toBe("button");
321349});
322350323351it("includes HMAC _token in integration context", () => {
@@ -326,7 +354,7 @@ describe("buildButtonAttachments", () => {
326354buttons: [{ id: "test", name: "Test" }],
327355});
328356329-const action = result[0].actions![0];
357+const action = requireAction(result);
330358expect(action.integration.context._token).toMatch(/^[0-9a-f]{64}$/);
331359});
332360@@ -336,7 +364,7 @@ describe("buildButtonAttachments", () => {
336364buttons: [{ id: "my_action", name: "Do It" }],
337365});
338366339-const action = result[0].actions![0];
367+const action = requireAction(result);
340368// sanitizeActionId strips hyphens and underscores (Mattermost routing bug #25747)
341369expect(action.integration.context.action_id).toBe("myaction");
342370expect(action.id).toBe("myaction");
@@ -348,7 +376,7 @@ describe("buildButtonAttachments", () => {
348376buttons: [{ id: "btn", name: "Go", context: { tweet_id: "123", batch: true } }],
349377});
350378351-const ctx = result[0].actions![0].integration.context;
379+const ctx = requireAction(result).integration.context;
352380expect(ctx.tweet_id).toBe("123");
353381expect(ctx.batch).toBe(true);
354382expect(ctx.action_id).toBe("btn");
@@ -365,7 +393,7 @@ describe("buildButtonAttachments", () => {
365393],
366394});
367395368-for (const action of result[0].actions!) {
396+for (const action of requireActions(result)) {
369397expect(action.integration.url).toBe(url);
370398}
371399});
@@ -379,8 +407,8 @@ describe("buildButtonAttachments", () => {
379407],
380408});
381409382-expect(result[0].actions![0].style).toBe("primary");
383-expect(result[0].actions![1].style).toBe("danger");
410+expect(requireAction(result, 0).style).toBe("primary");
411+expect(requireAction(result, 1).style).toBe("danger");
384412});
385413386414it("uses provided text for the attachment", () => {
@@ -390,7 +418,7 @@ describe("buildButtonAttachments", () => {
390418text: "Choose an action:",
391419});
392420393-expect(result[0].text).toBe("Choose an action:");
421+expect(requireFirstAttachment(result).text).toBe("Choose an action:");
394422});
395423396424it("defaults to empty string text when not provided", () => {
@@ -399,7 +427,7 @@ describe("buildButtonAttachments", () => {
399427buttons: [{ id: "x", name: "X" }],
400428});
401429402-expect(result[0].text).toBe("");
430+expect(requireFirstAttachment(result).text).toBe("");
403431});
404432405433it("generates verifiable tokens", () => {
@@ -408,7 +436,7 @@ describe("buildButtonAttachments", () => {
408436buttons: [{ id: "verify_me", name: "V", context: { extra: "data" } }],
409437});
410438411-const ctx = result[0].actions![0].integration.context;
439+const ctx = requireAction(result).integration.context;
412440const token = ctx._token as string;
413441const { _token, ...contextWithoutToken } = ctx;
414442expect(verifyInteractionToken(contextWithoutToken, token)).toBe(true);
@@ -420,7 +448,7 @@ describe("buildButtonAttachments", () => {
420448buttons: [{ id: "do_action", name: "Do", context: { tweet_id: "42", category: "ai" } }],
421449});
422450423-const ctx = result[0].actions![0].integration.context;
451+const ctx = requireAction(result).integration.context;
424452const token = ctx._token as string;
425453426454// Simulate Mattermost returning context with keys in a different order
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。