Skip to content
105 linesCodeBlameRaw
1/**
2 * Who may open a build from a workflow, what a run builds, and whether it
3 * is trusted. `@g1t/contracts` imports its modules without extensions, so
4 * a resolve hook adds them (as in moves.flow.test.ts).
5 */
6
7import assert from "node:assert/strict";
8import { registerHooks } from "node:module";
9import { test } from "node:test";
10
11import type { User } from "@g1t/contracts";
12
13registerHooks({
14 resolve(specifier, context, next) {
15 try {
16 return next(specifier, context);
17 } catch (error) {
18 if (specifier.startsWith(".")) return next(`${specifier}.ts`, context);
19 throw error;
20 }
21 },
22});
23const { commitOf, jobOf, projectNamed, readOnlyAllowed, runTrusted, targetOf } = await import("./workflow-builds.ts");
24
25const repo = { namespace: "acme", name: "web" };
26const jobToken = (token: Partial<NonNullable<User["token"]>>): User =>
27 ({
28 id: "wsp_acme",
29 username: "acme",
30 kind: "workspace",
31 verified: true,
32 workspaces: [{ slug: "acme", role: "member" }],
33 token: { token_id: "tok_1", repo: "acme/web", job: { run_id: "run_1", job_id: "job_1" }, scopes: ["deployments:write"], ...token },
34 }) as User;
35
36test("only a workflow job's token for the repository, with deployments: write, opens a build", () => {
37 assert.deepEqual(jobOf(jobToken({}), repo), { runId: "run_1", jobId: "job_1", writes: true });
38 // write-all, and contents with deployments.
39 assert.ok(!("error" in jobOf(jobToken({ scopes: ["contents:read", "deployments:write"] }), repo)));
40 // A person's token, however wide, is not a job's.
41 const person = { id: "usr_1", username: "ada", kind: "user", verified: true, token: { token_id: "tok_2", scopes: null } } as User;
42 assert.match((jobOf(person, repo) as { error: string }).error, /g1t Actions job's token/);
43 assert.match((jobOf(null, repo) as { error: string }).error, /g1t Actions job's token/);
44 // Read only: what a pull request from outside gets. Its job is named,
45 // and only a managed workflow's preview lets it through.
46 assert.deepEqual(jobOf(jobToken({ scopes: ["deployments:read", "contents:read"] }), repo), { runId: "run_1", jobId: "job_1", writes: false });
47 // Another repository's job.
48 assert.match((jobOf(jobToken({ repo: "acme/api" }), repo) as { error: string }).error, /acme\/api, not acme\/web/);
49 // Case does not matter in names.
50 assert.ok(!("error" in jobOf(jobToken({ repo: "Acme/Web" }), repo)));
51});
52
53test("a run on the production branch builds production; another branch, its preview; a pull request, its own", () => {
54 for (const event of ["push", "workflow_dispatch", "schedule"]) {
55 assert.deepEqual(targetOf({ event, ref: "refs/heads/main", pull: null }, "main"), { kind: "production" });
56 }
57 assert.deepEqual(targetOf({ event: "push", ref: "refs/heads/feature/x", pull: null }, "main"), { kind: "branch", branch: "feature/x" });
58 assert.deepEqual(targetOf({ event: "push", ref: "main", pull: null }, "main"), { kind: "production" });
59 assert.deepEqual(targetOf({ event: "pull_request", ref: "refs/pull/9/merge", pull: 9 }, "main"), { kind: "pull", number: 9 });
60 assert.deepEqual(targetOf({ event: "pull_request_target", ref: "refs/heads/main", pull: 9 }, "main"), { kind: "pull", number: 9 });
61 // The production branch is the project's, whatever it is called.
62 assert.deepEqual(targetOf({ event: "push", ref: "refs/heads/main", pull: null }, "trunk"), { kind: "branch", branch: "main" });
63 // A tag deploys nothing.
64 assert.match((targetOf({ event: "push", ref: "refs/tags/v1.0.0", pull: null }, "main") as { error: string }).error, /refs\/tags\/v1.0.0/);
65});
66
67test("a run from a fork, from someone who cannot push, or one that needed approval gets no secrets", () => {
68 assert.equal(runTrusted({ approval: null, pull: null, insider: true }), true);
69 assert.equal(runTrusted({ approval: null, pull: { fork: null }, insider: true }), true);
70 assert.equal(runTrusted({ approval: null, pull: { fork: { namespace: "eve", name: "web" } }, insider: true }), false);
71 assert.equal(runTrusted({ approval: null, pull: { fork: null }, insider: false }), false);
72 assert.equal(runTrusted({ approval: { state: "approved" }, pull: { fork: null }, insider: true }), false);
73 assert.equal(runTrusted({ approval: { state: "required" }, pull: null, insider: true }), false);
74});
75
76test("the commit is the run's unless one is given", () => {
77 const run = { sha: "4b8d0f2a6c1e3579bd02468ace13579bdf02468a" };
78 assert.equal(commitOf(undefined, run), run.sha);
79 assert.equal(commitOf("", run), run.sha);
80 assert.equal(commitOf(" ABCDEF1 ", run), "abcdef1");
81 assert.ok(typeof commitOf("main", run) === "object");
82 assert.ok(typeof commitOf(7, run) === "object");
83});
84
85test("the project is the one named, or the only one with Deployments on", () => {
86 const web = { slug: "web", on: true };
87 const docs = { slug: "docs", on: false };
88 const api = { slug: "api", on: true };
89 const on = (p: { on: boolean }) => p.on;
90 assert.equal(projectNamed([web, docs], on, null), web);
91 assert.equal(projectNamed([web, docs], on, "Docs"), docs);
92 assert.deepEqual(projectNamed([web, docs], on, "nope"), { error: "This repository has no project nope.", code: "not_found" });
93 assert.deepEqual(projectNamed([docs], on, undefined), { error: "No project of this repository has Deployments on.", code: "conflict" });
94 assert.deepEqual(projectNamed([web, api], on, ""), { error: "Name the project to build: web, api.", code: "invalid" });
95});
96
97test("a read-only token opens only a preview, and only from the deploy workflow g1t manages", () => {
98 const managed = { path: "managed/deploy-web.yml" };
99 const own = { path: ".g1t/workflows/deploy.yml" };
100 assert.ok(readOnlyAllowed(managed, { kind: "pull", number: 4 }));
101 assert.ok(readOnlyAllowed(managed, { kind: "branch", branch: "fix" }));
102 assert.ok(!readOnlyAllowed(managed, { kind: "production" }));
103 assert.ok(!readOnlyAllowed(own, { kind: "pull", number: 4 }));
104 assert.ok(!readOnlyAllowed(managed, { error: "a tag" }));
105});