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
|
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", () => {
expect(ROUTE).toContain('r.status === "success"');
expect(ROUTE).not.toContain('r.status === "imported"');
});
it("a failure in onboarding cannot fail the completed migration", () => {
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");
expect(block).not.toMatch(/throw\s/);
});
it("skips the welcome issue on imported repos", () => {
expect(SRC).toContain("skipWelcomeIssue: true");
});
it("bounds concurrency rather than scanning every repo at once", () => {
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);
const bad = report.repos.find((r) => r.name === "explodes")!;
expect(bad.error).toContain("scanner blew up");
expect(bad.bootstrapped).toBe(true);
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");
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);
});
});
|