teamdigitale/italia-ts-commons

View on GitHub
src/__tests__/request_middleware.test.ts

Summary

Maintainability
C
1 day
Test Coverage
// eslint-disable @typescript-eslint/no-explicit-any

import {
  IRequestMiddleware,
  withRequestMiddlewares
} from "../request_middleware";
import {
  IResponse,
  IResponseErrorValidation,
  ResponseErrorConflict,
  ResponseErrorValidation
} from "../responses";

import { left, right } from "fp-ts/lib/Either";

const ResolvingMiddleware: IRequestMiddleware<
  "IResponseErrorNever",
  string
> = req => {
  return Promise.resolve(right<never, string>(req.params.dummy));
};

const ResolvingNumberMiddleware: IRequestMiddleware<
  "IResponseErrorNever",
  number
> = req => {
  return Promise.resolve(right<never, number>(1));
};

const RejectingMiddleware: IRequestMiddleware<
  "IResponseErrorValidation",
  string
> = _ => {
  return Promise.resolve(
    left<IResponseErrorValidation, string>(
      ResponseErrorValidation("NOK", "NOT")
    )
  );
};

const RejectingConflictMiddleware: IRequestMiddleware<
  "IResponseErrorConflict",
  number
> = async _ =>
  left(ResponseErrorConflict("Conflict"));

const DelayedFailureMiddleware: IRequestMiddleware<
  "IResponseErrorValidation",
  string
> = _ => {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error("Error")), 500);
  });
};

const request = {
  params: {
    dummy: "dummy"
  }
};

const response = {} as IResponse<{}>;

describe("withRequestMiddlewares", () => {
  // one case for any number of supported middlewares
  const cases = [1, 5, 10, 100];
  it.each(cases)(
    "should process a request with %i resolving middlewares",
    n => {
      const mockHandler = jest.fn(() => Promise.resolve(response));

      const middlewares = Array(n).fill(ResolvingMiddleware);
      const expected = Array(n).fill("dummy");

      const handler = withRequestMiddlewares(...middlewares)(mockHandler);

      return handler(request as any).then((r: any) => {
        expect(mockHandler).toHaveBeenCalledWith(...expected);
        expect(r).toEqual(response);
      });
    }
  );

  it.each(cases)(
    "should process a request with %i middlewares whose last one rejects",
    n => {
      const mockHandler = jest.fn(() => Promise.resolve(response));

      // Provide a series of resolving middlewares followed by a rejecting one
      //   by providing the rejecting middleware at last, we ensure it's actually executed and its result matters
      const middlewares = [
        ...Array(n - 1).fill(ResolvingMiddleware),
        RejectingMiddleware
      ];

      const handler = withRequestMiddlewares(...middlewares)(mockHandler);

      return handler(request as any).then((r: any) => {
        expect(mockHandler).not.toHaveBeenCalled();
        expect(r.kind).toBe("IResponseErrorValidation");
      });
    }
  );

  it("should accept handler with right parameters", () => {
    const mockHandler: (
      param1: string,
      param2: number
    ) => Promise<IResponse<{}>> = jest.fn((param1, param2) =>
      Promise.resolve(response)
    );

    const handler = withRequestMiddlewares(
      ResolvingMiddleware,
      ResolvingNumberMiddleware
    )(mockHandler);

    return handler(request as any).then(r => {
      expect(mockHandler).toHaveBeenCalledWith(...["dummy", 1]);
      expect(r).toEqual(response);
    });
  });

  it("should NOT accept handler with type mismatching parameters", () => {
    // an handler with [string, string] arguments
    const mockHandler: (
      param1: string,
      param2: string
    ) => Promise<IResponse<{}>> = jest.fn(() => Promise.resolve(response));

    // composing middleware resulting in [string, number]
    const withComposedMiddlewares = withRequestMiddlewares(
      ResolvingMiddleware,
      ResolvingNumberMiddleware
    );

    // @ts-expect-error as we want the compiler to notice we're passing an handler with wrong signature
    const _handler = withComposedMiddlewares(mockHandler);
  });

  it("should NOT accept handler with more parameters than middlewares", () => {
    const mockHandler: (
      param1: string,
      param2: number,
      param3: number
    ) => Promise<IResponse<{}>> = jest.fn(() => Promise.resolve(response));

    const withComposedMiddlewares = withRequestMiddlewares(
      ResolvingMiddleware,
      ResolvingNumberMiddleware
    );

    // @ts-expect-error
    const _handler = withComposedMiddlewares(mockHandler);
  });

  it("should accept handler with less parameters than middlewares", () => {
    const mockHandler: (
      param1: string,
      param2: number
    ) => Promise<IResponse<{}>> = jest.fn(() => Promise.resolve(response));

    const withComposedMiddlewares = withRequestMiddlewares(
      ResolvingMiddleware,
      ResolvingNumberMiddleware,
      ResolvingNumberMiddleware
    );

    const _handler = withComposedMiddlewares(mockHandler);
  });

  it("should process a request with a rejecting middleware", () => {
    const mockHandler = jest.fn(() => Promise.resolve(response));
    const handler = withRequestMiddlewares(RejectingMiddleware)(mockHandler);

    return handler(request as any).then(r => {
      expect(mockHandler).not.toHaveBeenCalled();
      expect(r.kind).toBe("IResponseErrorValidation");
    });
  });

  it("should return an IResponseErrorInternal in case middleware fails", () => {
    const mockHandler = jest.fn(() => Promise.resolve(response));
    const handler = withRequestMiddlewares(DelayedFailureMiddleware)(
      mockHandler
    );

    return handler(request as any).then(r => {
      expect(mockHandler).not.toHaveBeenCalled();
      expect(r.kind).toBe("IResponseErrorInternal");
    });
  });

  it("should stop processing middlewares after a rejecting middleware", () => {
    const mockHandler = jest.fn(() => Promise.resolve(response));

    const mockMiddlewareAfterReject = jest.fn();

    const handler = withRequestMiddlewares(
      ResolvingMiddleware,
      ResolvingMiddleware,
      RejectingMiddleware,
      RejectingConflictMiddleware,
      mockMiddlewareAfterReject
    )(mockHandler);

    return handler(request as any).then(r => {
      expect(mockHandler).not.toHaveBeenCalled();
      expect(mockMiddlewareAfterReject).not.toHaveBeenCalled();
      expect(r.kind).toBe("IResponseErrorValidation");
    });
  });
});