import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime" ;
import { beforeEach, describe, expect, it, vi } from "vitest" ;
const sendReactionsModule = await import ("./send-reactions.js" );
const sendReactionSignalMock = vi
.spyOn(sendReactionsModule, "sendReactionSignal" )
.mockResolvedValue({ ok: true });
const removeReactionSignalMock = vi
.spyOn(sendReactionsModule, "removeReactionSignal" )
.mockResolvedValue({ ok: true });
const { signalMessageActions } = await import ("./message-actions.js" );
function createSignalAccountOverrideCfg(): OpenClawConfig {
return {
channels: {
signal: {
account: "+15550002222" ,
actions: { reactions: false },
accounts: {
work: { account: "+15550001111" , actions: { reactions: true } },
},
},
},
} as OpenClawConfig;
}
describe("signalMessageActions" , () => {
beforeEach(() => {
sendReactionSignalMock.mockClear();
removeReactionSignalMock.mockClear();
});
it("lists actions based on configured accounts and reaction gates" , () => {
expect(
signalMessageActions.describeMessageTool?.({ cfg: {} as OpenClawConfig })?.actions ?? [],
).toEqual([]);
expect(
signalMessageActions.describeMessageTool?.({
cfg: {
channels: { signal: { account: "+15550001111" , actions: { reactions: false } } },
} as OpenClawConfig,
})?.actions,
).toEqual(["send" ]);
expect(
signalMessageActions.describeMessageTool?.({ cfg: createSignalAccountOverrideCfg() })
?.actions,
).toEqual(["send" , "react" ]);
});
it("honors account-scoped reaction gates during discovery" , () => {
const cfg = createSignalAccountOverrideCfg();
expect(
signalMessageActions.describeMessageTool?.({ cfg, accountId: "default" })?.actions,
).toEqual(["send" ]);
expect(signalMessageActions.describeMessageTool?.({ cfg, accountId: "work" })?.actions).toEqual(
["send" , "react" ],
);
});
it("skips send for plugin dispatch" , () => {
expect(signalMessageActions.supportsAction?.({ action: "send" })).toBe(false );
expect(signalMessageActions.supportsAction?.({ action: "react" })).toBe(true );
});
it("blocks reactions when the action gate is disabled" , async () => {
const cfg = {
channels: { signal: { account: "+15550001111" , actions: { reactions: false } } },
} as OpenClawConfig;
await expect(
signalMessageActions.handleAction?.({
channel: "signal" ,
action: "react" ,
params: { to: "+15550001111" , messageId: "123" , emoji: "✅" },
cfg,
}),
).rejects.toThrow(/actions\.reactions/);
});
it("maps reaction targets into sendReactionSignal calls" , async () => {
const cases = [
{
name: "uses account-level actions when enabled" ,
cfg: createSignalAccountOverrideCfg(),
accountId: "work" ,
params: { to: "+15550001111" , messageId: "123" , emoji: "" },
expectedRecipient: "+15550001111" ,
expectedTimestamp: 123 ,
expectedEmoji: "" ,
expectedOptions: { accountId: "work" },
},
{
name: "normalizes uuid recipients" ,
cfg: { channels: { signal: { account: "+15550001111" } } } as OpenClawConfig,
params: {
recipient: "uuid:123e4567-e89b-12d3-a456-426614174000" ,
messageId: "123" ,
emoji: "" ,
},
expectedRecipient: "123e4567-e89b-12d3-a456-426614174000" ,
expectedTimestamp: 123 ,
expectedEmoji: "" ,
expectedOptions: {},
},
{
name: "passes groupId and targetAuthor for group reactions" ,
cfg: { channels: { signal: { account: "+15550001111" } } } as OpenClawConfig,
params: {
to: "signal:group:group-id" ,
targetAuthor: "uuid:123e4567-e89b-12d3-a456-426614174000" ,
messageId: "123" ,
emoji: "✅" ,
},
expectedRecipient: "" ,
expectedTimestamp: 123 ,
expectedEmoji: "✅" ,
expectedOptions: {
groupId: "group-id" ,
targetAuthor: "uuid:123e4567-e89b-12d3-a456-426614174000" ,
},
},
{
name: "falls back to toolContext.currentMessageId when messageId is omitted" ,
cfg: { channels: { signal: { account: "+15550001111" } } } as OpenClawConfig,
params: { to: "+15559999999" , emoji: "" },
expectedRecipient: "+15559999999" ,
expectedTimestamp: 1737630212345 ,
expectedEmoji: "" ,
expectedOptions: {},
toolContext: { currentMessageId: "1737630212345" },
},
] as const ;
for (const testCase of cases) {
sendReactionSignalMock.mockClear();
await signalMessageActions.handleAction?.({
channel: "signal" ,
action: "react" ,
params: testCase.params,
cfg: testCase.cfg,
accountId: "accountId" in testCase ? testCase.accountId : undefined,
toolContext: "toolContext" in testCase ? testCase.toolContext : undefined,
});
expect(sendReactionSignalMock, testCase.name).toHaveBeenCalledWith(
testCase.expectedRecipient,
testCase.expectedTimestamp,
testCase.expectedEmoji,
expect.objectContaining({
cfg: testCase.cfg,
...testCase.expectedOptions,
}),
);
}
});
it("rejects invalid reaction inputs before dispatch" , async () => {
const cfg = {
channels: { signal: { account: "+15550001111" } },
} as OpenClawConfig;
await expect(
signalMessageActions.handleAction?.({
channel: "signal" ,
action: "react" ,
params: { to: "+15559999999" , emoji: "✅" },
cfg,
}),
).rejects.toThrow(/messageId.*required/);
await expect(
signalMessageActions.handleAction?.({
channel: "signal" ,
action: "react" ,
params: { to: "signal:group:group-id" , messageId: "123" , emoji: "✅" },
cfg,
}),
).rejects.toThrow(/targetAuthor/);
});
});
Messung V0.5 in Prozent C=100 H=97 G=98
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland