File
Blob: test/pack-indexer.connectivity.tags.worker.test.ts
| 1 | import { describe, expect, it } from "vitest"; |
| 2 | import { env } from "cloudflare:workers"; |
| 3 | import { buildPack, makeCommit, makeTree } from "./util/git-pack"; |
| 4 | import { createTestCacheContext } from "./util/pack-first"; |
| 5 | import { uniqueRepoId } from "./util/test-helpers"; |
| 6 | import { makeLimiter, packIndexerLog as log } from "./util/pack-indexer.helpers"; |
| 7 | |
| 8 | import { |
| 9 | runPackConnectivityCheck, |
| 10 | scanPack, |
| 11 | resolveDeltasAndWriteIdx, |
| 12 | } from "@/worker/git/pack/indexer"; |
| 13 | import { computeOid } from "@/worker/git/core/objects"; |
| 14 | |
| 15 | type ConnectivityStatus = { ref: string; ok: boolean; msg?: string }; |
| 16 | |
| 17 | describe("runPackConnectivityCheck annotated tags", () => { |
| 18 | it("unwraps annotated tags and validates the final target", async () => { |
| 19 | const tree = await makeTree(); |
| 20 | const commit = await makeCommit(tree.oid, "tagged commit"); |
| 21 | const tagPayload = new TextEncoder().encode( |
| 22 | `object ${commit.oid}\ntype commit\ntag v1.0\ntagger You <you@example.com> 0 +0000\n\nrelease\n` |
| 23 | ); |
| 24 | const tagOid = await computeOid("tag", tagPayload); |
| 25 | |
| 26 | const packBytes = await buildPack([ |
| 27 | { type: "tree", payload: tree.payload }, |
| 28 | { type: "commit", payload: commit.payload }, |
| 29 | { type: "tag", payload: tagPayload }, |
| 30 | ]); |
| 31 | |
| 32 | const packKey = "test/conn-tag.pack"; |
| 33 | await env.REPO_BUCKET.put(packKey, packBytes); |
| 34 | const head = await env.REPO_BUCKET.head(packKey); |
| 35 | |
| 36 | const scanResult = await scanPack({ |
| 37 | env, |
| 38 | packKey, |
| 39 | packSize: head!.size, |
| 40 | limiter: makeLimiter(), |
| 41 | countSubrequest: () => {}, |
| 42 | log, |
| 43 | }); |
| 44 | |
| 45 | const repoId = uniqueRepoId(); |
| 46 | const resolveResult = await resolveDeltasAndWriteIdx({ |
| 47 | env, |
| 48 | packKey, |
| 49 | packSize: head!.size, |
| 50 | limiter: makeLimiter(), |
| 51 | countSubrequest: () => {}, |
| 52 | log, |
| 53 | scanResult, |
| 54 | repoId, |
| 55 | }); |
| 56 | |
| 57 | const statuses: ConnectivityStatus[] = [{ ref: "refs/tags/v1.0", ok: true }]; |
| 58 | const cacheCtx = createTestCacheContext("http://localhost/test"); |
| 59 | |
| 60 | await runPackConnectivityCheck({ |
| 61 | env, |
| 62 | repoId, |
| 63 | newPackKey: packKey, |
| 64 | newIdxView: resolveResult.idxView, |
| 65 | newPackSize: head!.size, |
| 66 | activeCatalog: [], |
| 67 | commands: [{ oldOid: "0".repeat(40), newOid: tagOid, ref: "refs/tags/v1.0" }], |
| 68 | statuses, |
| 69 | log, |
| 70 | cacheCtx, |
| 71 | }); |
| 72 | |
| 73 | expect(statuses[0].ok).toBe(true); |
| 74 | }); |
| 75 | |
| 76 | it("accepts annotated tag chains up to the hard limit", async () => { |
| 77 | const tree = await makeTree(); |
| 78 | const commit = await makeCommit(tree.oid, "tag depth limit"); |
| 79 | const tagPayloads: Uint8Array[] = []; |
| 80 | let targetOid = commit.oid; |
| 81 | for (let depth = 0; depth < 8; depth++) { |
| 82 | const payload = new TextEncoder().encode( |
| 83 | `object ${targetOid}\ntype ${depth === 0 ? "commit" : "tag"}\ntag v${depth}\ntagger You <you@example.com> 0 +0000\n\ndepth ${depth}\n` |
| 84 | ); |
| 85 | tagPayloads.push(payload); |
| 86 | targetOid = await computeOid("tag", payload); |
| 87 | } |
| 88 | |
| 89 | const packEntries = [ |
| 90 | { type: "tree" as const, payload: tree.payload }, |
| 91 | { type: "commit" as const, payload: commit.payload }, |
| 92 | ...tagPayloads.map((payload) => ({ type: "tag" as const, payload })), |
| 93 | ]; |
| 94 | const packBytes = await buildPack(packEntries); |
| 95 | |
| 96 | const packKey = "test/conn-tag-depth-limit.pack"; |
| 97 | await env.REPO_BUCKET.put(packKey, packBytes); |
| 98 | const head = await env.REPO_BUCKET.head(packKey); |
| 99 | |
| 100 | const scanResult = await scanPack({ |
| 101 | env, |
| 102 | packKey, |
| 103 | packSize: head!.size, |
| 104 | limiter: makeLimiter(), |
| 105 | countSubrequest: () => {}, |
| 106 | log, |
| 107 | }); |
| 108 | |
| 109 | const repoId = uniqueRepoId(); |
| 110 | const resolveResult = await resolveDeltasAndWriteIdx({ |
| 111 | env, |
| 112 | packKey, |
| 113 | packSize: head!.size, |
| 114 | limiter: makeLimiter(), |
| 115 | countSubrequest: () => {}, |
| 116 | log, |
| 117 | scanResult, |
| 118 | repoId, |
| 119 | }); |
| 120 | |
| 121 | const statuses: ConnectivityStatus[] = [{ ref: "refs/tags/limit", ok: true }]; |
| 122 | const cacheCtx = createTestCacheContext("http://localhost/test"); |
| 123 | |
| 124 | await runPackConnectivityCheck({ |
| 125 | env, |
| 126 | repoId, |
| 127 | newPackKey: packKey, |
| 128 | newIdxView: resolveResult.idxView, |
| 129 | newPackSize: head!.size, |
| 130 | activeCatalog: [], |
| 131 | commands: [{ oldOid: "0".repeat(40), newOid: targetOid, ref: "refs/tags/limit" }], |
| 132 | statuses, |
| 133 | log, |
| 134 | cacheCtx, |
| 135 | }); |
| 136 | |
| 137 | expect(statuses[0].ok).toBe(true); |
| 138 | }); |
| 139 | |
| 140 | it("rejects annotated tag chains deeper than the hard limit", async () => { |
| 141 | const tree = await makeTree(); |
| 142 | const commit = await makeCommit(tree.oid, "deep tag commit"); |
| 143 | const tagPayloads: Uint8Array[] = []; |
| 144 | let targetOid = commit.oid; |
| 145 | for (let depth = 0; depth < 9; depth++) { |
| 146 | const payload = new TextEncoder().encode( |
| 147 | `object ${targetOid}\ntype ${depth === 0 ? "commit" : "tag"}\ntag v${depth}\ntagger You <you@example.com> 0 +0000\n\ndepth ${depth}\n` |
| 148 | ); |
| 149 | tagPayloads.push(payload); |
| 150 | targetOid = await computeOid("tag", payload); |
| 151 | } |
| 152 | |
| 153 | const packEntries = [ |
| 154 | { type: "tree" as const, payload: tree.payload }, |
| 155 | { type: "commit" as const, payload: commit.payload }, |
| 156 | ...tagPayloads.map((payload) => ({ type: "tag" as const, payload })), |
| 157 | ]; |
| 158 | const packBytes = await buildPack(packEntries); |
| 159 | |
| 160 | const packKey = "test/conn-tag-depth.pack"; |
| 161 | await env.REPO_BUCKET.put(packKey, packBytes); |
| 162 | const head = await env.REPO_BUCKET.head(packKey); |
| 163 | |
| 164 | const scanResult = await scanPack({ |
| 165 | env, |
| 166 | packKey, |
| 167 | packSize: head!.size, |
| 168 | limiter: makeLimiter(), |
| 169 | countSubrequest: () => {}, |
| 170 | log, |
| 171 | }); |
| 172 | |
| 173 | const repoId = uniqueRepoId(); |
| 174 | const resolveResult = await resolveDeltasAndWriteIdx({ |
| 175 | env, |
| 176 | packKey, |
| 177 | packSize: head!.size, |
| 178 | limiter: makeLimiter(), |
| 179 | countSubrequest: () => {}, |
| 180 | log, |
| 181 | scanResult, |
| 182 | repoId, |
| 183 | }); |
| 184 | |
| 185 | const statuses: ConnectivityStatus[] = [{ ref: "refs/tags/deep", ok: true }]; |
| 186 | const cacheCtx = createTestCacheContext("http://localhost/test"); |
| 187 | |
| 188 | await runPackConnectivityCheck({ |
| 189 | env, |
| 190 | repoId, |
| 191 | newPackKey: packKey, |
| 192 | newIdxView: resolveResult.idxView, |
| 193 | newPackSize: head!.size, |
| 194 | activeCatalog: [], |
| 195 | commands: [{ oldOid: "0".repeat(40), newOid: targetOid, ref: "refs/tags/deep" }], |
| 196 | statuses, |
| 197 | log, |
| 198 | cacheCtx, |
| 199 | }); |
| 200 | |
| 201 | expect(statuses[0].ok).toBe(false); |
| 202 | expect(statuses[0].msg).toBe("missing-objects"); |
| 203 | }); |
| 204 | }); |