File
Blob: test/d1-schema.worker.test.ts
| 1 | import { applyD1Migrations } from "cloudflare:test"; |
| 2 | import { env } from "cloudflare:workers"; |
| 3 | import { beforeAll, describe, expect, it } from "vitest"; |
| 4 | |
| 5 | import { createDb } from "@/worker/db/d1/client"; |
| 6 | import { |
| 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 | |
| 23 | import { readAppD1Migrations } from "./util/d1Migrations"; |
| 24 | |
| 25 | beforeAll(async () => { |
| 26 | await applyD1Migrations(env.DB, readAppD1Migrations()); |
| 27 | }); |
| 28 | |
| 29 | describe("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 | }); |