Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/middleware-retry/src/configurations.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { resolveRetryConfig } from "./configurations";
import { StandardRetryStrategy } from "./defaultStrategy";

describe("resolveRetryConfig", () => {
describe("maxAttempts", () => {
it("uses passed maxAttempts value if present", () => {
[1, 2, 3].forEach(maxAttempts => {
expect(resolveRetryConfig({ maxAttempts }).maxAttempts).toEqual(
maxAttempts
);
});
});

it("assigns default value of 3 if maxAttempts not passed", () => {
expect(resolveRetryConfig({}).maxAttempts).toEqual(3);
});
});

describe("retryStrategy", () => {
it("uses passed retryStrategy if present", () => {
const mockRetryStrategy = {
maxAttempts: 2,
retry: jest.fn()
};
const { retryStrategy } = resolveRetryConfig({
retryStrategy: mockRetryStrategy
});
expect(retryStrategy).toEqual(mockRetryStrategy);
});

describe("creates StandardRetryStrategy if retryStrategy not present", () => {
describe("uses maxAttempts if present", () => {
[1, 2, 3].forEach(maxAttempts => {
const { retryStrategy } = resolveRetryConfig({ maxAttempts });
expect(retryStrategy).toBeInstanceOf(StandardRetryStrategy);
expect(retryStrategy.maxAttempts).toBe(maxAttempts);
});
});

it("uses default 3 if maxAttempts is not present", () => {
const { retryStrategy } = resolveRetryConfig({});
expect(retryStrategy).toBeInstanceOf(StandardRetryStrategy);
expect(retryStrategy.maxAttempts).toBe(3);
});
});
});
});
8 changes: 4 additions & 4 deletions packages/middleware-retry/src/configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export interface RetryResolvedConfig {
retryStrategy: RetryStrategy;
}

export function resolveRetryConfig<T>(
export const resolveRetryConfig = <T>(
input: T & RetryInputConfig
): T & RetryResolvedConfig {
const maxAttempts = input.maxAttempts === undefined ? 3 : input.maxAttempts;
): T & RetryResolvedConfig => {
const maxAttempts = input.maxAttempts ?? 3;
return {
...input,
maxAttempts,
retryStrategy: input.retryStrategy || new StandardRetryStrategy(maxAttempts)
};
}
};
203 changes: 203 additions & 0 deletions packages/middleware-retry/src/defaultStrategy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import {
DEFAULT_RETRY_DELAY_BASE,
THROTTLING_RETRY_DELAY_BASE
} from "./constants";
import { isThrottlingError } from "@aws-sdk/service-error-classification";
import { defaultDelayDecider } from "./delayDecider";
import { defaultRetryDecider } from "./retryDecider";
import { StandardRetryStrategy } from "./defaultStrategy";

jest.mock("@aws-sdk/service-error-classification", () => ({
isThrottlingError: jest.fn().mockReturnValue(true)
}));

jest.mock("./delayDecider", () => ({
defaultDelayDecider: jest.fn().mockReturnValue(0)
}));

jest.mock("./retryDecider", () => ({
defaultRetryDecider: jest.fn().mockReturnValue(true)
}));

