Skip to content

Commit

Permalink
Merge pull request #19 from alexandergoncharov/develop
Browse files Browse the repository at this point in the history
Add tests
  • Loading branch information
alexandergoncharov authored Jan 24, 2023
2 parents 6030bb8 + 642b41f commit 8839fc1
Show file tree
Hide file tree
Showing 16 changed files with 2,200 additions and 103 deletions.
1,806 changes: 1,742 additions & 64 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"build": "tsc",
"dev": "nodemon"
"dev": "nodemon",
"test": "ts-mocha test/*.ts --recursive"
},
"nodemonConfig": {
"watch": [
Expand All @@ -27,12 +28,21 @@
},
"dependencies": {
"@types/bcrypt": "^5.0.0",
"@types/chai": "^4.3.4",
"@types/chai-http": "^4.2.0",
"@types/mocha": "^10.0.1",
"@types/supertest": "^2.0.12",
"@types/uuid": "^9.0.0",
"bcrypt": "^5.1.0",
"chai": "^4.3.7",
"chai-http": "^4.3.0",
"express": "^4.18.2",
"mocha": "^10.2.0",
"morgan": "^1.10.0",
"pg": "^8.8.0",
"reflect-metadata": "^0.1.13",
"supertest": "^6.3.3",
"ts-mocha": "^10.0.0",
"typeorm": "^0.3.11",
"uuid": "^9.0.0"
}
Expand Down
11 changes: 8 additions & 3 deletions src/businessLogic/user.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import bcrypt from "bcrypt";
import { v4 as uuidv4 } from "uuid";
import { User } from "../models";
import { wrongEmailOrPassword } from "../utils/const";
import { DUPLICATED_EMAIL_ERROR, WRONG_EMAIL_OR_PASSWORD } from "../utils/const";
import { LoginParams, UserParams } from "../utils/types";
import { getUser, insertUser } from "../repositories/user";
import { insertToken } from "../repositories/token";

export const addUser = async (user: UserParams) => {
const checkUser: User | null = await getUser(user.email);
if (checkUser) {
throw new Error(DUPLICATED_EMAIL_ERROR);
}

const encryptedUser: UserParams = { ...user };
encryptedUser.password = bcrypt.hashSync(user.password, 10);

Expand All @@ -16,12 +21,12 @@ export const addUser = async (user: UserParams) => {
export const loginUser = async (userParams: LoginParams): Promise<string> => {
const user: User | null = await getUser(userParams.email);
if (!user) {
throw new Error(wrongEmailOrPassword);
throw new Error(WRONG_EMAIL_OR_PASSWORD);
}

const matches = bcrypt.compareSync(userParams.password, user.password);
if (!matches) {
throw new Error(wrongEmailOrPassword);
throw new Error(WRONG_EMAIL_OR_PASSWORD);
}

const token = uuidv4();
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ app.use(express.static("public"));
app.use(Router);

export const appDataSource = new DataSource(dbConfig);
export const server = app;

appDataSource
.initialize()
Expand Down
2 changes: 1 addition & 1 deletion src/models/author.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
@Entity()
export class Author {
@PrimaryGeneratedColumn()
authorId!: number;
id!: number;

@Column()
name!: string;
Expand Down
2 changes: 1 addition & 1 deletion src/repositories/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const getQuotes = async (authorId: number): Promise<Array<Quote>> => {
const quoteRepository = appDataSource.getRepository(Quote);
const result = await quoteRepository.find({
relations: ["author"],
where: { author: { authorId } },
where: { author: { id: authorId } },
});

return result;
Expand Down
59 changes: 32 additions & 27 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { logout, validateToken } from "../businessLogic/token";
import { addUser, loginUser } from "../businessLogic/user";
import { Author, Quote } from "../models";
import {
delayMs,
DELAY_IN_MS,
DUPLICATED_EMAIL_ERROR,
INFO_MESSAGE,
StatusCode,
unknowError,
validationErrorMessage,
UNKNOW_ERROR,
VALIDATION_ERROR,
} from "../utils/const";
import {
AuthorResponse,
Expand All @@ -25,10 +27,8 @@ import {

const router = express.Router();

const infoMessage = "Some information about the <b>company</b>.";

router.get("/info", async (req, res) => {
const infoResponse: InfoResponse = { info: infoMessage };
const infoResponse: InfoResponse = { info: INFO_MESSAGE };

return res.send(infoResponse);
});
Expand All @@ -42,18 +42,18 @@ router.get("/author", async (req, res) => {
try {
const user: User | null = await validateToken(token);
if (!user) {
return res.status(StatusCode.Unauthorized).send(validationErrorMessage);
return res.status(StatusCode.Unauthorized).send(VALIDATION_ERROR);
}

await delay(delayMs);
await delay(DELAY_IN_MS);

const author: Author = await getRandomAuthor();
const responseAuthor: AuthorResponse = toAuthtorResponse(author);

return res.send(responseAuthor);
} catch (error) {
const message = handleErrorMessage(error as Error);
res.status(StatusCode.BadRequest).send(message);
const { message, statusCode } = handleErrorMessage(error as Error);
return res.status(statusCode).send(message);
}
});

Expand All @@ -70,18 +70,18 @@ router.get("/quote", async (req, res) => {
try {
const user: User | null = await validateToken(token);
if (!user) {
return res.status(StatusCode.Unauthorized).send(validationErrorMessage);
return res.status(StatusCode.Unauthorized).send(VALIDATION_ERROR);
}

await delay(delayMs);
await delay(DELAY_IN_MS);

const quote: Quote = await getRandomQuote(authorId);
const quoteRepsonse: QuoteRepsonse = toQuoteRepsonse(quote);

return res.send(quoteRepsonse);
} catch (error) {
const message = handleErrorMessage(error as Error);
res.status(StatusCode.BadRequest).send(message);
const { message, statusCode } = handleErrorMessage(error as Error);
return res.status(statusCode).send(message);
}
});

Expand All @@ -100,8 +100,8 @@ router.post("/register", async (req, res) => {

return res.sendStatus(StatusCode.Successful);
} catch (error) {
const message = handleErrorMessage(error as Error);
return res.status(StatusCode.BadRequest).send(message);
const { message, statusCode } = handleErrorMessage(error as Error);
return res.status(statusCode).send(message);
}
});

Expand All @@ -117,10 +117,10 @@ router.post("/login", async (req, res) => {
password,
});

return res.status(StatusCode.Successful).send(token);
return res.status(StatusCode.Successful).send({ token });
} catch (error) {
const message = handleErrorMessage(error as Error);
return res.status(StatusCode.BadRequest).send(message);
const { message, statusCode } = handleErrorMessage(error as Error);
return res.status(statusCode).send(message);
}
});

Expand All @@ -134,15 +134,15 @@ router.get("/profile", async (req, res) => {
const user: User | null = await validateToken(token);

if (!user) {
return res.status(403).send(validationErrorMessage);
return res.status(403).send(VALIDATION_ERROR);
}

const profileResponse: ProfileRespons = toProfileResponse(user);

res.status(StatusCode.Successful).send(profileResponse);
} catch (error) {
const message = handleErrorMessage(error as Error);
return res.status(StatusCode.BadRequest).send(message);
const { message, statusCode } = handleErrorMessage(error as Error);
return res.status(statusCode).send(message);
}
});

Expand All @@ -155,23 +155,28 @@ router.delete("/logout", async (req, res) => {
try {
const user: User | null = await validateToken(token);
if (!user) {
return res.status(403).send(validationErrorMessage);
return res.status(403).send(VALIDATION_ERROR);
}

await logout(token);

res.sendStatus(StatusCode.Successful);
} catch (error) {
const message = handleErrorMessage(error as Error);
return res.status(StatusCode.BadRequest).send(message);
const { message, statusCode } = handleErrorMessage(error as Error);
return res.status(statusCode).send(message);
}
});

const handleErrorMessage = (error: Error) => {
let message = unknowError;
let message = UNKNOW_ERROR;
let statusCode = StatusCode.BadRequest;

if (error instanceof Error) message = error.message;

return message;
if (error instanceof Error && error.message === DUPLICATED_EMAIL_ERROR)
statusCode = StatusCode.Duplicated;

return { message, statusCode };
};

const delay = (ms: number) => {
Expand Down
11 changes: 7 additions & 4 deletions src/utils/const.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
export const validationErrorMessage: string = "Wrong validation token";
export const unknowError: string = "Unknow Error";
export const wrongEmailOrPassword: string = "Wrong Email or Password";
export const VALIDATION_ERROR: string = "Wrong validation token";
export const UNKNOW_ERROR: string = "Unknow Error";
export const WRONG_EMAIL_OR_PASSWORD: string = "Wrong Email or Password";
export const DUPLICATED_EMAIL_ERROR: string = "Email already exist";
export const INFO_MESSAGE = "Some information about the <b>company</b>.";

export const delayMs: number = 5000;
export const DELAY_IN_MS: number = 5000;

export enum StatusCode {
Successful = 200,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
Duplicated = 409,
}
4 changes: 2 additions & 2 deletions src/utils/convertor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AuthorResponse, ProfileRespons, QuoteRepsonse } from "./types";

export const toAuthtorResponse = (author: Author): AuthorResponse => {
const responseAuthor: AuthorResponse = {
authorId: author.authorId,
authorId: author.id,
name: author.name,
};

Expand All @@ -12,7 +12,7 @@ export const toAuthtorResponse = (author: Author): AuthorResponse => {

export const toQuoteRepsonse = (quote: Quote): QuoteRepsonse => {
const quoteRepsonse: QuoteRepsonse = {
authorId: quote.author.authorId,
authorId: quote.author.id,
quoteId: quote.quoteId,
quote: quote.quote,
};
Expand Down
60 changes: 60 additions & 0 deletions test/author.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import chai from "chai";
import supertest from "supertest";
import { server } from "../src/index";
import { User } from "../src/models";
import { deleteToken } from "../src/repositories/token";
import { createAuthor, deleteAuthor, getAllAuthors } from "./dbUtils/author";
import { createUser, createUserToken, deleteUser } from "./dbUtils/user";

const app = supertest(server);
const { expect } = chai;

describe("Author api", () => {
describe("for non authorized users", async () => {
it("/author should return 401 if token missed", async () => {
await app.get("/author").expect(401);
});

it("/author should return 401 if token is invalid", async () => {
await app.get("/author?token=invalidToken").expect(401);
});
});

describe("for authorized users", () => {
let user: User;
let token: string;

before(async () => {
user = await createUser();
token = await createUserToken(user);
});

after(async () => {
await deleteToken(token);
await deleteUser(user);
});

it("/author should return random author", async () => {
const author1 = await createAuthor();
const author2 = await createAuthor();

try {
const response = await app.get(`/author?token=${token}`).expect(200);
const responseAuthor = response.body;

const allAuthors = await getAllAuthors();

const isAuthorExists = allAuthors.some(
(author) =>
author.id === responseAuthor.authorId &&
author.name === responseAuthor.name
);

expect(isAuthorExists).to.be.true;
} finally {
await deleteAuthor(author1);
await deleteAuthor(author2);
}
}).timeout(10000);
});
});
28 changes: 28 additions & 0 deletions test/dbUtils/author.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { v4 as uuidv4 } from "uuid";
import { appDataSource } from "../../src";
import { Author } from "../../src/models";

export const getAllAuthors = (): Promise<Author[]> => {
const authorRepository = appDataSource.getRepository(Author);
return authorRepository.find();
};

export const createAuthor = async (): Promise<Author> => {
const authorRepository = appDataSource.getRepository(Author);
const authorParams = {
name: "testName" + uuidv4(), // For uniq name
};

await authorRepository.insert(authorParams);

const author: Author | null = await authorRepository.findOne({
where: { name: authorParams.name },
});

return author!;
};

export const deleteAuthor = async (author: Author): Promise<void> => {
const authorRepository = appDataSource.getRepository(Author);
await authorRepository.delete(author);
};
23 changes: 23 additions & 0 deletions test/dbUtils/quote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { v4 as uuidv4 } from "uuid";
import { appDataSource } from "../../src";
import { Author, Quote } from "../../src/models";

export const createQuote = async (author: Author): Promise<Quote> => {
const quoteRepository = appDataSource.getRepository(Quote);
const quoteText = "testQuote" + uuidv4(); // For uniq quote

await quoteRepository.insert({ quote: quoteText, author });

const quote = await quoteRepository.findOne({
relations: ["author"],
where: { quote: quoteText },
});

return quote!;
};

export const deleteQuote = async (quote: Quote) => {
const quoteRepository = appDataSource.getRepository(Quote);

await quoteRepository.delete(quote);
};
Loading

0 comments on commit 8839fc1

Please sign in to comment.