Skip to content

Commit ca77dcf

Browse files
test(lifecycle): add retries for transient failures
This update introduces tests for the lifecycle endpoint. It verifies that transient private-network failures are retried with bounded backoff. Additionally, it checks that a safe transport cause code is reported after retries are exhausted.
1 parent 05c02f0 commit ca77dcf

1 file changed

Lines changed: 64 additions & 1 deletion

File tree

scripts/run-account-lifecycle-cron.test.mjs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import assert from "node:assert/strict";
22
import test from "node:test";
33

4-
import { lifecycleEndpoint } from "./run-account-lifecycle-cron.mjs";
4+
import {
5+
invokeLifecycleEndpoint,
6+
lifecycleEndpoint,
7+
} from "./run-account-lifecycle-cron.mjs";
58

69
const CRON_SECRET = "a".repeat(32);
710
const privateOriginWithCredentials = new URL(
@@ -67,3 +70,63 @@ test("still rejects insecure public fallback origins", () => {
6770
/APP_ORIGIN_MUST_USE_HTTPS/
6871
);
6972
});
73+
74+
test("retries transient private-network failures with bounded backoff", async () => {
75+
let attempts = 0;
76+
const delays = [];
77+
const response = await invokeLifecycleEndpoint(
78+
lifecycleEndpoint({
79+
ACCOUNT_LIFECYCLE_CRON_ORIGIN:
80+
"http://study-buddy-v2.railway.internal:8080",
81+
ACCOUNT_DELETION_CRON_SECRET: CRON_SECRET,
82+
}),
83+
{
84+
fetchImpl: async () => {
85+
attempts += 1;
86+
if (attempts < 3) {
87+
throw Object.assign(new TypeError("fetch failed"), {
88+
cause: { code: "EAI_AGAIN" },
89+
});
90+
}
91+
return { ok: true, status: 200 };
92+
},
93+
sleepImpl: async (delayMs) => delays.push(delayMs),
94+
retryDelays: [0, 1_000, 3_000],
95+
onRetry: () => {},
96+
}
97+
);
98+
99+
assert.equal(response.status, 200);
100+
assert.equal(attempts, 3);
101+
assert.deepEqual(delays, [1_000, 3_000]);
102+
});
103+
104+
test("reports a safe transport cause code after retries are exhausted", async () => {
105+
let attempts = 0;
106+
107+
await assert.rejects(
108+
invokeLifecycleEndpoint(
109+
lifecycleEndpoint({
110+
ACCOUNT_LIFECYCLE_CRON_ORIGIN:
111+
"http://study-buddy-v2.railway.internal:8080",
112+
ACCOUNT_DELETION_CRON_SECRET: CRON_SECRET,
113+
}),
114+
{
115+
fetchImpl: async () => {
116+
attempts += 1;
117+
throw Object.assign(new TypeError("fetch failed"), {
118+
cause: { code: "ECONNREFUSED" },
119+
});
120+
},
121+
sleepImpl: async () => {},
122+
retryDelays: [0, 0],
123+
onRetry: () => {},
124+
}
125+
),
126+
(error) =>
127+
error?.reason === "TRANSPORT_FAILURE" &&
128+
error?.causeCode === "ECONNREFUSED"
129+
);
130+
131+
assert.equal(attempts, 2);
132+
});

0 commit comments

Comments
 (0)