|
|
@@ -13,8 +13,8 @@ function apiError(headers?: Record<string, string>): MessageV2.APIError {
|
|
|
describe("session.retry.getRetryDelayInMs", () => {
|
|
|
test("doubles delay on each attempt when headers missing", () => {
|
|
|
const error = apiError()
|
|
|
- const delays = Array.from({ length: 7 }, (_, index) => SessionRetry.getRetryDelayInMs(error, index + 1))
|
|
|
- expect(delays).toStrictEqual([2000, 4000, 8000, 16000, 32000, 64000, 128000])
|
|
|
+ const delays = Array.from({ length: 10 }, (_, index) => SessionRetry.getRetryDelayInMs(error, index + 1))
|
|
|
+ expect(delays).toStrictEqual([2000, 4000, 8000, 16000, 32000, 64000, 128000, 256000, 512000, undefined])
|
|
|
})
|
|
|
|
|
|
test("prefers retry-after-ms when shorter than exponential", () => {
|
|
|
@@ -27,11 +27,6 @@ describe("session.retry.getRetryDelayInMs", () => {
|
|
|
expect(SessionRetry.getRetryDelayInMs(error, 3)).toBe(30000)
|
|
|
})
|
|
|
|
|
|
- test("falls back to exponential when server delay is long", () => {
|
|
|
- const error = apiError({ "retry-after": "120" })
|
|
|
- expect(SessionRetry.getRetryDelayInMs(error, 2)).toBe(4000)
|
|
|
- })
|
|
|
-
|
|
|
test("accepts http-date retry-after values", () => {
|
|
|
const date = new Date(Date.now() + 20000).toUTCString()
|
|
|
const error = apiError({ "retry-after": date })
|
|
|
@@ -44,4 +39,134 @@ describe("session.retry.getRetryDelayInMs", () => {
|
|
|
const error = apiError({ "retry-after": "not-a-number" })
|
|
|
expect(SessionRetry.getRetryDelayInMs(error, 1)).toBe(2000)
|
|
|
})
|
|
|
+
|
|
|
+ test("ignores malformed date retry hints", () => {
|
|
|
+ const error = apiError({ "retry-after": "Invalid Date String" })
|
|
|
+ expect(SessionRetry.getRetryDelayInMs(error, 1)).toBe(2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("ignores past date retry hints", () => {
|
|
|
+ const pastDate = new Date(Date.now() - 5000).toUTCString()
|
|
|
+ const error = apiError({ "retry-after": pastDate })
|
|
|
+ expect(SessionRetry.getRetryDelayInMs(error, 1)).toBe(2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("returns undefined when delay exceeds 10 minutes", () => {
|
|
|
+ const error = apiError()
|
|
|
+ expect(SessionRetry.getRetryDelayInMs(error, 10)).toBeUndefined()
|
|
|
+ })
|
|
|
+
|
|
|
+ test("returns undefined when retry-after exceeds 10 minutes", () => {
|
|
|
+ const error = apiError({ "retry-after": "50" })
|
|
|
+ expect(SessionRetry.getRetryDelayInMs(error, 1)).toBe(50000)
|
|
|
+
|
|
|
+ const longError = apiError({ "retry-after-ms": "700000" })
|
|
|
+ expect(SessionRetry.getRetryDelayInMs(longError, 1)).toBeUndefined()
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+describe("session.retry.getBoundedDelay", () => {
|
|
|
+ test("returns full delay when under time budget", () => {
|
|
|
+ const error = apiError()
|
|
|
+ const startTime = Date.now()
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 1,
|
|
|
+ startTime,
|
|
|
+ })
|
|
|
+ expect(delay).toBe(2000)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("returns remaining time when delay exceeds budget", () => {
|
|
|
+ const error = apiError()
|
|
|
+ const startTime = Date.now() - 598_000 // 598 seconds elapsed, 2 seconds remaining
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 1,
|
|
|
+ startTime,
|
|
|
+ })
|
|
|
+ expect(delay).toBeGreaterThanOrEqual(1900)
|
|
|
+ expect(delay).toBeLessThanOrEqual(2100)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("returns undefined when time budget exhausted", () => {
|
|
|
+ const error = apiError()
|
|
|
+ const startTime = Date.now() - 600_000 // exactly 10 minutes elapsed
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 1,
|
|
|
+ startTime,
|
|
|
+ })
|
|
|
+ expect(delay).toBeUndefined()
|
|
|
+ })
|
|
|
+
|
|
|
+ test("returns undefined when time budget exceeded", () => {
|
|
|
+ const error = apiError()
|
|
|
+ const startTime = Date.now() - 700_000 // 11+ minutes elapsed
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 1,
|
|
|
+ startTime,
|
|
|
+ })
|
|
|
+ expect(delay).toBeUndefined()
|
|
|
+ })
|
|
|
+
|
|
|
+ test("respects custom maxDuration", () => {
|
|
|
+ const error = apiError()
|
|
|
+ const startTime = Date.now() - 58_000 // 58 seconds elapsed
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 1,
|
|
|
+ startTime,
|
|
|
+ maxDuration: 60_000, // 1 minute max
|
|
|
+ })
|
|
|
+ expect(delay).toBeGreaterThanOrEqual(1900)
|
|
|
+ expect(delay).toBeLessThanOrEqual(2100)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("caps exponential backoff to remaining time", () => {
|
|
|
+ const error = apiError()
|
|
|
+ const startTime = Date.now() - 595_000 // 595 seconds elapsed, 5 seconds remaining
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 5, // would normally be 32 seconds
|
|
|
+ startTime,
|
|
|
+ })
|
|
|
+ expect(delay).toBeGreaterThanOrEqual(4900)
|
|
|
+ expect(delay).toBeLessThanOrEqual(5100)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("respects server retry-after within budget", () => {
|
|
|
+ const error = apiError({ "retry-after": "30" })
|
|
|
+ const startTime = Date.now() - 550_000 // 550 seconds elapsed, 50 seconds remaining
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 1,
|
|
|
+ startTime,
|
|
|
+ })
|
|
|
+ expect(delay).toBe(30000)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("caps server retry-after to remaining time", () => {
|
|
|
+ const error = apiError({ "retry-after": "30" })
|
|
|
+ const startTime = Date.now() - 590_000 // 590 seconds elapsed, 10 seconds remaining
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 1,
|
|
|
+ startTime,
|
|
|
+ })
|
|
|
+ expect(delay).toBeGreaterThanOrEqual(9900)
|
|
|
+ expect(delay).toBeLessThanOrEqual(10100)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("returns undefined when getRetryDelayInMs returns undefined", () => {
|
|
|
+ const error = apiError()
|
|
|
+ const startTime = Date.now()
|
|
|
+ const delay = SessionRetry.getBoundedDelay({
|
|
|
+ error,
|
|
|
+ attempt: 10, // exceeds RETRY_MAX_DELAY
|
|
|
+ startTime,
|
|
|
+ })
|
|
|
+ expect(delay).toBeUndefined()
|
|
|
+ })
|
|
|
})
|