g1t/services/deployments/src/environments.test.ts
| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { |
| 5 | actionsTransition, |
| 6 | address, |
| 7 | buildState, |
| 8 | commitState, |
| 9 | compareEnvironments, |
| 10 | environmentName, |
| 11 | payloadOf, |
| 12 | runOutcome, |
| 13 | shortRef, |
| 14 | statusContext, |
| 15 | } from "./environments.ts"; |
| 16 | import { buildStatuses, fromBuild, type BuildRow } from "./environments.ts"; |
| 17 | |
| 18 | const appUrl = (script: string) => `https://${script}.g1t.page`; |
| 19 | |
| 20 | test("an environment is named as given, production when not", () => { |
| 21 | assert.equal(environmentName(undefined), "production"); |
| 22 | assert.equal(environmentName(" "), "production"); |
| 23 | assert.equal(environmentName(" staging "), "staging"); |
| 24 | assert.equal(environmentName("review/feature-x"), "review/feature-x"); |
| 25 | assert.deepEqual(environmentName("a\nb"), { error: "`environment` cannot hold control characters." }); |
| 26 | assert.ok(typeof environmentName("x".repeat(256)) === "object"); |
| 27 | assert.ok(typeof environmentName(3) === "object"); |
| 28 | }); |
| 29 | |
| 30 | test("addresses are http(s), and a payload is an object", () => { |
| 31 | assert.equal(address("https://g1t.sh", "environment_url"), "https://g1t.sh"); |
| 32 | assert.equal(address(null, "log_url"), null); |
| 33 | assert.ok(typeof address("ftp://example.com", "log_url") === "object"); |
| 34 | assert.ok(typeof address("not a url", "log_url") === "object"); |
| 35 | assert.deepEqual(payloadOf(undefined), { value: {} }); |
| 36 | assert.deepEqual(payloadOf({ error: "kept" }), { value: { error: "kept" } }); |
| 37 | assert.deepEqual(payloadOf('{"units":["api"]}'), { value: { units: ["api"] } }); |
| 38 | assert.ok("error" in payloadOf("[1]")); |
| 39 | assert.ok("error" in payloadOf("{")); |
| 40 | }); |
| 41 | |
| 42 | test("refs read as their branch or tag", () => { |
| 43 | assert.equal(shortRef("refs/heads/main"), "main"); |
| 44 | assert.equal(shortRef("refs/tags/v1.2.0"), "v1.2.0"); |
| 45 | assert.equal(shortRef("main"), "main"); |
| 46 | }); |
| 47 | |
| 48 | test("each state says something on the commit, but inactive", () => { |
| 49 | assert.equal(statusContext("production"), "deploy / production"); |
| 50 | assert.equal(commitState("queued"), "pending"); |
| 51 | assert.equal(commitState("in_progress"), "pending"); |
| 52 | assert.equal(commitState("success"), "success"); |
| 53 | assert.equal(commitState("failure"), "failure"); |
| 54 | assert.equal(commitState("error"), "error"); |
| 55 | assert.equal(commitState("inactive"), null); |
| 56 | }); |
| 57 | |
| 58 | test("a g1t.page build's status reads as a deployment state", () => { |
| 59 | assert.equal(buildState("queued"), "queued"); |
| 60 | assert.equal(buildState("building"), "in_progress"); |
| 61 | assert.equal(buildState("ready"), "success"); |
| 62 | assert.equal(buildState("failed"), "failure"); |
| 63 | assert.equal(buildState("skipped"), "error"); |
| 64 | assert.equal(buildState("replaced"), "inactive"); |
| 65 | assert.equal(buildState("down"), "inactive"); |
| 66 | }); |
| 67 | |
| 68 | test("a run's deployment moves on, and a failure sticks until the run ends", () => { |
| 69 | assert.equal(actionsTransition(null, "in_progress", false), "in_progress"); |
| 70 | assert.equal(actionsTransition("in_progress", "in_progress", false), null); |
| 71 | assert.equal(actionsTransition("in_progress", "failure", false), "failure"); |
| 72 | assert.equal(actionsTransition("failure", "in_progress", false), null, "a later job does not undo a failure"); |
| 73 | assert.equal(actionsTransition("failure", "success", true), "success", "the run's outcome is the last word"); |
| 74 | assert.equal(actionsTransition("success", "in_progress", false), null); |
| 75 | assert.equal(actionsTransition("in_progress", "success", true), "success"); |
| 76 | }); |
| 77 | |
| 78 | test("a run's outcome for an environment comes from the jobs that ran", () => { |
| 79 | assert.equal(runOutcome(["success", "skipped"]), "success"); |
| 80 | assert.equal(runOutcome(["success", "failure"]), "failure"); |
| 81 | assert.equal(runOutcome(["success", "cancelled"]), "error"); |
| 82 | assert.equal(runOutcome(["skipped", "skipped"]), null); |
| 83 | assert.equal(runOutcome([]), null); |
| 84 | }); |
| 85 | |
| 86 | test("production comes first, then the newest", () => { |
| 87 | const env = (name: string, production: boolean, at: string) => ({ name, production_environment: production, updated_at: at }); |
| 88 | const sorted = [env("preview", false, "2026-10-07"), env("staging", false, "2026-10-06"), env("Production", true, "2026-10-01"), env("eu", true, "2026-10-05")].sort( |
| 89 | compareEnvironments, |
| 90 | ); |
| 91 | assert.deepEqual( |
| 92 | sorted.map((e) => e.name), |
| 93 | ["Production", "eu", "preview", "staging"], |
| 94 | ); |
| 95 | }); |
| 96 | |
| 97 | const build = (over: Partial<BuildRow>): BuildRow => ({ |
| 98 | id: "dpl_1", |
| 99 | workspace: "acme", |
| 100 | slug: "web", |
| 101 | repo_id: "rep_1", |
| 102 | kind: "production", |
| 103 | branch: null, |
| 104 | number: null, |
| 105 | commit_sha: "a".repeat(40), |
| 106 | script: "web-acme", |
| 107 | status: "ready", |
| 108 | error: null, |
| 109 | created_by: "usr_1", |
| 110 | created_at: "2026-10-07T10:00:00.000Z", |
| 111 | started_at: "2026-10-07T10:00:05.000Z", |
| 112 | finished_at: "2026-10-07T10:01:00.000Z", |
| 113 | ...over, |
| 114 | }); |
| 115 | |
| 116 | test("a g1t.page build is a deployment in the same model", () => { |
| 117 | const live = fromBuild(build({}), "main", "https://g1t.sh", appUrl, { usr_1: "ada" }); |
| 118 | assert.equal(live.environment, "production"); |
| 119 | assert.equal(live.ref, "main"); |
| 120 | assert.equal(live.state, "success"); |
| 121 | assert.equal(live.source, "g1t_page"); |
| 122 | assert.equal(live.creator, "ada"); |
| 123 | assert.equal(live.environment_url, "https://web-acme.g1t.page"); |
| 124 | assert.equal(live.log_url, "https://g1t.sh/acme/web/deployments/dpl_1"); |
| 125 | assert.equal(live.production_environment, true); |
| 126 | const preview = fromBuild(build({ kind: "preview", branch: "fix", number: 7, status: "failed", error: "The build failed.", finished_at: null }), "main", "https://g1t.sh", appUrl); |
| 127 | assert.equal(preview.ref, "fix"); |
| 128 | assert.equal(preview.number, 7); |
| 129 | assert.equal(preview.transient_environment, true); |
| 130 | assert.equal(preview.environment_url, null, "a failed build is not served"); |
| 131 | assert.equal(preview.creator, "g1t", "an id no one could name is g1t"); |
| 132 | }); |
| 133 | |
| 134 | test("a build's statuses come from its own timestamps", () => { |
| 135 | const row = build({ status: "replaced" }); |
| 136 | const statuses = buildStatuses(row, fromBuild(row, "main", "https://g1t.sh", appUrl)); |
| 137 | assert.deepEqual( |
| 138 | statuses.map((s) => s.state), |
| 139 | ["queued", "in_progress", "success", "inactive"], |
| 140 | ); |
| 141 | const queued = build({ status: "queued", started_at: null, finished_at: null }); |
| 142 | assert.deepEqual( |
| 143 | buildStatuses(queued, fromBuild(queued, "main", "https://g1t.sh", appUrl)).map((s) => s.state), |
| 144 | ["queued"], |
| 145 | ); |
| 146 | }); |