Skip to content

Commit 0544a58

Browse files
committed
refactor(selfhost): remove the dead checkAndMarkDelivery helper
checkAndMarkDelivery (the documented webhook-delivery-dedup helper in redis-cache.ts) was exported but never called anywhere in src/. The real webhook dedup path in server.ts implements its own get-then-set inline, duplicating the same logic with a deliberate and CORRECT mark-after-success ordering difference: it marks a delivery ID as seen only once the response is confirmed ok, so a failed/rejected webhook stays retryable. checkAndMarkDelivery marked on the FIRST call regardless of outcome, which would have incorrectly suppressed a retry of a webhook whose processing actually failed, had anything called it. Delete it -- the inline version in server.ts is already correct and live, and two divergent implementations of the same dedup logic can silently drift out of sync.
1 parent 1092bf3 commit 0544a58

2 files changed

Lines changed: 1 addition & 54 deletions

File tree

src/selfhost/redis-cache.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,3 @@ export function createRedisCache(redis: Redis) {
2727
}
2828

2929
export type RedisCache = ReturnType<typeof createRedisCache>;
30-
31-
/**
32-
* Idempotency check for GitHub webhook deliveries. Returns true if the delivery was
33-
* already seen (caller should short-circuit with 204). Marks the delivery as seen
34-
* for `ttlSeconds` (default 5 min — covers GitHub's retry window) on the FIRST call.
35-
* Best-effort: a Redis error is swallowed to avoid blocking webhook processing.
36-
*/
37-
export async function checkAndMarkDelivery(cache: RedisCache, deliveryId: string, ttlSeconds = 300): Promise<boolean> {
38-
try {
39-
const seen = await cache.get(`delivery:${deliveryId}`);
40-
if (seen) return true;
41-
await cache.set(`delivery:${deliveryId}`, "1", ttlSeconds);
42-
return false;
43-
} catch {
44-
// Redis unavailable → treat as first-time (never block processing on cache failure)
45-
return false;
46-
}
47-
}

test/unit/selfhost-redis-cache.test.ts

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Redis } from "ioredis";
22
import { describe, expect, it } from "vitest";
3-
import { checkAndMarkDelivery, createRedisCache } from "../../src/selfhost/redis-cache";
3+
import { createRedisCache } from "../../src/selfhost/redis-cache";
44

55
/** Minimal in-memory stand-in for the ioredis methods the cache uses. Emulates real Redis SET NX
66
* semantics (refuse + return null when NX is requested and the key already exists) so a test
@@ -64,38 +64,3 @@ describe("createRedisCache (#1216 webhook dedup cache)", () => {
6464
await expect(cache.claim("lock", "1", 60)).rejects.toThrow("connection refused");
6565
});
6666
});
67-
68-
describe("checkAndMarkDelivery (#1216 webhook idempotency)", () => {
69-
it("returns false (first-time) for a new delivery ID and marks it as seen", async () => {
70-
const cache = createRedisCache(fakeRedis());
71-
const result = await checkAndMarkDelivery(cache, "delivery-abc", 300);
72-
expect(result).toBe(false);
73-
// second call with the same ID should be a duplicate
74-
const duplicate = await checkAndMarkDelivery(cache, "delivery-abc", 300);
75-
expect(duplicate).toBe(true);
76-
});
77-
78-
it("returns true (duplicate) for an already-seen delivery ID", async () => {
79-
const r = fakeRedis();
80-
r._store.set("delivery:existing-id", "1");
81-
const cache = createRedisCache(r);
82-
expect(await checkAndMarkDelivery(cache, "existing-id")).toBe(true);
83-
});
84-
85-
it("different delivery IDs are tracked independently", async () => {
86-
const cache = createRedisCache(fakeRedis());
87-
expect(await checkAndMarkDelivery(cache, "id-A")).toBe(false);
88-
expect(await checkAndMarkDelivery(cache, "id-B")).toBe(false); // different ID → first-time
89-
expect(await checkAndMarkDelivery(cache, "id-A")).toBe(true); // id-A seen before
90-
});
91-
92-
it("swallows Redis errors and returns false (never blocks processing)", async () => {
93-
const brokenRedis = {
94-
async get() { throw new Error("connection refused"); },
95-
async set() { throw new Error("connection refused"); },
96-
} as unknown as Redis;
97-
const cache = createRedisCache(brokenRedis);
98-
// Must not throw — error is swallowed, returns false (first-time / let it through)
99-
expect(await checkAndMarkDelivery(cache, "any-id")).toBe(false);
100-
});
101-
});

0 commit comments

Comments
 (0)