Skip to content

Commit

Permalink
4.23*: bloglist expansion, step11. Add tests and refactoring the code
Browse files Browse the repository at this point in the history
  • Loading branch information
patchamama committed Sep 11, 2023
1 parent fdcd8fa commit 36c6062
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 41 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ npm test
## d. Token authentication

- [Exercises 4.15.-4.23.](https://fullstackopen.com/en/part4/token_authentication#exercises-4-15-4-23)
_Solution details: [4.15](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/5ec001cec3b44f41a111681af2ae785289d76b6d) | [4.16](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/7839750f9aa7d52deaa62b6d8a8eafa46dd98ca1) | [4.17](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/fd871d2de79352ff62c26c6aeec438fe43f7167a) | [4.18](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/530d7c2eab9c8ce3bbfd2220e904290e28f9b262) | [4.19](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/41f1994a2a145dc97b01e4477efb8b689d626c47) | [4.20](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/b2327ebc80681aa98b7e7c0826345872a6fdb647) | [4.21](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/e3a78c91dcde8dc6c82df469c1f0e83494a81c4c) | [4.22]() | [4.23]()_
_Solution details: [4.15](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/5ec001cec3b44f41a111681af2ae785289d76b6d) | [4.16](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/7839750f9aa7d52deaa62b6d8a8eafa46dd98ca1) | [4.17](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/fd871d2de79352ff62c26c6aeec438fe43f7167a) | [4.18](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/530d7c2eab9c8ce3bbfd2220e904290e28f9b262) | [4.19](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/41f1994a2a145dc97b01e4477efb8b689d626c47) | [4.20](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/b2327ebc80681aa98b7e7c0826345872a6fdb647) | [4.21](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/e3a78c91dcde8dc6c82df469c1f0e83494a81c4c) | [4.22](https://github.com/patchamama/fullstackopen-part4-bloglist/commit/fdcd8fa415546323d10ab76394ded1bdc6bcdd4f) | [4.23]()_

# Deploy
2 changes: 1 addition & 1 deletion controllers/blogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ blogsRouter.delete('/:id', async (request, response) => {
if (!request.token || !decodedToken.id) {
return response.status(401).json({ error: 'token missing or invalid' })
}
console.log(decodedToken)
// console.log(decodedToken)

// Check if the user is the creator of the blog
const blog = await Blog.findById(request.params.id)
Expand Down
5 changes: 4 additions & 1 deletion requests/login.rest
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ GET http://localhost:3003/api/blogs
###
POST http://localhost:3003/api/blogs
Content-Type: application/json
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJpZCI6IjY0ZmVkMjkyM2EyMDYzZGIyYzAzYmJjNyIsImlhdCI6MTY5NDQyODYzNiwiZXhwIjoxNjk0NDMyMjM2fQ.Z5GHfVEJgYwC_TVS6eBmWYrG6j5etoNpqg4TUkpEfRU
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJpZCI6IjY0ZmVkMjkyM2EyMDYzZGIyYzAzYmJjNyIsImlhdCI6MTY5NDQzNjA3OSwiZXhwIjoxNjk0NDM5Njc5fQ.2ZC5N-rGD1twdlvQQ9oINHhJePWoKb8lduU0r7UH1xo

{
"title": "Fugas o la ansiedad de sentirse vivo",
Expand All @@ -30,3 +30,6 @@ Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb
###
DELETE http://localhost:3003/api/blogs/64feedea0f268b96ab900f3e
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJpZCI6IjY0ZmVkMjkyM2EyMDYzZGIyYzAzYmJjNyIsImlhdCI6MTY5NDQyODYzNiwiZXhwIjoxNjk0NDMyMjM2fQ.Z5GHfVEJgYwC_TVS6eBmWYrG6j5etoNpqg4TUkpEfRU

###
DELETE http://localhost:3003/api/blogs/64fef25f0b795f2794275998
206 changes: 173 additions & 33 deletions tests/blog_api.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const mongoose = require('mongoose')
const supertest = require('supertest')
const bcrypt = require('bcrypt')
const helper = require('./test_helper')
const app = require('../app')
const api = supertest(app)
const Blog = require('../models/blog')
const User = require('../models/user')

beforeEach(async () => {
await Blog.deleteMany({})
Expand Down Expand Up @@ -43,6 +45,36 @@ describe('when there is initially some blogs saved', () => {
})

describe('viewing a specific blog', () => {
let token // Token of authenticated user
let userId // ID of authenticated user

beforeEach(async () => {
await Blog.deleteMany({})

const blogObjects = helper.initialBlogs.map((blog) => new Blog(blog))
const promiseArray = blogObjects.map((blog) => blog.save())
await Promise.all(promiseArray)

// for (let blog of helper.initialBlogs) {
// let blogObject = new Blog(blog)
// await blogObject.save()
// }

await User.deleteMany({})

const passwordHash = await bcrypt.hash('sekret', 10)
const user = new User({ username: 'root', passwordHash })

await user.save()

const response = await api
.post('/api/login')
.send({ username: 'root', password: 'sekret' })

token = response.body.token
userId = response.body.id
})

test('a valid blog can be added', async () => {
const newBlog = {
title: 'Fugas o la ansiedad de sentirse vivo',
Expand All @@ -53,6 +85,7 @@ describe('viewing a specific blog', () => {

await api
.post('/api/blogs')
.set('Authorization', `bearer ${token}`)
.send(newBlog)
.expect(201)
.expect('Content-Type', /application\/json/)
Expand Down Expand Up @@ -116,52 +149,42 @@ describe('viewing a specific blog', () => {

test('if the title or url are missing from the request data, the backend responds 400 Bad Request', async () => {
// url is missing
let response = await api.post('/api/blogs').send({
title: 'test title',
author: 'A. Pacheco',
likes: 4,
})
let response = await api
.post('/api/blogs')
.set('Authorization', `bearer ${token}`)
.send({
title: 'test title',
author: 'A. Pacheco',
likes: 4,
})
expect(response.status).toBe(400)

// title is missing
response = await api.post('/api/blogs').send({
author: 'A. Pacheco',
likes: 4,
url: 'https://unlibroenmimochila.blogspot.com/2017/12/fugas-o-la-ansiedad-de-sentirse-vivo.html',
})
response = await api
.post('/api/blogs')
.set('Authorization', `bearer ${token}`)
.send({
author: 'A. Pacheco',
likes: 4,
url: 'https://unlibroenmimochila.blogspot.com/2017/12/fugas-o-la-ansiedad-de-sentirse-vivo.html',
})
expect(response.status).toBe(400)

// both are missing (title and url)
response = await api.post('/api/blogs').send({
author: 'A. Pacheco',
likes: 4,
})
response = await api
.post('/api/blogs')
.set('Authorization', `bearer ${token}`)
.send({
author: 'A. Pacheco',
likes: 4,
})
expect(response.status).toBe(400)

const blogsAtEnd = await helper.blogsInDb()
expect(blogsAtEnd).toHaveLength(helper.initialBlogs.length)
// expect(response.body).toHaveLength(initialBlogs.length)
})
})

describe('deletion of a blog', () => {
test('succeeds with status code 204 if id is valid', async () => {
const blogsAtStart = await helper.blogsInDb()
const blogToDelete = blogsAtStart[0]

await api.delete(`/api/blogs/${blogToDelete.id}`).expect(204)

const blogsAtEnd = await helper.blogsInDb()

expect(blogsAtEnd).toHaveLength(helper.initialBlogs.length - 1)

const contents = blogsAtEnd.map((r) => r.title)

expect(contents).not.toContain(blogToDelete.title)
})
})

describe('updating of a blog entry', () => {
test('succeeds with status code 200 with likes update', async () => {
const blogsAtStart = await helper.blogsInDb()
let blogToUpdate = blogsAtStart[0]
Expand Down Expand Up @@ -202,6 +225,123 @@ describe('updating of a blog entry', () => {
const contents2 = blogsAtEnd.map((r) => r.url)
expect(contents2).toContain(blogToUpdate.url)
})

test('should return an error if the user is not authenticated when add a blog', async () => {
const newBlog = {
title: 'Test Blog Post',
author: 'Test Author',
url: 'https://example.com/test-blog',
likes: 5,
user: userId,
}

const response = await api.post('/api/blogs').send(newBlog)

expect(response.status).toBe(401)
expect(response.body.error).toContain('JsonWebTokenError')
})

test('should create a new blog post for an authenticated user', async () => {
const newBlog = {
title: 'Test Blog Post',
author: 'Test Author',
url: 'https://example.com/test-blog',
likes: 5,
user: userId, // Attach the ID of the authenticated user to the blog post
}

const response = await api
.post('/api/blogs')
.set('Authorization', `bearer ${token}`)
.send(newBlog)

expect(response.status).toBe(201)
expect(response.body.title).toEqual('Test Blog Post')
const blogs = await Blog.find({})
expect(blogs).toHaveLength(3)
// expect(blogs[2].title).toEqual('Test Blog Post')
})
})

describe('deletion of a blog', () => {
let token // Token of authenticated user
let userId // ID of authenticated user

beforeEach(async () => {
await Blog.deleteMany({})

const blogObjects = helper.initialBlogs.map((blog) => new Blog(blog))
const promiseArray = blogObjects.map((blog) => blog.save())
await Promise.all(promiseArray)

await User.deleteMany({})

const passwordHash = await bcrypt.hash('sekret', 10)
const user = new User({ username: 'root', passwordHash })

await user.save()

const response = await api
.post('/api/login')
.send({ username: 'root', password: 'sekret' })

token = response.body.token
userId = response.body.id
})

test('delete a blog with not auth user: status code 400', async () => {
const blogsAtStart = await helper.blogsInDb()
const blogToDelete = blogsAtStart[0]

await api.delete(`/api/blogs/${blogToDelete.id}`).expect(401)
})

test('succeeds with status code 204 if id is valid', async () => {
const blog = {
title: 'Test Blog Post',
author: 'Test Author',
url: 'url test',
likes: 2,
user: userId,
}
// let blogObject = new Blog(blog)
// await blogObject.save()

const resp = await api
.post('/api/blogs')
.set('Authorization', `bearer ${token}`)
.send(blog)
expect(resp.status).toBe(201)

await api
.delete(`/api/blogs/${resp.body.id}`)
.set('Authorization', `bearer ${token}`)
.expect(204)

const blogsAtEnd = await helper.blogsInDb()

expect(blogsAtEnd).toHaveLength(helper.initialBlogs.length)

const contents = blogsAtEnd.map((r) => r.title)

expect(contents).not.toContain(blog.title)
})

test('should return an error if the user is not authenticated', async () => {
const blog = new Blog({
title: 'Test Blog Post',
author: 'Test Author',
url: 'https://example.com/test-blog',
likes: 5,
user: userId,
})
await blog.save()

const response = await api.delete(`/api/blogs/${blog.id}`)

expect(response.status).toBe(401)
expect(response.body.error).toContain('JsonWebTokenError')
})
})

test('unknown endpoint in api url', async () => {
Expand Down
15 changes: 10 additions & 5 deletions utils/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ const userExtractor = (request, response, next) => {
process.env.SECRET
)
request.user = decodedToken.username
console.log('request.user:', request.user)
} else {
request.user = null
}
console.log('request.user:', request.user)

next()
}

Expand All @@ -42,14 +43,18 @@ const unknownEndpoint = (request, response) => {
}

const errorHandler = (error, request, response, next) => {
logger.error(error.message)
logger.error('ERROR: ', error.name, error.message)

if (error.name === 'CastError') {
return response.status(400).send({ error: 'malformatted id' })
return response.status(400).send({ error: 'malformatted id (CastError)' })
} else if (error.name === 'ValidationError') {
return response.status(400).json({ error: error.message })
return response
.status(400)
.json({ error: `${error.message} (ValidationError)` })
} else if (error.name === 'JsonWebTokenError') {
return response.status(400).json({ error: error.message })
return response
.status(401)
.json({ error: `${error.message} (JsonWebTokenError)` })
} else if (error.name === 'TokenExpiredError') {
return response.status(401).json({
error: 'token expired',
Expand Down

0 comments on commit 36c6062

Please sign in to comment.