Skip to content

Commit

Permalink
refactor: Local server for tests
Browse files Browse the repository at this point in the history
  • Loading branch information
NT committed Jan 1, 2021
1 parent 3256649 commit 1ea4d54
Show file tree
Hide file tree
Showing 23 changed files with 202 additions and 148 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

| Statements | Branches | Functions | Lines |
| --------------------------- | ----------------------- | ------------------------- | -------------------- |
| ![Statements](https://img.shields.io/badge/Coverage-99.5%25-brightgreen.svg) | ![Branches](https://img.shields.io/badge/Coverage-94.83%25-brightgreen.svg) | ![Functions](https://img.shields.io/badge/Coverage-99.09%25-brightgreen.svg) | ![Lines](https://img.shields.io/badge/Coverage-99.71%25-brightgreen.svg) |
| ![Statements](https://img.shields.io/badge/Coverage-99.27%25-brightgreen.svg) | ![Branches](https://img.shields.io/badge/Coverage-97.27%25-brightgreen.svg) | ![Functions](https://img.shields.io/badge/Coverage-99.12%25-brightgreen.svg) | ![Lines](https://img.shields.io/badge/Coverage-99.72%25-brightgreen.svg) |

This branch is a fork from [https://github.com/nullcc/ts-retrofit/](https://github.com/nullcc/ts-retrofit/)
> A declarative and axios based retrofit implementation for JavaScript and TypeScript.
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = {
testURL: "http://localhost/",
testEnvironment: "node",
rootDir: ".",
setupFilesAfterEnv: ["./tests/testSetupFile.js"],
setupFilesAfterEnv: ["./tests/testSetupFile.ts"],
coverageReporters: ["json-summary", "text", "lcov"],
coverageThreshold: {
global: {
Expand Down
2 changes: 2 additions & 0 deletions src/baseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const STUB_RESPONSE = <T>() => ({} as T);

export const ErrorMessages = {
NO_HTTP_METHOD: "No http method for method (Add @GET / @POST ...)",

EMPTY_HEADER_KEY: "Header key can't be empty",
WRONG_HEADERS_PROPERTY_TYPE: "Header's property can be only number / string / boolean",
WRONG_HEADER_TYPE: "Header type can be only number / string / boolean",
Expand All @@ -25,6 +26,7 @@ export const ErrorMessages = {
WRONG_QUERY_MAP_PROPERTY_TYPE: "QueryMap should only contain number / string / boolean",

EMPTY_FIELD_KEY: "Field key can't be empty",
EMPTY_PART_KEY: "Part key can't be empty",
};

export class BaseService {
Expand Down
4 changes: 2 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export type MethodMetadata = {
options?: HttpMethodOptions;
httpMethod?: HttpMethod;
bodyIndex?: number;
fields: { [key: number]: any };
parts: { [key: number]: any };
fields: { [key: number]: string };
parts: { [key: number]: string };
fieldMapIndex?: number;

headers: HeadersParamType;
Expand Down
65 changes: 38 additions & 27 deletions src/request-resolvers/body-request-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CONTENT_TYPE, CONTENT_TYPE_HEADER, MethodMetadata } from "../constants";
import { DataResolverFactory } from "../dataResolver";
import { ErrorMessages } from "../baseService";

export const requestBodyResolver = (metadata: MethodMetadata, methodName: string, headers: any, args: any[]): any => {
const bodyIndex = metadata.bodyIndex;
Expand All @@ -18,58 +19,68 @@ export const requestBodyResolver = (metadata: MethodMetadata, methodName: string
// @MultiPart
data = resolveMultipart(metadata, args, data);

const contentType = headers[CONTENT_TYPE_HEADER] || CONTENT_TYPE.APPLICATION_JSON;
const contentType = headers[CONTENT_TYPE_HEADER];
const dataResolverFactory = new DataResolverFactory();
const dataResolver = dataResolverFactory.createDataResolver(contentType);
return dataResolver.resolve(headers, data);
};

// @Body
function resolveBody(bodyIndex: number | undefined, args: any[], data: {}) {
if (bodyIndex === undefined || bodyIndex < 0) return data;
if (bodyIndex === undefined) return data;

if (Array.isArray(args[bodyIndex])) {
data = args[bodyIndex];
const argValue = args[bodyIndex];

if (Array.isArray(argValue)) {
data = argValue;
} else {
data = { ...data, ...args[bodyIndex] };
data = { ...data, ...argValue };
}
return data;
}

// @Field
const resolveField = (metadata: MethodMetadata, args: any[], data: {}) => {
if (Object.keys(metadata.fields).length === 0) return data;

const reqData = {};
for (const pos in metadata.fields) {
if (metadata.fields[pos]) {
reqData[metadata.fields[pos]] = args[pos];
}
}
return { ...data, ...reqData };
const result = {};
Object.entries(metadata.fields).map((e) => {
const [idx, fieldKey] = e;
if (fieldKey === "") throw Error(ErrorMessages.EMPTY_FIELD_KEY);

result[fieldKey] = args[idx];
});

return { ...data, ...result };
};

// @MultiPart
const resolveMultipart = (metadata: MethodMetadata, args: any[], data: {}) => {
if (Object.keys(metadata.parts).length === 0) return data;

const reqData = {};
for (const pos in metadata.parts) {
if (metadata.parts[pos]) {
reqData[metadata.parts[pos]] = args[pos];
}
}
return { ...data, ...reqData };
const result = {};
Object.entries(metadata.parts).map((e) => {
const [idx, partKey] = e;
if (partKey === "") throw Error(ErrorMessages.EMPTY_PART_KEY);

result[partKey] = args[idx];
});
return { ...data, ...result };
};

// @FieldMap
const resolveFieldMap = (fieldMapIndex: number | undefined, args: any[], data: {}) => {
if (fieldMapIndex === undefined || fieldMapIndex < 0) return data;
if (fieldMapIndex === undefined) return data;

const reqData = {};
for (const key in args[fieldMapIndex]) {
if (args[fieldMapIndex][key]) {
reqData[key] = args[fieldMapIndex][key];
}
}
return { ...data, ...reqData };
const fieldMap = args[fieldMapIndex];

const result = {};
Object.entries(fieldMap).map((e) => {
const [fieldKey, fieldValue] = e;
if (fieldKey === "") throw Error(ErrorMessages.EMPTY_FIELD_KEY);

result[fieldKey] = fieldValue;
});

return { ...data, ...result };
};
15 changes: 15 additions & 0 deletions tests/fixture/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ export interface SearchQuery {
userId?: number;
}

export const posts: Post[] = [
{
id: 1,
userId: 1,
body: "body1",
title: "title1",
},
{
id: 2,
userId: 2,
body: "body2",
title: "title2",
},
];

@BasePath(PostsApiService.BASE_PATH)
export class PostsApiService extends BaseService {
static BASE_PATH = "/posts";
Expand Down
13 changes: 13 additions & 0 deletions tests/fixture/fixtures.wrong-cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Body,
Query,
QueryMap,
FieldMap,
} from "../../src";
import { Post, PostCreateDTO, SearchQuery } from "./fixtures";

Expand All @@ -36,6 +37,18 @@ export class WrongHeaderService extends BaseService {
}
}

export class WrongFieldService extends BaseService {
@GET("/")
async wrongFieldMap(@FieldMap param: { [key: string]: unknown }): Promise<Response> {
return <Response>{};
}

@GET("/")
async emptyFieldKey(@Field("") param: unknown): Promise<Response> {
return <Response>{};
}
}

export class WrongQueryService extends BaseService {
@GET("/")
async wrongQuery(@Query("userId") userId: unknown): Promise<Response> {
Expand Down
59 changes: 26 additions & 33 deletions tests/fixture/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from "express";
import bodyParser from "body-parser";
import multer from "multer";
import { Post, posts } from "./fixtures";

export const app = express();

Expand All @@ -16,66 +17,58 @@ const sleep = async (milliseconds: number): Promise<void> => {
});
};

app.get("/api/v1/posts", jsonParser, function (req, res) {
res.status(200).json({});
app.get("/posts", jsonParser, function (req, res) {
res.status(200).json(posts);
});

app.post("/api/v1/form-url-encoded", jsonParser, function (req, res) {
res.status(200).json(req.body);
app.post("/posts", jsonParser, function (req, res) {
res.status(201).json(req.body);
});

app.post("/api/v1/posts", jsonParser, function (req, res) {
res.status(200).json({});
app.get("/posts/:id", jsonParser, function (req, res) {
res.status(200).json(posts.find((p) => p.id === Number(req.params["id"])));
});

app.post("/api/v1/upload", upload.any(), function (req, res) {
// get fields of form data from `req.body`
// get files from req.files array
res.status(200).json({});
app.put("/posts/:id", jsonParser, function (req, res) {
const found = posts.find((p) => p.id === Number(req.params["id"]));
res.status(200).json({ ...found, ...req.body });
});

app.get("/api/v1/file", upload.any(), function (req, res) {
res.status(200).json({});
app.patch("/posts/:id", jsonParser, function (req, res) {
const found = posts.find((p) => p.id === Number(req.params["id"]));
res.status(200).json({ ...found, ...req.body });
});

app.post("/api/v1/sms", jsonParser, function (req, res) {
// get fields of form data from `req.body`
// get files from req.files array
res.status(200).json({});
app.delete("/posts/:id", jsonParser, function (req, res) {
res.status(200).json(posts.find((p) => p.id === Number(req.params["id"])));
});

app.post("/api/v1/groups", jsonParser, function (req, res) {
// get fields of form data from `req.body`
// get files from req.files array
res.status(200).json({});
app.head("/posts/:id", jsonParser, function (req, res) {
res.status(200).json(posts.find((p) => p.id === Number(req.params["id"])));
});

app.get("/api/v1/interceptor", function (req, res) {
res.status(200).json(req.query);
app.options("/posts/:id", jsonParser, function (req, res) {
res.status(204).json(posts.find((p) => p.id === Number(req.params["id"])));
});

app.post("/api/v1/interceptor", function (req, res) {
res.status(200).json(req.body);
});
///////////////////////////////////////////////////////////////////////////////////////////////////

app.get("/api/v1/header", function (req, res) {
res.status(200).json({ role: req.header("X-Role") });
app.post("/api/v1/form-url-encoded", jsonParser, function (req, res) {
res.status(200).json(req.body);
});

app.post("/api/v1/request-transformer", function (req, res) {
app.post("/api/v1/upload", upload.any(), function (req, res) {
res.status(200).json({});
});

app.get("/api/v1/response-transformer", function (req, res) {
app.get("/api/v1/file", upload.any(), function (req, res) {
res.status(200).json({});
});

app.get("/api/v1/sleep-5000", async function (req, res) {
await sleep(5000);
app.post("/api/v1/sms", jsonParser, function (req, res) {
res.status(200).json({});
});

app.get("/api/v1/config", async function (req, res) {
app.get("/api/v1/sleep-5000", async function (req, res) {
await sleep(5000);
res.status(200).json({});
});
Expand Down
4 changes: 2 additions & 2 deletions tests/test/dataResolver.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {
BaseDataResolver,
DataResolverFactory,
FormUrlencodedResolver,
MultiPartResolver,
JsonResolver,
MultiPartResolver,
TextXmlResolver,
DataResolverFactory,
} from "../../src/dataResolver";
import * as fs from "fs";
import { CONTENT_TYPE, CONTENT_TYPE_HEADER } from "../../src/constants";
Expand Down
14 changes: 2 additions & 12 deletions tests/test/decorators/decorators.formurlencoded.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import { ServiceBuilder } from "../../../src/service.builder";
import { testServerUrl } from "../../testHelpers";
import { testServer } from "../../testHelpers";
import { CHARSET_UTF_8, CONTENT_TYPE, CONTENT_TYPE_HEADER } from "../../../src/constants";
import http from "http";
import { app } from "../../fixture/server";
import { FormUrlEncodedService } from "../../fixture/fixtures.formurlencoded";

describe("Decorators - @FormUrlEncoded", () => {
let service: FormUrlEncodedService;
let server: http.Server;
let url: string;

beforeAll(() => {
server = app.listen(0);
url = testServerUrl(server.address());
service = new ServiceBuilder().setEndpoint(url).build(FormUrlEncodedService);
});

afterAll(() => {
server.close();
service = new ServiceBuilder().setEndpoint(testServer.url).build(FormUrlEncodedService);
});

const object = {
Expand Down
25 changes: 5 additions & 20 deletions tests/test/decorators/decorators.multipart.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
import { ServiceBuilder } from "../../../src/service.builder";
import { JSONPLACEHOLDER_URL, testServerUrl } from "../../testHelpers";
import { testServer } from "../../testHelpers";
import fs from "fs";
import http from "http";
import { app } from "../../fixture/server";
import { AddressInfo } from "net";
import { CONTENT_TYPE_HEADER, CONTENT_TYPE } from "../../../src/constants";
import { CONTENT_TYPE, CONTENT_TYPE_HEADER } from "../../../src/constants";
import { FileService, MessagingService } from "../../fixture/fixtures.multipart";

describe("Decorators - Multipart", () => {
let server: http.Server;
let url: string;

beforeAll(() => {
server = app.listen(0);
url = testServerUrl(server.address());
});

afterAll(() => {
server.close();
});

test("@Multipart", async () => {
const fileService = new ServiceBuilder().setEndpoint(url).build(FileService);
const fileService = new ServiceBuilder().setEndpoint(testServer.url).build(FileService);
const bucket = {
value: "test-bucket",
};
Expand All @@ -34,15 +19,15 @@ describe("Decorators - Multipart", () => {
});

test("@Multipart - test2", async () => {
const messagingService = new ServiceBuilder().setEndpoint(url).build(MessagingService);
const messagingService = new ServiceBuilder().setEndpoint(testServer.url).build(MessagingService);
const from = { value: "+11111111" };
const to = { value: ["+22222222", "+33333333"] };
const response = await messagingService.createSMS(from, to);
expect(response.config.headers[CONTENT_TYPE_HEADER]).toContain(CONTENT_TYPE.MULTIPART_FORM_DATA);
});

test("@ResponseType", async () => {
const fileService = new ServiceBuilder().setEndpoint(url).build(FileService);
const fileService = new ServiceBuilder().setEndpoint(testServer.url).build(FileService);
const response = await fileService.getFile("x-y-z");
expect(response.config.responseType).toEqual("stream");
});
Expand Down
Loading

0 comments on commit 1ea4d54

Please sign in to comment.