Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

first-repo-scaffold.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

first-repo-scaffold.test.tsBlame148 lines · 1 contributor
47b1dc7ccantynz-alt1/**
2 * A new repository should arrive working, not empty.
3 *
4 * POST /new called bootstrapRepository(), which creates the DB side only —
5 * gate settings, branch protection, labels, welcome issue. It created no git
6 * content, so a new user landed on an EMPTY repository: no commit, no README,
7 * no workflow, and therefore no CI run to be green. Every push-triggered
8 * feature the product sells had nothing to act on, and the first thing a new
9 * user saw was an empty state telling them to go and configure something.
10 *
11 * scaffoldFirstRepo seeds a README and a CI workflow, then calls the SAME
12 * syncAndEnqueuePushWorkflows() seam the git post-receive hook calls. That
13 * last step is the one that is easy to miss and impossible to notice: files
14 * written through the plumbing API do not fire the git hook, so without it
15 * the workflow sits on disk with no `workflows` row and no run — the repo
16 * looks configured and does nothing.
17 */
18
19import { describe, expect, it } from "bun:test";
20import { readFileSync } from "fs";
21
22const SRC = readFileSync("src/lib/first-repo-scaffold.ts", "utf8");
23const WEB = readFileSync("src/routes/web.tsx", "utf8");
24
25describe("wiring", () => {
26 it("POST /new scaffolds after bootstrapping", () => {
27 const handler = WEB.slice(
28 WEB.indexOf('web.post("/new"'),
29 WEB.indexOf("// Daily brief")
30 );
31 expect(handler.length).toBeGreaterThan(200);
32 expect(handler).toContain("bootstrapRepository");
33 expect(handler).toContain("scaffoldFirstRepo");
34 // Bootstrap first: the scaffold's CI run is gated by settings that
35 // bootstrapRepository creates.
36 expect(handler.indexOf("bootstrapRepository")).toBeLessThan(
37 handler.indexOf("scaffoldFirstRepo")
38 );
39 });
40
41 it("a scaffold failure cannot break repo creation", () => {
42 const handler = WEB.slice(
43 WEB.indexOf('web.post("/new"'),
44 WEB.indexOf("// Daily brief")
45 );
46 const block = handler.slice(handler.indexOf("scaffoldFirstRepo") - 400);
47 expect(block).toContain("catch");
48 });
49
50 it("uses the same sync seam as the post-receive hook", () => {
51 // Writing via the plumbing API does not fire the git hook, so the
52 // workflow would never be discovered without calling this directly.
53 expect(SRC).toContain("syncAndEnqueuePushWorkflows");
54 const hook = readFileSync("src/hooks/post-receive.ts", "utf8");
55 expect(hook).toContain("syncAndEnqueuePushWorkflows");
56 });
57});
58
59describe("what gets seeded", () => {
60 it("writes a README and a CI workflow at the discovered path", () => {
61 expect(SRC).toContain('"README.md"');
62 // workflows.tsx documents .gluecron/workflows/ as the discovery dir;
63 // a workflow written anywhere else is invisible.
64 expect(SRC).toContain('".gluecron/workflows/ci.yml"');
65 });
66
67 it("the workflow triggers on push", () => {
68 // syncAndEnqueuePushWorkflows only enqueues workflows whose `on:`
69 // includes push — a workflow without it syncs but never runs.
70 const wf = SRC.slice(SRC.indexOf("function ciWorkflow"), SRC.indexOf("function readme"));
71 expect(wf).toMatch(/on:\s*\n\s*- push/);
72 });
73
74 it("the workflow has no external dependencies", () => {
75 // It runs on a bare runner. The point is proving the pipeline works end
76 // to end, not guessing the user's stack — anything needing a package
77 // manager would fail and hand a new user a red X as their first result.
78 const wf = SRC.slice(SRC.indexOf("function ciWorkflow"), SRC.indexOf("function readme"));
79 expect(wf).not.toMatch(/npm |yarn |pnpm |bun install|pip |cargo /);
80 });
81});
82
83describe("fault isolation", () => {
84 const ok = async () => ({ commitSha: "abc", blobSha: "b", parentSha: null });
85 const base = {
86 owner: "alice",
87 repoName: "demo",
88 repositoryId: "r1",
89 defaultBranch: "main",
90 authorName: "alice",
91 authorEmail: "a@b.c",
92 userId: "u1",
93 };
94
95 it("reports a write failure instead of throwing", async () => {
96 const { scaffoldFirstRepo } = await import("../lib/first-repo-scaffold");
97 const r = await scaffoldFirstRepo(base, {
98 createOrUpdateFileOnBranch: async () => ({ error: "write-failed" }),
99 syncAndEnqueuePushWorkflows: async () => ({ synced: 0, enqueued: 0, errors: [] }),
100 });
101 expect(r.readmeCommitted).toBe(false);
102 expect(r.workflowCommitted).toBe(false);
103 expect(r.errors.join(" ")).toContain("write-failed");
104 });
105
106 it("does not attempt workflow sync when the workflow never committed", async () => {
107 let syncCalled = false;
108 const { scaffoldFirstRepo } = await import("../lib/first-repo-scaffold");
109 const r = await scaffoldFirstRepo(base, {
110 createOrUpdateFileOnBranch: async (i) =>
111 i.filePath === "README.md" ? await ok() : { error: "write-failed" },
112 syncAndEnqueuePushWorkflows: async () => {
113 syncCalled = true;
114 return { synced: 1, enqueued: 1, errors: [] };
115 },
116 });
117 expect(r.readmeCommitted).toBe(true);
118 expect(r.workflowCommitted).toBe(false);
119 // Syncing a commit with no workflow in it would report a phantom run.
120 expect(syncCalled).toBe(false);
121 expect(r.runsEnqueued).toBe(0);
122 });
123
124 it("surfaces a sync failure without losing the commits", async () => {
125 const { scaffoldFirstRepo } = await import("../lib/first-repo-scaffold");
126 const r = await scaffoldFirstRepo(base, {
127 createOrUpdateFileOnBranch: ok,
128 syncAndEnqueuePushWorkflows: async () => {
129 throw new Error("sync exploded");
130 },
131 });
132 expect(r.readmeCommitted).toBe(true);
133 expect(r.workflowCommitted).toBe(true);
134 expect(r.errors.join(" ")).toContain("sync exploded");
135 });
136
137 it("reports the enqueued run on the happy path", async () => {
138 const { scaffoldFirstRepo } = await import("../lib/first-repo-scaffold");
139 const r = await scaffoldFirstRepo(base, {
140 createOrUpdateFileOnBranch: ok,
141 syncAndEnqueuePushWorkflows: async () => ({ synced: 1, enqueued: 1, errors: [] }),
142 });
143 expect(r.workflowsSynced).toBe(1);
144 // This is the whole point: a green CI run with zero configuration.
145 expect(r.runsEnqueued).toBe(1);
146 expect(r.errors).toEqual([]);
147 });
148});