mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 14:11:24 +00:00
fix(hooks): backport internal message hook bridge with safe delivery semantics
This commit is contained in:
@@ -4,10 +4,14 @@ import {
|
||||
createInternalHookEvent,
|
||||
getRegisteredEventKeys,
|
||||
isAgentBootstrapEvent,
|
||||
isMessageReceivedEvent,
|
||||
isMessageSentEvent,
|
||||
registerInternalHook,
|
||||
triggerInternalHook,
|
||||
unregisterInternalHook,
|
||||
type AgentBootstrapHookContext,
|
||||
type MessageReceivedHookContext,
|
||||
type MessageSentHookContext,
|
||||
} from "./internal-hooks.js";
|
||||
|
||||
describe("hooks", () => {
|
||||
@@ -181,6 +185,191 @@ describe("hooks", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("isMessageReceivedEvent", () => {
|
||||
it("returns true for message:received events with expected context", () => {
|
||||
const context: MessageReceivedHookContext = {
|
||||
from: "+1234567890",
|
||||
content: "Hello world",
|
||||
channelId: "whatsapp",
|
||||
conversationId: "chat-123",
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
const event = createInternalHookEvent("message", "received", "test-session", context);
|
||||
expect(isMessageReceivedEvent(event)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for non-message events", () => {
|
||||
const event = createInternalHookEvent("command", "new", "test-session");
|
||||
expect(isMessageReceivedEvent(event)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for message:sent events", () => {
|
||||
const context: MessageSentHookContext = {
|
||||
to: "+1234567890",
|
||||
content: "Hello world",
|
||||
success: true,
|
||||
channelId: "whatsapp",
|
||||
};
|
||||
const event = createInternalHookEvent("message", "sent", "test-session", context);
|
||||
expect(isMessageReceivedEvent(event)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when context is missing required fields", () => {
|
||||
const event = createInternalHookEvent("message", "received", "test-session", {
|
||||
from: "+1234567890",
|
||||
// missing channelId
|
||||
});
|
||||
expect(isMessageReceivedEvent(event)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isMessageSentEvent", () => {
|
||||
it("returns true for message:sent events with expected context", () => {
|
||||
const context: MessageSentHookContext = {
|
||||
to: "+1234567890",
|
||||
content: "Hello world",
|
||||
success: true,
|
||||
channelId: "telegram",
|
||||
conversationId: "chat-456",
|
||||
messageId: "msg-789",
|
||||
};
|
||||
const event = createInternalHookEvent("message", "sent", "test-session", context);
|
||||
expect(isMessageSentEvent(event)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true when success is false (error case)", () => {
|
||||
const context: MessageSentHookContext = {
|
||||
to: "+1234567890",
|
||||
content: "Hello world",
|
||||
success: false,
|
||||
error: "Network error",
|
||||
channelId: "whatsapp",
|
||||
};
|
||||
const event = createInternalHookEvent("message", "sent", "test-session", context);
|
||||
expect(isMessageSentEvent(event)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for non-message events", () => {
|
||||
const event = createInternalHookEvent("command", "new", "test-session");
|
||||
expect(isMessageSentEvent(event)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for message:received events", () => {
|
||||
const context: MessageReceivedHookContext = {
|
||||
from: "+1234567890",
|
||||
content: "Hello world",
|
||||
channelId: "whatsapp",
|
||||
};
|
||||
const event = createInternalHookEvent("message", "received", "test-session", context);
|
||||
expect(isMessageSentEvent(event)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when context is missing required fields", () => {
|
||||
const event = createInternalHookEvent("message", "sent", "test-session", {
|
||||
to: "+1234567890",
|
||||
channelId: "whatsapp",
|
||||
// missing success
|
||||
});
|
||||
expect(isMessageSentEvent(event)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("message hooks", () => {
|
||||
it("should trigger message:received handlers", async () => {
|
||||
const handler = vi.fn();
|
||||
registerInternalHook("message:received", handler);
|
||||
|
||||
const context: MessageReceivedHookContext = {
|
||||
from: "+1234567890",
|
||||
content: "Hello world",
|
||||
channelId: "whatsapp",
|
||||
conversationId: "chat-123",
|
||||
};
|
||||
const event = createInternalHookEvent("message", "received", "test-session", context);
|
||||
await triggerInternalHook(event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(event);
|
||||
});
|
||||
|
||||
it("should trigger message:sent handlers", async () => {
|
||||
const handler = vi.fn();
|
||||
registerInternalHook("message:sent", handler);
|
||||
|
||||
const context: MessageSentHookContext = {
|
||||
to: "+1234567890",
|
||||
content: "Hello world",
|
||||
success: true,
|
||||
channelId: "telegram",
|
||||
messageId: "msg-123",
|
||||
};
|
||||
const event = createInternalHookEvent("message", "sent", "test-session", context);
|
||||
await triggerInternalHook(event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(event);
|
||||
});
|
||||
|
||||
it("should trigger general message handlers for both received and sent", async () => {
|
||||
const handler = vi.fn();
|
||||
registerInternalHook("message", handler);
|
||||
|
||||
const receivedContext: MessageReceivedHookContext = {
|
||||
from: "+1234567890",
|
||||
content: "Hello",
|
||||
channelId: "whatsapp",
|
||||
};
|
||||
const receivedEvent = createInternalHookEvent(
|
||||
"message",
|
||||
"received",
|
||||
"test-session",
|
||||
receivedContext,
|
||||
);
|
||||
await triggerInternalHook(receivedEvent);
|
||||
|
||||
const sentContext: MessageSentHookContext = {
|
||||
to: "+1234567890",
|
||||
content: "World",
|
||||
success: true,
|
||||
channelId: "whatsapp",
|
||||
};
|
||||
const sentEvent = createInternalHookEvent("message", "sent", "test-session", sentContext);
|
||||
await triggerInternalHook(sentEvent);
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(2);
|
||||
expect(handler).toHaveBeenNthCalledWith(1, receivedEvent);
|
||||
expect(handler).toHaveBeenNthCalledWith(2, sentEvent);
|
||||
});
|
||||
|
||||
it("should handle hook errors without breaking message processing", async () => {
|
||||
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
const errorHandler = vi.fn(() => {
|
||||
throw new Error("Hook failed");
|
||||
});
|
||||
const successHandler = vi.fn();
|
||||
|
||||
registerInternalHook("message:received", errorHandler);
|
||||
registerInternalHook("message:received", successHandler);
|
||||
|
||||
const context: MessageReceivedHookContext = {
|
||||
from: "+1234567890",
|
||||
content: "Hello",
|
||||
channelId: "whatsapp",
|
||||
};
|
||||
const event = createInternalHookEvent("message", "received", "test-session", context);
|
||||
await triggerInternalHook(event);
|
||||
|
||||
// Both handlers were called
|
||||
expect(errorHandler).toHaveBeenCalled();
|
||||
expect(successHandler).toHaveBeenCalled();
|
||||
// Error was logged but didn't prevent second handler
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Hook error"),
|
||||
expect.stringContaining("Hook failed"),
|
||||
);
|
||||
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRegisteredEventKeys", () => {
|
||||
it("should return all registered event keys", () => {
|
||||
registerInternalHook("command:new", vi.fn());
|
||||
|
||||
Reference in New Issue
Block a user