Skip to content
File

Blob: test/ls-refs-filters.worker.test.ts

typescript160 lines
1import { it, expect } from "vitest";
2import { env, exports as workerExports } from "cloudflare:workers";
3import { pktLine, delimPkt, flushPkt, concatChunks, encodeGitObject } from "@/worker/git";
4import { uniqueRepoId, runDOWithRetry, toRequestBody } from "./util/test-helpers";
5import { setupRepoForTests } from "./util/repoSeed";
6import { registerTestPack } from "./util/packed-repo";
7import { decodePktLinePayloads } from "./util/fetch-protocol";
8 
9function buildLsRefsBody(args: string[] = []) {
10 const chunks: Uint8Array[] = [];
11 chunks.push(pktLine("command=ls-refs\n"));
12 chunks.push(delimPkt());
13 for (const a of args) chunks.push(pktLine(a + "\n"));
14 chunks.push(flushPkt());
15 return concatChunks(chunks);
16}
17 
18it("ls-refs: ref-prefix filters refs and peel adds peeled attribute for annotated tags", async () => {
19 const owner = "o";
20 const repo = uniqueRepoId("r-lsrefs-filters");
21 await setupRepoForTests(env, owner, repo);
22 const repoId = `${owner}/${repo}`;
23 
24 // Seed minimal repo to create HEAD -> refs/heads/main
25 const id = env.REPO_DO.idFromName(repoId);
26 const { commitOid } = await runDOWithRetry(
27 () => env.REPO_DO.get(id),
28 async (instance) => instance.seedMinimalRepo()
29 );
30 
31 // Create an annotated tag pointing to the commit
32 const tagPayload = new TextEncoder().encode(
33 `object ${commitOid}\n` +
34 `type commit\n` +
35 `tag v1\n` +
36 `tagger You <you@example.com> 0 +0000\n\nmsg\n`
37 );
38 const { oid: tagOid } = await encodeGitObject("tag", tagPayload);
39 
40 // Register the annotated tag in a pack (post-closure: no loose objects from push)
41 await registerTestPack({
42 env,
43 repoId,
44 getStub: () => env.REPO_DO.get(id),
45 packName: `pack-tag-${Date.now()}.pack`,
46 objects: [{ type: "tag" as const, payload: tagPayload }],
47 });
48 // Add the tag ref
49 await runDOWithRetry(
50 () => env.REPO_DO.get(id),
51 async (instance) => {
52 const { refs } = await instance.getHeadAndRefs();
53 refs.push({ name: "refs/tags/v1", oid: tagOid });
54 await instance.setRefs(refs);
55 }
56 );
57 
58 // Ask only for refs/tags/* and request peeling
59 const body = buildLsRefsBody(["ref-prefix refs/tags/", "peel"]);
60 const url = `https://example.com/${owner}/${repo}/git-upload-pack`;
61 const res = await workerExports.default.fetch(url, {
62 method: "POST",
63 headers: {
64 "Content-Type": "application/x-git-upload-pack-request",
65 "Git-Protocol": "version=2",
66 },
67 body: toRequestBody(body),
68 });
69 expect(res.status).toBe(200);
70 const lines = decodePktLinePayloads(new Uint8Array(await res.arrayBuffer()));
71 
72 // HEAD should be first and present
73 expect(lines[0]?.startsWith("unborn HEAD") || lines[0]?.startsWith(commitOid + " HEAD")).toBe(
74 true
75 );
76 // The tag must be listed and include peeled:<commitOid>
77 const tagLine = lines.find((l: string) => l.includes(" refs/tags/v1"));
78 expect(tagLine).toBeDefined();
79 expect(tagLine!.includes(`peeled:${commitOid}`)).toBe(true);
80 // No branch refs (refs/heads/*) beyond HEAD line
81 expect(lines.slice(1).some((l: string) => /\srefs\/heads\//.test(l))).toBe(false);
82});
83 
84it("ls-refs: peel resolves many annotated tags stored across multiple packs", async () => {
85 const owner = "o";
86 const repo = uniqueRepoId("r-lsrefs-many-tags");
87 await setupRepoForTests(env, owner, repo);
88 const repoId = `${owner}/${repo}`;
89 const id = env.REPO_DO.idFromName(repoId);
90 const getStub = () => env.REPO_DO.get(id);
91 
92 const { commitOid } = await runDOWithRetry(getStub, async (instance) =>
93 instance.seedMinimalRepo()
94 );
95 
96 const tagCount = 48;
97 const refs: { name: string; oid: string }[] = [];
98 const firstPackObjects: Array<{ type: "tag"; payload: Uint8Array }> = [];
99 const secondPackObjects: Array<{ type: "tag"; payload: Uint8Array }> = [];
100 
101 for (let index = 0; index < tagCount; index++) {
102 const tagName = `v${index.toString().padStart(2, "0")}`;
103 const tagPayload = new TextEncoder().encode(
104 `object ${commitOid}\n` +
105 `type commit\n` +
106 `tag ${tagName}\n` +
107 `tagger You <you@example.com> 0 +0000\n\nmsg ${tagName}\n`
108 );
109 const { oid } = await encodeGitObject("tag", tagPayload);
110 refs.push({ name: `refs/tags/${tagName}`, oid });
111 const object = { type: "tag" as const, payload: tagPayload };
112 if (index < tagCount / 2) {
113 firstPackObjects.push(object);
114 } else {
115 secondPackObjects.push(object);
116 }
117 }
118 
119 await registerTestPack({
120 env,
121 repoId,
122 getStub,
123 packName: `pack-tags-a-${Date.now()}.pack`,
124 objects: firstPackObjects,
125 });
126 await registerTestPack({
127 env,
128 repoId,
129 getStub,
130 packName: `pack-tags-b-${Date.now()}.pack`,
131 objects: secondPackObjects,
132 });
133 
134 await runDOWithRetry(getStub, async (instance) => {
135 const current = await instance.getHeadAndRefs();
136 await instance.setRefs([...current.refs, ...refs]);
137 });
138 
139 const url = `https://example.com/${owner}/${repo}/git-upload-pack`;
140 const res = await workerExports.default.fetch(url, {
141 method: "POST",
142 headers: {
143 "Content-Type": "application/x-git-upload-pack-request",
144 "Git-Protocol": "version=2",
145 },
146 body: toRequestBody(buildLsRefsBody(["ref-prefix refs/tags/", "peel"])),
147 });
148 expect(res.status).toBe(200);
149 
150 const lines = decodePktLinePayloads(new Uint8Array(await res.arrayBuffer()));
151 const tagLines = lines.filter((line) => line.includes(" refs/tags/"));
152 expect(tagLines.length).toBe(tagCount);
153 for (let index = 0; index < tagCount; index++) {
154 const tagName = `refs/tags/v${index.toString().padStart(2, "0")}`;
155 const line = tagLines.find((candidate) => candidate.includes(` ${tagName}`));
156 expect(line, `missing ${tagName}`).toBeDefined();
157 expect(line?.includes(`peeled:${commitOid}`), `missing peeled attr for ${tagName}`).toBe(true);
158 }
159});