| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { |
| 5 | PER_PAGE, |
| 6 | SOURCE_LABEL, |
| 7 | STATE_WORD, |
| 8 | buildError, |
| 9 | choices, |
| 10 | environmentLabel, |
| 11 | environmentUrl, |
| 12 | groupBuilds, |
| 13 | hasPayload, |
| 14 | isFiltered, |
| 15 | isPageBuild, |
| 16 | orderEnvironments, |
| 17 | pageRange, |
| 18 | parseFilter, |
| 19 | productionEnvironment, |
| 20 | shortSha, |
| 21 | withFilter, |
| 22 | } from "./deployments.ts"; |
| 23 | |
| 24 | const GIT = |
| 25 | "the commit could not be checked out: git -c failed: fatal: reference is not a tree: d2ca224ba0fea430dcc9933cda14d8aa9fa94b4b"; |
| 26 | |
| 27 | type Build = Parameters<typeof groupBuilds>[0][number] & { id: string }; |
| 28 | |
| 29 | let hour = 0; |
| 30 | const build = (over: Partial<Build>): Build => ({ |
| 31 | id: `dep_${hour}`, |
| 32 | kind: "preview", |
| 33 | branch: "v2", |
| 34 | number: 1, |
| 35 | status: "failed", |
| 36 | error: GIT, |
| 37 | createdAt: new Date(Date.parse("2026-10-07T12:00:00Z") - hour++ * 3_600_000).toISOString(), |
| 38 | ...over, |
| 39 | }); |
| 40 | |
| 41 | test("a commit that is gone is said plainly, naming the pull request", () => { |
| 42 | assert.equal( |
| 43 | buildError({ kind: "preview", number: 1, error: GIT }), |
| 44 | "This pull request's commit no longer exists. Push again, or close pull request #1.", |
| 45 | ); |
| 46 | assert.match(buildError({ kind: "production", number: null, error: GIT })!, /default branch/); |
| 47 | assert.equal(buildError({ kind: "preview", number: 1, error: "npm run build exited 1." }), "npm run build exited 1."); |
| 48 | assert.equal(buildError({ kind: "preview", number: 1, error: null }), null); |
| 49 | }); |
| 50 | |
| 51 | test("a run of the same failure of one app is one row, counted, linking to the newest", () => { |
| 52 | hour = 0; |
| 53 | const builds = [build({}), build({}), build({}), build({ status: "ready", error: null }), build({})]; |
| 54 | const groups = groupBuilds(builds); |
| 55 | assert.deepEqual( |
| 56 | groups.map((g) => [g.build.id, g.count]), |
| 57 | [ |
| 58 | ["dep_0", 3], |
| 59 | ["dep_3", 1], |
| 60 | ["dep_4", 1], |
| 61 | ], |
| 62 | ); |
| 63 | assert.equal(groups[0]!.firstAt, builds[2]!.createdAt); |
| 64 | }); |
| 65 | |
| 66 | test("other apps' builds in between do not break a run; a different error does", () => { |
| 67 | hour = 0; |
| 68 | const groups = groupBuilds([ |
| 69 | build({}), |
| 70 | build({ kind: "production", branch: null, number: null, status: "ready", error: null }), |
| 71 | build({}), |
| 72 | build({ branch: "add-ci", number: 2, error: "fatal: reference is not a tree: 213e9095" }), |
| 73 | build({}), |
| 74 | build({ error: "npm run build exited 1." }), |
| 75 | build({ error: "npm run build exited 1." }), |
| 76 | ]); |
| 77 | assert.deepEqual( |
| 78 | groups.map((g) => [g.build.id, g.count]), |
| 79 | [ |
| 80 | ["dep_0", 3], |
| 81 | ["dep_1", 1], |
| 82 | ["dep_3", 1], |
| 83 | ["dep_5", 2], |
| 84 | ], |
| 85 | ); |
| 86 | }); |
| 87 | |
| 88 | test("builds that did not fail are never folded", () => { |
| 89 | hour = 0; |
| 90 | const ready = groupBuilds([build({ status: "ready", error: null }), build({ status: "ready", error: null })]); |
| 91 | assert.deepEqual(ready.map((g) => g.count), [1, 1]); |
| 92 | assert.deepEqual(groupBuilds([]), []); |
| 93 | }); |
| 94 | |
| 95 | // --- A repository's deployments ------------------------------------------------ |
| 96 | |
| 97 | type Env = Parameters<typeof productionEnvironment>[0][number]; |
| 98 | const env = (name: string, over: Partial<Env> = {}): Env => ({ |
| 99 | name, |
| 100 | production_environment: false, |
| 101 | transient_environment: false, |
| 102 | updated_at: "2026-10-07T12:00:00Z", |
| 103 | latest: null, |
| 104 | ...over, |
| 105 | }); |
| 106 | const deployed = { id: "dep_1" } as unknown as Env["latest"]; |
| 107 | |
| 108 | test("environments list production first, then lasting ones, then previews, newest first in each", () => { |
| 109 | const ordered = orderEnvironments([ |
| 110 | env("preview", { transient_environment: true, updated_at: "2026-10-07T11:00:00Z" }), |
| 111 | env("staging", { updated_at: "2026-10-01T00:00:00Z" }), |
| 112 | env("qa", { updated_at: "2026-10-06T00:00:00Z" }), |
| 113 | env("production-eu", { production_environment: true, updated_at: "2026-10-07T11:59:00Z" }), |
| 114 | env("production", { production_environment: true, updated_at: "2026-10-01T00:00:00Z" }), |
| 115 | ]); |
| 116 | assert.deepEqual( |
| 117 | ordered.map((e) => e.name), |
| 118 | ["production", "production-eu", "qa", "staging", "preview"], |
| 119 | ); |
| 120 | }); |
| 121 | |
| 122 | test("production is the production environment that has had a deployment, named production first", () => { |
| 123 | assert.equal(productionEnvironment([env("staging", { latest: deployed })]), null); |
| 124 | assert.equal(productionEnvironment([env("production", { production_environment: true })]), null); |
| 125 | assert.equal( |
| 126 | productionEnvironment([ |
| 127 | env("live", { production_environment: true, latest: deployed }), |
| 128 | env("production", { production_environment: true, latest: deployed }), |
| 129 | ])?.name, |
| 130 | "production", |
| 131 | ); |
| 132 | assert.equal( |
| 133 | productionEnvironment([env("production", { production_environment: true }), env("live", { production_environment: true, latest: deployed })]) |
| 134 | ?.name, |
| 135 | "live", |
| 136 | ); |
| 137 | }); |
| 138 | |
| 139 | test("an environment is served at its own address, else its current deployment's", () => { |
| 140 | const at = (environment_url: string | null, state = "success") => ({ environment_url, state }) as never; |
| 141 | assert.equal(environmentUrl({ url: "https://g1t.sh", current: null, latest: null }), "https://g1t.sh"); |
| 142 | assert.equal( |
| 143 | environmentUrl({ url: null, current: at("https://a.g1t.page"), latest: at("https://b.g1t.page", "failure") }), |
| 144 | "https://a.g1t.page", |
| 145 | ); |
| 146 | assert.equal(environmentUrl({ url: null, current: null, latest: at("https://b.g1t.page", "failure") }), null); |
| 147 | }); |
| 148 | |
| 149 | test("the list's filter is read from the address, ignoring what it cannot be", () => { |
| 150 | const filter = parseFilter(new URLSearchParams("environment=production&state=failure&source=actions&creator=syntaqx&ref=main&page=3")); |
| 151 | assert.deepEqual(filter, { |
| 152 | environment: "production", |
| 153 | state: "failure", |
| 154 | source: "actions", |
| 155 | creator: "syntaqx", |
| 156 | ref: "main", |
| 157 | page: 3, |
| 158 | per_page: PER_PAGE, |
| 159 | }); |
| 160 | const junk = parseFilter(new URLSearchParams("state=exploded&source=ftp&page=-2&per_page=1000&environment=%20")); |
| 161 | assert.equal(junk.state, null); |
| 162 | assert.equal(junk.source, null); |
| 163 | assert.equal(junk.environment, null); |
| 164 | assert.equal(junk.page, 1); |
| 165 | assert.equal(junk.per_page, 100); |
| 166 | assert.equal(isFiltered(junk), false); |
| 167 | assert.equal(isFiltered(filter), true); |
| 168 | }); |
| 169 | |
| 170 | test("changing a filter keeps the others and starts again from the first page", () => { |
| 171 | const filter = parseFilter(new URLSearchParams("environment=production&state=failure&page=2")); |
| 172 | assert.equal(withFilter("/a/b/deployments", filter, "state", "success"), "/a/b/deployments?environment=production&state=success#history"); |
| 173 | assert.equal(withFilter("/a/b/deployments", filter, "environment", null), "/a/b/deployments?state=failure#history"); |
| 174 | assert.equal(withFilter("/a/b/deployments", filter, "page", 3), "/a/b/deployments?environment=production&state=failure&page=3#history"); |
| 175 | assert.equal(withFilter("/a/b/deployments", parseFilter(new URLSearchParams()), "page", 1), "/a/b/deployments#history"); |
| 176 | }); |
| 177 | |
| 178 | test("a filter's choices are each value once, sorted, keeping the chosen one", () => { |
| 179 | const list = [{ creator: "g1t", ref: "main" }, { creator: "syntaqx", ref: "main" }, null, { creator: "g1t", ref: "v2" }]; |
| 180 | assert.deepEqual(choices(list, "creator"), ["g1t", "syntaqx"]); |
| 181 | assert.deepEqual(choices(list, "ref", "release"), ["main", "release", "v2"]); |
| 182 | }); |
| 183 | |
| 184 | test("words for states, sources, environments and pages", () => { |
| 185 | assert.equal(STATE_WORD.in_progress, "Deploying"); |
| 186 | assert.equal(SOURCE_LABEL.g1t_page, "g1t.page"); |
| 187 | assert.equal(environmentLabel("production"), "Production"); |
| 188 | assert.equal(shortSha("d2ca224ba0fea430"), "d2ca224"); |
| 189 | assert.equal(isPageBuild("dpl_123"), true); |
| 190 | assert.equal(isPageBuild("dep_123"), false); |
| 191 | assert.equal(pageRange(2, 30, 304), "31–60 of 304"); |
| 192 | assert.equal(pageRange(11, 30, 304), "301–304 of 304"); |
| 193 | assert.equal(pageRange(1, 30, 0), "0 of 0"); |
| 194 | assert.equal(hasPayload({}), false); |
| 195 | assert.equal(hasPayload({ region: "wnam" }), true); |
| 196 | assert.equal(hasPayload(null), false); |
| 197 | }); |