mirror of
https://github.com/tiennm99/claude-status-webhook.git
synced 2026-04-17 15:20:37 +00:00
fix: harden webhook reliability, fix bugs, add test suite
- Statuspage webhook always returns 200 to prevent subscriber removal - Fix parseKvKey returning string chatId instead of number - Queue consumer retries on Telegram 5xx instead of acking (prevents message loss) - Fix observability top-level enabled flag (false → true) - Add defensive null checks for webhook payload body - Cache Bot instance per isolate to avoid middleware rebuild per request - Add vitest + @cloudflare/vitest-pool-workers with 31 tests - Document DLQ and KV sharding as declined features
This commit is contained in:
79
test/queue-consumer.test.js
Normal file
79
test/queue-consumer.test.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { handleQueue } from "../src/queue-consumer.js";
|
||||
|
||||
/**
|
||||
* Create a mock queue message with ack/retry tracking
|
||||
*/
|
||||
function mockMessage(body) {
|
||||
return {
|
||||
body,
|
||||
ack: vi.fn(),
|
||||
retry: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
describe("handleQueue", () => {
|
||||
let env;
|
||||
|
||||
beforeEach(() => {
|
||||
env = {
|
||||
BOT_TOKEN: "test-token",
|
||||
claude_status: {
|
||||
delete: vi.fn(),
|
||||
},
|
||||
};
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("acks on successful send", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, status: 200 }));
|
||||
const msg = mockMessage({ chatId: 123, html: "<b>test</b>" });
|
||||
await handleQueue({ messages: [msg] }, env);
|
||||
expect(msg.ack).toHaveBeenCalled();
|
||||
expect(msg.retry).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes subscriber and acks on 403", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: false, status: 403 }));
|
||||
const msg = mockMessage({ chatId: 123, threadId: null, html: "<b>test</b>" });
|
||||
await handleQueue({ messages: [msg] }, env);
|
||||
expect(msg.ack).toHaveBeenCalled();
|
||||
expect(env.claude_status.delete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retries on 429 rate limit", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 429,
|
||||
headers: new Headers({ "Retry-After": "5" }),
|
||||
})
|
||||
);
|
||||
const msg = mockMessage({ chatId: 123, html: "<b>test</b>" });
|
||||
await handleQueue({ messages: [msg] }, env);
|
||||
expect(msg.retry).toHaveBeenCalled();
|
||||
expect(msg.ack).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retries on 5xx server error", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: false, status: 502 }));
|
||||
const msg = mockMessage({ chatId: 123, html: "<b>test</b>" });
|
||||
await handleQueue({ messages: [msg] }, env);
|
||||
expect(msg.retry).toHaveBeenCalled();
|
||||
expect(msg.ack).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retries on network error", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("network fail")));
|
||||
const msg = mockMessage({ chatId: 123, html: "<b>test</b>" });
|
||||
await handleQueue({ messages: [msg] }, env);
|
||||
expect(msg.retry).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips malformed messages", async () => {
|
||||
const msg = mockMessage({ chatId: null, html: null });
|
||||
await handleQueue({ messages: [msg] }, env);
|
||||
expect(msg.ack).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user