mirror of https://github.com/openclaw/openclaw.git
test: reduce webhook auth test duplication
This commit is contained in:
parent
de9ea76b6c
commit
d5d2fe1b0e
|
|
@ -302,65 +302,101 @@ describe("BlueBubbles webhook monitor", () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("webhook parsing + auth handling", () => {
|
async function dispatchWebhook(req: IncomingMessage) {
|
||||||
it("rejects non-POST requests", async () => {
|
const res = createMockResponse();
|
||||||
const account = createMockAccount();
|
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||||
const config: OpenClawConfig = {};
|
return { handled, res };
|
||||||
const core = createMockRuntime();
|
}
|
||||||
setBlueBubblesRuntime(core);
|
|
||||||
|
|
||||||
unregister = registerBlueBubblesWebhookTarget({
|
function createWebhookRequestForTest(params?: {
|
||||||
|
method?: string;
|
||||||
|
url?: string;
|
||||||
|
body?: unknown;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
remoteAddress?: string;
|
||||||
|
}) {
|
||||||
|
const req = createMockRequest(
|
||||||
|
params?.method ?? "POST",
|
||||||
|
params?.url ?? "/bluebubbles-webhook",
|
||||||
|
params?.body ?? {},
|
||||||
|
params?.headers,
|
||||||
|
);
|
||||||
|
if (params?.remoteAddress) {
|
||||||
|
setRequestRemoteAddress(req, params.remoteAddress);
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHangingWebhookRequest(url = "/bluebubbles-webhook?password=test-password") {
|
||||||
|
const req = new EventEmitter() as IncomingMessage & { destroy: ReturnType<typeof vi.fn> };
|
||||||
|
req.method = "POST";
|
||||||
|
req.url = url;
|
||||||
|
req.headers = {};
|
||||||
|
req.destroy = vi.fn();
|
||||||
|
setRequestRemoteAddress(req, "127.0.0.1");
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerWebhookTargets(
|
||||||
|
params: Array<{
|
||||||
|
account: ResolvedBlueBubblesAccount;
|
||||||
|
statusSink?: (event: unknown) => void;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const config: OpenClawConfig = {};
|
||||||
|
const core = createMockRuntime();
|
||||||
|
setBlueBubblesRuntime(core);
|
||||||
|
|
||||||
|
const unregisterFns = params.map(({ account, statusSink }) =>
|
||||||
|
registerBlueBubblesWebhookTarget({
|
||||||
account,
|
account,
|
||||||
config,
|
config,
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
runtime: { log: vi.fn(), error: vi.fn() },
|
||||||
core,
|
core,
|
||||||
path: "/bluebubbles-webhook",
|
path: "/bluebubbles-webhook",
|
||||||
});
|
statusSink,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const req = createMockRequest("GET", "/bluebubbles-webhook", {});
|
unregister = () => {
|
||||||
const res = createMockResponse();
|
for (const unregisterFn of unregisterFns) {
|
||||||
|
unregisterFn();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
async function expectWebhookStatus(
|
||||||
|
req: IncomingMessage,
|
||||||
|
expectedStatus: number,
|
||||||
|
expectedBody?: string,
|
||||||
|
) {
|
||||||
|
const { handled, res } = await dispatchWebhook(req);
|
||||||
|
expect(handled).toBe(true);
|
||||||
|
expect(res.statusCode).toBe(expectedStatus);
|
||||||
|
if (expectedBody !== undefined) {
|
||||||
|
expect(res.body).toBe(expectedBody);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
expect(handled).toBe(true);
|
describe("webhook parsing + auth handling", () => {
|
||||||
expect(res.statusCode).toBe(405);
|
it("rejects non-POST requests", async () => {
|
||||||
|
setupWebhookTarget();
|
||||||
|
const req = createWebhookRequestForTest({ method: "GET" });
|
||||||
|
await expectWebhookStatus(req, 405);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("accepts POST requests with valid JSON payload", async () => {
|
it("accepts POST requests with valid JSON payload", async () => {
|
||||||
setupWebhookTarget();
|
setupWebhookTarget();
|
||||||
const payload = createNewMessagePayload({ date: Date.now() });
|
const payload = createNewMessagePayload({ date: Date.now() });
|
||||||
|
const req = createWebhookRequestForTest({ body: payload });
|
||||||
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
await expectWebhookStatus(req, 200, "ok");
|
||||||
const res = createMockResponse();
|
|
||||||
|
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
||||||
|
|
||||||
expect(handled).toBe(true);
|
|
||||||
expect(res.statusCode).toBe(200);
|
|
||||||
expect(res.body).toBe("ok");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects requests with invalid JSON", async () => {
|
it("rejects requests with invalid JSON", async () => {
|
||||||
const account = createMockAccount();
|
setupWebhookTarget();
|
||||||
const config: OpenClawConfig = {};
|
const req = createWebhookRequestForTest({ body: "invalid json {{" });
|
||||||
const core = createMockRuntime();
|
await expectWebhookStatus(req, 400);
|
||||||
setBlueBubblesRuntime(core);
|
|
||||||
|
|
||||||
unregister = registerBlueBubblesWebhookTarget({
|
|
||||||
account,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
});
|
|
||||||
|
|
||||||
const req = createMockRequest("POST", "/bluebubbles-webhook", "invalid json {{");
|
|
||||||
const res = createMockResponse();
|
|
||||||
|
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
||||||
|
|
||||||
expect(handled).toBe(true);
|
|
||||||
expect(res.statusCode).toBe(400);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("accepts URL-encoded payload wrappers", async () => {
|
it("accepts URL-encoded payload wrappers", async () => {
|
||||||
|
|
@ -369,42 +405,17 @@ describe("BlueBubbles webhook monitor", () => {
|
||||||
const encodedBody = new URLSearchParams({
|
const encodedBody = new URLSearchParams({
|
||||||
payload: JSON.stringify(payload),
|
payload: JSON.stringify(payload),
|
||||||
}).toString();
|
}).toString();
|
||||||
|
const req = createWebhookRequestForTest({ body: encodedBody });
|
||||||
const req = createMockRequest("POST", "/bluebubbles-webhook", encodedBody);
|
await expectWebhookStatus(req, 200, "ok");
|
||||||
const res = createMockResponse();
|
|
||||||
|
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
||||||
|
|
||||||
expect(handled).toBe(true);
|
|
||||||
expect(res.statusCode).toBe(200);
|
|
||||||
expect(res.body).toBe("ok");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 408 when request body times out (Slow-Loris protection)", async () => {
|
it("returns 408 when request body times out (Slow-Loris protection)", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
try {
|
try {
|
||||||
const account = createMockAccount();
|
setupWebhookTarget();
|
||||||
const config: OpenClawConfig = {};
|
|
||||||
const core = createMockRuntime();
|
|
||||||
setBlueBubblesRuntime(core);
|
|
||||||
|
|
||||||
unregister = registerBlueBubblesWebhookTarget({
|
|
||||||
account,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a request that never sends data or ends (simulates slow-loris)
|
// Create a request that never sends data or ends (simulates slow-loris)
|
||||||
const req = new EventEmitter() as IncomingMessage;
|
const req = createHangingWebhookRequest();
|
||||||
req.method = "POST";
|
|
||||||
req.url = "/bluebubbles-webhook?password=test-password";
|
|
||||||
req.headers = {};
|
|
||||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
|
||||||
remoteAddress: "127.0.0.1",
|
|
||||||
};
|
|
||||||
req.destroy = vi.fn();
|
|
||||||
|
|
||||||
const res = createMockResponse();
|
const res = createMockResponse();
|
||||||
|
|
||||||
|
|
@ -424,140 +435,62 @@ describe("BlueBubbles webhook monitor", () => {
|
||||||
|
|
||||||
it("rejects unauthorized requests before reading the body", async () => {
|
it("rejects unauthorized requests before reading the body", async () => {
|
||||||
const account = createMockAccount({ password: "secret-token" });
|
const account = createMockAccount({ password: "secret-token" });
|
||||||
const config: OpenClawConfig = {};
|
setupWebhookTarget({ account });
|
||||||
const core = createMockRuntime();
|
const req = createHangingWebhookRequest("/bluebubbles-webhook?password=wrong-token");
|
||||||
setBlueBubblesRuntime(core);
|
|
||||||
|
|
||||||
unregister = registerBlueBubblesWebhookTarget({
|
|
||||||
account,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
});
|
|
||||||
|
|
||||||
const req = new EventEmitter() as IncomingMessage;
|
|
||||||
req.method = "POST";
|
|
||||||
req.url = "/bluebubbles-webhook?password=wrong-token";
|
|
||||||
req.headers = {};
|
|
||||||
const onSpy = vi.spyOn(req, "on");
|
const onSpy = vi.spyOn(req, "on");
|
||||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
await expectWebhookStatus(req, 401);
|
||||||
remoteAddress: "127.0.0.1",
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = createMockResponse();
|
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
||||||
|
|
||||||
expect(handled).toBe(true);
|
|
||||||
expect(res.statusCode).toBe(401);
|
|
||||||
expect(onSpy).not.toHaveBeenCalledWith("data", expect.any(Function));
|
expect(onSpy).not.toHaveBeenCalledWith("data", expect.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("authenticates via password query parameter", async () => {
|
it("authenticates via password query parameter", async () => {
|
||||||
const account = createMockAccount({ password: "secret-token" });
|
const account = createMockAccount({ password: "secret-token" });
|
||||||
|
|
||||||
// Mock non-localhost request
|
|
||||||
const req = createMockRequest(
|
|
||||||
"POST",
|
|
||||||
"/bluebubbles-webhook?password=secret-token",
|
|
||||||
createNewMessagePayload(),
|
|
||||||
);
|
|
||||||
setRequestRemoteAddress(req, "192.168.1.100");
|
|
||||||
setupWebhookTarget({ account });
|
setupWebhookTarget({ account });
|
||||||
|
const req = createWebhookRequestForTest({
|
||||||
const res = createMockResponse();
|
url: "/bluebubbles-webhook?password=secret-token",
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
body: createNewMessagePayload(),
|
||||||
|
remoteAddress: "192.168.1.100",
|
||||||
expect(handled).toBe(true);
|
});
|
||||||
expect(res.statusCode).toBe(200);
|
await expectWebhookStatus(req, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("authenticates via x-password header", async () => {
|
it("authenticates via x-password header", async () => {
|
||||||
const account = createMockAccount({ password: "secret-token" });
|
const account = createMockAccount({ password: "secret-token" });
|
||||||
|
|
||||||
const req = createMockRequest(
|
|
||||||
"POST",
|
|
||||||
"/bluebubbles-webhook",
|
|
||||||
createNewMessagePayload(),
|
|
||||||
{ "x-password": "secret-token" }, // pragma: allowlist secret
|
|
||||||
);
|
|
||||||
setRequestRemoteAddress(req, "192.168.1.100");
|
|
||||||
setupWebhookTarget({ account });
|
setupWebhookTarget({ account });
|
||||||
|
const req = createWebhookRequestForTest({
|
||||||
const res = createMockResponse();
|
body: createNewMessagePayload(),
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
headers: { "x-password": "secret-token" }, // pragma: allowlist secret
|
||||||
|
remoteAddress: "192.168.1.100",
|
||||||
expect(handled).toBe(true);
|
});
|
||||||
expect(res.statusCode).toBe(200);
|
await expectWebhookStatus(req, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects unauthorized requests with wrong password", async () => {
|
it("rejects unauthorized requests with wrong password", async () => {
|
||||||
const account = createMockAccount({ password: "secret-token" });
|
const account = createMockAccount({ password: "secret-token" });
|
||||||
const req = createMockRequest(
|
|
||||||
"POST",
|
|
||||||
"/bluebubbles-webhook?password=wrong-token",
|
|
||||||
createNewMessagePayload(),
|
|
||||||
);
|
|
||||||
setRequestRemoteAddress(req, "192.168.1.100");
|
|
||||||
setupWebhookTarget({ account });
|
setupWebhookTarget({ account });
|
||||||
|
const req = createWebhookRequestForTest({
|
||||||
const res = createMockResponse();
|
url: "/bluebubbles-webhook?password=wrong-token",
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
body: createNewMessagePayload(),
|
||||||
|
remoteAddress: "192.168.1.100",
|
||||||
expect(handled).toBe(true);
|
});
|
||||||
expect(res.statusCode).toBe(401);
|
await expectWebhookStatus(req, 401);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects ambiguous routing when multiple targets match the same password", async () => {
|
it("rejects ambiguous routing when multiple targets match the same password", async () => {
|
||||||
const accountA = createMockAccount({ password: "secret-token" });
|
const accountA = createMockAccount({ password: "secret-token" });
|
||||||
const accountB = createMockAccount({ password: "secret-token" });
|
const accountB = createMockAccount({ password: "secret-token" });
|
||||||
const config: OpenClawConfig = {};
|
|
||||||
const core = createMockRuntime();
|
|
||||||
setBlueBubblesRuntime(core);
|
|
||||||
|
|
||||||
const sinkA = vi.fn();
|
const sinkA = vi.fn();
|
||||||
const sinkB = vi.fn();
|
const sinkB = vi.fn();
|
||||||
|
registerWebhookTargets([
|
||||||
|
{ account: accountA, statusSink: sinkA },
|
||||||
|
{ account: accountB, statusSink: sinkB },
|
||||||
|
]);
|
||||||
|
|
||||||
const req = createMockRequest("POST", "/bluebubbles-webhook?password=secret-token", {
|
const req = createWebhookRequestForTest({
|
||||||
type: "new-message",
|
url: "/bluebubbles-webhook?password=secret-token",
|
||||||
data: {
|
body: createNewMessagePayload(),
|
||||||
text: "hello",
|
|
||||||
handle: { address: "+15551234567" },
|
|
||||||
isGroup: false,
|
|
||||||
isFromMe: false,
|
|
||||||
guid: "msg-1",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
|
||||||
remoteAddress: "192.168.1.100",
|
remoteAddress: "192.168.1.100",
|
||||||
};
|
|
||||||
|
|
||||||
const unregisterA = registerBlueBubblesWebhookTarget({
|
|
||||||
account: accountA,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
statusSink: sinkA,
|
|
||||||
});
|
});
|
||||||
const unregisterB = registerBlueBubblesWebhookTarget({
|
await expectWebhookStatus(req, 401);
|
||||||
account: accountB,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
statusSink: sinkB,
|
|
||||||
});
|
|
||||||
unregister = () => {
|
|
||||||
unregisterA();
|
|
||||||
unregisterB();
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = createMockResponse();
|
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
||||||
|
|
||||||
expect(handled).toBe(true);
|
|
||||||
expect(res.statusCode).toBe(401);
|
|
||||||
expect(sinkA).not.toHaveBeenCalled();
|
expect(sinkA).not.toHaveBeenCalled();
|
||||||
expect(sinkB).not.toHaveBeenCalled();
|
expect(sinkB).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
@ -565,107 +498,38 @@ describe("BlueBubbles webhook monitor", () => {
|
||||||
it("ignores targets without passwords when a password-authenticated target matches", async () => {
|
it("ignores targets without passwords when a password-authenticated target matches", async () => {
|
||||||
const accountStrict = createMockAccount({ password: "secret-token" });
|
const accountStrict = createMockAccount({ password: "secret-token" });
|
||||||
const accountWithoutPassword = createMockAccount({ password: undefined });
|
const accountWithoutPassword = createMockAccount({ password: undefined });
|
||||||
const config: OpenClawConfig = {};
|
|
||||||
const core = createMockRuntime();
|
|
||||||
setBlueBubblesRuntime(core);
|
|
||||||
|
|
||||||
const sinkStrict = vi.fn();
|
const sinkStrict = vi.fn();
|
||||||
const sinkWithoutPassword = vi.fn();
|
const sinkWithoutPassword = vi.fn();
|
||||||
|
registerWebhookTargets([
|
||||||
|
{ account: accountStrict, statusSink: sinkStrict },
|
||||||
|
{ account: accountWithoutPassword, statusSink: sinkWithoutPassword },
|
||||||
|
]);
|
||||||
|
|
||||||
const req = createMockRequest("POST", "/bluebubbles-webhook?password=secret-token", {
|
const req = createWebhookRequestForTest({
|
||||||
type: "new-message",
|
url: "/bluebubbles-webhook?password=secret-token",
|
||||||
data: {
|
body: createNewMessagePayload(),
|
||||||
text: "hello",
|
|
||||||
handle: { address: "+15551234567" },
|
|
||||||
isGroup: false,
|
|
||||||
isFromMe: false,
|
|
||||||
guid: "msg-1",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
|
||||||
remoteAddress: "192.168.1.100",
|
remoteAddress: "192.168.1.100",
|
||||||
};
|
|
||||||
|
|
||||||
const unregisterStrict = registerBlueBubblesWebhookTarget({
|
|
||||||
account: accountStrict,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
statusSink: sinkStrict,
|
|
||||||
});
|
});
|
||||||
const unregisterNoPassword = registerBlueBubblesWebhookTarget({
|
await expectWebhookStatus(req, 200);
|
||||||
account: accountWithoutPassword,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
statusSink: sinkWithoutPassword,
|
|
||||||
});
|
|
||||||
unregister = () => {
|
|
||||||
unregisterStrict();
|
|
||||||
unregisterNoPassword();
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = createMockResponse();
|
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
||||||
|
|
||||||
expect(handled).toBe(true);
|
|
||||||
expect(res.statusCode).toBe(200);
|
|
||||||
expect(sinkStrict).toHaveBeenCalledTimes(1);
|
expect(sinkStrict).toHaveBeenCalledTimes(1);
|
||||||
expect(sinkWithoutPassword).not.toHaveBeenCalled();
|
expect(sinkWithoutPassword).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requires authentication for loopback requests when password is configured", async () => {
|
it("requires authentication for loopback requests when password is configured", async () => {
|
||||||
const account = createMockAccount({ password: "secret-token" });
|
const account = createMockAccount({ password: "secret-token" });
|
||||||
const config: OpenClawConfig = {};
|
setupWebhookTarget({ account });
|
||||||
const core = createMockRuntime();
|
|
||||||
setBlueBubblesRuntime(core);
|
|
||||||
for (const remoteAddress of ["127.0.0.1", "::1", "::ffff:127.0.0.1"]) {
|
for (const remoteAddress of ["127.0.0.1", "::1", "::ffff:127.0.0.1"]) {
|
||||||
const req = createMockRequest("POST", "/bluebubbles-webhook", {
|
const req = createWebhookRequestForTest({
|
||||||
type: "new-message",
|
body: createNewMessagePayload(),
|
||||||
data: {
|
|
||||||
text: "hello",
|
|
||||||
handle: { address: "+15551234567" },
|
|
||||||
isGroup: false,
|
|
||||||
isFromMe: false,
|
|
||||||
guid: "msg-1",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
|
||||||
remoteAddress,
|
remoteAddress,
|
||||||
};
|
|
||||||
|
|
||||||
const loopbackUnregister = registerBlueBubblesWebhookTarget({
|
|
||||||
account,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
});
|
});
|
||||||
|
await expectWebhookStatus(req, 401);
|
||||||
const res = createMockResponse();
|
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
||||||
expect(handled).toBe(true);
|
|
||||||
expect(res.statusCode).toBe(401);
|
|
||||||
|
|
||||||
loopbackUnregister();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects targets without passwords for loopback and proxied-looking requests", async () => {
|
it("rejects targets without passwords for loopback and proxied-looking requests", async () => {
|
||||||
const account = createMockAccount({ password: undefined });
|
const account = createMockAccount({ password: undefined });
|
||||||
const config: OpenClawConfig = {};
|
setupWebhookTarget({ account });
|
||||||
const core = createMockRuntime();
|
|
||||||
setBlueBubblesRuntime(core);
|
|
||||||
|
|
||||||
unregister = registerBlueBubblesWebhookTarget({
|
|
||||||
account,
|
|
||||||
config,
|
|
||||||
runtime: { log: vi.fn(), error: vi.fn() },
|
|
||||||
core,
|
|
||||||
path: "/bluebubbles-webhook",
|
|
||||||
});
|
|
||||||
|
|
||||||
const headerVariants: Record<string, string>[] = [
|
const headerVariants: Record<string, string>[] = [
|
||||||
{ host: "localhost" },
|
{ host: "localhost" },
|
||||||
|
|
@ -673,28 +537,12 @@ describe("BlueBubbles webhook monitor", () => {
|
||||||
{ host: "localhost", forwarded: "for=203.0.113.10;proto=https;host=example.com" },
|
{ host: "localhost", forwarded: "for=203.0.113.10;proto=https;host=example.com" },
|
||||||
];
|
];
|
||||||
for (const headers of headerVariants) {
|
for (const headers of headerVariants) {
|
||||||
const req = createMockRequest(
|
const req = createWebhookRequestForTest({
|
||||||
"POST",
|
body: createNewMessagePayload(),
|
||||||
"/bluebubbles-webhook",
|
|
||||||
{
|
|
||||||
type: "new-message",
|
|
||||||
data: {
|
|
||||||
text: "hello",
|
|
||||||
handle: { address: "+15551234567" },
|
|
||||||
isGroup: false,
|
|
||||||
isFromMe: false,
|
|
||||||
guid: "msg-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
headers,
|
headers,
|
||||||
);
|
|
||||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
|
||||||
remoteAddress: "127.0.0.1",
|
remoteAddress: "127.0.0.1",
|
||||||
};
|
});
|
||||||
const res = createMockResponse();
|
await expectWebhookStatus(req, 401);
|
||||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
|
||||||
expect(handled).toBe(true);
|
|
||||||
expect(res.statusCode).toBe(401);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue