Skip to content
This repository has been archived by the owner on May 22, 2024. It is now read-only.

Commit

Permalink
feat: throwOnUnexpectedApiResponse
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcel Kloubert committed Jan 7, 2024
1 parent e5f0382 commit 11fc9a3
Show file tree
Hide file tree
Showing 19 changed files with 541 additions and 86 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Change Log (@egomobile/api-utils)

## 4.1.0
## 5.0.0

- implement `throwOnUnexpectedApiResponse()` function
- code cleanups and improvements
- `npm update`s

## 4.1.1

- implement `createApiHandlerFactory()` factory function, which create strong typed API handlers

Expand Down
146 changes: 77 additions & 69 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@egomobile/api-utils",
"version": "4.1.1",
"version": "5.0.0",
"description": "REST API extensions for extensions for @egomobile/http-server module, a very fast alternative to Express.js",
"main": "lib/index.js",
"engines": {
Expand Down Expand Up @@ -52,22 +52,22 @@
"joi": "17.11.0"
},
"devDependencies": {
"@egomobile/http-server": "^0.65.0",
"@egomobile/http-server": "^0.65.3",
"@egomobile/tsconfig": "^5.0.0",
"@types/jest": "29.5.11",
"@types/node": "18.18.13",
"@types/supertest": "2.0.16",
"axios": "1.6.2",
"@types/supertest": "6.0.2",
"axios": "1.6.5",
"babel-jest": "29.7.0",
"del-cli": "5.1.0",
"eslint": "8.55.0",
"eslint": "8.56.0",
"eslint-config-ego": "^0.19.0",
"jest": "29.7.0",
"nodemon": "3.0.2",
"supertest": "6.3.3",
"ts-jest": "29.1.1",
"ts-node": "10.9.2",
"typedoc": "0.25.4",
"typedoc": "0.25.6",
"typescript": "4.7.4"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/utils.ts → src/__tests__/_utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Response } from "supertest";
import type { Response } from "supertest";