describe("defaultStrategy", () => {
const maxAttempts = 2;

afterEach(() => {
jest.clearAllMocks();
});

it("sets maxAttempts as class member variable", () => {
[1, 2, 3].forEach(maxAttempts => {
const retryStrategy = new StandardRetryStrategy(maxAttempts);
expect(retryStrategy.maxAttempts).toBe(maxAttempts);
});
});

describe("retryDecider", () => {
it("sets defaultRetryDecider if options is undefined", () => {
const retryStrategy = new StandardRetryStrategy(maxAttempts);
expect(retryStrategy["retryDecider"]).toBe(defaultRetryDecider);
});

it("sets defaultRetryDecider if options.retryDecider is undefined", () => {
const retryStrategy = new StandardRetryStrategy(maxAttempts, {});
expect(retryStrategy["retryDecider"]).toBe(defaultRetryDecider);
});

it("sets options.retryDecider if defined", () => {
const retryDecider = jest.fn();
const retryStrategy = new StandardRetryStrategy(maxAttempts, {
retryDecider
});
expect(retryStrategy["retryDecider"]).toBe(retryDecider);
});
});

describe("delayDecider", () => {
it("sets defaultDelayDecider if options is undefined", () => {
const retryStrategy = new StandardRetryStrategy(maxAttempts);
expect(retryStrategy["delayDecider"]).toBe(defaultDelayDecider);
});

it("sets defaultDelayDecider if options.delayDecider undefined", () => {
const retryStrategy = new StandardRetryStrategy(maxAttempts, {});
expect(retryStrategy["delayDecider"]).toBe(defaultDelayDecider);
});

it("sets options.delayDecider if defined", () => {
const delayDecider = jest.fn();
const retryStrategy = new StandardRetryStrategy(maxAttempts, {
delayDecider
});
expect(retryStrategy["delayDecider"]).toBe(delayDecider);
});
});

describe("delayBase passed to delayDecider", () => {
const testDelayBasePassed = async (
delayBaseToTest: number,
mockThrottlingError: boolean
) => {
(isThrottlingError as jest.Mock).mockReturnValueOnce(mockThrottlingError);

const mockError = new Error("mockError");
const mockResponse = {
response: "mockResponse",
output: { $metadata: {} }
};

const next = jest
.fn()
.mockRejectedValueOnce(mockError)
.mockResolvedValueOnce(mockResponse);

const retryStrategy = new StandardRetryStrategy(maxAttempts);
await retryStrategy.retry(next, {} as any);

expect(isThrottlingError as jest.Mock).toHaveBeenCalledTimes(1);
expect(isThrottlingError as jest.Mock).toHaveBeenCalledWith(mockError);
expect(defaultDelayDecider as jest.Mock).toHaveBeenCalledTimes(1);
expect(defaultDelayDecider as jest.Mock).toHaveBeenCalledWith(
delayBaseToTest,
1
);
};

it("should be equal to THROTTLING_RETRY_DELAY_BASE if error is throttling error", async () => {
return testDelayBasePassed(THROTTLING_RETRY_DELAY_BASE, true);
});

it("should be equal to DEFAULT_RETRY_DELAY_BASE in error is not a throttling error", async () => {
return testDelayBasePassed(DEFAULT_RETRY_DELAY_BASE, false);
});
});

describe("should not retry", () => {
it("when the handler completes successfully", async () => {
const mockResponse = {
response: "mockResponse",
output: { $metadata: {} }
};

const next = jest.fn().mockResolvedValueOnce(mockResponse);

const retryStrategy = new StandardRetryStrategy(maxAttempts);
const { response, output } = await retryStrategy.retry(next, {} as any);

expect(response).toStrictEqual(mockResponse.response);
expect(output.$metadata.attempts).toBe(1);
expect(output.$metadata.totalRetryDelay).toBe(0);
expect(defaultRetryDecider as jest.Mock).not.toHaveBeenCalled();
expect(defaultDelayDecider as jest.Mock).not.toHaveBeenCalled();
});

it("when retryDecider returns false", async () => {
(defaultRetryDecider as jest.Mock).mockReturnValueOnce(false);

const mockError = new Error("mockError");
const next = jest.fn().mockRejectedValueOnce(mockError);

const retryStrategy = new StandardRetryStrategy(maxAttempts);
try {
await retryStrategy.retry(next, {} as any);
} catch (error) {
expect(error).toStrictEqual(mockError);
}

expect(defaultRetryDecider as jest.Mock).toHaveBeenCalledTimes(1);
expect(defaultRetryDecider as jest.Mock).toHaveBeenCalledWith(mockError);
});

it("when the the maximum number of attempts is reached", async () => {
const mockError = new Error("mockError");
const next = jest.fn().mockRejectedValue(mockError);

const retryStrategy = new StandardRetryStrategy(maxAttempts);
try {
await retryStrategy.retry(next, {} as any);
} catch (error) {
expect(error).toStrictEqual(mockError);
}
expect(defaultRetryDecider as jest.Mock).toHaveBeenCalledTimes(
maxAttempts - 1
);
});
});

it("should delay equal to the value returned by delayDecider", async () => {
jest.spyOn(global, "setTimeout");

const FIRST_DELAY = 100;
const SECOND_DELAY = 200;

(defaultDelayDecider as jest.Mock)
.mockReturnValueOnce(FIRST_DELAY)
.mockReturnValueOnce(SECOND_DELAY);

const mockError = new Error("mockError");
const next = jest.fn().mockRejectedValue(mockError);

const retryStrategy = new StandardRetryStrategy(3);
try {
await retryStrategy.retry(next, {} as any);
} catch (error) {
expect(error).toStrictEqual(mockError);
expect(error.$metadata.totalRetryDelay).toEqual(
FIRST_DELAY + SECOND_DELAY
);
}

expect(defaultDelayDecider as jest.Mock).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenNthCalledWith(
1,
expect.any(Function),
FIRST_DELAY
);
expect(setTimeout).toHaveBeenNthCalledWith(
2,
expect.any(Function),
SECOND_DELAY
);
});
});
92 changes: 0 additions & 92 deletions packages/middleware-retry/src/index.spec.ts

This file was deleted.

Loading