From 53333c68fdb557d93fb01b754874a7fd098a7e03 Mon Sep 17 00:00:00 2001 From: SebastienBtr Date: Sat, 7 Nov 2020 12:59:32 +0100 Subject: [PATCH] generate user service --- generator/generators/app/usedPorts.json | 2 +- services/user/.dockerignore | 1 + services/user/.env.default | 11 ++ services/user/.eslintignore | 1 + services/user/.eslintrc.json | 10 + services/user/.gitignore | 3 + services/user/Dockerfile | 8 + services/user/README.md | 24 +++ services/user/datamodel.prisma | 9 + services/user/docker-compose.hot-reload.yaml | 5 + services/user/docker-compose.local.yaml | 44 +++++ services/user/docker-compose.yaml | 42 ++++ services/user/package.json | 43 +++++ services/user/prisma.yml | 5 + .../user/src/controllers/createUser.ctrl.js | 30 +++ .../user/src/controllers/deleteUser.ctrl.js | 15 ++ .../user/src/controllers/getUserById.ctrl.js | 15 ++ .../user/src/controllers/getUsers.ctrl.js | 15 ++ services/user/src/controllers/index.js | 5 + .../src/controllers/updateUserById.ctrl.js | 30 +++ services/user/src/index.js | 9 + services/user/src/repository.js | 3 + services/user/src/routes.js | 47 +++++ services/user/src/server.js | 31 +++ services/user/src/tests/createUser.test.js | 46 +++++ services/user/src/tests/deleteUser.test.js | 36 ++++ services/user/src/tests/getUserById.test.js | 48 +++++ services/user/src/tests/getUsers.test.js | 42 ++++ .../user/src/tests/updateUserById.test.js | 67 +++++++ services/user/wait-for-it.sh | 182 ++++++++++++++++++ specification/user/datamodel.prisma | 2 +- 31 files changed, 829 insertions(+), 2 deletions(-) create mode 100644 services/user/.dockerignore create mode 100644 services/user/.env.default create mode 100644 services/user/.eslintignore create mode 100644 services/user/.eslintrc.json create mode 100644 services/user/.gitignore create mode 100644 services/user/Dockerfile create mode 100644 services/user/README.md create mode 100644 services/user/datamodel.prisma create mode 100644 services/user/docker-compose.hot-reload.yaml create mode 100644 services/user/docker-compose.local.yaml create mode 100644 services/user/docker-compose.yaml create mode 100644 services/user/package.json create mode 100644 services/user/prisma.yml create mode 100644 services/user/src/controllers/createUser.ctrl.js create mode 100644 services/user/src/controllers/deleteUser.ctrl.js create mode 100644 services/user/src/controllers/getUserById.ctrl.js create mode 100644 services/user/src/controllers/getUsers.ctrl.js create mode 100644 services/user/src/controllers/index.js create mode 100644 services/user/src/controllers/updateUserById.ctrl.js create mode 100644 services/user/src/index.js create mode 100644 services/user/src/repository.js create mode 100644 services/user/src/routes.js create mode 100644 services/user/src/server.js create mode 100644 services/user/src/tests/createUser.test.js create mode 100644 services/user/src/tests/deleteUser.test.js create mode 100644 services/user/src/tests/getUserById.test.js create mode 100644 services/user/src/tests/getUsers.test.js create mode 100644 services/user/src/tests/updateUserById.test.js create mode 100755 services/user/wait-for-it.sh diff --git a/generator/generators/app/usedPorts.json b/generator/generators/app/usedPorts.json index c38d411..4ca5238 100644 --- a/generator/generators/app/usedPorts.json +++ b/generator/generators/app/usedPorts.json @@ -1 +1 @@ -{"used":[3500,3501,3502,3503,3504,3505]} \ No newline at end of file +{"used":[3500,3501,3502,3503,3504,3505,3506]} \ No newline at end of file diff --git a/services/user/.dockerignore b/services/user/.dockerignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/services/user/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/services/user/.env.default b/services/user/.env.default new file mode 100644 index 0000000..5a3fcb0 --- /dev/null +++ b/services/user/.env.default @@ -0,0 +1,11 @@ +SERVER_PORT=3500 + +PRISMA_MANAGEMENT_API_SECRET=wsflk897dh%£ + +LOGGING_DRIVER=json-file + +DB_HOST= +DB_PORT= +DB_NAME= +DB_USER= +DB_PASSWORD= \ No newline at end of file diff --git a/services/user/.eslintignore b/services/user/.eslintignore new file mode 100644 index 0000000..dc9b237 --- /dev/null +++ b/services/user/.eslintignore @@ -0,0 +1 @@ +generated \ No newline at end of file diff --git a/services/user/.eslintrc.json b/services/user/.eslintrc.json new file mode 100644 index 0000000..4389d94 --- /dev/null +++ b/services/user/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "env": { + "es6": true, + "node": true + }, + "extends": [ + "airbnb-base" + ], + "rules": {} +} \ No newline at end of file diff --git a/services/user/.gitignore b/services/user/.gitignore new file mode 100644 index 0000000..fa5dfe1 --- /dev/null +++ b/services/user/.gitignore @@ -0,0 +1,3 @@ +.env +node_modules +generated \ No newline at end of file diff --git a/services/user/Dockerfile b/services/user/Dockerfile new file mode 100644 index 0000000..33b964b --- /dev/null +++ b/services/user/Dockerfile @@ -0,0 +1,8 @@ +FROM node:13 +RUN curl -o- -L https://yarnpkg.com/install.sh | bash +RUN yarn global add prisma + +WORKDIR /app +COPY package.json yarn.lock ./ +RUN yarn install +COPY . . \ No newline at end of file diff --git a/services/user/README.md b/services/user/README.md new file mode 100644 index 0000000..cc6b697 --- /dev/null +++ b/services/user/README.md @@ -0,0 +1,24 @@ +# User + +Generated Express.js microservice using our [generator](../../generator). + +To run this service use the [launcher script](../launcher.sh) as described in the [readme](../README.md). + +# Description + +Service to manage users + +# Install + +* Run `yarn install` +* Install prisma CLI: https://v1.prisma.io/docs/1.34/prisma-cli-and-configuration/using-the-prisma-cli-alx4/ +* Run `prisma generate` + +These steps are necessary to have autocompletion. +Now you can start coding but make sure to not edit code that will be overriden by the generator. In case you want to do any change in the service contracts, you need to do it through the [specification](../specification). + +# Documentation + +A generated swagger documentation can be found at the route `/documentation` through the api-gateway. + +You can also find the postman collection file under [services/documentation/postman/user-postman.json](../documentation/postman/user-postman.json). \ No newline at end of file diff --git a/services/user/datamodel.prisma b/services/user/datamodel.prisma new file mode 100644 index 0000000..0f45c78 --- /dev/null +++ b/services/user/datamodel.prisma @@ -0,0 +1,9 @@ +type User { + id: ID! @id + firstName: String! + lastName: String! + email: String! @unique + password: String! + createdAt: DateTime! @createdAt + updatedAt: DateTime! @updatedAt +} diff --git a/services/user/docker-compose.hot-reload.yaml b/services/user/docker-compose.hot-reload.yaml new file mode 100644 index 0000000..07c9104 --- /dev/null +++ b/services/user/docker-compose.hot-reload.yaml @@ -0,0 +1,5 @@ +version: '3' +services: + app: + volumes: + - ./src:/app/src \ No newline at end of file diff --git a/services/user/docker-compose.local.yaml b/services/user/docker-compose.local.yaml new file mode 100644 index 0000000..bb7aac2 --- /dev/null +++ b/services/user/docker-compose.local.yaml @@ -0,0 +1,44 @@ +version: '3' +services: + app: + ports: + - '${SERVER_PORT:-3506}:3500' + networks: + - kafka_broker + + prisma: + networks: + - postgres + environment: + PRISMA_CONFIG: | + port: 4466 + managementApiSecret: ${PRISMA_MANAGEMENT_API_SECRET:-wsflk897dh%£} + databases: + default: + connector: postgres + host: postgres + port: 5432 + user: postgres + password: postgres + + postgres: + image: postgres:10.3 + restart: always + networks: + - postgres + environment: + POSTGRES_DB: prisma + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - postgres-${ENV:-dev}:/var/lib/postgresql/data + +networks: + kafka_broker: + external: true + postgres: + driver: bridge + +volumes: + postgres-dev: + postgres-test: \ No newline at end of file diff --git a/services/user/docker-compose.yaml b/services/user/docker-compose.yaml new file mode 100644 index 0000000..f58a8c8 --- /dev/null +++ b/services/user/docker-compose.yaml @@ -0,0 +1,42 @@ +version: "3" +services: + app: + build: . + command: sh -c "./wait-for-it.sh -t 15 prisma:4466 --strict -- prisma generate && prisma deploy && yarn lint-check && yarn ${ENV:-dev}" + volumes: + - /app/node_modules + - /app/generated/prisma-client + networks: + - app + depends_on: + - prisma + logging: + driver: ${LOGGING_DRIVER:-json-file} + environment: + PRISMA_MANAGEMENT_API_SECRET: ${PRISMA_MANAGEMENT_API_SECRET:-wsflk897dh%£} + restart: always + + prisma: + image: prismagraphql/prisma:1.34 + restart: always + networks: + - app + logging: + driver: ${LOGGING_DRIVER:-json-file} + environment: + PRISMA_CONFIG: | + port: 4466 + managementApiSecret: ${PRISMA_MANAGEMENT_API_SECRET:-wsflk897dh%£} + databases: + default: + connector: postgres + host: ${DB_HOST} + port: ${DB_PORT} + database: ${DB_NAME} + schema: public + user: ${DB_USER} + password: ${DB_PASSWORD} + +networks: + app: + driver: bridge diff --git a/services/user/package.json b/services/user/package.json new file mode 100644 index 0000000..3f26771 --- /dev/null +++ b/services/user/package.json @@ -0,0 +1,43 @@ +{ + "name": "user", + "description": "", + "version": "1.0.0", + "license": "MIT", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "dev": "nodemon src/index.js", + "prod": "node src/index.js", + "test": "jest --testTimeout=20000 --runInBand", + "lint-check": "eslint ./ --ext js", + "lint-fix": "eslint ./ --ext js --fix" + }, + "dependencies": { + "body-parser": "^1.18.3", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-routes-versioning": "^1.0.1", + + "prisma-client-lib": "^1.34.10", + "winston": "^3.2.1" + }, + "devDependencies": { + "eslint": "^5.3.0", + "eslint-config-airbnb-base": "^13.1.0", + "eslint-plugin-import": "^2.16.0", + "jest": "^25.2.2", + "nodemon": "^1.18.9", + "supertest": "^4.0.2" + }, + "jest": { + "testEnvironment": "node", + "collectCoverage": true, + "coverageReporters": [ + "text-summary" + ], + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/generated/" + ] + } +} \ No newline at end of file diff --git a/services/user/prisma.yml b/services/user/prisma.yml new file mode 100644 index 0000000..726f34f --- /dev/null +++ b/services/user/prisma.yml @@ -0,0 +1,5 @@ +endpoint: http://prisma:4466 +datamodel: datamodel.prisma +generate: + - generator: javascript-client + output: ./generated/prisma-client/ \ No newline at end of file diff --git a/services/user/src/controllers/createUser.ctrl.js b/services/user/src/controllers/createUser.ctrl.js new file mode 100644 index 0000000..12a465c --- /dev/null +++ b/services/user/src/controllers/createUser.ctrl.js @@ -0,0 +1,30 @@ +const winston = require('winston'); + +/** + * Check if the body of the request contains the good elements + */ +const bodyIsValid = (body) => { + const { firstName, lastName, email, password } = body; + if (!firstName || !lastName || !email || !password) { + return false; + } + return true; +}; + +/** + * Create a user + * @see PUT /users + */ +module.exports.createUser = async (req, res) => { + if (bodyIsValid(req.body)) { + try { + // TODO: + res.status(200).send({}); + } catch (e) { + winston.error(e); + res.status(500).send({ message: e }); + } + } else { + res.status(400).send({ message: 'Invalid body format' }); + } +}; diff --git a/services/user/src/controllers/deleteUser.ctrl.js b/services/user/src/controllers/deleteUser.ctrl.js new file mode 100644 index 0000000..cef2f3d --- /dev/null +++ b/services/user/src/controllers/deleteUser.ctrl.js @@ -0,0 +1,15 @@ +const winston = require('winston'); + +/** + * Delete a user + * @see DELETE /users/:id + */ +module.exports.deleteUser = async (req, res) => { + try { + // TODO: + res.status(200).send({}); + } catch (e) { + winston.error(e); + res.status(500).send({ message: e }); + } +}; diff --git a/services/user/src/controllers/getUserById.ctrl.js b/services/user/src/controllers/getUserById.ctrl.js new file mode 100644 index 0000000..0e97e29 --- /dev/null +++ b/services/user/src/controllers/getUserById.ctrl.js @@ -0,0 +1,15 @@ +const winston = require('winston'); + +/** + * Get a user by id + * @see GET /users/:id + */ +module.exports.getUserById = async (req, res) => { + try { + // TODO: + res.status(200).send({}); + } catch (e) { + winston.error(e); + res.status(500).send({ message: e }); + } +}; diff --git a/services/user/src/controllers/getUsers.ctrl.js b/services/user/src/controllers/getUsers.ctrl.js new file mode 100644 index 0000000..fcf14aa --- /dev/null +++ b/services/user/src/controllers/getUsers.ctrl.js @@ -0,0 +1,15 @@ +const winston = require('winston'); + +/** + * Get users + * @see GET /users + */ +module.exports.getUsers = async (req, res) => { + try { + // TODO: + res.status(200).send({}); + } catch (e) { + winston.error(e); + res.status(500).send({ message: e }); + } +}; diff --git a/services/user/src/controllers/index.js b/services/user/src/controllers/index.js new file mode 100644 index 0000000..2a7dce8 --- /dev/null +++ b/services/user/src/controllers/index.js @@ -0,0 +1,5 @@ +exports.getUsers = require('./getUsers.ctrl').getUsers; +exports.getUserById = require('./getUserById.ctrl').getUserById; +exports.createUser = require('./createUser.ctrl').createUser; +exports.updateUserById = require('./updateUserById.ctrl').updateUserById; +exports.deleteUser = require('./deleteUser.ctrl').deleteUser; diff --git a/services/user/src/controllers/updateUserById.ctrl.js b/services/user/src/controllers/updateUserById.ctrl.js new file mode 100644 index 0000000..f91f7a1 --- /dev/null +++ b/services/user/src/controllers/updateUserById.ctrl.js @@ -0,0 +1,30 @@ +const winston = require('winston'); + +/** + * Check if the body of the request contains the good elements + */ +const bodyIsValid = (body) => { + const { firstName, lastName, email, password } = body; + if (!firstName || !lastName || !email || !password) { + return false; + } + return true; +}; + +/** + * Update a user by id + * @see PUT /users/:id + */ +module.exports.updateUserById = async (req, res) => { + if (bodyIsValid(req.body)) { + try { + // TODO: + res.status(200).send({}); + } catch (e) { + winston.error(e); + res.status(500).send({ message: e }); + } + } else { + res.status(400).send({ message: 'Invalid body format' }); + } +}; diff --git a/services/user/src/index.js b/services/user/src/index.js new file mode 100644 index 0000000..fc72b15 --- /dev/null +++ b/services/user/src/index.js @@ -0,0 +1,9 @@ +const winston = require('winston'); +const app = require('./server'); + +/** + * Start the server + */ +app.listen(3500, () => { + winston.info('Server is running on: http://localhost:3500'); +}); diff --git a/services/user/src/repository.js b/services/user/src/repository.js new file mode 100644 index 0000000..a0d53a7 --- /dev/null +++ b/services/user/src/repository.js @@ -0,0 +1,3 @@ +const { prisma } = require('../generated/prisma-client'); + +// TODO: add your prisma queries here diff --git a/services/user/src/routes.js b/services/user/src/routes.js new file mode 100644 index 0000000..a743814 --- /dev/null +++ b/services/user/src/routes.js @@ -0,0 +1,47 @@ +const express = require('express'); +const routesVersioning = require('express-routes-versioning')(); + +const router = express.Router(); +const ctrl = require('./controllers'); + +/** + * Health of the service + */ +router.get('/health', (req, res) => res.status(200).send({ message: 'ok' })); + +/** + * Get users + */ +router.get('/users', routesVersioning({ + '^1.0.0': ctrl.getUsers, +})); + +/** + * Get a user by id + */ +router.get('/users/:id', routesVersioning({ + '^1.0.0': ctrl.getUserById, +})); + +/** + * Create a user + */ +router.put('/users', routesVersioning({ + '^1.0.0': ctrl.createUser, +})); + +/** + * Update a user by id + */ +router.put('/users/:id', routesVersioning({ + '^1.0.0': ctrl.updateUserById, +})); + +/** + * Delete a user + */ +router.delete('/users/:id', routesVersioning({ + '^1.0.0': ctrl.deleteUser, +})); + +module.exports = router; diff --git a/services/user/src/server.js b/services/user/src/server.js new file mode 100644 index 0000000..a68a341 --- /dev/null +++ b/services/user/src/server.js @@ -0,0 +1,31 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const winston = require('winston'); +const routes = require('./routes'); + +const app = express(); + +/** + * Configure app to use bodyParser + * This will let us get the data from a POST + */ +app.use(bodyParser.urlencoded({ extended: true, limit: '100mb' })); +app.use(bodyParser.json()); + +/** + * Configure winston + */ +winston.add(new winston.transports.Console({ + level: 'info', + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple(), + ), +})); + +/** + * Register our routes + */ +app.use(routes); + +module.exports = app; diff --git a/services/user/src/tests/createUser.test.js b/services/user/src/tests/createUser.test.js new file mode 100644 index 0000000..baacc79 --- /dev/null +++ b/services/user/src/tests/createUser.test.js @@ -0,0 +1,46 @@ +/* eslint-disable no-undef */ +const request = require('supertest'); +const app = require('../server'); +const { prisma } = require('../../generated/prisma-client'); + +const requestBody = { + firstName: 'firstName!', + lastName: 'lastName!', + email: 'email!', + password: 'password!', +}; + + +beforeEach(async (done) => { + await prisma.deleteManyUsers(); + done(); +}); + +describe('Create a user', () => { + it('Nominal case', async (done) => { + const res = await request(app) + .put('/users') + .send(requestBody); + expect(res.statusCode).toEqual(201); + expect(res.body).toHaveProperty('id'); + expect(res.body).toHaveProperty('firstName'); + expect(res.body.firstName).toEqual(requestBody.firstName); + expect(res.body).toHaveProperty('lastName'); + expect(res.body.lastName).toEqual(requestBody.lastName); + expect(res.body).toHaveProperty('email'); + expect(res.body.email).toEqual(requestBody.email); + expect(res.body).toHaveProperty('createdAt'); + expect(res.body).toHaveProperty('updatedAt'); + done(); + }); + it.each( + [{"lastName":"lastName","email":"email","password":"password"},{"firstName":"firstName","email":"email","password":"password"},{"firstName":"firstName","lastName":"lastName","password":"password"},{"firstName":"firstName","lastName":"lastName","email":"email"}] + )('Should return a 400 because a required field is missing', async (data, done) => { + const res = await request(app) + .put('/users') + .send(data); + expect(res.statusCode).toEqual(400); + done(); + }); + +}); diff --git a/services/user/src/tests/deleteUser.test.js b/services/user/src/tests/deleteUser.test.js new file mode 100644 index 0000000..7b0696b --- /dev/null +++ b/services/user/src/tests/deleteUser.test.js @@ -0,0 +1,36 @@ +/* eslint-disable no-undef */ +const request = require('supertest'); +const app = require('../server'); +const { prisma } = require('../../generated/prisma-client'); + +const sampleOk = { + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + password: 'password', +}; + +const notPresentId = '000000'; +let presentId; + +beforeEach(async (done) => { + await prisma.deleteManyUsers(); + const data = await prisma.createUser(sampleOk); + presentId = data.id; + done(); +}); + +describe('Delete a user', () => { + it('Nominal case', async (done) => { + const res = await request(app) + .delete(`/users/${presentId}`); + expect(res.statusCode).toEqual(204); + done(); + }); + it('404 error case', async (done) => { + const res = await request(app) + .delete(`/users/${notPresentId}`); + expect(res.statusCode).toEqual(404); + done(); + }); +}); diff --git a/services/user/src/tests/getUserById.test.js b/services/user/src/tests/getUserById.test.js new file mode 100644 index 0000000..0f78f71 --- /dev/null +++ b/services/user/src/tests/getUserById.test.js @@ -0,0 +1,48 @@ +/* eslint-disable no-undef */ +const request = require('supertest'); +const app = require('../server'); +const { prisma } = require('../../generated/prisma-client'); + +const sampleOk = { + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + password: 'password', +}; + +const notPresentId = '000000'; +let presentId; + +beforeEach(async (done) => { + await prisma.deleteManyUsers(); + const data = await prisma.createUser(sampleOk); + presentId = data.id; + done(); +}); + +describe('Get a user by id', () => { + it('Nominal case', async (done) => { + const res = await request(app) + .get(`/users/${presentId}`); + expect(res.statusCode).toEqual(200); + expect(res.body).toHaveProperty('id'); + expect(res.body.id).toEqual(presentId); + expect(res.body).toHaveProperty('firstName'); + expect(res.body.firstName).toEqual(sampleOk.firstName); + expect(res.body).toHaveProperty('lastName'); + expect(res.body.lastName).toEqual(sampleOk.lastName); + expect(res.body).toHaveProperty('email'); + expect(res.body.email).toEqual(sampleOk.email); + expect(res.body).toHaveProperty('createdAt'); + expect(res.body.createdAt).toEqual(sampleOk.createdAt); + expect(res.body).toHaveProperty('updatedAt'); + expect(res.body.updatedAt).toEqual(sampleOk.updatedAt); + done(); + }); + it('404 error case', async (done) => { + const res = await request(app) + .get(`/users/${notPresentId}`); + expect(res.statusCode).toEqual(404); + done(); + }); +}); diff --git a/services/user/src/tests/getUsers.test.js b/services/user/src/tests/getUsers.test.js new file mode 100644 index 0000000..46d3e8d --- /dev/null +++ b/services/user/src/tests/getUsers.test.js @@ -0,0 +1,42 @@ +/* eslint-disable no-undef */ +const request = require('supertest'); +const app = require('../server'); +const { prisma } = require('../../generated/prisma-client'); + +const sampleOk = { + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + password: 'password', +}; + + +beforeEach(async (done) => { + await prisma.deleteManyUsers(); + done(); +}); + +describe('Get users', () => { + it('Nominal case', async (done) => { + const data = await prisma.createUser(sampleOk); + const res = await request(app) + .get('/users'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBeTruthy(); + expect(res.body.length).toEqual(1); + [res.body] = res.body; + expect(res.body).toHaveProperty('id'); + expect(res.body.id).toEqual(data.id); + expect(res.body).toHaveProperty('firstName'); + expect(res.body.firstName).toEqual(sampleOk.firstName); + expect(res.body).toHaveProperty('lastName'); + expect(res.body.lastName).toEqual(sampleOk.lastName); + expect(res.body).toHaveProperty('email'); + expect(res.body.email).toEqual(sampleOk.email); + expect(res.body).toHaveProperty('createdAt'); + expect(res.body.createdAt).toEqual(sampleOk.createdAt); + expect(res.body).toHaveProperty('updatedAt'); + expect(res.body.updatedAt).toEqual(sampleOk.updatedAt); + done(); + }); +}); diff --git a/services/user/src/tests/updateUserById.test.js b/services/user/src/tests/updateUserById.test.js new file mode 100644 index 0000000..6d86d0a --- /dev/null +++ b/services/user/src/tests/updateUserById.test.js @@ -0,0 +1,67 @@ +/* eslint-disable no-undef */ +const request = require('supertest'); +const app = require('../server'); +const { prisma } = require('../../generated/prisma-client'); + +const sampleOk = { + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + password: 'password', +}; + +const requestBody = { + firstName: 'firstName!', + lastName: 'lastName!', + email: 'email!', + password: 'password!', +}; + +const notPresentId = '000000'; +let presentId; + +beforeEach(async (done) => { + await prisma.deleteManyUsers(); + const data = await prisma.createUser(sampleOk); + presentId = data.id; + done(); +}); + +describe('Update a user by id', () => { + it('Nominal case', async (done) => { + const res = await request(app) + .put(`/users/${presentId}`) + .send(requestBody); + expect(res.statusCode).toEqual(200); + expect(res.body).toHaveProperty('id'); + expect(res.body.id).toEqual(presentId); + expect(res.body).toHaveProperty('firstName'); + expect(res.body.firstName).toEqual(requestBody.firstName); + expect(res.body).toHaveProperty('lastName'); + expect(res.body.lastName).toEqual(requestBody.lastName); + expect(res.body).toHaveProperty('email'); + expect(res.body.email).toEqual(requestBody.email); + expect(res.body).toHaveProperty('createdAt'); + expect(res.body.createdAt).toEqual(sampleOk.createdAt); + expect(res.body).toHaveProperty('updatedAt'); + expect(res.body.updatedAt).toEqual(sampleOk.updatedAt); + done(); + }); + it.each( + [{"lastName":"lastName","email":"email","password":"password"},{"firstName":"firstName","email":"email","password":"password"},{"firstName":"firstName","lastName":"lastName","password":"password"},{"firstName":"firstName","lastName":"lastName","email":"email"}] + )('Should return a 400 because a required field is missing', async (data, done) => { + const res = await request(app) + .put(`/users/${presentId}`) + .send(data); + expect(res.statusCode).toEqual(400); + done(); + }); + + it('404 error case', async (done) => { + const res = await request(app) + .put(`/users/${notPresentId}`) + .send(requestBody); + expect(res.statusCode).toEqual(404); + done(); + }); +}); diff --git a/services/user/wait-for-it.sh b/services/user/wait-for-it.sh new file mode 100755 index 0000000..5e8679e --- /dev/null +++ b/services/user/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi diff --git a/specification/user/datamodel.prisma b/specification/user/datamodel.prisma index d779a70..0f45c78 100644 --- a/specification/user/datamodel.prisma +++ b/specification/user/datamodel.prisma @@ -2,7 +2,7 @@ type User { id: ID! @id firstName: String! lastName: String! - email: String + email: String! @unique password: String! createdAt: DateTime! @createdAt updatedAt: DateTime! @updatedAt