Simple movies middleware.
Typescript
Express
PostgreSQL
Prisma
Jest
Supertest
node
postgresql db created (pgadmin recommended)
git clone https://github.com/wojciechszmelczerczyk/ts-express-swapi-middleware.git
cd /ts-express-swapi-middleware
npm i
npx prisma db push
npm run start
Application implements REST style architecture. Client communicate with server.
Create .env
in project root directory
DB_URL=your-postgres-url
PORT=3000
BASE_FILMS_URL=https://swapi.dev/api/films
XLSX_FILE_NAME=someFileName.xlsx
Import this file postman collection in Postman in order to explore API.
Endpoint | Method |
---|---|
GET | /films |
GET | /films/:id |
POST | /favorites |
GET | /favorites |
GET | /favorites/:id |
GET | /favorites/:id/file |
Unit tests
npm run test-unit
Integration/API tests
npm run test-api
Both
npm run test
Create new List table
Before tests one Film list table is created.
export const createDB = async (name: string) =>
await prisma.filmList.create({
data: {
name,
},
});
Flush all tables
Before and after tests all tables are flushed.
export const flushDBs = async () => {
await prisma.character.deleteMany({});
await prisma.film.deleteMany({});
await prisma.filmList.deleteMany({});
};
Refresh sequences on id's
Before and after tests launch sequences are restarted.
export const resetSequence = async () => {
await prisma.$executeRaw`ALTER SEQUENCE "Character_id_seq" RESTART WITH 1;`;
await prisma.$executeRaw`ALTER SEQUENCE "Film_id_seq" RESTART WITH 1;`;
await prisma.$executeRaw`ALTER SEQUENCE "FilmList_id_seq" RESTART WITH 1;`;
};
FilmService -> getFilms()
return all films
test("when id is not provided return all films", async () => {
const { data } = await getFilmsService();
expect(data.results.length).toBe(6);
});
GET /films
when id is not provided return all films
test("when id is not provided return all films", async () => {
const res = await request(app).get("/films");
expect(res.status).toBe(200);
expect(res.body).toBeTruthy();
});
GET /films/id
when provided id is valid return specific film
test("when id is provided return specific film", async () => {
const id = 2;
const res = await request(app).get(`/films/${id}`);
expect(res.status).toBe(200);
expect(res.body).toBeTruthy();
});
when provided id is not a numeric value, return error message
test("when provided id is not a numeric value, return error message", async () => {
const id = "id";
const res = await request(app).get(`/films/${id}`);
expect(res.status).toBe(404);
expect(res.body.err).toBe("id has to be number");
});
when film with provided id doesn't exist, return error message
test("when film with provided id doesn't exist, return error message", async () => {
const id = 7;
const res = await request(app).get(`/films/${id}`);
expect(res.status).toBe(404);
expect(res.body.err).toBe("film with this id doesn't exist");
});
POST /favorites
when list with provided name exist and film id is correct, add film with provided id to list
test("when list with provided name exist and film id is correct, add film with provided id to list", async () => {
let listName = "Old Saga";
let id = "2";
const res = await request(app)
.post("/favorites")
.send({ id, name: listName });
expect(res).toBeTruthy();
});
when list with provided name doesn't exist and film id is correct, create new list and add film with provided id to it
test("when list with provided name doesn't exist and film id is correct, create new list and add film with provided id to it", async () => {
let listName = "New Saga";
let id = "2";
const res = await request(app)
.post("/favorites")
.send({ id, name: listName });
expect(res).toBeTruthy();
});
when film with provided id doesn't exist, return error
test("when film with provided id doesn't exist, return error", async () => {
let listName = "New Saga";
let id = "7";
const res = await request(app)
.post("/favorites")
.send({ id, name: listName });
expect(res.body.err).toBe("film with this id doesn't exist");
});
when provided id is not a numeric value, return error
test("when provided id is not a numeric value, return error", async () => {
let listName = "New Saga";
let id = "ss";
const res = await request(app)
.post("/favorites")
.send({ id, name: listName });
expect(res.body.err).toBe("id has to be number");
});
when added film with provided id already exist in list, return duplicate error
test("when added film with provided id already exist in list, return duplicate error", async () => {
let listName = "New Saga";
let id = "2";
const res = await request(app)
.post("/favorites")
.send({ id, name: listName });
expect(res.body.err).toBe(
"film duplication error. Film with this id already exist in list"
);
});
GET /favorites
when name query is not provided, return all lists
test("when name query is not provided, return all lists", async () => {
const res = await request(app).get("/favorites");
// number of list tables added each time tests launch
expect(res.body.length).toBe(8);
});
when name query is provided, return list by name
test("when query name is provided, return list by name", async () => {
const name = "New Saga";
const res = await request(app).get("/favorites").query({ name });
expect(res.body.name).toBe(name);
});
when page query is provided, return paginated lists by default 5
test("when page query is provided, return paginated lists by default 5", async () => {
const page = 1;
const res = await request(app).get("/favorites").query({ page });
expect(res.body.length).toBe(5);
});
when query page is not a numeric value, return error
test("when query page is not a numeric value, return error", async () => {
const page = "x";
const res = await request(app).get("/favorites").query({ page });
expect(res.body.err).toBe("Provided page value is not a number");
});
when limit query is provided, return specified number of lists
test("when limit query is provided, return specified number of lists", async () => {
const page = 2;
const limit = 3;
const res = await request(app).get("/favorites").query({ page, limit });
expect(res.body.length).toBe(3);
});
GET /favorites/:id
when provided id is correct, return specific list with correlated films
test("when provided id is correct, return specific list with correlated films", async () => {
// New Saga list id
const id = 8;
const res = await request(app).get(`/favorites/${id}`);
expect(res.body.name).toBe("New Saga");
});
when provided id is not a number value, return error message
test("when provided id is not a number value, return error message", async () => {
const id = "ss";
const res = await request(app).get(`/favorites/${id}`);
expect(res.body.err).toBe("Provided id has to be a number.");
});
GET /favorites/:id/file
when provided id is correct, download .xlsx file with list of characters
test("when provided id is correct, download .xlsx file with list of characters", async () => {
const id = 2;
const res = await request(app).get(`/favorites/${id}/file`);
expect(res.headers["content-type"]).toBe(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
});