export function binaryParser(response: Response, done: (ex: any, data?: Buffer) => any) {
response.setEncoding("binary");
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/handlers/handleApiError.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import createServer from "@egomobile/http-server";
import request from "supertest";
import { handleApiError } from "../../responses/handlers";
import { binaryParser } from "../utils";
import { binaryParser } from "../_utils";

describe("handleApiError()", () => {
it.each(["foo", "bar", "baz"])("should save valid value to internal prop", async (txt) => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/handlers/handleApiNotFound.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import createServer from "@egomobile/http-server";
import request from "supertest";
import { handleApiNotFound } from "../../responses/handlers";
import { binaryParser } from "../utils";
import { binaryParser } from "../_utils";

describe("handleApiNotFound()", () => {
it.each(["foo", "bar", "baz"])("should save valid value to internal prop", async (subPath) => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/handlers/handleApiParseError.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import createServer, { json } from "@egomobile/http-server";
import request from "supertest";
import { handleApiParseError } from "../../responses/handlers";
import { binaryParser } from "../utils";
import { binaryParser } from "../_utils";

describe("handleApiParseError()", () => {
it("should return 400 if submit invalid JSON data", async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/handlers/handleApiValidationError.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import createServer, { json, schema, validate } from "@egomobile/http-server";
import request from "supertest";
import { handleApiValidationError } from "../../responses/handlers";
import { binaryParser } from "../utils";
import { binaryParser } from "../_utils";

const testSchema = schema.object({
"email": schema.string().strict().email().required(),
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/middlewares/extendRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import createServer from "@egomobile/http-server";
import request from "supertest";
import { extendRequest } from "../..";
import { binaryParser } from "../utils";
import { binaryParser } from "../_utils";

describe("extendRequest()", () => {
it("should fill request props with valid data", async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/middlewares/parseListQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import createServer, { query } from "@egomobile/http-server";
import request from "supertest";
import { parseListQuery } from "../..";
import { binaryParser } from "../utils";
import { binaryParser } from "../_utils";

const validParams = [
{},
Expand Down
159 changes: 159 additions & 0 deletions src/__tests__/utils/throwOnUnexpectedApiResponse.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import createServer from "@egomobile/http-server";
import type { AxiosResponse } from "axios";
import assert from "node:assert";
import request from "supertest";
import { apiResponse } from "../../responses";
import { UnexpectedApiResponseError } from "../../types";
import { ApiResponseValidator, throwOnUnexpectedApiResponse } from "../../utils";
import { binaryParser } from "../_utils";

const NO_ERROR = Symbol("NO_ERROR");

describe("throwOnUnexpectedApiResponse()", () => {
it("should throw UnexpectedApiResponseError using single status code", async () => {
const app = createServer();

app.get("/", async (request, response) => {
apiResponse(request, response)
.noSuccess()
.withStatus(400)
.addMessage({
"code": 40000,
"message": "Invalid input data"
})
.send();
});

const response = await request(app).get("/")
.send()
.parse(binaryParser);

const data = response.body;
expect(Buffer.isBuffer(data)).toBe(true);

const dataObj = JSON.parse(
data.toString("utf8")
);

const axiosResponse = {
"config": undefined!,
"data": dataObj,
"headers": response.headers,
"status": response.statusCode,
"statusText": response.text
} as AxiosResponse;

let lastError: any = NO_ERROR;

try {
await throwOnUnexpectedApiResponse(axiosResponse, 200);
}
catch (error) {
lastError = error;
}

assert.strictEqual(typeof lastError, "object");
assert.strictEqual(lastError instanceof UnexpectedApiResponseError, true);
});

it("should throw UnexpectedApiResponseError using list of status codes", async () => {
const app = createServer();

app.get("/", async (request, response) => {
apiResponse(request, response)
.noSuccess()
.withStatus(400)
.addMessage({
"code": 40000,
"message": "Invalid input data"
})
.send();
});

const response = await request(app).get("/")
.send()
.parse(binaryParser);

const data = response.body;
expect(Buffer.isBuffer(data)).toBe(true);

const dataObj = JSON.parse(
data.toString("utf8")
);

const axiosResponse = {
"config": undefined!,
"data": dataObj,
"headers": response.headers,
"status": response.statusCode,
"statusText": response.text
} as AxiosResponse;

let lastError: any = NO_ERROR;

try {
await throwOnUnexpectedApiResponse(axiosResponse, [200, 201]);
}
catch (error) {
lastError = error;
}

assert.strictEqual(lastError instanceof UnexpectedApiResponseError, true);
});

it("should throw UnexpectedApiResponseError using validation functions", async () => {
const app = createServer();

app.get("/", async (request, response) => {
apiResponse(request, response)
.noSuccess()
.withStatus(400)
.addMessage({
"code": 40000,
"message": "Invalid input data"
})
.send();
});

const response = await request(app).get("/")
.send()
.parse(binaryParser);

const data = response.body;
expect(Buffer.isBuffer(data)).toBe(true);

const dataObj = JSON.parse(
data.toString("utf8")
);

const axiosResponse = {
"config": undefined!,
"data": dataObj,
"headers": response.headers,
"status": response.statusCode,
"statusText": response.text
} as AxiosResponse;

const validatorFunctions: ApiResponseValidator[] = [
(context) => {
return context.response.status === 200;
},
async (context) => {
return context.response.status === 200;
}
];

for (const validator of validatorFunctions) {
let lastError: any = NO_ERROR;

try {
await throwOnUnexpectedApiResponse(axiosResponse, validator);
}
catch (error) {
lastError = error;
}

assert.strictEqual(lastError instanceof UnexpectedApiResponseError, true);
}
});
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export * from "./requests";
export * from "./responses";
export * from "./swagger";
export * from "./types";
export * from "./utils";

2 changes: 1 addition & 1 deletion src/requests/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import type { HttpMiddleware } from "@egomobile/http-server";
import joi from "joi";
import { apiResponse } from "..";
import { Nilable } from "../types/internal";
import type { Nilable } from "../types/internal";
import { getEmptyArray } from "../utils/internal";

/**
Expand Down
2 changes: 1 addition & 1 deletion src/responses/handlers/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import { HttpErrorHandler, HttpNotFoundHandler, ParseErrorHandler, ValidationFailedHandler } from "@egomobile/http-server";
import type { HttpErrorHandler, HttpNotFoundHandler, ParseErrorHandler, ValidationFailedHandler } from "@egomobile/http-server";
import { apiResponse } from "..";

/**
Expand Down
16 changes: 16 additions & 0 deletions src/types/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// This file is part of the @egomobile/api-utils distribution.
// Copyright (c) Next.e.GO Mobile SE, Aachen, Germany (https://e-go-mobile.com/)
//
// @egomobile/api-utils is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3.
//
// @egomobile/api-utils is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

export * from "./unexpectedApiResponseError";
34 changes: 34 additions & 0 deletions src/types/errors/unexpectedApiResponseError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This file is part of the @egomobile/api-utils distribution.
// Copyright (c) Next.e.GO Mobile SE, Aachen, Germany (https://e-go-mobile.com/)
//
// @egomobile/api-utils is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3.
//
// @egomobile/api-utils is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import type { AxiosResponse } from "axios";

/**
* Is thrown on an unexpected API response.
*/
export class UnexpectedApiResponseError extends Error {
/**
* Initializes a new instance of that class.
*
* @param {AxiosResponse} response The response context.
* @param {string} [message] The optional message.
*/
public constructor(
public readonly response: AxiosResponse,
message?: string,
) {
super(message);
}
}
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,5 @@ export interface IApiResponseBuilderFactoryOptions {
*/
response: ServerResponse;
}

export * from "./errors";
2 changes: 1 addition & 1 deletion src/types/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import { DebugIcon } from ".";
import type { DebugIcon } from ".";

export type DebugActionWithoutSource = (message: string, icon: DebugIcon) => any;

Expand Down

0 comments on commit 11fc9a3

Please sign in to comment.