49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
canRetryTransport,
|
|
isRetryableHttpStatus,
|
|
isRetryableSemantics,
|
|
jitteredDelay,
|
|
retryDelayFor,
|
|
} from "../../src/adapters/http/http-retry-lifecycle.ts";
|
|
|
|
describe("HTTP retry lifecycle policy", () => {
|
|
it("allows KEYED replay only before a physical dispatch", () => {
|
|
expect(canRetryTransport("KEYED", "PREPARING")).toBe(true);
|
|
expect(canRetryTransport("KEYED", "READY_TO_SEND")).toBe(true);
|
|
expect(canRetryTransport("KEYED", "DISPATCHED")).toBe(false);
|
|
expect(canRetryTransport("KEYED", "RESPONSE_HEADERS")).toBe(false);
|
|
});
|
|
|
|
it("keeps SAFE and IDEMPOTENT retryable while NEVER is terminal", () => {
|
|
expect(isRetryableSemantics("SAFE")).toBe(true);
|
|
expect(isRetryableSemantics("IDEMPOTENT")).toBe(true);
|
|
expect(isRetryableSemantics("KEYED")).toBe(false);
|
|
expect(isRetryableSemantics("NEVER")).toBe(false);
|
|
|
|
expect(canRetryTransport("SAFE", "DISPATCHED")).toBe(true);
|
|
expect(canRetryTransport("IDEMPOTENT", "READING_BODY")).toBe(true);
|
|
expect(canRetryTransport("NEVER", "PREPARING")).toBe(false);
|
|
});
|
|
|
|
it("owns the closed retryable status vocabulary", () => {
|
|
for (const status of [408, 425, 429, 502, 503, 504]) {
|
|
expect(isRetryableHttpStatus(status)).toBe(true);
|
|
}
|
|
for (const status of [400, 401, 403, 404, 409, 500, 501]) {
|
|
expect(isRetryableHttpStatus(status)).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("uses bounded full jitter and rejects excessive Retry-After", () => {
|
|
expect(jitteredDelay(0, () => 0)).toBe(0);
|
|
expect(jitteredDelay(0, () => 0.5)).toBe(125);
|
|
expect(jitteredDelay(8, () => 0.5)).toBe(1_000);
|
|
|
|
expect(retryDelayFor(null, 0, () => 0.5)).toBe(125);
|
|
expect(retryDelayFor(400, 0, () => 0.5)).toBe(400);
|
|
expect(retryDelayFor(5_001, 0, () => 0.5)).toBeNull();
|
|
});
|
|
});
|