fix: retry login across deploy cutovers
CI / check (push) Successful in 23s
CI / release (push) Skipped
CI / deploy (push) Successful in 41s

This commit is contained in:
Simo committed 2026-08-02 11:31:35 +02:00
1 parent bfc951cfd9
commit a57c73055f
4 files changed
+94 -12

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
import { describe, expect, it, vi } from "vitest";
import { signInWithTransientRetry } from "./sign-in-retry";
describe("signInWithTransientRetry", () => {
it("retries thrown transport errors with a short backoff", async () => {
const transportError = new SyntaxError("unexpected character in JSON");
const signIn = vi
.fn<() => Promise<{ ok: boolean }>>()
.mockRejectedValueOnce(transportError)
.mockRejectedValueOnce(transportError)
.mockResolvedValue({ ok: true });
const wait = vi.fn(async () => undefined);
await expect(signInWithTransientRetry(signIn, wait)).resolves.toEqual({
ok: true,
});
expect(signIn).toHaveBeenCalledTimes(3);
expect(wait).toHaveBeenNthCalledWith(1, 400);
expect(wait).toHaveBeenNthCalledWith(2, 800);
});
it("returns ordinary auth responses without retrying", async () => {
const response = { ok: false, error: "CredentialsSignin" };
const signIn = vi.fn(async () => response);
const wait = vi.fn(async () => undefined);
await expect(signInWithTransientRetry(signIn, wait)).resolves.toBe(
response,
);
expect(signIn).toHaveBeenCalledOnce();
expect(wait).not.toHaveBeenCalled();
});
it("rethrows the last transport error after four attempts", async () => {
const transportError = new Error("temporary upstream failure");
const signIn = vi
.fn<() => Promise<never>>()
.mockRejectedValue(transportError);
const wait = vi.fn(async () => undefined);
await expect(signInWithTransientRetry(signIn, wait)).rejects.toBe(
transportError,
);
expect(signIn).toHaveBeenCalledTimes(4);
expect(wait).toHaveBeenCalledTimes(3);
});
});
+25
View File
@@ -0,0 +1,25 @@
type Wait = (delayMs: number) => Promise<unknown>;
const waitForDelay: Wait = (delayMs) =>
new Promise((resolve) => setTimeout(resolve, delayMs));
export async function signInWithTransientRetry<T>(
signIn: () => Promise<T>,
wait: Wait = waitForDelay,
): Promise<T> {
const maxAttempts = 4;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await signIn();
} catch (error) {
if (attempt === maxAttempts - 1) {
throw error;
}
await wait(400 * 2 ** attempt);
}
}
throw new Error("Unreachable auth retry state");
}