Skip to content
File

Blob: tests/worker/dispatch/shared/run-environment/branch-checkout.test.ts

typescript257 lines
1import { describe, expect, it, vi } from "vitest";
2 
3import { CommitSha } from "@/contracts";
4import { prepareExecutionEnvironment } from "@/worker/dispatch/shared/run-environment";
5 
6import {
7 VALID_REPO_CONFIG,
8 createExecResult,
9 createHarness,
10 createLeaseStub,
11} from "../../../../helpers/run-environment";
12 
13const expectBranchCheckoutInitialized = (events: string[]): void => {
14 expect(events).toContain("init");
15 expect(events.indexOf("remote-add")).toBeGreaterThan(events.indexOf("init"));
16 expect(events.indexOf("fetch-depth-1")).toBeGreaterThan(events.indexOf("remote-add"));
17};
18 
19describe("run environment preparation", () => {
20 describe("branch checkout fallback", () => {
21 it("falls back to a full branch fetch when the server rejects shallow requests", async () => {
22 const commitSha = CommitSha.assertDecode("2222222222222222222222222222222222222222");
23 const { context, events, projectId, recordRunResolvedCommit, runId, sandbox } = createHarness({
24 projectIdValue: "prj_2222222222222222222222",
25 runIdValue: "run_2222222222222222222222",
26 snapshotCommitSha: null,
27 execImpl: (command) => {
28 if (command.startsWith("git init ")) {
29 events.push("init");
30 return createExecResult();
31 }
32 
33 if (command.includes("remote add origin")) {
34 events.push("remote-add");
35 return createExecResult();
36 }
37 
38 if (command.includes("fetch --depth=1 --update-shallow origin 'main'")) {
39 events.push("fetch-depth-1");
40 return createExecResult({
41 success: false,
42 stderr: "fatal: Server does not support shallow requests",
43 });
44 }
45 
46 if (command.includes("fetch origin 'main'")) {
47 events.push("fetch-full");
48 return createExecResult();
49 }
50 
51 if (command.includes("checkout -B 'main' FETCH_HEAD")) {
52 events.push("checkout-branch");
53 return createExecResult();
54 }
55 
56 if (command.includes("branch --set-upstream-to='origin/main' 'main'")) {
57 events.push("set-upstream");
58 return createExecResult();
59 }
60 
61 if (command.includes("rev-parse HEAD")) {
62 events.push("rev-parse");
63 return createExecResult({
64 stdout: `${commitSha}\n`,
65 });
66 }
67 
68 throw new Error(`Unexpected exec command: ${command}`);
69 },
70 });
71 
72 await expect(prepareExecutionEnvironment(context, createLeaseStub())).rejects.toThrow(
73 "Repository config is not valid YAML",
74 );
75 
76 expect(recordRunResolvedCommit).toHaveBeenCalledWith({
77 projectId,
78 runId,
79 commitSha,
80 });
81 expectBranchCheckoutInitialized(events);
82 expect(events.indexOf("fetch-full")).toBeGreaterThan(events.indexOf("fetch-depth-1"));
83 expect(events.indexOf("checkout-branch")).toBeGreaterThan(events.indexOf("fetch-full"));
84 expect(events.indexOf("set-upstream")).toBeGreaterThan(events.indexOf("checkout-branch"));
85 expect(events.indexOf("record-run-resolved-commit")).toBeGreaterThan(events.indexOf("checkout-branch"));
86 expect(sandbox.deleteSession).toHaveBeenCalledWith("checkout-session");
87 expect(context.state.session).toBeNull();
88 });
89 
90 it("skips branch depth expansion after a full-history fallback", async () => {
91 const commitSha = CommitSha.assertDecode("3333333333333333333333333333333333333333");
92 const { context, events, projectId, recordRunResolvedCommit, runId, sandbox } = createHarness({
93 projectIdValue: "prj_3333333333333333333333",
94 runIdValue: "run_3333333333333333333333",
95 snapshotCommitSha: null,
96 configContent: VALID_REPO_CONFIG,
97 execImpl: (command) => {
98 if (command.startsWith("git init ")) {
99 events.push("init");
100 return createExecResult();
101 }
102 
103 if (command.includes("remote add origin")) {
104 events.push("remote-add");
105 return createExecResult();
106 }
107 
108 if (command.includes("fetch --depth=1 --update-shallow origin 'main'")) {
109 events.push("fetch-depth-1");
110 return createExecResult({
111 success: false,
112 stderr: "fatal: dumb http transport does not support shallow capabilities",
113 });
114 }
115 
116 if (command.includes("fetch --depth=3 --update-shallow origin 'main'")) {
117 throw new Error(`Branch depth expansion should be skipped after full fallback: ${command}`);
118 }
119 
120 if (command.includes("fetch origin 'main'")) {
121 events.push("fetch-full");
122 return createExecResult();
123 }
124 
125 if (command.includes("checkout -B 'main' FETCH_HEAD")) {
126 events.push("checkout-branch");
127 return createExecResult();
128 }
129 
130 if (command.includes("branch --set-upstream-to='origin/main' 'main'")) {
131 events.push("set-upstream");
132 return createExecResult();
133 }
134 
135 if (command.includes("rev-parse --is-shallow-repository")) {
136 events.push("check-shallow");
137 return createExecResult({
138 stdout: "false\n",
139 });
140 }
141 
142 if (command.includes("rev-parse HEAD")) {
143 events.push("rev-parse");
144 return createExecResult({
145 stdout: `${commitSha}\n`,
146 });
147 }
148 
149 throw new Error(`Unexpected exec command: ${command}`);
150 },
151 });
152 
153 await expect(prepareExecutionEnvironment(context, createLeaseStub())).resolves.toEqual({
154 repoConfig: expect.objectContaining({
155 checkout: expect.objectContaining({ depth: 3 }),
156 }),
157 workingDirectory: "/workspace/repo",
158 });
159 
160 expect(recordRunResolvedCommit).toHaveBeenCalledWith({
161 projectId,
162 runId,
163 commitSha,
164 });
165 expectBranchCheckoutInitialized(events);
166 expect(events.indexOf("fetch-full")).toBeGreaterThan(events.indexOf("fetch-depth-1"));
167 expect(events.indexOf("record-run-resolved-commit")).toBeLessThan(events.indexOf("read-file"));
168 expect(events.indexOf("set-upstream")).toBeGreaterThan(events.indexOf("checkout-branch"));
169 expect(events.indexOf("check-shallow")).toBeGreaterThan(events.indexOf("read-file"));
170 expect(events).not.toContain("fetch-depth-3");
171 expect(sandbox.deleteSession).toHaveBeenCalledWith("checkout-session");
172 expect(context.state.session).toMatchObject({ id: "run-session" });
173 });
174 
175 it("creates the named execution session before falling back to getSession", async () => {
176 const commitSha = CommitSha.assertDecode("4444444444444444444444444444444444444444");
177 const { checkoutSession, context, recordRunResolvedCommit, replaceSteps, runId, runSession, sandbox } =
178 createHarness({
179 projectIdValue: "prj_4444444444444444444444",
180 runIdValue: "run_4444444444444444444444",
181 snapshotCommitSha: null,
182 configContent: VALID_REPO_CONFIG,
183 execImpl: (command) => {
184 if (command.startsWith("git init ")) {
185 return createExecResult();
186 }
187 
188 if (command.includes("remote add origin")) {
189 return createExecResult();
190 }
191 
192 if (command.includes("fetch --depth=1 --update-shallow origin 'main'")) {
193 return createExecResult();
194 }
195 
196 if (command.includes("checkout -B 'main' FETCH_HEAD")) {
197 return createExecResult();
198 }
199 
200 if (command.includes("branch --set-upstream-to='origin/main' 'main'")) {
201 return createExecResult();
202 }
203 
204 if (command.includes("rev-parse HEAD")) {
205 return createExecResult({
206 stdout: `${commitSha}\n`,
207 });
208 }
209 
210 if (command.includes("rev-parse --is-shallow-repository")) {
211 return createExecResult({
212 stdout: "true\n",
213 });
214 }
215 
216 if (command.includes("fetch --depth=3 --update-shallow origin 'main'")) {
217 return createExecResult();
218 }
219 
220 throw new Error(`Unexpected exec command: ${command}`);
221 },
222 });
223 
224 const sessionExistsError = Object.assign(new Error("session exists"), {
225 code: "SESSION_ALREADY_EXISTS",
226 name: "SessionAlreadyExistsError",
227 });
228 const createSession = sandbox.createSession as ReturnType<typeof vi.fn>;
229 createSession.mockReset();
230 createSession.mockResolvedValueOnce(checkoutSession);
231 createSession.mockRejectedValueOnce(sessionExistsError);
232 context.runtime.getSession = vi.fn(async () => runSession as never);
233 
234 await expect(
235 prepareExecutionEnvironment(context, createLeaseStub(), {
236 executionSessionId: "workflow-run-session",
237 }),
238 ).resolves.toEqual({
239 repoConfig: expect.objectContaining({
240 checkout: expect.objectContaining({ depth: 3 }),
241 }),
242 workingDirectory: "/workspace/repo",
243 });
244 
245 expect(recordRunResolvedCommit).toHaveBeenCalled();
246 expect(replaceSteps).toHaveBeenCalled();
247 expect(createSession).toHaveBeenNthCalledWith(2, {
248 id: "workflow-run-session",
249 cwd: "/workspace/repo",
250 });
251 expect(context.runtime.getSession).toHaveBeenCalledWith("workflow-run-session");
252 expect(context.state.session).toBe(runSession);
253 expect(runId).toBeDefined();
254 });
255 });
256});