Skip to content
77 linesCodeBlameRaw
1// node --test "scripts/deploy/*.test.mjs" (npm run test:deploy)
2import assert from "node:assert/strict";
3import { readFileSync } from "node:fs";
4import { join } from "node:path";
5import { test } from "node:test";
6
7import { announceDeploy, withDeployWindow } from "./status-window.mjs";
8import { ROOT } from "./stack.mjs";
9
10/** A fetch that keeps what it was asked, and answers `status`. */
11function recorder(status = 200) {
12 const calls = [];
13 const fetchImpl = async (url, init) => {
14 calls.push({ url, auth: init.headers.authorization, body: JSON.parse(init.body) });
15 return new Response("{}", { status });
16 };
17 return { calls, fetchImpl };
18}
19
20test("a deploy is announced to status.g1t.sh with the token, and without one nothing is sent", async () => {
21 const { calls, fetchImpl } = recorder();
22 assert.equal(await announceDeploy("started", { id: "abc123", env: { STATUS_DEPLOY_TOKEN: " t0k " }, fetchImpl }), true);
23 assert.deepEqual(calls, [{ url: "https://status.g1t.sh/deploys", auth: "Bearer t0k", body: { phase: "started", id: "abc123" } }]);
24 await announceDeploy("finished", { env: { STATUS_DEPLOY_TOKEN: "t", STATUS_URL: "http://localhost:8787/" }, fetchImpl });
25 assert.deepEqual(calls[1], { url: "http://localhost:8787/deploys", auth: "Bearer t", body: { phase: "finished" } });
26 const none = recorder();
27 assert.equal(await announceDeploy("started", { id: "abc", env: {}, fetchImpl: none.fetchImpl }), false);
28 assert.equal(none.calls.length, 0, "no token: silently nothing");
29});
30
31test("an announcement that fails is a warning, never a failed deploy", async () => {
32 const lines = [];
33 const refused = recorder(401);
34 assert.equal(await announceDeploy("started", { env: { STATUS_DEPLOY_TOKEN: "t" }, fetchImpl: refused.fetchImpl, log: (l) => lines.push(l) }), false);
35 const broken = async () => {
36 throw new TypeError("fetch failed");
37 };
38 assert.equal(await announceDeploy("finished", { env: { STATUS_DEPLOY_TOKEN: "t" }, fetchImpl: broken, log: (l) => lines.push(l) }), false);
39 assert.equal(lines.length, 2);
40 assert.match(lines[0], /401/);
41 assert.match(lines[1], /fetch failed/);
42});
43
44test("the stages run inside the window: started before, finished after, even when they throw; a dry run says nothing", async () => {
45 const said = [];
46 const announce = async (phase, { id }) => void said.push(`${phase} ${id}`);
47 const order = [];
48 const value = await withDeployWindow(
49 async () => {
50 order.push(said.length);
51 return 42;
52 },
53 { id: "abc", announce },
54 );
55 assert.equal(value, 42);
56 assert.deepEqual(said, ["started abc", "finished abc"]);
57 assert.deepEqual(order, [1], "the work ran after started, before finished");
58
59 said.length = 0;
60 await assert.rejects(withDeployWindow(async () => Promise.reject(new Error("wrangler failed")), { id: "def", announce }), /wrangler failed/);
61 assert.deepEqual(said, ["started def", "finished def"]);
62
63 said.length = 0;
64 await withDeployWindow(async () => 1, { id: "ghi", dryRun: true, announce });
65 assert.deepEqual(said, []);
66});
67
68test("deploy.mjs announces every deploy, and the workflow passes it the token", () => {
69 const tool = readFileSync(join(ROOT, "scripts", "deploy.mjs"), "utf8");
70 assert.match(tool, /import \{ withDeployWindow \} from "\.\/deploy\/status-window\.mjs"/);
71 assert.match(tool, /await withDeployWindow\(/);
72 const workflow = readFileSync(join(ROOT, ".g1t", "workflows", "deploy.yml"), "utf8");
73 // Every step that runs `deploy.mjs deploy` has the secret in its env.
74 const steps = workflow.split(/\n\s+- name: /).filter((s) => /node scripts\/deploy\.mjs deploy /.test(s));
75 assert.ok(steps.length >= 1);
76 for (const s of steps) assert.match(s, /STATUS_DEPLOY_TOKEN: \$\{\{ secrets\.STATUS_DEPLOY_TOKEN \}\}/);
77});