Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepfix/audit-sweep-2026-07-26gatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
migration-onboarding.test.ts5.6 KB · 149 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
 * Post-import onboarding for a team migrating from GitHub.
 *
 * POST /import/bulk cloned repositories and stopped. A team that migrated an
 * org landed on bare repos — no branch protection, no labels, no gate
 * settings — and no evidence the migration was worth doing. The platform's
 * whole pitch (it finds problems in your code) was something they had to go
 * and discover for themselves, one repo at a time.
 *
 * runMigrationOnboarding bootstraps green defaults and scans the imported
 * code, returning a report the results page renders.
 *
 * The property that matters most here is FAULT ISOLATION: a migration that
 * imported 40 repositories must not be reported as a failure because the
 * scanner threw on one of them. These use injected failures to prove that.
 */

import { describe, expect, it, mock, afterEach } from "bun:test";
import { readFileSync } from "fs";

const SRC = readFileSync("src/lib/migration-onboarding.ts", "utf8");
const ROUTE = readFileSync("src/routes/import-bulk.tsx", "utf8");

describe("wiring", () => {
  it("import-bulk runs onboarding after a real import", () => {
    expect(ROUTE).toContain("runMigrationOnboarding");
  });

  it("only newly cloned repos are onboarded", () => {
    // "skipped-exists" repos were already on the platform and have settings
    // their owner chose; re-bootstrapping would fight those.
    expect(ROUTE).toContain('r.status === "success"');
    expect(ROUTE).not.toContain('r.status === "imported"');
  });

  it("a failure in onboarding cannot fail the completed migration", () => {
    // lastIndexOf: there is an earlier `return c.html(` for the dry-run
    // preview, which would slice to an empty string and pass vacuously.
    const block = ROUTE.slice(
      ROUTE.indexOf("const importedNames"),
      ROUTE.lastIndexOf("return c.html(")
    );
    expect(block.length).toBeGreaterThan(100);
    expect(block).toContain("try {");
    expect(block).toContain("catch");
    // The repositories are already cloned at this point; the results page
    // must still render.
    expect(block).not.toMatch(/throw\s/);
  });

  it("skips the welcome issue on imported repos", () => {
    // An imported repo arrives with real history and real issues. A
    // "welcome" issue on top of 400 migrated ones is noise.
    expect(SRC).toContain("skipWelcomeIssue: true");
  });

  it("bounds concurrency rather than scanning every repo at once", () => {
    // Each scan is a tree walk plus blob reads; an unbounded Promise.all over
    // a 100-repo org opens 100 concurrent walks against one disk and pool.
    expect(SRC).toContain("mapWithConcurrency");
    expect(SRC).toContain("DB_FANOUT_LIMIT");
  });
});

describe("fault isolation", () => {
  afterEach(() => {
    mock.restore();
  });

  it("one repo's scan failure does not sink the others", async () => {
    mock.module("../lib/repo-bootstrap", () => ({
      bootstrapRepository: async () => ({
        settingsCreated: true,
        protectionCreated: true,
        labelsCreated: 5,
      }),
    }));
    mock.module("../lib/gate", () => ({
      runSecretAndSecurityScan: async (_o: string, repo: string) => {
        if (repo === "explodes") throw new Error("scanner blew up");
        return {
          secretResult: { name: "Secrets", passed: true, details: "clean" },
          securityResult: { name: "Security", passed: true, details: "clean" },
          secrets: repo === "leaky" ? [{ a: 1 }, { b: 2 }] : [],
          securityIssues: [],
        };
      },
    }));

    const { runMigrationOnboarding } = await import("../lib/migration-onboarding");
    const report = await runMigrationOnboarding(
      [
        { id: "1", owner: "acme", name: "ok" },
        { id: "2", owner: "acme", name: "explodes" },
        { id: "3", owner: "acme", name: "leaky" },
      ],
      "user-1"
    );

    expect(report.totalRepos).toBe(3);
    // The thrower is reported, not thrown.
    const bad = report.repos.find((r) => r.name === "explodes")!;
    expect(bad.error).toContain("scanner blew up");
    // ...and it still got its green defaults.
    expect(bad.bootstrapped).toBe(true);
    // The others are unaffected and their findings are counted.
    expect(report.totalSecrets).toBe(2);
    expect(report.reposWithFindings).toBe(1);
    expect(report.reposBootstrapped).toBe(3);
  });

  it("a bootstrap failure still lets the scan run", async () => {
    mock.module("../lib/repo-bootstrap", () => ({
      bootstrapRepository: async () => {
        throw new Error("no protection for you");
      },
    }));
    mock.module("../lib/gate", () => ({
      runSecretAndSecurityScan: async () => ({
        secretResult: { name: "Secrets", passed: false, details: "1 secret" },
        securityResult: { name: "Security", passed: true, details: "clean" },
        secrets: [{ a: 1 }],
        securityIssues: [],
      }),
    }));

    const { runMigrationOnboarding } = await import("../lib/migration-onboarding");
    const report = await runMigrationOnboarding(
      [{ id: "1", owner: "acme", name: "repo" }],
      "user-1"
    );

    const r = report.repos[0];
    expect(r.bootstrapped).toBe(false);
    expect(r.error).toContain("bootstrap");
    // The scan is the value proposition — it must not be skipped because
    // the bootstrap step failed.
    expect(r.secretsFound).toBe(1);
    expect(report.totalSecrets).toBe(1);
  });

  it("returns an empty report for an empty import rather than throwing", async () => {
    const { runMigrationOnboarding } = await import("../lib/migration-onboarding");
    const report = await runMigrationOnboarding([], "user-1");
    expect(report.totalRepos).toBe(0);
    expect(report.totalSecrets).toBe(0);
    expect(report.reposWithFindings).toBe(0);
  });
});