Files
claude-status-webhook/test/kv-store.test.js
tiennm99 8c993df72b 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
2026-04-09 10:29:30 +07:00

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");
});
});
});