Skip to content

g1t/services/deployments/src/retries.test.ts

77 lines4,097 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Usage, Billing settings and prepaid AI credit; fixes from the UX audit1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { commitMissing, missingCommitMessage, retryDecision, type PastBuild } from "./retries.ts";
5
6const HOUR = 60 * 60 * 1000;
7const NOW = Date.parse("2026-10-07T12:00:00Z");
8const GIT =
9 "the commit could not be checked out: git -c failed: fatal: reference is not a tree: d2ca224ba0fea430dcc9933cda14d8aa9fa94b4b";
10
11const build = (hoursAgo: number, over: Partial<PastBuild> = {}): PastBuild => ({
12 status: "failed",
13 error: "The build failed: npm run build exited 1.",
14 commit_sha: "d2ca224",
15 created_at: new Date(NOW - hoursAgo * HOUR).toISOString(),
16 ...over,
17});
18
19test("git's words for a commit that is gone are recognised, and only those", () => {
20 assert.equal(commitMissing(GIT), true);
21 assert.equal(commitMissing("fatal: remote error: upload-pack: not our ref d2ca224"), true);
22 assert.equal(commitMissing(missingCommitMessage({ kind: "preview", number: 1 })), true);
23 assert.equal(commitMissing("The build failed: npm run build exited 1."), false);
24 assert.equal(commitMissing(null), false);
25});
26
27test("a commit that is gone says so plainly, naming the pull request", () => {
28 assert.equal(
29 missingCommitMessage({ kind: "preview", number: 1 }),
30 "This pull request's commit no longer exists. Push again, or close pull request #1.",
31 );
32 assert.match(missingCommitMessage({ kind: "preview", number: null }), /branch's commit no longer exists/);
33 assert.match(missingCommitMessage({ kind: "production", number: null }), /default branch/);
34});
35
36test("nothing built yet, or the last build went up long ago: build", () => {
37 assert.deepEqual(retryDecision([], NOW, HOUR, { backoff: true }), { kind: "build" });
38 assert.deepEqual(retryDecision([build(3, { status: "ready", error: null })], NOW, HOUR, { backoff: true }), { kind: "build" });
39});
40
41test("a commit that is gone is never tried again, with or without backoff", () => {
42 const history = [build(30, { error: GIT })];
43 assert.deepEqual(retryDecision(history, NOW, HOUR, { backoff: true }), { kind: "stop", reason: "missing_commit" });
44 assert.deepEqual(retryDecision(history, NOW, HOUR), { kind: "stop", reason: "missing_commit" });
45});
46
47test("each identical failure doubles the wait", () => {
48 // One failure: an hour.
49 assert.equal(retryDecision([build(0.5)], NOW, HOUR, { backoff: true }).kind, "wait");
50 assert.equal(retryDecision([build(1)], NOW, HOUR, { backoff: true }).kind, "build");
51 // Two in a row: two hours after the last.
52 const two = [build(1.5), build(3)];
53 assert.deepEqual(retryDecision(two, NOW, HOUR, { backoff: true }), { kind: "wait", until: NOW + 0.5 * HOUR });
54 assert.equal(retryDecision([build(2), build(4)], NOW, HOUR, { backoff: true }).kind, "build");
55});
56
57test("three identical failures in a row stop it", () => {
58 const history = [build(10), build(12), build(14)];
59 assert.deepEqual(retryDecision(history, NOW, HOUR, { backoff: true }), { kind: "stop", reason: "failing" });
60 assert.deepEqual(retryDecision(history, NOW, HOUR), { kind: "stop", reason: "failing" });
61});
62
63test("a different error or commit starts the count again", () => {
64 const otherError = [build(10), build(12), build(14, { error: "Cloudflare did not take the app: 500" })];
65 assert.equal(retryDecision(otherError, NOW, HOUR, { backoff: true }).kind, "build");
66 const otherCommit = [build(10), build(12, { commit_sha: "8e500b1" }), build(14, { commit_sha: "8e500b1" })];
67 assert.equal(retryDecision(otherCommit, NOW, HOUR, { backoff: true }).kind, "build");
68 // A build that went up in between ends the run.
69 const recovered = [build(10), build(12, { status: "ready", error: null }), build(14)];
70 assert.equal(retryDecision(recovered, NOW, HOUR, { backoff: true }).kind, "build");
71});
72
73test("a refused or skipped rebuild waits the base delay with backoff, not without", () => {
74 const skipped = [build(0.5, { status: "skipped", error: "Over the limit." })];
75 assert.equal(retryDecision(skipped, NOW, HOUR, { backoff: true }).kind, "wait");
76 assert.equal(retryDecision(skipped, NOW, HOUR).kind, "build");
77});