Skip to content

g1t/services/runner/src/bump.test.ts

159 lines8,401 bytesCodeBlame
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { BumpArgs } from "@g1t/contracts";
5
6import { bumpEnv, bumpProblem, bumpSandboxName, isBranchName, isSystem, registryHosts, systemActor } from "./bump.ts";
7import { buildHosts } from "./egress.ts";
8
9const PREFIX = "g1t/security/";
10
11const args: BumpArgs = {
12 repo: { namespace: "Acme", name: "site" },
13 ecosystem: "npm",
14 package: "@babel/traverse",
15 version: "7.23.2",
16 lockfiles: ["package-lock.json", "web/package-lock.json"],
17 branch: "g1t/security/babel-traverse-7.23.2",
18 message: "Update @babel/traverse to 7.23.2",
19};
20
21test("a well-formed update can start", () => {
22 assert.equal(bumpProblem(args, PREFIX), null);
23 for (const ecosystem of ["crates.io", "Go", "PyPI"]) {
24 assert.equal(bumpProblem({ ...args, ecosystem }, PREFIX), null, ecosystem);
25 }
26});
27
28test("the branch must be a security update's", () => {
29 for (const branch of ["main", "g1t/security/", "feature/g1t/security/x", "g1t/security/a..b", "g1t/security/a b", "g1t/security/a:b", "g1t/security/x.lock"]) {
30 assert.match(bumpProblem({ ...args, branch }, PREFIX) ?? "", /starts with g1t\/security\//, branch);
31 }
32});
33
34test("names, versions and lockfiles are checked before anything starts", () => {
35 assert.match(bumpProblem({ ...args, ecosystem: "RubyGems" }, PREFIX) ?? "", /cannot update RubyGems/);
36 assert.match(bumpProblem({ ...args, package: "--registry=evil" }, PREFIX) ?? "", /package's name/);
37 assert.match(bumpProblem({ ...args, version: "1.0; rm -rf /" }, PREFIX) ?? "", /version/);
38 assert.match(bumpProblem({ ...args, lockfiles: [] }, PREFIX) ?? "", /between 1 and/);
39 assert.match(bumpProblem({ ...args, lockfiles: ["../Cargo.lock"] }, PREFIX) ?? "", /not a path inside/);
40 assert.match(bumpProblem({ ...args, lockfiles: ["/etc/Cargo.lock"] }, PREFIX) ?? "", /not a path inside/);
41 assert.match(bumpProblem({ ...args, repo: { namespace: "", name: "site" } }, PREFIX) ?? "", /repository/);
42 assert.match(bumpProblem(null, PREFIX) ?? "", /arguments/);
43});
44
45test("g1t acts as itself, a member of the workspace", () => {
46 const actor = systemActor("Acme");
47 assert.deepEqual(actor, { id: "g1t", username: "g1t", kind: "system", verified: true, workspaces: [{ slug: "acme", role: "member" }] });
48 assert.equal(isSystem(actor), true);
49 assert.equal(isSystem({ id: "usr_1", username: "ada" }), false);
50 assert.equal(isSystem(null), false);
51});
52
53test("the sandbox is given what bump mode reads", () => {
54 const env = bumpEnv(args, "main", "g1t_token");
55 assert.deepEqual(env, {
56 MODE: "bump",
57 G1T_USER: "acme",
58 G1T_TOKEN: "g1t_token",
59 GIT_REMOTE: "https://g1t.sh/Acme/site.git",
60 GIT_BRANCH_BASE: "main",
61 GIT_BRANCH: "g1t/security/babel-traverse-7.23.2",
62 BUMP_KIND: "security",
63 BUMP_ECOSYSTEM: "npm",
64 BUMP_PACKAGE: "@babel/traverse",
65 BUMP_VERSION: "7.23.2",
66 BUMP_PACKAGES: '[{"package":"@babel/traverse","version":"7.23.2"}]',
67 BUMP_STRATEGY: "increase",
68 BUMP_FORCE: "0",
69 BUMP_REGISTRIES: "[]",
70 BUMP_LOCKFILES: '["package-lock.json","web/package-lock.json"]',
71 COMMIT_MESSAGE: "Update @babel/traverse to 7.23.2",
72 });
73 assert.equal(bumpEnv({ ...args, message: " " }, "main", "t").COMMIT_MESSAGE, "Update @babel/traverse to 7.23.2");
74 assert.equal(bumpSandboxName(args), "bump:acme/site:g1t/security/babel-traverse-7.23.2");
75});
76
77const update: BumpArgs = {
78 ...args,
79 kind: "version",
80 package: "lodash",
81 version: "4.17.21",
82 packages: [
83 { package: "lodash", version: "4.17.21" },
84 { package: " vitest ", version: "1.6.0" },
85 ],
86 branch: "deps/npm/lodash",
87 message: "Bump lodash and vitest",
88 strategy: "widen",
89 force: true,
90 registries: [{ type: "npm-registry", url: "https://npm.acme.dev/", token: "s3cret", scopes: ["@acme"] }],
91};
92
93test("a version update names any branch git takes", () => {
94 assert.equal(bumpProblem(update, PREFIX), null);
95 for (const branch of ["main", "dependabot/npm/lodash-4.17.21", "deps"]) {
96 assert.equal(bumpProblem({ ...update, branch }, PREFIX), null, branch);
97 }
98 for (const branch of ["", "a b", "a..b", "a~1", "a^", "a:b", "a?", "a*", "a[b", "a\\b", "a@{1}", "-x", "/x", "refs/heads/x", "x/", "x.lock", "x".repeat(201)]) {
99 assert.match(bumpProblem({ ...update, branch }, PREFIX) ?? "", /not a branch name/, branch);
100 }
101 assert.equal(isBranchName("deps/npm/lodash"), true);
102 assert.equal(isBranchName(undefined), false);
103});
104
105test("a version update's packages, strategy and registries are checked", () => {
106 assert.match(bumpProblem({ ...update, kind: "major" as "version" }, PREFIX) ?? "", /cannot make a major update/);
107 assert.match(bumpProblem({ ...update, package: "" }, PREFIX) ?? "", /A version update needs the package's name/);
108 assert.match(bumpProblem({ ...update, packages: [{ package: "--evil", version: "1" }] }, PREFIX) ?? "", /each package's name/);
109 assert.match(bumpProblem({ ...update, packages: [{ package: "lodash", version: "1 2" }] }, PREFIX) ?? "", /raise lodash to/);
110 const many = Array.from({ length: 51 }, (_, i) => ({ package: `p${i}`, version: "1.0.0" }));
111 assert.match(bumpProblem({ ...update, packages: many }, PREFIX) ?? "", /at most 50 packages/);
112 for (const strategy of ["increase", "increase-if-necessary", "widen", "lockfile-only"]) {
113 assert.equal(bumpProblem({ ...update, strategy }, PREFIX), null, strategy);
114 }
115 assert.match(bumpProblem({ ...update, strategy: "auto" }, PREFIX) ?? "", /not a versioning strategy/);
116 const registry = update.registries![0]!;
117 assert.match(bumpProblem({ ...update, registries: [{ ...registry, type: "maven-repository" }] }, PREFIX) ?? "", /cannot read a maven-repository registry/);
118 for (const url of ["http://npm.acme.dev", "npm.acme.dev", "https://", "https:// x"]) {
119 assert.match(bumpProblem({ ...update, registries: [{ ...registry, url }] }, PREFIX) ?? "", /starts with https/, url);
120 }
121 assert.match(bumpProblem({ ...update, registries: [{ ...registry, token: "x".repeat(2001) }] }, PREFIX) ?? "", /credentials/);
122 assert.match(bumpProblem({ ...update, registries: [{ ...registry, scopes: ["acme"] }] }, PREFIX) ?? "", /not an npm scope/);
123 assert.match(bumpProblem({ ...update, registries: Array.from({ length: 21 }, () => registry) }, PREFIX) ?? "", /at most 20 private registries/);
124 for (const type of ["cargo-registry", "python-index", "goproxy-server"]) {
125 assert.equal(bumpProblem({ ...update, registries: [{ type, url: "https://r.acme.dev/x", username: "u", password: "p", replacesBase: true }] }, PREFIX), null, type);
126 }
127});
128
129test("a version update's sandbox is told what to raise, how and from where", () => {
130 const env = bumpEnv(update, "main", "t");
131 assert.equal(env.BUMP_KIND, "version");
132 assert.equal(env.GIT_BRANCH, "deps/npm/lodash");
133 assert.equal(env.BUMP_PACKAGE, "lodash");
134 assert.equal(env.BUMP_VERSION, "4.17.21");
135 assert.equal(env.BUMP_PACKAGES, '[{"package":"lodash","version":"4.17.21"},{"package":"vitest","version":"1.6.0"}]');
136 assert.equal(env.BUMP_STRATEGY, "widen");
137 assert.equal(env.BUMP_FORCE, "1");
138 assert.deepEqual(JSON.parse(env.BUMP_REGISTRIES!), update.registries);
139 assert.equal(env.COMMIT_MESSAGE, "Bump lodash and vitest");
140 assert.equal(bumpEnv({ ...update, message: "" }, "main", "t").COMMIT_MESSAGE, "Update lodash and 1 more");
141 const one = bumpEnv({ ...update, packages: [], strategy: undefined, force: undefined, registries: undefined }, "main", "t");
142 assert.equal(one.BUMP_PACKAGES, '[{"package":"lodash","version":"4.17.21"}]');
143 assert.equal(one.BUMP_STRATEGY, "increase");
144 assert.equal(one.BUMP_FORCE, "0");
145 assert.equal(one.BUMP_REGISTRIES, "[]");
146});
147
148test("a security update reaches the package registries and nothing else builds get", () => {
149 const hosts = buildHosts("bump");
150 for (const host of ["registry.npmjs.org", "repo.yarnpkg.com", "index.crates.io", "static.crates.io", "proxy.golang.org", "sum.golang.org", "pypi.org", "files.pythonhosted.org"]) {
151 assert.ok(hosts.includes(host), host);
152 }
153 for (const host of ["github.com", "api.cloudflare.com", "ghcr.io"]) assert.ok(!hosts.includes(host), host);
154});
155
156test("an update's private registries are reachable from its sandbox", () => {
157 assert.deepEqual(registryHosts(update), [...new Set((update.registries ?? []).map((registry) => new URL(registry.url).host))]);
158 assert.deepEqual(registryHosts(args), []);
159});