mirror of
https://github.com/tiennm99/claude-status-webhook.git
synced 2026-04-17 13:21:01 +00:00
- 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
125 lines
4.5 KiB
JavaScript
125 lines
4.5 KiB
JavaScript
import { describe, it, expect } from "vitest";
|
|
import { env } from "cloudflare:test";
|
|
import {
|
|
addSubscriber,
|
|
removeSubscriber,
|
|
getSubscriber,
|
|
updateSubscriberTypes,
|
|
updateSubscriberComponents,
|
|
getSubscribersByType,
|
|
} from "../src/kv-store.js";
|
|
|
|
// Each test uses unique chatIds to avoid cross-test interference (miniflare KV persists across tests)
|
|
describe("kv-store", () => {
|
|
const kv = env.claude_status;
|
|
|
|
describe("addSubscriber / getSubscriber", () => {
|
|
it("adds subscriber with default types", async () => {
|
|
await addSubscriber(kv, 100, null);
|
|
const sub = await getSubscriber(kv, 100, null);
|
|
expect(sub).toEqual({ types: ["incident", "component"], components: [] });
|
|
});
|
|
|
|
it("adds subscriber with threadId", async () => {
|
|
await addSubscriber(kv, 101, 456);
|
|
const sub = await getSubscriber(kv, 101, 456);
|
|
expect(sub).toEqual({ types: ["incident", "component"], components: [] });
|
|
});
|
|
|
|
it("handles threadId=0 (General topic)", async () => {
|
|
await addSubscriber(kv, 102, 0);
|
|
const sub = await getSubscriber(kv, 102, 0);
|
|
expect(sub).toEqual({ types: ["incident", "component"], components: [] });
|
|
});
|
|
|
|
it("preserves existing data on re-subscribe", async () => {
|
|
await addSubscriber(kv, 103, null);
|
|
await updateSubscriberTypes(kv, 103, null, ["incident"]);
|
|
await addSubscriber(kv, 103, null);
|
|
const sub = await getSubscriber(kv, 103, null);
|
|
expect(sub.types).toEqual(["incident"]);
|
|
});
|
|
});
|
|
|
|
describe("removeSubscriber", () => {
|
|
it("removes existing subscriber", async () => {
|
|
await addSubscriber(kv, 200, null);
|
|
await removeSubscriber(kv, 200, null);
|
|
const sub = await getSubscriber(kv, 200, null);
|
|
expect(sub).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("updateSubscriberTypes", () => {
|
|
it("updates types for existing subscriber", async () => {
|
|
await addSubscriber(kv, 300, null);
|
|
const result = await updateSubscriberTypes(kv, 300, null, ["incident"]);
|
|
expect(result).toBe(true);
|
|
const sub = await getSubscriber(kv, 300, null);
|
|
expect(sub.types).toEqual(["incident"]);
|
|
});
|
|
|
|
it("returns false for non-existent subscriber", async () => {
|
|
const result = await updateSubscriberTypes(kv, 99999, null, ["incident"]);
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("updateSubscriberComponents", () => {
|
|
it("sets component filter", async () => {
|
|
await addSubscriber(kv, 400, null);
|
|
await updateSubscriberComponents(kv, 400, null, ["API"]);
|
|
const sub = await getSubscriber(kv, 400, null);
|
|
expect(sub.components).toEqual(["API"]);
|
|
});
|
|
});
|
|
|
|
describe("getSubscribersByType", () => {
|
|
it("filters by event type", async () => {
|
|
// Use unique IDs unlikely to collide with other tests
|
|
await addSubscriber(kv, 50001, null);
|
|
await updateSubscriberTypes(kv, 50001, null, ["incident"]);
|
|
await addSubscriber(kv, 50002, null);
|
|
await updateSubscriberTypes(kv, 50002, null, ["component"]);
|
|
|
|
const incident = await getSubscribersByType(kv, "incident");
|
|
const incidentIds = incident.map((s) => s.chatId);
|
|
expect(incidentIds).toContain(50001);
|
|
expect(incidentIds).not.toContain(50002);
|
|
|
|
const component = await getSubscribersByType(kv, "component");
|
|
const componentIds = component.map((s) => s.chatId);
|
|
expect(componentIds).toContain(50002);
|
|
expect(componentIds).not.toContain(50001);
|
|
});
|
|
|
|
it("filters by component name", async () => {
|
|
await addSubscriber(kv, 60001, null);
|
|
await updateSubscriberComponents(kv, 60001, null, ["API"]);
|
|
await addSubscriber(kv, 60002, null); // no component filter = all
|
|
|
|
const results = await getSubscribersByType(kv, "component", "API");
|
|
const ids = results.map((s) => s.chatId);
|
|
expect(ids).toContain(60001);
|
|
expect(ids).toContain(60002);
|
|
});
|
|
|
|
it("excludes non-matching component filter", async () => {
|
|
await addSubscriber(kv, 70001, null);
|
|
await updateSubscriberComponents(kv, 70001, null, ["Console"]);
|
|
|
|
const results = await getSubscribersByType(kv, "component", "API");
|
|
const ids = results.map((s) => s.chatId);
|
|
expect(ids).not.toContain(70001);
|
|
});
|
|
|
|
it("returns chatId as number", async () => {
|
|
await addSubscriber(kv, 80001, null);
|
|
const results = await getSubscribersByType(kv, "incident");
|
|
const match = results.find((s) => s.chatId === 80001);
|
|
expect(match).toBeDefined();
|
|
expect(typeof match.chatId).toBe("number");
|
|
});
|
|
});
|
|
});
|