From a74067f101ff3a1d30cf423dae1f38f33282912f Mon Sep 17 00:00:00 2001 From: Veeti Date: Thu, 18 Jul 2024 20:42:50 +0300 Subject: [PATCH] fixed issues with test environment --- .github/workflows/pipeline.yml | 30 ++++++---- {apitesting => api_calls}/blogs.rest | 6 +- {apitesting => api_calls}/testing.rest | 0 {apitesting => api_calls}/users.rest | 0 app.js | 9 +-- controllers/testing.js | 28 +++++++++- jest.config.js | 8 +++ package.json | 67 +++++++++++------------ tests/api/blogs.test.js | 76 ++++++++++---------------- tests/api/login.test.js | 24 +++----- tests/api/test_helper.js | 23 +++++--- tests/api/users.test.js | 57 ++++--------------- utils/config.js | 19 +++++-- 13 files changed, 169 insertions(+), 178 deletions(-) rename {apitesting => api_calls}/blogs.rest (75%) rename {apitesting => api_calls}/testing.rest (100%) rename {apitesting => api_calls}/users.rest (100%) create mode 100644 jest.config.js diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 8c4dd2c..dddc118 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -10,8 +10,12 @@ on: - main env: - COMMIT_MESSAGES: ${{ toJson(github.event.commits.*.message) }} - COMMIT: ${{ toJson(github.event.commits)[0] }} + COMMIT_MESSAGES: ${{ toJson(github.event.commits.*.message) }} + COMMIT: ${{ toJson(github.event.commits)[0] }} + SECRET: secrets.SECRET + MONGODB_URI: secrets.MONGODB_URI + TEST_MONGODB_URI: secrets.TEST_MONGODB_URI + DEV_MONGODB_URI: secrets.DEV_MONGODB_URI jobs: simple_deployment_pipeline: @@ -25,12 +29,14 @@ jobs: - run: npm run eslint && cd frontend && npm run eslint - run: npm run build - run: npm run test + - name: frontend tests + run: cd frontend && npm run test - name: e2e tests uses: cypress-io/github-action@v5 with: - command: cd frontend && npm run cypress - start: npm run start - wait-on: http://localhost:5000 + command: cd frontend && npm run cypress + start: npm run start + wait-on: http://localhost:5000 - name: deployment if: ${{ github.event_name == 'push' }} run: curl https://api.render.com/deploy/srv-${{ secrets.RENDER_SERVICE_ID }}?key=${{ secrets.RENDER_API_KEY }} @@ -54,10 +60,10 @@ jobs: needs: [simple_deployment_pipeline] runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v4 - - name: Bump version and push tag - if: ${{ github.event_name == 'push' && !contains(join(env.COMMIT_MESSAGES, ', '), '#skip')}} - uses: anothrNick/github-tag-action@8c8163ef62cf9c4677c8e800f36270af27930f42 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DEFAULT_BUMP: patch \ No newline at end of file + - uses: actions/checkout@v4 + - name: Bump version and push tag + if: ${{ github.event_name == 'push' && !contains(join(env.COMMIT_MESSAGES, ', '), '#skip')}} + uses: anothrNick/github-tag-action@8c8163ef62cf9c4677c8e800f36270af27930f42 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEFAULT_BUMP: patch diff --git a/apitesting/blogs.rest b/api_calls/blogs.rest similarity index 75% rename from apitesting/blogs.rest rename to api_calls/blogs.rest index 55a75c0..b6a43ca 100644 --- a/apitesting/blogs.rest +++ b/api_calls/blogs.rest @@ -1,7 +1,7 @@ -GET http://localhost:3001/api/blogs +GET http://localhost:3003/api/blogs ### -POST http://localhost:3001/api/blogs +POST http://localhost:3003/api/blogs Content-Type : application/json Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIxIiwiaWQiOiI2M2NhZmJjZmFlNTdmOWE1NGMxNGY2ZWUiLCJpYXQiOjE2NzQyNTQxODUsImV4cCI6MTY3NDI1Nzc4NX0.s_n4ZplhBS-lnbPUafmjEnedOtNDppi2CYxjpE1ZDxY @@ -12,7 +12,7 @@ Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZ "likes": 5 } ### -PUT http://localhost:3001/api/blogs/63c6ca1fb0152ad35a59b7c9 +PUT http://localhost:3003/api/blogs/63c6ca1fb0152ad35a59b7c9 Content-Type: application/json { diff --git a/apitesting/testing.rest b/api_calls/testing.rest similarity index 100% rename from apitesting/testing.rest rename to api_calls/testing.rest diff --git a/apitesting/users.rest b/api_calls/users.rest similarity index 100% rename from apitesting/users.rest rename to api_calls/users.rest diff --git a/app.js b/app.js index a1eff82..51a62c7 100644 --- a/app.js +++ b/app.js @@ -6,11 +6,13 @@ const app = express(); const loginRouter = require('./controllers/login'); const blogsRouter = require('./controllers/blogs'); const userRouter = require('./controllers/users'); +const testingRouter = require('./controllers/testing'); const { tokenExtractor, errorHandler } = require('./utils/middleware'); const config = require('./utils/config'); const logger = require('./utils/logger'); -mongoose.connect(config.MONGODB_URI) +mongoose + .connect(config.MONGODB_URI) .then(() => { logger.info('connected to MongoDB'); }) @@ -26,11 +28,10 @@ app.use('/api/blogs', blogsRouter); app.use('/api/users', userRouter); app.use(express.static('frontend/build')); -if(process.env.NODE_ENV === 'test') { - const testingRouter = require('./controllers/testing'); +if (config.ENV === 'testing') { app.use('/api/testing', testingRouter); } app.use(errorHandler); -module.exports = app; \ No newline at end of file +module.exports = app; diff --git a/controllers/testing.js b/controllers/testing.js index 63eea0e..919ef8f 100644 --- a/controllers/testing.js +++ b/controllers/testing.js @@ -1,12 +1,38 @@ const router = require('express').Router(); +const bcrypt = require('bcrypt'); const Blog = require('../models/blog'); const User = require('../models/user'); +const { initialBlogs, initialUsers } = require('../tests/api/test_helper'); router.post('/reset', async (request, response) => { await Blog.deleteMany({}); await User.deleteMany({}); + await Promise.all( + initialUsers.map(async (user) => { + const newUser = new User({ + username: user.username, + name: user.name, + password: await bcrypt.hash(user.password, 10), + }); + return await newUser.save(); + }) + ); + + const someUser = await User.findOne({ username: 'user1' }); + + const savedBlogs = await Promise.all( + initialBlogs.map((blog) => { + const newBlog = Blog(blog); + newBlog.user = someUser._id; + return newBlog.save(); + }) + ); + + someUser.blogs = savedBlogs.map((blog) => blog._id); + await someUser.save(); + response.status(204).end(); }); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..f3c47cd --- /dev/null +++ b/jest.config.js @@ -0,0 +1,8 @@ +const config = { + testPathIgnorePatterns: ['frontend/*'], + verbose: true, + forceExit: true, + testEnvironment: 'node', +}; + +module.exports = config; diff --git a/package.json b/package.json index a0d0632..47fce34 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,34 @@ { - "name": "bloglist", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start": "NODE_ENV=production node index.js", - "dev": "NODE_ENV=development nodemon index.js", - "build": "cd frontend && npm run build", - "render:deploy": "npm install && cd frontend && npm install && npm run build", - "eslint": "eslint .", - "test": "NODE_ENV=test jest --verbose --runInBand --forceExit", - "start:test": "NODE_ENV=test node index.js" - }, - "author": "", - "license": "ISC", - "dependencies": { - "bcrypt": "^5.1.0", - "cors": "^2.8.5", - "dotenv": "^16.0.3", - "express": "^4.18.2", - "express-async-errors": "^3.1.1", - "jsonwebtoken": "^9.0.0", - "mongoose": "^6.8.3" - }, - "devDependencies": { - "eslint": "^8.32.0", - "eslint-plugin-cypress": "^3.3.0", - "eslint-plugin-react": "^7.34.3", - "jest": "^29.3.1", - "nodemon": "^2.0.20", - "supertest": "^6.3.3" - }, - "jest": { - "testEnvironment": "node" - } + "name": "bloglist", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "NODE_ENV=production node index.js", + "dev": "NODE_ENV=development nodemon index.js", + "build": "cd frontend && npm run build", + "render:deploy": "npm install && cd frontend && npm install && npm run build", + "eslint": "eslint .", + "test": "NODE_ENV=testing jest --verbose --runInBand --forceExit", + "start:test": "NODE_ENV=testing node index.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "bcrypt": "^5.1.0", + "cors": "^2.8.5", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "express-async-errors": "^3.1.1", + "jsonwebtoken": "^9.0.0", + "mongoose": "^6.8.3" + }, + "devDependencies": { + "eslint": "^8.32.0", + "eslint-plugin-cypress": "^3.3.0", + "eslint-plugin-react": "^7.34.3", + "jest": "^29.3.1", + "nodemon": "^2.0.20", + "supertest": "^6.3.3" + } } diff --git a/tests/api/blogs.test.js b/tests/api/blogs.test.js index f8da49c..1380590 100644 --- a/tests/api/blogs.test.js +++ b/tests/api/blogs.test.js @@ -1,43 +1,21 @@ const mongoose = require('mongoose'); -const bcrypt = require('bcrypt'); const app = require('../../app'); const supertest = require('supertest'); +const { initialBlogs, blogsInDb, invalidId } = require('./test_helper'); + const api = supertest(app); -const Blog = require('../../models/blog'); -const User = require('../../models/user'); -const { initialBlogs, initialUsers, blogsInDb, invalidId } = require('./test_helper'); const getToken = async () => { - const token = (await api - .post('/api/login') - .send({ username: 'user1', password: 'salasana' })) - .body.token; + const token = ( + await api + .post('/api/login') + .send({ username: 'user1', password: 'salasana' }) + ).body.token; return token; }; beforeEach(async () => { - await Blog.deleteMany({}); - await User.deleteMany({}); - - await Promise.all(initialUsers.map(async user => { - const newUser = new User({ - username: user.username, - name: user.name, - password: await bcrypt.hash(user.password, 10) - }); - return await newUser.save(); - })); - - const someUser = await User.findOne({ username: 'user1' }); - - const savedBlogs = await Promise.all(initialBlogs.map(blog => { - const newBlog = Blog(blog); - newBlog.user = someUser._id; - return newBlog.save(); - })); - - someUser.blogs = savedBlogs.map(blog => blog._id); - await someUser.save(); + await api.post('/api/testing/reset'); }); describe('GET api/blogs', () => { @@ -69,13 +47,13 @@ describe('POST api/blogs', () => { title: 'uusiblogi', author: 'kirjoittaja', url: 'osoite', - likes: 1 + likes: 1, }; test('response has status 400 when title or url is missing', async () => { const token = await getToken(); - const blogWithMissingProps = { ... newBlog }; + const blogWithMissingProps = { ...newBlog }; delete blogWithMissingProps.title, blogWithMissingProps.url; await api @@ -98,15 +76,21 @@ describe('POST api/blogs', () => { test('saves blog with user property', async () => { const token = await getToken(); - const response = await api.post('/api/blogs').set('authorization', `Bearer ${token}`).send(newBlog); + const response = await api + .post('/api/blogs') + .set('authorization', `Bearer ${token}`) + .send(newBlog); expect(response.body.user).toBeDefined(); }); test('gives property "likes" a default value of 0', async () => { const token = await getToken(); - const blogWith0Likes = { ... newBlog }; + const blogWith0Likes = { ...newBlog }; delete blogWith0Likes.likes; - const response = await api.post('/api/blogs').set('authorization', `Bearer ${token}`).send(blogWith0Likes); + const response = await api + .post('/api/blogs') + .set('authorization', `Bearer ${token}`) + .send(blogWith0Likes); expect(response.body.likes).toBe(0); }); }); @@ -143,16 +127,15 @@ describe('DELETE api/blogs/id', () => { test('returns status code 401 on missing token', async () => { const toDelete = (await blogsInDb())[0].id; - await api - .delete(`/api/blogs/${toDelete}`) - .expect(401); + await api.delete(`/api/blogs/${toDelete}`).expect(401); }); test('returns status code 401 if user is not authorized to delete the blog', async () => { - const wrongUserToken = (await api - .post('/') - .send({ username: 'user2', password: 'salasana' })) - .body.token; + const wrongUserToken = ( + await api + .post('/') + .send({ username: 'user2', password: 'salasana' }) + ).body.token; const toDelete = (await blogsInDb())[0].id; await api .delete(`/api/blogs/${toDelete}`) @@ -170,15 +153,12 @@ describe('PUT api/blogs/id', () => { expect(response.body.likes).toBe(10); }); - test('returns 200 on succesfull update', async () => { + test('returns 200 on successful update', async () => { const toUpdate = (await blogsInDb())[0].id; - await api - .put(`/api/blogs/${toUpdate}`) - .send({ likes: 10 }) - .expect(200); + await api.put(`/api/blogs/${toUpdate}`).send({ likes: 10 }).expect(200); }); }); afterAll(() => { mongoose.connection.close(); -}); \ No newline at end of file +}); diff --git a/tests/api/login.test.js b/tests/api/login.test.js index 60b1a04..bc93101 100644 --- a/tests/api/login.test.js +++ b/tests/api/login.test.js @@ -2,34 +2,26 @@ const mongoose = require('mongoose'); const app = require('../../app'); const supertest = require('supertest'); const api = supertest(app); -const User = require('../../models/user'); -const { initialUsers } = require('./test_helper'); const jwt = require('jsonwebtoken'); -const bcrypt = require('bcrypt'); const config = require('../../utils/config'); beforeEach(async () => { - await User.deleteMany({}); - - await Promise.all(initialUsers.map(async user => { - const newUser = new User({ - username: user.username, - name: user.name, - password: await bcrypt.hash(user.password, 10) - }); - return await newUser.save(); - })); + await api.post('/api/testing/reset'); }); describe('POST /api/login', () => { test('returns valid token when correct credentials are given', async () => { - const response = await api.post('/api/login').send({ username: 'user1', password: 'salasana' }); + const response = await api + .post('/api/login') + .send({ username: 'user1', password: 'salasana' }); expect(response.body.token).toBeDefined(); expect(jwt.verify(response.body.token, config.SECRET).id).toBeDefined(); }); test('returns user information when correct credentials are given', async () => { - const response = await api.post('/api/login').send({ username: 'user1', password: 'salasana' }); + const response = await api + .post('/api/login') + .send({ username: 'user1', password: 'salasana' }); expect(response.body.username).toBe('user1'); }); @@ -43,4 +35,4 @@ describe('POST /api/login', () => { afterAll(() => { mongoose.connection.close(); -}); \ No newline at end of file +}); diff --git a/tests/api/test_helper.js b/tests/api/test_helper.js index cab1648..d8c737f 100644 --- a/tests/api/test_helper.js +++ b/tests/api/test_helper.js @@ -6,38 +6,37 @@ const initialBlogs = [ title: 'ErkinBlogi', author: 'Erkki Esimerkki', url: 'http://google.com', - likes: 5 + likes: 5, }, { title: 'MaijanBlogi', author: 'Maija Meikäläinen', url: 'http://localhost:8080', - likes: 3 - } + likes: 3, + }, ]; const initialUsers = [ { username: 'user1', name: 'käyttäjä', - password: 'salasana' + password: 'salasana', }, { username: 'user2', name: 'käyttäjä toinen', - password: 'salasana' + password: 'salasana', }, ]; - const blogsInDb = async () => { const blogs = await Blog.find({}); - return blogs.map(blog => blog.toJSON()); + return blogs.map((blog) => blog.toJSON()); }; const usersInDb = async () => { const users = await User.find({}); - return users.map(user => user.toJSON()); + return users.map((user) => user.toJSON()); }; const invalidId = async () => { @@ -47,4 +46,10 @@ const invalidId = async () => { return toBeDeleted._id.toString(); }; -module.exports = { initialBlogs, initialUsers, blogsInDb, usersInDb, invalidId }; \ No newline at end of file +module.exports = { + initialBlogs, + initialUsers, + blogsInDb, + usersInDb, + invalidId, +}; diff --git a/tests/api/users.test.js b/tests/api/users.test.js index 32e5489..f9cd3cf 100644 --- a/tests/api/users.test.js +++ b/tests/api/users.test.js @@ -1,84 +1,51 @@ const mongoose = require('mongoose'); -const bcrypt = require('bcrypt'); const app = require('../../app'); const supertest = require('supertest'); const api = supertest(app); -const Blog = require('../../models/blog'); -const User = require('../../models/user'); -const { initialBlogs, initialUsers, usersInDb } = require('./test_helper'); +const { initialUsers, usersInDb } = require('./test_helper'); beforeEach(async () => { - await Blog.deleteMany({}); - await User.deleteMany({}); - - await Promise.all(initialUsers.map(async user => { - const newUser = new User({ - username: user.username, - name: user.name, - password: await bcrypt.hash(user.password, 10) - }); - return await newUser.save(); - })); - - const someUser = await User.findOne({}); - - const savedBlogs = await Promise.all(initialBlogs.map(blog => { - const newBlog = Blog(blog); - newBlog.user = someUser._id; - return newBlog.save(); - })); - - someUser.blogs = savedBlogs.map(blog => blog._id); - await someUser.save(); + await api.post('/api/testing/reset'); }); describe('POST api/users', () => { test('responds with 400 when missing username', async () => { const newUser = { name: 'erkki', - password: 'salasana' + password: 'salasana', }; - await api - .post('/api/users') - .send(newUser) - .expect(400); + await api.post('/api/users').send(newUser).expect(400); }); test('responds with 400 when password is too short', async () => { const newUser = { name: 'erkki', username: 'esimerkki', - password: 's' + password: 's', }; - await api - .post('/api/users') - .send(newUser) - .expect(400); + await api.post('/api/users').send(newUser).expect(400); }); test('responds with 400 when username already in use', async () => { const newUser = { name: 'erkki', username: 'esimerkki', - password: 'salasana' + password: 'salasana', }; await api.post('/api/users').send(newUser); - await api - .post('/api/users') - .send(newUser) - .expect(400); + await api.post('/api/users').send(newUser).expect(400); }); test('succesfully adds new user to db', async () => { const newUser = { name: 'erkki', username: 'esimerkki', - password: 'salasana' + password: 'salasana', }; await api.post('/api/users').send(newUser); const usersAtEnd = await usersInDb(); expect(usersAtEnd).toHaveLength(initialUsers.length + 1); - expect(usersAtEnd.map(user => user.username)).toContain('esimerkki'); + expect(usersAtEnd.map((user) => user.username)).toContain('esimerkki'); }); }); @@ -106,8 +73,6 @@ describe('GET api/users', () => { }); }); - - afterAll(() => { mongoose.connection.close(); -}); \ No newline at end of file +}); diff --git a/utils/config.js b/utils/config.js index e08df9c..8f41f1c 100644 --- a/utils/config.js +++ b/utils/config.js @@ -2,9 +2,20 @@ require('dotenv').config(); let PORT = process.env.PORT; let ENV = process.env.NODE_ENV; -let MONGODB_URI = ENV === 'production' - ? process.env.MONGODB_URI - : process.env.DEV_MONGODB_URI; +let MONGODB_URI; +switch (ENV) { + case 'testing': + MONGODB_URI = process.env.TEST_MONGODB_URI; + break; + case 'development': + MONGODB_URI = process.env.DEV_MONGODB_URI; + break; + case 'production': + MONGODB_URI = process.env.MONGODB_URI; + break; + default: + MONGODB_URI = process.env.DEV_MONGODB_URI; +} let SECRET = process.env.SECRET; -module.exports = { ENV, PORT, MONGODB_URI, SECRET }; \ No newline at end of file +module.exports = { ENV, PORT, MONGODB_URI, SECRET };