
























@@ -952,7 +952,7 @@ describe("registerSlackInteractionEvents", () => {
952952approvalId: "req-123",
953953decision: "allow-once",
954954senderId: "U123",
955-allowPluginFallback: true,
955+allowPluginFallback: false,
956956clientDisplayName: "Slack approval (U123)",
957957});
958958expect(resolvePluginConversationBindingApprovalMock).not.toHaveBeenCalled();
@@ -967,6 +967,220 @@ describe("registerSlackInteractionEvents", () => {
967967expect(respond).not.toHaveBeenCalled();
968968});
969969970+it("resolves plugin approval buttons from plugin approvers", async () => {
971+const { ctx, app, getHandler } = createContext({
972+cfg: {
973+channels: {
974+slack: {
975+accounts: {
976+default: {
977+allowFrom: ["U123OWNER"],
978+execApprovals: {
979+enabled: true,
980+approvers: ["U999EXEC"],
981+target: "both",
982+},
983+},
984+},
985+},
986+},
987+},
988+});
989+registerSlackInteractionEvents({ ctx: ctx as never });
990+991+const handler = getHandler();
992+993+const ack = vi.fn().mockResolvedValue(undefined);
994+const respond = vi.fn().mockResolvedValue(undefined);
995+await handler({
996+ ack,
997+ respond,
998+body: {
999+user: { id: "U123OWNER" },
1000+channel: { id: "C1" },
1001+container: { channel_id: "C1", message_ts: "100.200" },
1002+message: {
1003+ts: "100.200",
1004+text: "Plugin approval required",
1005+blocks: [
1006+{
1007+type: "actions",
1008+block_id: "plugin_actions",
1009+elements: [{ type: "button", action_id: "openclaw:reply_button" }],
1010+},
1011+],
1012+},
1013+},
1014+action: {
1015+type: "button",
1016+action_id: "openclaw:reply_button",
1017+block_id: "plugin_actions",
1018+value: "/approve plugin:req-123 allow-always",
1019+text: { type: "plain_text", text: "Always allow" },
1020+},
1021+});
1022+1023+expect(ack).toHaveBeenCalled();
1024+expect(resolveApprovalOverGatewayMock).toHaveBeenCalledWith({
1025+cfg: ctx.cfg,
1026+approvalId: "plugin:req-123",
1027+decision: "allow-always",
1028+senderId: "U123OWNER",
1029+allowPluginFallback: false,
1030+clientDisplayName: "Slack approval (U123OWNER)",
1031+});
1032+expect(resolvePluginConversationBindingApprovalMock).not.toHaveBeenCalled();
1033+expect(dispatchPluginInteractiveHandlerMock).not.toHaveBeenCalled();
1034+expect(enqueueSystemEventMock).not.toHaveBeenCalled();
1035+expectRecordFields(chatUpdateCall(app), {
1036+channel: "C1",
1037+ts: "100.200",
1038+text: "Plugin approval required",
1039+blocks: [],
1040+});
1041+expect(respond).not.toHaveBeenCalled();
1042+});
1043+1044+it("allows unprefixed plugin approval fallback from plugin approvers", async () => {
1045+const { ctx, app, getHandler } = createContext({
1046+cfg: {
1047+channels: {
1048+slack: {
1049+accounts: {
1050+default: {
1051+allowFrom: ["U123OWNER"],
1052+execApprovals: {
1053+enabled: true,
1054+approvers: ["U999EXEC"],
1055+target: "both",
1056+},
1057+},
1058+},
1059+},
1060+},
1061+},
1062+});
1063+registerSlackInteractionEvents({ ctx: ctx as never });
1064+1065+const handler = getHandler();
1066+1067+const ack = vi.fn().mockResolvedValue(undefined);
1068+const respond = vi.fn().mockResolvedValue(undefined);
1069+await handler({
1070+ ack,
1071+ respond,
1072+body: {
1073+user: { id: "U123OWNER" },
1074+channel: { id: "C1" },
1075+container: { channel_id: "C1", message_ts: "100.200" },
1076+message: {
1077+ts: "100.200",
1078+text: "Plugin approval required",
1079+blocks: [
1080+{
1081+type: "actions",
1082+block_id: "plugin_actions",
1083+elements: [{ type: "button", action_id: "openclaw:reply_button" }],
1084+},
1085+],
1086+},
1087+},
1088+action: {
1089+type: "button",
1090+action_id: "openclaw:reply_button",
1091+block_id: "plugin_actions",
1092+value: "/approve req-legacy allow-once",
1093+text: { type: "plain_text", text: "Allow once" },
1094+},
1095+});
1096+1097+expect(ack).toHaveBeenCalled();
1098+expect(resolveApprovalOverGatewayMock).toHaveBeenCalledWith({
1099+cfg: ctx.cfg,
1100+approvalId: "req-legacy",
1101+decision: "allow-once",
1102+senderId: "U123OWNER",
1103+allowPluginFallback: false,
1104+resolveMethod: "plugin",
1105+clientDisplayName: "Slack approval (U123OWNER)",
1106+});
1107+expect(resolvePluginConversationBindingApprovalMock).not.toHaveBeenCalled();
1108+expect(dispatchPluginInteractiveHandlerMock).not.toHaveBeenCalled();
1109+expect(enqueueSystemEventMock).not.toHaveBeenCalled();
1110+expectRecordFields(chatUpdateCall(app), {
1111+channel: "C1",
1112+ts: "100.200",
1113+text: "Plugin approval required",
1114+blocks: [],
1115+});
1116+expect(respond).not.toHaveBeenCalled();
1117+});
1118+1119+it("rejects plugin approval buttons from exec-only approvers", async () => {
1120+const { ctx, app, getHandler } = createContext({
1121+cfg: {
1122+channels: {
1123+slack: {
1124+accounts: {
1125+default: {
1126+allowFrom: ["U123OWNER"],
1127+execApprovals: {
1128+enabled: true,
1129+approvers: ["U999EXEC"],
1130+target: "both",
1131+},
1132+},
1133+},
1134+},
1135+},
1136+},
1137+});
1138+registerSlackInteractionEvents({ ctx: ctx as never });
1139+1140+const handler = getHandler();
1141+1142+const ack = vi.fn().mockResolvedValue(undefined);
1143+const respond = vi.fn().mockResolvedValue(undefined);
1144+await handler({
1145+ ack,
1146+ respond,
1147+body: {
1148+user: { id: "U999EXEC" },
1149+channel: { id: "C1" },
1150+container: { channel_id: "C1", message_ts: "100.200" },
1151+message: {
1152+ts: "100.200",
1153+text: "Plugin approval required",
1154+blocks: [
1155+{
1156+type: "actions",
1157+block_id: "plugin_actions",
1158+elements: [{ type: "button", action_id: "openclaw:reply_button" }],
1159+},
1160+],
1161+},
1162+},
1163+action: {
1164+type: "button",
1165+action_id: "openclaw:reply_button",
1166+block_id: "plugin_actions",
1167+value: "/approve plugin:req-123 allow-always",
1168+text: { type: "plain_text", text: "Always allow" },
1169+},
1170+});
1171+1172+expect(ack).toHaveBeenCalled();
1173+expect(resolveApprovalOverGatewayMock).not.toHaveBeenCalled();
1174+expect(resolvePluginConversationBindingApprovalMock).not.toHaveBeenCalled();
1175+expect(dispatchPluginInteractiveHandlerMock).not.toHaveBeenCalled();
1176+expect(enqueueSystemEventMock).not.toHaveBeenCalled();
1177+expect(app.client.chat.update).not.toHaveBeenCalled();
1178+expect(respond).toHaveBeenCalledWith({
1179+text: "You are not authorized to approve this request.",
1180+response_type: "ephemeral",
1181+});
1182+});
1183+9701184it("keeps exec approval buttons when gateway resolution fails", async () => {
9711185resolveApprovalOverGatewayMock.mockRejectedValueOnce(new Error("gateway down"));
9721186const { ctx, app, getHandler } = createContext();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。