Skip to content
File

Blob: test/d1-schema.worker.test.ts

typescript174 lines
1import { applyD1Migrations } from "cloudflare:test";
2import { env } from "cloudflare:workers";
3import { beforeAll, describe, expect, it } from "vitest";
4 
5import { createDb } from "@/worker/db/d1/client";
6import {
7 claimNamespace,
8 findNamespaceBySlug,
9 findPatByPrefix,
10 findRepositoryByDoName,
11 findUserByTesseraSub,
12 insertMembershipIfMissing,
13 insertPatWithGrants,
14 insertRepositoryIfNew,
15 insertUserIfNew,
16 listNamespacesForUser,
17 listPatsForUser,
18 listRepositoriesForNamespace,
19 listRepositoriesForUser,
20 revokePatById,
21} from "@/worker/db/d1/dal";
22 
23import { readAppD1Migrations } from "./util/d1Migrations";
24 
25beforeAll(async () => {
26 await applyD1Migrations(env.DB, readAppD1Migrations());
27});
28 
29describe("D1 schema + DAL round-trips", () => {
30 it("inserts a user and reads it back by tessera_sub", async () => {
31 const db = createDb(env.DB);
32 const now = Date.now();
33 const inserted = await insertUserIfNew(db, {
34 id: "user-1",
35 tesseraSub: "sub-1",
36 createdAt: now,
37 });
38 expect(inserted?.id).toBe("user-1");
39 const found = await findUserByTesseraSub(db, "sub-1");
40 expect(found?.id).toBe("user-1");
41 // Insert with same `tessera_sub` is a no-op (returns undefined).
42 const dup = await insertUserIfNew(db, {
43 id: "user-1-dup",
44 tesseraSub: "sub-1",
45 createdAt: now + 1,
46 });
47 expect(dup).toBeUndefined();
48 });
49 
50 it("claims a namespace then refuses a duplicate slug", async () => {
51 const db = createDb(env.DB);
52 const now = Date.now();
53 await insertUserIfNew(db, { id: "user-ns", tesseraSub: "sub-ns", createdAt: now });
54 const claimed = await claimNamespace(db, {
55 id: "ns-1",
56 slug: "alice",
57 createdBy: "user-ns",
58 createdAt: now,
59 });
60 expect(claimed?.id).toBe("ns-1");
61 const taken = await claimNamespace(db, {
62 id: "ns-1-dup",
63 slug: "alice",
64 createdBy: "user-ns",
65 createdAt: now,
66 });
67 expect(taken).toBeUndefined();
68 expect((await findNamespaceBySlug(db, "alice"))?.id).toBe("ns-1");
69 });
70 
71 it("inserts membership, lists namespaces for user, then repository listing", async () => {
72 const db = createDb(env.DB);
73 const now = Date.now();
74 await insertUserIfNew(db, { id: "user-r", tesseraSub: "sub-r", createdAt: now });
75 await claimNamespace(db, {
76 id: "ns-r",
77 slug: "rachel",
78 createdBy: "user-r",
79 createdAt: now,
80 });
81 await insertMembershipIfMissing(db, {
82 namespaceId: "ns-r",
83 userId: "user-r",
84 createdAt: now,
85 });
86 expect((await listNamespacesForUser(db, "user-r")).map((n) => n.slug)).toEqual(["rachel"]);
87 const created = await insertRepositoryIfNew(db, {
88 id: "repo-1",
89 namespaceId: "ns-r",
90 createdBy: "user-r",
91 slug: "site",
92 doName: "rachel/site",
93 visibility: "public",
94 createdAt: now,
95 updatedAt: now,
96 });
97 expect(created?.id).toBe("repo-1");
98 // Replay: ON CONFLICT DO NOTHING returns undefined.
99 const replay = await insertRepositoryIfNew(db, {
100 id: "repo-1-dup",
101 namespaceId: "ns-r",
102 createdBy: "user-r",
103 slug: "site",
104 doName: "rachel/site",
105 visibility: "public",
106 createdAt: now + 1,
107 updatedAt: now + 1,
108 });
109 expect(replay).toBeUndefined();
110 expect((await findRepositoryByDoName(db, "rachel/site"))?.id).toBe("repo-1");
111 const createdLater = await insertRepositoryIfNew(db, {
112 id: "repo-2",
113 namespaceId: "ns-r",
114 createdBy: "user-r",
115 slug: "api",
116 doName: "rachel/api",
117 visibility: "public",
118 createdAt: now + 2,
119 updatedAt: now + 2,
120 });
121 expect(createdLater?.id).toBe("repo-2");
122 const list = await listRepositoriesForUser(db, "user-r");
123 expect(list.map((entry) => entry.repository.slug)).toEqual(["api", "site"]);
124 expect(list[0]?.namespace.slug).toBe("rachel");
125 const namespaceList = await listRepositoriesForNamespace(db, "ns-r", null);
126 expect(namespaceList.map((entry) => entry.slug)).toEqual(["api", "site"]);
127 });
128 
129 it("inserts a PAT with grants and revokes it via the result-union DAL", async () => {
130 const db = createDb(env.DB);
131 const now = Date.now();
132 await insertUserIfNew(db, { id: "user-p", tesseraSub: "sub-p", createdAt: now });
133 await claimNamespace(db, {
134 id: "ns-p",
135 slug: "patowner",
136 createdBy: "user-p",
137 createdAt: now,
138 });
139 await insertMembershipIfMissing(db, {
140 namespaceId: "ns-p",
141 userId: "user-p",
142 createdAt: now,
143 });
144 await insertPatWithGrants(db, {
145 pat: {
146 id: "pat-1",
147 userId: "user-p",
148 name: "ci",
149 prefix: "goc_aaaaaaaa",
150 hash: "hash-aaaaaaaa",
151 createdAt: now,
152 expiresAt: null,
153 revokedAt: null,
154 lastUsedAt: null,
155 },
156 namespaceGrants: [{ patId: "pat-1", namespaceId: "ns-p", level: "pull" }],
157 repoGrants: [],
158 });
159 expect((await findPatByPrefix(db, "goc_aaaaaaaa"))?.id).toBe("pat-1");
160 expect((await listPatsForUser(db, "user-p")).map((row) => row.id)).toEqual(["pat-1"]);
161 // Cross-user revoke fails closed.
162 expect(await revokePatById(db, "pat-1", "user-r", now + 1)).toEqual({
163 ok: false,
164 reason: "not-owner",
165 });
166 expect(await revokePatById(db, "pat-1", "user-p", now + 2)).toEqual({ ok: true });
167 // Re-revoke is reported as already-revoked.
168 expect(await revokePatById(db, "pat-1", "user-p", now + 3)).toEqual({
169 ok: false,
170 reason: "already-revoked",
171 });
172 });
173});