1/**
2 * Push-ingest profile against the LOCAL workerd (no cloud deploy): pushes
3 * the repository at `GIT_PROFILE_REPO` and prints the server's per-phase
4 * split. Skipped unless the env var is set — it is a measurement tool, not
5 * a regression test (DESIGN §22.4).
6 *
7 * GIT_PROFILE_REPO=/path/to/checkout pnpm test test/Git/GitIngestProfile.local.test.ts
8 *
9 * Do NOT export CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN for this: the
10 * profile loader treats env credentials as "configure this profile as env"
11 * and rewrites ~/.alchemy/profiles.json.
12 */
13import * as Alchemy from "@/index.ts";
14import * as Cloudflare from "@/Cloudflare";
15import { GitApi } from "@/Git/Api.ts";
16import * as Test from "@/Test/Alchemy";
17import { expect } from "alchemy-test";
18import * as Effect from "effect/Effect";
19import * as FileSystem from "effect/FileSystem";
20import * as Schedule from "effect/Schedule";
21import * as Stream from "effect/Stream";
22import * as HttpClient from "effect/http/HttpClient";
23import * as HttpClientRequest from "effect/http/HttpClientRequest";
24import * as HttpApiClient from "effect/http-api/HttpApiClient";
25import * as ChildProcess from "effect/process/ChildProcess";
26import TestGitHost, { TEST_SECRET } from "./fixtures/stack.ts";
27
28const PROFILE_REPO = process.env.GIT_PROFILE_REPO;
29
30const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
31 providers: Cloudflare.providers(),
32 dev: true,
33});
34
35const LocalStack = Alchemy.Stack(
36 "GitIngestProfileStack",
37 { providers: Cloudflare.providers(), state: Alchemy.localState() },
38 Effect.gen(function* () {
39 const host = yield* TestGitHost;
40 return { url: host.url.as<string>() };
41 }),
42);
43
44const git = Effect.fn(function* (cwd: string, ...args: Array<string>) {
45 const handle = yield* ChildProcess.make("git", args, {
46 cwd,
47 env: { GIT_TERMINAL_PROMPT: "0" },
48 extendEnv: true,
49 });
50 const [exitCode, stderr] = yield* Effect.all(
51 [handle.exitCode, Stream.mkString(Stream.decodeText(handle.stderr))],
52 { concurrency: 2 },
53 );
54 return { exitCode, stderr };
55}, Effect.timeout("10 minutes"));
56
57if (PROFILE_REPO === undefined) {
58 test.skip(
59 "profile: set GIT_PROFILE_REPO=/path/to/checkout to run",
60 Effect.void,
61 { tags: ["provider:cloudflare", "local"] },
62 );
63} else {
64 const stack = beforeAll(deploy(LocalStack));
65 afterAll(destroy(LocalStack));
66 test(
67 "profile: push GIT_PROFILE_REPO into local workerd and print the ingest split",
68 Effect.gen(function* () {
69 const { url } = yield* stack;
70 const client = yield* HttpApiClient.make(GitApi, {
71 baseUrl: url,
72 transformClient: HttpClient.mapRequest((r) =>
73 r.pipe(HttpClientRequest.bearerToken(TEST_SECRET)),
74 ),
75 });
76 yield* client.repos
77 .get({ params: { owner: "profile", repo: "repo" } })
78 .pipe(
79 Effect.retry({ schedule: Schedule.spaced("500 millis"), times: 20 }),
80 Effect.catchTag("RepoNotFound", () => Effect.void),
81 );
82 const created = yield* client.repos.create({
83 payload: { owner: "profile", name: "repo" },
84 });
85 const parsed = new URL(url);
86 const remote = `${parsed.protocol}//x:${TEST_SECRET}@${parsed.host}/profile/repo.git`;
87 const t0 = performance.now();
88 const push = yield* git(
89 PROFILE_REPO!,
90 "push",
91 "-q",
92 remote,
93 "HEAD:refs/heads/main",
94 );
95 const wall = performance.now() - t0;
96 if (push.exitCode !== 0)
97 console.log(
98 "[ingest-profile] PUSH FAILED:",
99 push.stderr.slice(0, 2000),
100 );
101 expect(push.exitCode, push.stderr).toBe(0);
102 const meta = yield* client.repos.get({
103 params: { owner: "profile", repo: "repo" },
104 });
105 // Clone it back and let real git verify — a promoted push must read
106 // back byte-for-byte through the wire pack.
107 const fs = yield* FileSystem.FileSystem;
108 const tmp = yield* fs.makeTempDirectory({
109 prefix: "git-ingest-profile-",
110 });
111 const clone = yield* git(tmp, "clone", "-q", remote, "back");
112 if (clone.exitCode !== 0)
113 console.log(
114 "[ingest-profile] CLONE FAILED:",
115 clone.stderr.slice(0, 2000),
116 );
117 expect(clone.exitCode, clone.stderr).toBe(0);
118 const fsck = yield* git(`${tmp}/back`, "fsck", "--connectivity-only");
119 if (fsck.exitCode !== 0)
120 console.log(
121 "[ingest-profile] FSCK FAILED:",
122 fsck.stderr.slice(0, 2000),
123 );
124 expect(fsck.exitCode, fsck.stderr).toBe(0);
125 console.log(
126 `[ingest-profile] objects: ${JSON.stringify(meta.objects)}; clone back + fsck ok`,
127 );
128 const p = meta.lastPush!;
129 // Counts (chunks, regions, unresolved) are reported alongside the
130 // wall-clock phases; label them as such.
131 const counts = new Set(["chunks", "regions", "unresolved"]);
132 const phases = Object.entries(p.phases ?? {})
133 .sort((a, b) => b[1] - a[1])
134 .map(([k, v]) => (counts.has(k) ? `${k}=${v}` : `${k} ${v}ms`))
135 .join(", ");
136 console.log(
137 `[ingest-profile] ${p.objects} objects, ${(p.bytes / 1048576).toFixed(1)} MiB: ` +
138 `ingest ${p.ingestMs}ms (sql ${p.stageMs}ms, cpu ${p.ingestMs - p.stageMs}ms → ${((p.ingestMs - p.stageMs) / p.objects).toFixed(2)} ms/object) ` +
139 `connectivity ${p.connectivityMs}ms finalize ${p.finalizeMs}ms total ${p.totalMs}ms; wall ${wall.toFixed(0)}ms\n` +
140 `[ingest-profile] phases: ${phases}`,
141 );
142 }),
143 {
144 tags: [
145 "provider:cloudflare",
146 "provider:cloudflare:r2",
147 "provider:cloudflare:worker",
148 "local",
149 ],
150 timeout: 600_000,
151 },
152 );
153}