├── .gitignore
├── Procfile
├── README.md
├── controllers
├── login.js
├── notes.js
└── users.js
├── images
└── logo.png
├── index.js
├── middleware
├── handleErrors.js
├── notFound.js
└── userExtractor.js
├── models
├── Note.js
└── User.js
├── mongo.js
├── package.json
├── requests
├── create_user.rest
├── delete_note.rest
├── get_all_notes.rest
├── get_all_users.rest
├── login_user.rest
├── post_note.rest
└── put_note.rest
├── tests
├── average.test.js
├── helpers.js
├── notes.test.js
├── palindrome.test.js
├── suma_test.js
└── user.test.js
└── utils
└── for_testing.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm start
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Backend de FullStack Bootcamp
2 |
3 | **Curso FullStack Bootcamp JavaScript gratuito:** https://www.youtube.com/watch?v=wTpuKOhGfJE&list=PLV8x_i1fqBw0Kn_fBIZTa3wS_VZAqddX7
4 |
5 | **Código en master: Código de la clase de MongoDB**
6 |
7 | Otras ramas con código:
8 | - [Código de la clase de Node y Express](https://github.com/midudev/notes-api/tree/clase-node-express)
9 | - [Código de la clase de MongoDB](https://github.com/midudev/notes-api/tree/clase-mongodb)
--------------------------------------------------------------------------------
/controllers/login.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 | const bcrypt = require('bcrypt')
3 | const loginRouter = require('express').Router()
4 | const User = require('../models/User')
5 |
6 | loginRouter.post('/', async (request, response) => {
7 | const { body } = request
8 | const { username, password } = body
9 |
10 | const user = await User.findOne({ username })
11 |
12 | const passwordCorrect = user === null
13 | ? false
14 | : await bcrypt.compare(password, user.passwordHash)
15 |
16 | if (!(user && passwordCorrect)) {
17 | response.status(401).json({
18 | error: 'invalid user or password'
19 | })
20 | }
21 |
22 | const userForToken = {
23 | id: user._id,
24 | username: user.username
25 | }
26 |
27 | const token = jwt.sign(
28 | userForToken,
29 | process.env.SECRET,
30 | {
31 | expiresIn: 60 * 60 * 24 * 7
32 | }
33 | )
34 |
35 | response.send({
36 | name: user.name,
37 | username: user.username,
38 | token
39 | })
40 | })
41 |
42 | module.exports = loginRouter
43 |
--------------------------------------------------------------------------------
/controllers/notes.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/notes-api/5bf27516c8dca829bfb30389f55adb2d931421d4/controllers/notes.js
--------------------------------------------------------------------------------
/controllers/users.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require('bcrypt')
2 | const usersRouter = require('express').Router()
3 | const User = require('../models/User')
4 |
5 | usersRouter.get('/', async (request, response) => {
6 | const users = await User.find({}).populate('notes', {
7 | content: 1,
8 | date: 1
9 | })
10 | response.json(users)
11 | })
12 |
13 | usersRouter.post('/', async (request, response) => {
14 | const { body } = request
15 | const { username, name, password } = body
16 |
17 | const saltRounds = 10
18 | const passwordHash = await bcrypt.hash(password, saltRounds)
19 |
20 | const user = new User({
21 | username,
22 | name,
23 | passwordHash
24 | })
25 |
26 | const savedUser = await user.save()
27 |
28 | response.status(201).json(savedUser)
29 | })
30 |
31 | module.exports = usersRouter
32 |
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/notes-api/5bf27516c8dca829bfb30389f55adb2d931421d4/images/logo.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 | require('./mongo')
3 |
4 | const Sentry = require('@sentry/node')
5 | const Tracing = require('@sentry/tracing')
6 | const express = require('express')
7 | const app = express()
8 | const cors = require('cors')
9 | const User = require('./models/User')
10 | const Note = require('./models/Note')
11 |
12 | const notFound = require('./middleware/notFound.js')
13 | const handleErrors = require('./middleware/handleErrors.js')
14 | const userExtractor = require('./middleware/userExtractor')
15 |
16 | const usersRouter = require('./controllers/users')
17 | const loginRouter = require('./controllers/login')
18 |
19 | app.use(cors())
20 | app.use(express.json())
21 | app.use('/images', express.static('images'))
22 |
23 | Sentry.init({
24 | dsn: 'https://ac034ebd99274911a8234148642e044c@o537348.ingest.sentry.io/5655435',
25 | integrations: [
26 | // enable HTTP calls tracing
27 | new Sentry.Integrations.Http({ tracing: true }),
28 | // enable Express.js middleware tracing
29 | new Tracing.Integrations.Express({ app })
30 | ],
31 |
32 | // We recommend adjusting this value in production, or using tracesSampler
33 | // for finer control
34 | tracesSampleRate: 1.0
35 | })
36 |
37 | // RequestHandler creates a separate execution context using domains, so that every
38 | // transaction/span/breadcrumb is attached to its own Hub instance
39 | app.use(Sentry.Handlers.requestHandler())
40 | // TracingHandler creates a trace for every incoming request
41 | app.use(Sentry.Handlers.tracingHandler())
42 |
43 | app.get('/', (request, response) => {
44 | console.log(request.ip)
45 | console.log(request.ips)
46 | console.log(request.originalUrl)
47 | response.send('
Hello World!
')
48 | })
49 |
50 | app.get('/api/notes', async (request, response) => {
51 | const notes = await Note.find({}).populate('user', {
52 | username: 1,
53 | name: 1
54 | })
55 | response.json(notes)
56 | })
57 |
58 | app.get('/api/notes/:id', (request, response, next) => {
59 | const { id } = request.params
60 |
61 | Note.findById(id)
62 | .then(note => {
63 | if (note) return response.json(note)
64 | response.status(404).end()
65 | })
66 | .catch(err => next(err))
67 | })
68 |
69 | app.put('/api/notes/:id', userExtractor, (request, response, next) => {
70 | const { id } = request.params
71 | const note = request.body
72 |
73 | const newNoteInfo = {
74 | content: note.content,
75 | important: note.important
76 | }
77 |
78 | Note.findByIdAndUpdate(id, newNoteInfo, { new: true })
79 | .then(result => {
80 | response.json(result)
81 | })
82 | .catch(next)
83 | })
84 |
85 | app.delete('/api/notes/:id', userExtractor, async (request, response, next) => {
86 | const { id } = request.params
87 | // const note = await Note.findById(id)
88 | // if (!note) return response.sendStatus(404)
89 |
90 | const res = await Note.findByIdAndDelete(id)
91 | if (res === null) return response.sendStatus(404)
92 |
93 | response.status(204).end()
94 | })
95 |
96 | app.post('/api/notes', userExtractor, async (request, response, next) => {
97 | const {
98 | content,
99 | important = false
100 | } = request.body
101 |
102 | // sacar userId de request
103 | const { userId } = request
104 |
105 | const user = await User.findById(userId)
106 |
107 | if (!content) {
108 | return response.status(400).json({
109 | error: 'required "content" field is missing'
110 | })
111 | }
112 |
113 | const newNote = new Note({
114 | content,
115 | date: new Date(),
116 | important,
117 | user: user._id
118 | })
119 |
120 | // newNote.save().then(savedNote => {
121 | // response.json(savedNote)
122 | // }).catch(err => next(err))
123 |
124 | try {
125 | const savedNote = await newNote.save()
126 |
127 | user.notes = user.notes.concat(savedNote._id)
128 | await user.save()
129 |
130 | response.json(savedNote)
131 | } catch (error) {
132 | next(error)
133 | }
134 | })
135 |
136 | app.use('/api/users', usersRouter)
137 | app.use('/api/login', loginRouter)
138 |
139 | app.use(notFound)
140 |
141 | app.use(Sentry.Handlers.errorHandler())
142 | app.use(handleErrors)
143 |
144 | const PORT = process.env.PORT || 3001
145 | const server = app.listen(PORT, () => {
146 | console.log(`Server running on port ${PORT}`)
147 | })
148 |
149 | module.exports = { app, server }
150 |
--------------------------------------------------------------------------------
/middleware/handleErrors.js:
--------------------------------------------------------------------------------
1 | const ERROR_HANDLERS = {
2 | CastError: res =>
3 | res.status(400).send({ error: 'id used is malformed' }),
4 |
5 | ValidationError: (res, { message }) =>
6 | res.status(409).send({ error: message }),
7 |
8 | JsonWebTokenError: (res) =>
9 | res.status(401).json({ error: 'token missing or invalid' }),
10 |
11 | TokenExpirerError: res =>
12 | res.status(401).json({ error: 'token expired' }),
13 |
14 | defaultError: (res, error) => {
15 | console.error(error.name)
16 | res.status(500).end()
17 | }
18 | }
19 |
20 | module.exports = (error, request, response, next) => {
21 | const handler =
22 | ERROR_HANDLERS[error.name] || ERROR_HANDLERS.defaultError
23 |
24 | handler(response, error)
25 | }
26 |
--------------------------------------------------------------------------------
/middleware/notFound.js:
--------------------------------------------------------------------------------
1 | module.exports = (request, response, next) => {
2 | response.status(404).end()
3 | }
4 |
--------------------------------------------------------------------------------
/middleware/userExtractor.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 |
3 | module.exports = (request, response, next) => {
4 | const authorization = request.get('authorization')
5 | let token = ''
6 |
7 | if (authorization && authorization.toLowerCase().startsWith('bearer')) {
8 | token = authorization.substring(7)
9 | }
10 |
11 | const decodedToken = jwt.verify(token, process.env.SECRET)
12 |
13 | if (!token || !decodedToken.id) {
14 | return response.status(401).json({ error: 'token missing or invalid' })
15 | }
16 |
17 | const { id: userId } = decodedToken
18 |
19 | request.userId = userId
20 |
21 | next()
22 | }
23 |
--------------------------------------------------------------------------------
/models/Note.js:
--------------------------------------------------------------------------------
1 | const { Schema, model } = require('mongoose')
2 |
3 | const noteSchema = new Schema({
4 | content: String,
5 | date: Date,
6 | important: Boolean,
7 | user: {
8 | type: Schema.Types.ObjectId,
9 | ref: 'User'
10 | }
11 | })
12 |
13 | noteSchema.set('toJSON', {
14 | transform: (document, returnedObject) => {
15 | returnedObject.id = returnedObject._id
16 | delete returnedObject._id
17 | delete returnedObject.__v
18 | }
19 | })
20 |
21 | const Note = model('Note', noteSchema)
22 |
23 | module.exports = Note
24 |
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | const { Schema, model } = require('mongoose')
2 | const uniqueValidator = require('mongoose-unique-validator')
3 |
4 | const userSchema = new Schema({
5 | username: {
6 | type: String,
7 | unique: true
8 | },
9 | name: String,
10 | passwordHash: String,
11 | notes: [{
12 | type: Schema.Types.ObjectId,
13 | ref: 'Note'
14 | }]
15 | })
16 |
17 | userSchema.set('toJSON', {
18 | transform: (document, returnedObject) => {
19 | returnedObject.id = returnedObject._id
20 | delete returnedObject._id
21 | delete returnedObject.__v
22 |
23 | delete returnedObject.passwordHash
24 | }
25 | })
26 |
27 | userSchema.plugin(uniqueValidator)
28 |
29 | const User = model('User', userSchema)
30 |
31 | module.exports = User
32 |
--------------------------------------------------------------------------------
/mongo.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose')
2 |
3 | const { MONGO_DB_URI, MONGO_DB_URI_TEST, NODE_ENV } = process.env
4 |
5 | const connectionString = NODE_ENV === 'test'
6 | ? MONGO_DB_URI_TEST
7 | : MONGO_DB_URI
8 |
9 | // comment for students puposes
10 | if (!connectionString) {
11 | console.error('Recuerda que tienes que tener un archivo .env con las variables de entorno definidas y el MONGO_DB_URI que servirá de connection string. En las clases usamos MongoDB Atlas pero puedes usar cualquier base de datos de MongoDB (local incluso).')
12 | }
13 |
14 | // conexión a mongodb
15 | mongoose.connect(connectionString, {
16 | useNewUrlParser: true,
17 | useUnifiedTopology: true,
18 | useFindAndModify: false,
19 | useCreateIndex: true
20 | })
21 | .then(() => {
22 | console.log('Database connected')
23 | }).catch(err => {
24 | console.error(err)
25 | })
26 |
27 | process.on('uncaughtException', error => {
28 | console.error(error)
29 | mongoose.disconnect()
30 | })
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "part3",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "NODE_ENV=development nodemon index.js",
8 | "lint": "npm run lint",
9 | "start": "NODE_ENV=production node index.js",
10 | "test": "NODE_ENV=test PORT=1234 jest --verbose tests/user.test.js",
11 | "test:watch": "npm run test -- --watch"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "@sentry/node": "6.2.0",
18 | "@sentry/tracing": "6.2.0",
19 | "bcrypt": "5.0.1",
20 | "cors": "2.8.5",
21 | "cross-env": "7.0.3",
22 | "dotenv": "8.2.0",
23 | "express": "4.17.1",
24 | "jsonwebtoken": "^8.5.1",
25 | "mongoose": "5.11.18",
26 | "mongoose-unique-validator": "2.0.3"
27 | },
28 | "devDependencies": {
29 | "eslint": "7.20.0",
30 | "jest": "26.6.3",
31 | "nodemon": "2.0.7",
32 | "standard": "16.0.3",
33 | "supertest": "6.1.3"
34 | },
35 | "eslintConfig": {
36 | "extends": "./node_modules/standard/eslintrc.json",
37 | "env": {
38 | "jest": true
39 | }
40 | },
41 | "jest": {
42 | "testEnvironment": "node"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/requests/create_user.rest:
--------------------------------------------------------------------------------
1 | POST http://localhost:3001/api/users
2 | Content-Type: application/json
3 |
4 | {
5 | "username": "midudev",
6 | "name": "Miguel",
7 | "password": "lamidupassword"
8 | }
--------------------------------------------------------------------------------
/requests/delete_note.rest:
--------------------------------------------------------------------------------
1 | DELETE http://localhost:3001/api/notes/603bf5fa1099e06f419b3984
--------------------------------------------------------------------------------
/requests/get_all_notes.rest:
--------------------------------------------------------------------------------
1 | GET http://localhost:3001/api/notes
--------------------------------------------------------------------------------
/requests/get_all_users.rest:
--------------------------------------------------------------------------------
1 | GET http://localhost:3001/api/users
--------------------------------------------------------------------------------
/requests/login_user.rest:
--------------------------------------------------------------------------------
1 | POST http://localhost:3001/api/login
2 | Content-Type: application/json
3 |
4 | {
5 | "username": "midudev",
6 | "password": "lamidupassword"
7 | }
--------------------------------------------------------------------------------
/requests/post_note.rest:
--------------------------------------------------------------------------------
1 | POST http://localhost:3001/api/notes
2 | Content-Type: application/json
3 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwNTc5ZjY3MGUzMTBlODRkYWQxZjcyNiIsInVzZXJuYW1lIjoibWlkdWRldiIsImlhdCI6MTYxNjM1Nzc5MiwiZXhwIjoxNjE2OTYyNTkyfQ.k8Zor0K8hYzkDVfow66_MhRbmfA0ejbcW7AG3pqeQjs
4 |
5 | {
6 | "content": "Utilizando el middleware del user extractor",
7 | "important": true
8 | }
--------------------------------------------------------------------------------
/requests/put_note.rest:
--------------------------------------------------------------------------------
1 | PUT http://localhost:3001/api/notes/603bfb496efbcb72db14c4da
2 | Content-Type: application/json
3 |
4 | {
5 | "content": "Suscríbete con PRIME que es gratishh",
6 | "important": true
7 | }
--------------------------------------------------------------------------------
/tests/average.test.js:
--------------------------------------------------------------------------------
1 | const { average } = require('../utils/for_testing')
2 |
3 | describe.skip('average', () => {
4 | test('of one value is the value itself', () => {
5 | expect(average([1])).toBe(1)
6 | })
7 |
8 | test('of many is calculated correctly', () => {
9 | expect(average([1, 2, 3, 4, 5, 6])).toBe(3.5)
10 | })
11 |
12 | test('of empty array is zero', () => {
13 | expect(average([])).toBe(0)
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/tests/helpers.js:
--------------------------------------------------------------------------------
1 | const { app } = require('../index')
2 | const supertest = require('supertest')
3 | const User = require('../models/User')
4 |
5 | const api = supertest(app)
6 |
7 | const initialNotes = [
8 | {
9 | content: 'Aprendiendo FullStack JS con midudev',
10 | important: true,
11 | date: new Date()
12 | },
13 | {
14 | content: 'Sígueme en https://midu.tube',
15 | important: true,
16 | date: new Date()
17 | },
18 | {
19 | content: 'Gracias al chat por vuestra ayuda! :D',
20 | important: true,
21 | date: new Date()
22 | }
23 | ]
24 |
25 | const getAllContentFromNotes = async () => {
26 | const response = await api.get('/api/notes')
27 | return {
28 | contents: response.body.map(note => note.content),
29 | response
30 | }
31 | }
32 |
33 | const getUsers = async () => {
34 | const usersDB = await User.find({})
35 | return usersDB.map(user => user.toJSON())
36 | }
37 |
38 | module.exports = {
39 | api,
40 | initialNotes,
41 | getAllContentFromNotes,
42 | getUsers
43 | }
44 |
--------------------------------------------------------------------------------
/tests/notes.test.js:
--------------------------------------------------------------------------------
1 | const moongose = require('mongoose')
2 |
3 | const { server } = require('../index')
4 | const Note = require('../models/Note')
5 | const {
6 | api,
7 | initialNotes,
8 | getAllContentFromNotes
9 | } = require('./helpers')
10 |
11 | beforeEach(async () => {
12 | await Note.deleteMany({})
13 |
14 | // sequential
15 | for (const note of initialNotes) {
16 | const noteObject = new Note(note)
17 | await noteObject.save()
18 | }
19 | })
20 |
21 | describe('GET all notes', () => {
22 | test('notes are returned as json', async () => {
23 | await api
24 | .get('/api/notes')
25 | .expect(200)
26 | .expect('Content-Type', /application\/json/)
27 | })
28 |
29 | test('there are two notes', async () => {
30 | const response = await api.get('/api/notes')
31 | expect(response.body).toHaveLength(initialNotes.length)
32 | })
33 |
34 | test('the first note is about midudev', async () => {
35 | const {
36 | contents
37 | } = await getAllContentFromNotes()
38 |
39 | expect(contents).toContain('Aprendiendo FullStack JS con midudev')
40 | })
41 | })
42 |
43 | describe('create a note', () => {
44 | test('is possible with a valid note', async () => {
45 | const newNote = {
46 | content: 'Proximamente async/await',
47 | important: true
48 | }
49 |
50 | await api
51 | .post('/api/notes')
52 | .send(newNote)
53 | .expect(200)
54 | .expect('Content-Type', /application\/json/)
55 |
56 | const { contents, response } = await getAllContentFromNotes()
57 |
58 | expect(response.body).toHaveLength(initialNotes.length + 1)
59 | expect(contents).toContain(newNote.content)
60 | })
61 |
62 | test('is not possible with an invalid note', async () => {
63 | const newNote = {
64 | important: true
65 | }
66 |
67 | await api
68 | .post('/api/notes')
69 | .send(newNote)
70 | .expect(400)
71 |
72 | const response = await api.get('/api/notes')
73 |
74 | expect(response.body).toHaveLength(initialNotes.length)
75 | })
76 | })
77 |
78 | test('a note can be deleted', async () => {
79 | const { response: firstResponse } = await getAllContentFromNotes()
80 | const { body: notes } = firstResponse
81 | const noteToDelete = notes[0]
82 |
83 | await api
84 | .delete(`/api/notes/${noteToDelete.id}`)
85 | .expect(204)
86 |
87 | const { contents, response: secondResponse } = await getAllContentFromNotes()
88 |
89 | expect(secondResponse.body).toHaveLength(initialNotes.length - 1)
90 |
91 | expect(contents).not.toContain(noteToDelete.content)
92 | })
93 |
94 | test('a note that has an invalid id can not be deleted', async () => {
95 | await api
96 | .delete('/api/notes/1234')
97 | .expect(400)
98 |
99 | const { response } = await getAllContentFromNotes()
100 |
101 | expect(response.body).toHaveLength(initialNotes.length)
102 | })
103 |
104 | test('a note that has a valid id but do not exist can not be deleted', async () => {
105 | const validObjectIdThatDoNotExist = '60451827152dc22ad768f442'
106 | await api
107 | .delete(`/api/notes/${validObjectIdThatDoNotExist}`)
108 | .expect(404)
109 |
110 | const { response } = await getAllContentFromNotes()
111 |
112 | expect(response.body).toHaveLength(initialNotes.length)
113 | })
114 |
115 | afterAll(() => {
116 | moongose.connection.close()
117 | server.close()
118 | })
119 |
--------------------------------------------------------------------------------
/tests/palindrome.test.js:
--------------------------------------------------------------------------------
1 | const { palindrome } = require('../utils/for_testing')
2 |
3 | test.skip('palindrome of midudev', () => {
4 | const result = palindrome('midudev')
5 |
6 | expect(result).toBe('vedudim')
7 | })
8 |
9 | test.skip('palindrome of empty string', () => {
10 | const result = palindrome('')
11 |
12 | expect(result).toBe('')
13 | })
14 |
15 | test.skip('palindrome of undefined', () => {
16 | const result = palindrome()
17 |
18 | expect(result).toBeUndefined()
19 | })
20 |
--------------------------------------------------------------------------------
/tests/suma_test.js:
--------------------------------------------------------------------------------
1 | const suma = (a, b) => {
2 | return a + b
3 | }
4 |
5 | const checks = [
6 | { a: 0, b: 0, result: 0 },
7 | { a: 1, b: 3, result: 4 },
8 | { a: -3, b: 3, result: 0 }
9 | ]
10 |
11 | checks.forEach(check => {
12 | const { a, b, result } = check
13 |
14 | console.assert(
15 | suma(a, b) === result,
16 | `Suma of ${a} and ${b} expected to be ${result}`
17 | )
18 | })
19 |
20 | console.log(`${checks.length} checks performed...`)
21 |
--------------------------------------------------------------------------------
/tests/user.test.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require('bcrypt')
2 | const User = require('../models/User')
3 | const { api, getUsers } = require('./helpers')
4 | const moongose = require('mongoose')
5 | const { server } = require('../index')
6 |
7 | describe('creating a new user', () => {
8 | beforeEach(async () => {
9 | await User.deleteMany({})
10 |
11 | const passwordHash = await bcrypt.hash('pswd', 10)
12 | const user = new User({ username: 'miduroot', passwordHash })
13 |
14 | await user.save()
15 | })
16 |
17 | test('works as expected creating a fresh username', async () => {
18 | const usersAtStart = await getUsers()
19 |
20 | const newUser = {
21 | username: 'midudev',
22 | name: 'Miguel',
23 | password: 'tw1tch'
24 | }
25 |
26 | await api
27 | .post('/api/users')
28 | .send(newUser)
29 | .expect(201)
30 | .expect('Content-Type', /application\/json/)
31 |
32 | const usersAtEnd = await getUsers()
33 |
34 | expect(usersAtEnd).toHaveLength(usersAtStart.length + 1)
35 |
36 | const usernames = usersAtEnd.map(u => u.username)
37 | expect(usernames).toContain(newUser.username)
38 | })
39 |
40 | test('creation fails with proper statuscode and message if username is already taken', async () => {
41 | const usersAtStart = await getUsers()
42 |
43 | const newUser = {
44 | username: 'miduroot',
45 | name: 'Miguel',
46 | password: 'midutest'
47 | }
48 |
49 | const result = await api
50 | .post('/api/users')
51 | .send(newUser)
52 | .expect(409)
53 | .expect('Content-Type', /application\/json/)
54 |
55 | console.log(result.body)
56 |
57 | expect(result.body.error).toContain('expected `username` to be unique')
58 |
59 | const usersAtEnd = await getUsers()
60 | expect(usersAtEnd).toHaveLength(usersAtStart.length)
61 | })
62 |
63 | afterAll(() => {
64 | moongose.connection.close()
65 | server.close()
66 | })
67 | })
68 |
--------------------------------------------------------------------------------
/utils/for_testing.js:
--------------------------------------------------------------------------------
1 | const palindrome = (string) => {
2 | if (typeof string === 'undefined') return
3 |
4 | return string
5 | .split('')
6 | .reverse()
7 | .join('')
8 | }
9 |
10 | const average = array => {
11 | if (array.length === 0) return 0
12 |
13 | let sum = 0
14 | array.forEach(num => { sum += num })
15 | return sum / array.length
16 | }
17 |
18 | module.exports = {
19 | palindrome,
20 | average
21 | }
22 |
--------------------------------------------------------------------------------