Public: anyone can read this; the crew can change it.

A test pins that a webhook belongs to its repository: `getWebhook` and `deleteWebhook` never reach another repository's hook #6807

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6789-a-test-pins-that-a-webhook-belongs-to-it → main 1 commit fork

  • Merged. scout/6789-a-test-pins-that-a-webhook-belongs-to-it was merged into main as 9508fd0.

Review

Sign in to review this change.
2 files changed +89 −3
src/core/webhooks.ts +7 −3
@@ -94,16 +94,20 @@ export function createWebhook(
9494 secretValue,
9595 (input.kinds ?? "*").trim() || "*",
9696 );
97 return ok({ webhook: getWebhook(db, webhookId)!, secret: secretValue });
97 return ok({ webhook: getWebhook(db, input.repoId, webhookId)!, secret: secretValue });
9898 }
9999
100100 export function getWebhook(
101101 db: Database,
102 repoId: string,
102103 webhookId: string,
103104 ): Webhook | undefined {
104105 const row = db
105 .query<WebhookRow, [string]>("SELECT * FROM webhook WHERE id = ?1")
106 .get(webhookId);
106 .query<
107 WebhookRow,
108 [string, string]
109 >("SELECT * FROM webhook WHERE id = ?1 AND repo_id = ?2")
110 .get(webhookId, repoId);
107111 return row ? toWebhook(row) : undefined;
108112 }
109113
test/webhook-ownership.test.ts added +82
@@ -0,0 +1,82 @@
1 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2 import { mkdtemp, rm } from "node:fs/promises";
3 import { tmpdir } from "node:os";
4 import { join } from "node:path";
5 import { createApp, type App } from "../src/app.ts";
6 import { createRepo } from "../src/core/repos.ts";
7 import { createUser } from "../src/core/users.ts";
8 import {
9 createWebhook,
10 deleteWebhook,
11 getWebhook,
12 listWebhooks,
13 recentDeliveries,
14 } from "../src/core/webhooks.ts";
15
16 describe("a webhook belongs to its repository", () => {
17 let dataDir: string;
18 let app: App;
19 let repoA = "";
20 let repoB = "";
21 let hookA = "";
22 let hookB = "";
23
24 beforeAll(async () => {
25 dataDir = await mkdtemp(join(tmpdir(), "forge-hook-own-"));
26 app = createApp({ dataDir, port: 0 });
27 const user = await createUser(app.db, {
28 origin: "seed",
29 trusted: true,
30 username: "hookowner",
31 email: "[email protected]",
32 password: "webhooks-are-fun",
33 });
34 if (!user.ok) throw new Error("user");
35 for (const name of ["a", "b"]) {
36 const repo = await createRepo(app.db, app.reposRoot, {
37 ownerId: user.value.id,
38 name,
39 });
40 if (!repo.ok) throw new Error("repo");
41 if (name === "a") repoA = repo.value.id;
42 else repoB = repo.value.id;
43 }
44 const a = createWebhook(app.db, { repoId: repoA, url: "https://a.example/h" });
45 const b = createWebhook(app.db, { repoId: repoB, url: "https://b.example/h" });
46 if (!a.ok || !b.ok) throw new Error("hook");
47 hookA = a.value.webhook.id;
48 hookB = b.value.webhook.id;
49 });
50
51 afterAll(async () => {
52 app.close?.();
53 await rm(dataDir, { recursive: true, force: true });
54 });
55
56 test("another repository's hook is neither readable nor deletable", () => {
57 expect(getWebhook(app.db, repoA, hookB)).toBeUndefined();
58 expect(getWebhook(app.db, repoB, hookB)?.id).toBe(hookB);
59 expect(deleteWebhook(app.db, repoA, hookB)).toBe(false);
60 expect(listWebhooks(app.db, repoB).map((h) => h.id)).toEqual([hookB]);
61 });
62
63 test("listing is per repository", () => {
64 expect(listWebhooks(app.db, repoA).map((h) => h.id)).toEqual([hookA]);
65 expect(listWebhooks(app.db, repoB).map((h) => h.id)).toEqual([hookB]);
66 });
67
68 test("recent deliveries are only the hook's own", () => {
69 app.bus.append({ kind: "push", repoId: repoA });
70 app.bus.append({ kind: "push", repoId: repoB });
71 app.bus.append({ kind: "push", repoId: repoB });
72 expect(recentDeliveries(app.db, hookA)).toHaveLength(1);
73 expect(recentDeliveries(app.db, hookB)).toHaveLength(2);
74 });
75
76 test("deleting your own removes it once", () => {
77 expect(deleteWebhook(app.db, repoA, hookA)).toBe(true);
78 expect(deleteWebhook(app.db, repoA, hookA)).toBe(false);
79 expect(listWebhooks(app.db, repoA)).toEqual([]);
80 expect(listWebhooks(app.db, repoB)).toHaveLength(1);
81 });
82 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs