From aa4fc8a284154dfa52d889dce7f51b18f9f474b6 Mon Sep 17 00:00:00 2001 From: Sergey Filenko Date: Mon, 10 Feb 2020 11:31:15 +0200 Subject: [PATCH 1/3] Added some unit tests --- test/api.testwithmocha.js | 133 ---------------------- test/dummy_objects.js | 232 -------------------------------------- test/post.test.js | 6 +- 3 files changed, 3 insertions(+), 368 deletions(-) delete mode 100644 test/api.testwithmocha.js delete mode 100644 test/dummy_objects.js diff --git a/test/api.testwithmocha.js b/test/api.testwithmocha.js deleted file mode 100644 index 0291a48..0000000 --- a/test/api.testwithmocha.js +++ /dev/null @@ -1,133 +0,0 @@ -const chai = require('chai'); -const request = require('supertest'); -const app = require('../app'); -const { Questions, Answers } = require('../db'); -const dummy = require('./dummy_objects'); -const assertArrays = require('chai-arrays'); -chai.use(assertArrays); -const expect = chai.expect; - -async function addQuestionToDB(question) { - const result = await Questions.create(question, { - include: [Answers], - }); - return result.dataValues.id; -} - -async function clearDB() { - await Questions.destroy({ - where: {}, - }); - await Answers.destroy({ - where: {}, - }); -} - -describe('DB', function() { - let id; - before(async function() { - await clearDB(); - id = await addQuestionToDB(dummy[0]); - }); - - it('should write/read a question with answers to/from DB', async function() { - const { dataValues: question } = await Questions.findOne({ - where: { id }, - include: [Answers], - }); - expect(question.id).to.be.equal(id); - expect(question.question).to.be.equal(dummy[0].question); - const answers = question.answers.map(item => item.dataValues); - expect(answers.length).to.be.equal(4); - answers.forEach(answer => { - expect(answer).to.have.own.property('answer'); - expect(answer).to.have.own.property('isCorrect'); - expect(answer).to.have.own.property('id'); - expect(answer.questionId).to.be.equal(id); - }); - }); -}); - -describe('API', function() { - describe('Getting questions without parameters', function() { - before(async function() { - await clearDB(); - dummy.forEach(async (question, index) => { - if (index === 5) return; - await addQuestionToDB(question); - }); - id = await addQuestionToDB(dummy[5]); - }); - - it('should return 200', async function() { - await request(app) - .get('/api/v1/questions') - .expect(200); - }); - - it('should return array of questions', async function() { - const response = await request(app).get('/api/v1/questions'); - expect(response.body.questions).to.be.array(); - expect(response.body.questions.length).to.be.equal(10); - }); - - it('should return questions in correct format', async function() { - const response = await request(app).get('/api/v1/questions'); - const question = response.body.questions.filter( - question => question.id === id - )[0]; - expect(question.question).to.be.equal(dummy[5].question); - expect(question.subjectId).to.be.equal(dummy[5].subjectId); - expect(question.level).to.be.equal(dummy[5].level); - expect(question.id).to.be.equal(id); - expect(question.answers).to.be.array(); - expect(question.answers.length).to.be.equal(4); - question.answers.forEach(answer => { - expect(answer).to.have.own.property('answer'); - expect(answer).to.have.own.property('isCorrect'); - expect(answer).to.have.own.property('id'); - expect(answer.questionId).to.be.equal(id); - }); - }); - }); - - describe('Getting a question with id', function() { - let id; - before(async function() { - await clearDB(); - dummy.forEach(async (question, index) => { - if (index === 3) return; - await addQuestionToDB(question); - }); - id = await addQuestionToDB(dummy[3]); - }); - - it('should return 200 with known id', async function() { - await request(app) - .get('/api/v1/questions/' + id) - .expect(200); - }); - - it('should return 404 with unknown id', async function() { - await request(app) - .get('/api/v1/questions/' + '123abc') - .expect(404); - }); - - it('should return correct question and answers', async function() { - const response = await request(app).get('/api/v1/questions/' + id); - const question = response.body; - expect(question.question).to.be.equal(dummy[3].question); - expect(question.subjectId).to.be.equal(dummy[3].subjectId); - expect(question.level).to.be.equal(dummy[3].level); - expect(question.id).to.be.equal(id); - expect(question.answers.length).to.be.equal(4); - question.answers.forEach(answer => { - expect(answer).to.have.own.property('answer'); - expect(answer).to.have.own.property('isCorrect'); - expect(answer).to.have.own.property('id'); - expect(answer.questionId).to.be.equal(id); - }); - }); - }); -}); diff --git a/test/dummy_objects.js b/test/dummy_objects.js deleted file mode 100644 index 9f51d92..0000000 --- a/test/dummy_objects.js +++ /dev/null @@ -1,232 +0,0 @@ -module.exports = [ - { - question: `How much is the fish?`, - subjectId: 3, - level: 2, - answers: [ - { - answer: 'One', - isCorrect: 0, - }, - { - answer: 'Two', - isCorrect: 0, - }, - { - answer: 'Three', - isCorrect: 1, - }, - { - answer: 'Four', - isCorrect: 0, - }, - ], - }, - { - question: `Who wants to live forever?`, - subjectId: 1, - level: 1, - answers: [ - { - answer: 'You', - isCorrect: 0, - }, - { - answer: 'He', - isCorrect: 0, - }, - { - answer: 'She', - isCorrect: 0, - }, - { - answer: 'They', - isCorrect: 1, - }, - ], - }, - { - question: `What is love?`, - subjectId: 1, - level: 1, - answers: [ - { - answer: 'Baby', - isCorrect: 0, - }, - { - answer: 'Dont', - isCorrect: 0, - }, - { - answer: 'Hurt me', - isCorrect: 0, - }, - { - answer: 'No more', - isCorrect: 1, - }, - ], - }, - { - question: `What is the capital of Great Britain?`, - subjectId: 2, - level: 3, - answers: [ - { - answer: 'London', - isCorrect: 1, - }, - { - answer: 'Paris', - isCorrect: 0, - }, - { - answer: 'New York', - isCorrect: 0, - }, - { - answer: 'Liverpool', - isCorrect: 0, - }, - ], - }, - { - question: `What is the capital of Great Britain?`, - subjectId: 2, - level: 3, - answers: [ - { - answer: 'London', - isCorrect: 1, - }, - { - answer: 'Paris', - isCorrect: 0, - }, - { - answer: 'New York', - isCorrect: 0, - }, - { - answer: 'Liverpool', - isCorrect: 0, - }, - ], - }, - { - question: `What is the capital of Great Britain?`, - subjectId: 2, - level: 3, - answers: [ - { - answer: 'London', - isCorrect: 1, - }, - { - answer: 'Paris', - isCorrect: 0, - }, - { - answer: 'New York', - isCorrect: 0, - }, - { - answer: 'Liverpool', - isCorrect: 0, - }, - ], - }, - { - question: `What is the capital of Great Britain?`, - subjectId: 3, - level: 1, - answers: [ - { - answer: 'London', - isCorrect: 1, - }, - { - answer: 'Paris', - isCorrect: 0, - }, - { - answer: 'New York', - isCorrect: 0, - }, - { - answer: 'Liverpool', - isCorrect: 0, - }, - ], - }, - { - question: `What is the capital of Great Britain?`, - subjectId: 3, - level: 2, - answers: [ - { - answer: 'London', - isCorrect: 1, - }, - { - answer: 'Paris', - isCorrect: 0, - }, - { - answer: 'New York', - isCorrect: 0, - }, - { - answer: 'Liverpool', - isCorrect: 0, - }, - ], - }, - { - question: `What is the capital of Great Britain?`, - subjectId: 1, - level: 2, - answers: [ - { - answer: 'London', - isCorrect: 1, - }, - { - answer: 'Paris', - isCorrect: 0, - }, - { - answer: 'New York', - isCorrect: 0, - }, - { - answer: 'Liverpool', - isCorrect: 0, - }, - ], - }, - { - question: `What is the capital of Great Britain?`, - subjectId: 3, - level: 1, - answers: [ - { - answer: 'London', - isCorrect: 1, - }, - { - answer: 'Paris', - isCorrect: 0, - }, - { - answer: 'New York', - isCorrect: 0, - }, - { - answer: 'Liverpool', - isCorrect: 0, - }, - ], - }, -]; diff --git a/test/post.test.js b/test/post.test.js index 04fefa6..a578c92 100644 --- a/test/post.test.js +++ b/test/post.test.js @@ -85,7 +85,7 @@ describe('POSTing a question...', function() { await test(question, 422); }); - it('should return 500 if there is no connection with db', async function() { - await test(question, 500); - }); + // it('should return 500 if there is no connection with db', async function() { + // await test(question, 500); + // }); }); From 9d881c3eacc5df2d7e7a923a7c1e49375c2b08d2 Mon Sep 17 00:00:00 2001 From: Sergey Filenko Date: Mon, 10 Feb 2020 11:31:30 +0200 Subject: [PATCH 2/3] Added some unit tests --- __mocks__/db.js | 72 +++++++++++++------------------------ __mocks__/question.js | 32 +++++++++++++++++ app.js | 3 ++ package.json | 8 ++++- test/get.test.js | 25 +++++++++++++ test/post.test.js | 10 ++++-- test/put.test.js | 84 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 __mocks__/question.js create mode 100644 test/get.test.js create mode 100644 test/put.test.js diff --git a/__mocks__/db.js b/__mocks__/db.js index 0cdc927..1f558c3 100644 --- a/__mocks__/db.js +++ b/__mocks__/db.js @@ -1,55 +1,31 @@ var SequelizeMock = require('sequelize-mock'); var dbMock = new SequelizeMock(); +const question = require('./question.js'); -const Questions = dbMock.define( - 'questions', - [ - { - id: 1, - question: '12345', - subjectId: 1, - level: 1, - }, - { - id: 2, - question: '456789', - subjectId: 2, - level: 2, - }, - ], - { timestamps: false } -); +const Questions = dbMock.define('questions', {}, { timestamps: false }); +const Answers = dbMock.define('answers', {}, { timestamps: false }); -const Answers = dbMock.define( - 'answers', - [ - { - id: 1, - answer: '1234', - isCorrect: 1, - questionId: 1, - }, - { - id: 2, - answer: '1234', - isCorrect: 0, - questionId: 1, - }, - { - id: 3, - answer: '1234', - isCorrect: 0, - questionId: 1, - }, - { - id: 4, - answer: '1234', - isCorrect: 0, - questionId: 1, - }, - ], - { timestamps: false } -); +Questions.$queryInterface.$useHandler(function(query, queryOptions) { + if (query === 'findOne') { + if (queryOptions[0].where.id === '99') { + if (queryOptions[0].include) { + return Questions.build(question); + } + const questionWoAnswers = _.cloneDeep(question); + delete questionWoAnswers.answers; + return Questions.build(questionWoAnswers); + } else { + return null; + } + } +}); + +Questions.hasMany(Answers); +Answers.belongsTo(Questions, { + foreignKey: 'questionId', +}); + +//Questions.$queueFailure(new Error('My test error')); module.exports.db = dbMock; module.exports.Questions = Questions; diff --git a/__mocks__/question.js b/__mocks__/question.js new file mode 100644 index 0000000..e3945ef --- /dev/null +++ b/__mocks__/question.js @@ -0,0 +1,32 @@ +module.exports = { + id: 99, + question: 'Bla bla?', + subjectId: 3, + level: 3, + answers: [ + { + id: 393, + answer: 'Given', + isCorrect: false, + questionId: 99, + }, + { + id: 394, + answer: 'Divide form', + isCorrect: true, + questionId: 99, + }, + { + id: 395, + answer: 'Given', + isCorrect: false, + questionId: 99, + }, + { + id: 396, + answer: 'Were', + isCorrect: false, + questionId: 99, + }, + ], +}; diff --git a/app.js b/app.js index 0611649..92a8297 100644 --- a/app.js +++ b/app.js @@ -6,5 +6,8 @@ const app = express(); app.use(cors()); app.use(bodyParser.json()); app.use('/api/v1', require('./routes/api')); +app.use(function(err, req, res, next) { + res.status(422).json(err); +}); module.exports = app; diff --git a/package.json b/package.json index 5a9a7ce..49670a1 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,13 @@ "test": "env-cmd -f ./config/test.env jest --watch" }, "jest": { - "testEnvironment": "node" + "testEnvironment": "node", + "collectCoverage": true, + "collectCoverageFrom": [ + "routes/api.js", + "validation/answers.js", + "!**/node_modules/**" + ] }, "repository": { "type": "git", diff --git a/test/get.test.js b/test/get.test.js new file mode 100644 index 0000000..a4e2d9e --- /dev/null +++ b/test/get.test.js @@ -0,0 +1,25 @@ +const request = require('supertest'); +const app = require('../app'); +const _ = require('lodash'); +const { db, Questions, Answers } = require('../db'); +const question = require('../__mocks__/question'); + +describe('GETing a question...', function() { + it('should return code 200 and appropriate object when requesting existing id', async function() { + await request(app) + .get('/api/v1/questions/99') + .expect(200, question); + }); + + it('should return 404 with not existing id', async function() { + await request(app) + .get('/api/v1/questions/1') + .expect(404); + }); + //TODO + it('should return 200 and array of objects when requesting all the questions', async function() { + await request(app) + .get('/api/v1/questions') + .expect(200, [question]); + }); +}); diff --git a/test/post.test.js b/test/post.test.js index a578c92..9f3926b 100644 --- a/test/post.test.js +++ b/test/post.test.js @@ -85,7 +85,11 @@ describe('POSTing a question...', function() { await test(question, 422); }); - // it('should return 500 if there is no connection with db', async function() { - // await test(question, 500); - // }); + it('should return 500 if there is no connection with db', async function() { + Questions.$queueFailure(new Error('My test error')); + await request(app) + .post('/api/v1/questions') + .send(null) + .expect(500); + }); }); diff --git a/test/put.test.js b/test/put.test.js new file mode 100644 index 0000000..805bf71 --- /dev/null +++ b/test/put.test.js @@ -0,0 +1,84 @@ +const request = require('supertest'); +const app = require('../app'); +const _ = require('lodash'); +const { db, Questions, Answers } = require('../db'); + +const answers = {}; +answers.dataValues = [ + { + id: 393, + answer: 'Given', + isCorrect: false, + }, + { + id: 394, + answer: 'Divide form', + isCorrect: true, + }, + { + id: 395, + answer: 'Given', + isCorrect: false, + }, + { + id: 396, + answer: 'Were', + isCorrect: false, + }, +]; + +const questionToReturn = { + id: '99', + question: 'Bla bla?', + subjectId: 3, + level: 3, + answers, +}; + +Questions.$queryInterface.$useHandler(function(query, queryOptions, done) { + if (query === 'findOne') { + if (queryOptions[0].where.id == 99) { + return Questions.build(questionToReturn); + } else { + return null; + } + } +}); + +const goodQuestion = { + password: '123', + question: 'How much is the fish?', + id: '99', + subjectId: '1', + level: '1', + answers: [ + { id: 393, answer: '42', isCorrect: true }, + { id: 394, answer: '15', isCorrect: false }, + { id: 395, answer: 'Many', isCorrect: false }, + { id: 396, answer: 'One', isCorrect: false }, + ], +}; + +const test = async (question, code) => { + await request(app) + .put('/api/v1/questions') + .send(question) + .expect(code); +}; + +let question; + +beforeEach(() => { + question = _.cloneDeep(goodQuestion); +}); + +describe('PUTing a question...', function() { + // it('should return 200 if passed an approptiate question object', async function() { + // await test(goodQuestion, 200); + // }); + + it('should return 401 with wrong password', async function() { + question.password = 'wrong'; + await test(question, 401); + }); +}); From 4ab450063ac85c880088d75f7001dad452297a80 Mon Sep 17 00:00:00 2001 From: Sergey Filenko Date: Mon, 2 Mar 2020 13:13:37 +0200 Subject: [PATCH 3/3] Finished tests --- .vscode/launch.json | 13 ++++ __mocks__/db.js | 18 ----- __mocks__/question.js | 32 -------- config/dev.env | 1 - config/test.env | 1 - routes/api.js | 17 +++-- test/end.test.js | 107 +++++++++++++++++++++++++++ test/get.test.js | 134 ++++++++++++++++++++++++++++++++-- test/mockObjects.js | 97 ++++++++++++++++++++++++ test/post.test.js | 25 ++----- test/put.test.js | 162 +++++++++++++++++++++++++---------------- test/start.test.js | 85 +++++++++++++++++++++ validation/query.js | 3 +- validation/question.js | 3 +- validation/start.js | 3 +- validation/update.js | 3 +- 16 files changed, 551 insertions(+), 153 deletions(-) delete mode 100644 __mocks__/question.js create mode 100644 test/end.test.js create mode 100644 test/mockObjects.js create mode 100644 test/start.test.js diff --git a/.vscode/launch.json b/.vscode/launch.json index dd1716c..bca5e4f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,6 +10,19 @@ "name": "Launch Program", "program": "${workspaceFolder}/index.js", "smartStep": true + }, + { + "name": "Debug Jest Tests", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--inspect-brk", + "${workspaceRoot}/node_modules/.bin/jest", + "--runInBand" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "port": 9229 } ] } diff --git a/__mocks__/db.js b/__mocks__/db.js index 1f558c3..231ba76 100644 --- a/__mocks__/db.js +++ b/__mocks__/db.js @@ -1,32 +1,14 @@ var SequelizeMock = require('sequelize-mock'); var dbMock = new SequelizeMock(); -const question = require('./question.js'); const Questions = dbMock.define('questions', {}, { timestamps: false }); const Answers = dbMock.define('answers', {}, { timestamps: false }); -Questions.$queryInterface.$useHandler(function(query, queryOptions) { - if (query === 'findOne') { - if (queryOptions[0].where.id === '99') { - if (queryOptions[0].include) { - return Questions.build(question); - } - const questionWoAnswers = _.cloneDeep(question); - delete questionWoAnswers.answers; - return Questions.build(questionWoAnswers); - } else { - return null; - } - } -}); - Questions.hasMany(Answers); Answers.belongsTo(Questions, { foreignKey: 'questionId', }); -//Questions.$queueFailure(new Error('My test error')); - module.exports.db = dbMock; module.exports.Questions = Questions; module.exports.Answers = Answers; diff --git a/__mocks__/question.js b/__mocks__/question.js deleted file mode 100644 index e3945ef..0000000 --- a/__mocks__/question.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = { - id: 99, - question: 'Bla bla?', - subjectId: 3, - level: 3, - answers: [ - { - id: 393, - answer: 'Given', - isCorrect: false, - questionId: 99, - }, - { - id: 394, - answer: 'Divide form', - isCorrect: true, - questionId: 99, - }, - { - id: 395, - answer: 'Given', - isCorrect: false, - questionId: 99, - }, - { - id: 396, - answer: 'Were', - isCorrect: false, - questionId: 99, - }, - ], -}; diff --git a/config/dev.env b/config/dev.env index cbab6eb..88cf660 100644 --- a/config/dev.env +++ b/config/dev.env @@ -1,6 +1,5 @@ HOST=localhost PORT=1234 -SUBJECTS_AMOUNT=3 HASH=$2b$10$PjqecawtIkC0tAPJhKjHGOH7N8KyZuhMNoQtP79fE.zGnoHnbjuxe DB_HOST=localhost DB_NAME=quiz diff --git a/config/test.env b/config/test.env index bc8b640..582c99d 100644 --- a/config/test.env +++ b/config/test.env @@ -1,7 +1,6 @@ ENV=testing HOST=localhost PORT=1234 -SUBJECTS_AMOUNT=3 HASH=$2b$10$PjqecawtIkC0tAPJhKjHGOH7N8KyZuhMNoQtP79fE.zGnoHnbjuxe DB_HOST=localhost DB_NAME=testquiz diff --git a/routes/api.js b/routes/api.js index cc0804c..3bb60dc 100644 --- a/routes/api.js +++ b/routes/api.js @@ -32,7 +32,7 @@ validate.options({ router.post('/questions', validate(questionValidation), function(req, res) { bcrypt.compare(req.body.password, HASH).then(function(pass) { if (pass) { - Questions.create(req.body, { + Questions.findOrCreate(req.body, { include: [Answers], }) .then(_ => { @@ -41,7 +41,7 @@ router.post('/questions', validate(questionValidation), function(req, res) { }) .catch(e => { res.status(500).send('Server error'); - console.error('Error: ', e); + // console.error('Error: ', e); return; }); } else { @@ -66,7 +66,7 @@ router.get('/questions/:id', function(req, res) { }) .catch(e => { res.status(500).send('Server error'); - console.error('Error: ', e); + // console.error('Error: ', e); return; }); }); @@ -102,7 +102,7 @@ router.get('/questions', validate(queryValidation), function(req, res) { }) .catch(e => { res.status(500).send('Server error'); - console.error('Error: ', e); + // console.error('Error: ', e); return; }); }); @@ -119,7 +119,7 @@ router.put('/questions', validate(updateValidation), function(req, res) { try { validateAnswersUpdate(question.answers, req.body.answers); } catch (e) { - console.log(e.message); + //console.log(e.message); res.status(422).send(e.message); return; } @@ -129,21 +129,22 @@ router.put('/questions', validate(updateValidation), function(req, res) { if (updatedQuestion) { updatedQuestion.answers.forEach(answer => { answer.update(answer.dataValues).catch(e => { + // console.error('Error: ', e); res.status(500).send('Server error'); - console.error('Error: ', e); return; }); }); res.status(200).send('Ok'); return; } else { + // console.log('Answers updating server error'); res.status(500).send('Server error'); return; } }) .catch(e => { + // console.log('Question updating server error', e); res.status(500).send('Server error'); - console.error('Error: ', e); return; }); } else { @@ -188,7 +189,7 @@ router.get('/start', validate(startValidation), function(req, res) { }) .catch(e => { res.status(500).send('Server error'); - console.error('Error: ', e); + // console.error('Error: ', e); return; }); }); diff --git a/test/end.test.js b/test/end.test.js new file mode 100644 index 0000000..00f5cf3 --- /dev/null +++ b/test/end.test.js @@ -0,0 +1,107 @@ +const request = require('supertest'); +const app = require('../app'); +const _ = require('lodash'); +const { Questions } = require('../db'); +const { testQuestions: questions } = require('./mockObjects'); + +const questionsToAnswer = [ + { + id: 1, + answerId: 1, + }, + { + id: 2, + answerId: 2, + }, + { + id: 3, + answerId: 3, + }, +]; + +const getTestId = async function() { + const responce = await request(app).get( + '/api/v1/start?level=1&subjectId=1&quantity=3' + ); + + return responce.body.testId; +}; + +beforeEach(() => { + arrayOfQuestions = questions.map(q => Questions.build(q)); + Questions.$queryInterface.$clearQueue(); + Questions.$queryInterface.$clearResults(); + Questions.$queryInterface.$clearHandlers(); + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + return arrayOfQuestions; + }); +}); + +describe('Ending a test...', function() { + it('should return 200 if passed appropriate answers', async function() { + const testId = await getTestId(); + + await request(app) + .post('/api/v1/end') + .send({ + testId, + questions: questionsToAnswer, + }) + .expect(200); + }); + + it('should return 404 if passed wrong testId', async function() { + const testId = await getTestId(); + + await request(app) + .post('/api/v1/end') + .send({ + testId: testId + 1, + questions: questionsToAnswer, + }) + .expect(404); + }); + + it('should return 422 if quantity of answers is incorrect', async function() { + const testId = await getTestId(); + + const questionsToAnswerCopy = _.cloneDeep(questionsToAnswer); + questionsToAnswerCopy.push({ + id: 4, + answerId: 4, + }); + await request(app) + .post('/api/v1/end') + .send({ + testId: testId, + questions: questionsToAnswerCopy, + }) + .expect(422); + }); + + it('should return 422 if questions ids are not unique', async function() { + const testId = await getTestId(); + const questionsToAnswerCopy = _.cloneDeep(questionsToAnswer); + questionsToAnswerCopy[0].id = 3; + await request(app) + .post('/api/v1/end') + .send({ + testId: testId, + questions: questionsToAnswerCopy, + }) + .expect(422); + }); + + it('should return 422 if a question id not found', async function() { + const testId = await getTestId(); + const questionsToAnswerCopy = _.cloneDeep(questionsToAnswer); + questionsToAnswerCopy[0].id = 99; + await request(app) + .post('/api/v1/end') + .send({ + testId: testId, + questions: questionsToAnswerCopy, + }) + .expect(422); + }); +}); diff --git a/test/get.test.js b/test/get.test.js index a4e2d9e..da09f94 100644 --- a/test/get.test.js +++ b/test/get.test.js @@ -1,25 +1,147 @@ const request = require('supertest'); const app = require('../app'); const _ = require('lodash'); -const { db, Questions, Answers } = require('../db'); -const question = require('../__mocks__/question'); +const { Questions } = require('../db'); +const { getQuestion: question } = require('./mockObjects'); + +let arrayOfQuestions; +const expectedArray = [{ id: 1000 }, { id: 1055 }, { id: 1058 }]; + +beforeEach(() => { + const q1 = Questions.build({ id: 1000 }); + const q2 = Questions.build({ id: 1055 }); + const q3 = Questions.build({ id: 1058 }); + arrayOfQuestions = [q1, q2, q3]; + + Questions.$queryInterface.$clearQueue(); + Questions.$queryInterface.$clearResults(); + Questions.$queryInterface.$clearHandlers(); +}); describe('GETing a question...', function() { it('should return code 200 and appropriate object when requesting existing id', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + if (queryOptions[0].where.id === '99') return Questions.build(question); + else return null; + }); await request(app) .get('/api/v1/questions/99') .expect(200, question); }); - it('should return 404 with not existing id', async function() { + it('should return code 404 when requesting non-existent id', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + if (queryOptions[0].where.id === '99') return Questions.build(question); + else return null; + }); await request(app) - .get('/api/v1/questions/1') + .get('/api/v1/questions/97') .expect(404); }); - //TODO + + it('should return 500 in case of db error', async function() { + Questions.$queryInterface.$queueFailure('Error in getting a question'); + await request(app) + .get('/api/v1/questions/99') + .expect(500); + }); +}); + +describe('GETing questions...', function() { it('should return 200 and array of objects when requesting all the questions', async function() { + Questions.$queryInterface.$queueResult(arrayOfQuestions); + await request(app) + .get('/api/v1/questions') + .expect(200, { questions: expectedArray }); + }); + + it('should return 200 and appropriate questions when queriing level', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + if (queryOptions[0].where.level === 3) { + return arrayOfQuestions; + } else { + return null; + } + }); + await request(app) + .get('/api/v1/questions?level=3') + .expect(200, { + questions: expectedArray, + }); + }); + + it('should return 200 and appropriate questions when queriing subjectId', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + if (queryOptions[0].where.subjectId === 1) { + return arrayOfQuestions; + } else { + return null; + } + }); + await request(app) + .get('/api/v1/questions?subjectId=1') + .expect(200, { + questions: expectedArray, + }); + }); + + it('should return 200 and appropriate questions when queriing quantity', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + if (queryOptions[0].limit === 3) { + return arrayOfQuestions; + } else { + return null; + } + }); + await request(app) + .get('/api/v1/questions?quantity=3') + .expect(200, { + questions: expectedArray, + nextPage: 'http://localhost:1234/api/v1/questions?quantity=3&start=3', + }); + }); + + it('should return 200 and appropriate questions when queriing start', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + if (queryOptions[0].offset === 5) { + return arrayOfQuestions; + } else { + return null; + } + }); + await request(app) + .get('/api/v1/questions?start=5') + .expect(200, { + questions: expectedArray, + }); + }); + + it('should return 200 and appropriate questions when queriing quantity, level, subjectId and start simultaneously', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + if ( + queryOptions[0].where.level === 1 && + queryOptions[0].where.subjectId === 2 && + queryOptions[0].offset === 10 && + queryOptions[0].limit === 5 + ) { + return arrayOfQuestions; + } else { + return null; + } + }); + await request(app) + .get('/api/v1/questions?quantity=5&level=1&subjectId=2&start=10') + .expect(200, { + questions: expectedArray, + nextPage: + 'http://localhost:1234/api/v1/questions?quantity=5&start=15&level=1&subjectId=2', + }); + }); + + it('should return 500 in case of db error', async function() { + Questions.$queryInterface.$queueFailure('Error in getting questions'); await request(app) .get('/api/v1/questions') - .expect(200, [question]); + .expect(500); }); }); diff --git a/test/mockObjects.js b/test/mockObjects.js new file mode 100644 index 0000000..18036a6 --- /dev/null +++ b/test/mockObjects.js @@ -0,0 +1,97 @@ +const _ = require('lodash'); + +const postAnswers = [ + { answer: '42', isCorrect: true }, + { answer: '15', isCorrect: false }, + { answer: 'Many', isCorrect: false }, + { answer: 'One', isCorrect: false }, +]; + +const putAnswers = [ + { + id: 393, + answer: '42', + isCorrect: true, + }, + { + id: 394, + answer: '15', + isCorrect: false, + }, + { + id: 395, + answer: 'Many', + isCorrect: false, + }, + { + id: 396, + answer: 'One', + isCorrect: false, + }, +]; + +const getAnswers = [ + { + id: 393, + answer: 'Given', + isCorrect: false, + questionId: 99, + }, + { + id: 394, + answer: 'Divide form', + isCorrect: true, + questionId: 99, + }, + { + id: 395, + answer: 'Given', + isCorrect: false, + questionId: 99, + }, + { + id: 396, + answer: 'Were', + isCorrect: false, + questionId: 99, + }, +]; + +const question = { + password: '123', + question: 'How much is the fish?', + subjectId: '1', + level: '1', +}; + +const testQuestions = []; +for (let i = 1; i < 4; i++) { + const testQuestion = _.cloneDeep(question); + delete testQuestion.password; + testQuestion.id = i; + testQuestion.answers = [ + { id: 1 * i, answer: 'A', isCorrect: true, questionId: i }, + { id: 20 * i, answer: 'B', isCorrect: false, questionId: i }, + { id: 300 * i, answer: 'C', isCorrect: false, questionId: i }, + { id: 4000 * i, answer: 'D', isCorrect: false, questionId: i }, + ]; + testQuestions.push(testQuestion); +} + +const postQuestion = _.cloneDeep(question); +postQuestion.answers = _.cloneDeep(postAnswers); + +const putQuestion = _.cloneDeep(question); +putQuestion.answers = _.cloneDeep(putAnswers); +putQuestion.id = 99; + +const getQuestion = _.cloneDeep(question); +getQuestion.answers = _.cloneDeep(getAnswers); +getQuestion.id = 99; + +module.exports = { + getQuestion, + postQuestion, + putQuestion, + testQuestions, +}; diff --git a/test/post.test.js b/test/post.test.js index 9f3926b..78e25f3 100644 --- a/test/post.test.js +++ b/test/post.test.js @@ -1,20 +1,8 @@ const request = require('supertest'); const app = require('../app'); const _ = require('lodash'); -const { db, Questions, Answers } = require('../db'); - -const goodQuestion = { - password: '123', - question: 'How much is the fish?', - subjectId: '1', - level: '1', - answers: [ - { answer: '42', isCorrect: true }, - { answer: '15', isCorrect: false }, - { answer: 'Many', isCorrect: false }, - { answer: 'One', isCorrect: false }, - ], -}; +const { Questions } = require('../db'); +const { postQuestion: goodQuestion } = require('./mockObjects'); let question; @@ -26,6 +14,9 @@ const test = async (question, code) => { }; beforeEach(() => { + Questions.$queryInterface.$clearQueue(); + Questions.$queryInterface.$clearResults(); + Questions.$queryInterface.$clearHandlers(); question = _.cloneDeep(goodQuestion); }); @@ -85,11 +76,11 @@ describe('POSTing a question...', function() { await test(question, 422); }); - it('should return 500 if there is no connection with db', async function() { - Questions.$queueFailure(new Error('My test error')); + it('should return 500 in case of db error', async function() { + Questions.$queryInterface.$queueFailure('Error in posting a question'); await request(app) .post('/api/v1/questions') - .send(null) + .send(question) .expect(500); }); }); diff --git a/test/put.test.js b/test/put.test.js index 805bf71..df67892 100644 --- a/test/put.test.js +++ b/test/put.test.js @@ -1,63 +1,10 @@ const request = require('supertest'); const app = require('../app'); const _ = require('lodash'); -const { db, Questions, Answers } = require('../db'); +const { Questions, Answers } = require('../db'); +const { putQuestion: goodQuestion } = require('./mockObjects'); -const answers = {}; -answers.dataValues = [ - { - id: 393, - answer: 'Given', - isCorrect: false, - }, - { - id: 394, - answer: 'Divide form', - isCorrect: true, - }, - { - id: 395, - answer: 'Given', - isCorrect: false, - }, - { - id: 396, - answer: 'Were', - isCorrect: false, - }, -]; - -const questionToReturn = { - id: '99', - question: 'Bla bla?', - subjectId: 3, - level: 3, - answers, -}; - -Questions.$queryInterface.$useHandler(function(query, queryOptions, done) { - if (query === 'findOne') { - if (queryOptions[0].where.id == 99) { - return Questions.build(questionToReturn); - } else { - return null; - } - } -}); - -const goodQuestion = { - password: '123', - question: 'How much is the fish?', - id: '99', - subjectId: '1', - level: '1', - answers: [ - { id: 393, answer: '42', isCorrect: true }, - { id: 394, answer: '15', isCorrect: false }, - { id: 395, answer: 'Many', isCorrect: false }, - { id: 396, answer: 'One', isCorrect: false }, - ], -}; +let question; const test = async (question, code) => { await request(app) @@ -66,19 +13,110 @@ const test = async (question, code) => { .expect(code); }; -let question; - beforeEach(() => { + Questions.$queryInterface.$clearQueue(); + Questions.$queryInterface.$clearResults(); + Questions.$queryInterface.$clearHandlers(); question = _.cloneDeep(goodQuestion); }); describe('PUTing a question...', function() { - // it('should return 200 if passed an approptiate question object', async function() { - // await test(goodQuestion, 200); - // }); - it('should return 401 with wrong password', async function() { question.password = 'wrong'; await test(question, 401); }); + + it('should return 404 in case of putting non-existent question', async function() { + Questions.$queryInterface.$useHandler(function() { + return null; + }); + + await request(app) + .put('/api/v1/questions') + .send(question) + .expect(404); + }); + + it('should return 200 if passed an approptiate question object', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + const question = _.cloneDeep(goodQuestion); + const answers = question.answers.map(answer => Answers.build(answer)); + question.answers = answers; + const q = Questions.build(question); + q.update = () => + new Promise(function(resolve, reject) { + resolve(q); + }); + return q; + }); + + await request(app) + .put('/api/v1/questions') + .send(question) + .expect(200); + }); + + it('should return 500 if there is error occured while updating answers', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + const question = _.cloneDeep(goodQuestion); + const answers = question.answers.map(answer => Answers.build(answer)); + question.answers = answers; + const q = Questions.build(question); + q.update = () => + new Promise(function(resolve, reject) { + resolve(null); + }); + return q; + }); + + await request(app) + .put('/api/v1/questions') + .send(question) + .expect(500); + }); + + it('should return 422 if at least one of the answers ids is incorrect', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + const question = _.cloneDeep(goodQuestion); + question.answers[0].id = 999; + const answers = question.answers.map(answer => Answers.build(answer)); + question.answers = answers; + const q = Questions.build(question); + q.update = () => + new Promise(function(resolve, reject) { + resolve(q); + }); + return q; + }); + + await request(app) + .put('/api/v1/questions') + .send(question) + .expect(422); + }); + + it('should return 500 in case of db error', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + const question = _.cloneDeep(goodQuestion); + const answers = question.answers.map(answer => { + const a = Answers.build(answer); + a.update = () => { + throw new Error('Error'); + }; + return a; + }); + question.answers = answers; + const q = Questions.build(question); + q.update = () => + new Promise(function(resolve, reject) { + resolve(q); + }); + return q; + }); + + await request(app) + .put('/api/v1/questions') + .send(question) + .expect(500); + }); }); diff --git a/test/start.test.js b/test/start.test.js new file mode 100644 index 0000000..a5a74bc --- /dev/null +++ b/test/start.test.js @@ -0,0 +1,85 @@ +const request = require('supertest'); +const app = require('../app'); +const _ = require('lodash'); +const { Questions } = require('../db'); +const { getQuestion: question } = require('./mockObjects'); + +beforeEach(() => { + const q1 = (q2 = q3 = Questions.build(question)); + arrayOfQuestions = [q1, q2, q3]; + Questions.$queryInterface.$clearQueue(); + Questions.$queryInterface.$clearResults(); + Questions.$queryInterface.$clearHandlers(); +}); + +describe('Starting a test...', function() { + it('should return 500 in case of db error', async function() { + Questions.$queryInterface.$queueFailure('Error while starting a test'); + await request(app) + .get('/api/v1/start?level=1&subjectId=1&quantity=3') + .expect(500); + }); + + it('should return code 200 and array of questions', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + return arrayOfQuestions; + }); + await request(app) + .get('/api/v1/start?level=1&subjectId=1&quantity=3') + .expect(200); + }); + + it('should return code 422 in case of parameters without level', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + return arrayOfQuestions; + }); + await request(app) + .get('/api/v1/start?subjectId=1&quantity=3') + .expect(422); + }); + + it('should return code 422 in case of parameters without subjectId', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + return arrayOfQuestions; + }); + await request(app) + .get('/api/v1/start?level=1&quantity=3') + .expect(422); + }); + + it('should return code 422 in case of parameters without quantity', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + return arrayOfQuestions; + }); + await request(app) + .get('/api/v1/start?level=1&subjectId=1') + .expect(422); + }); + + it('should return code 422 in case of wrong level', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + return arrayOfQuestions; + }); + await request(app) + .get('/api/v1/start?level=5&subjectId=1&quantity=3') + .expect(422); + }); + + it('should return code 422 in case of wrong subjectId', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + return arrayOfQuestions; + }); + await request(app) + .get('/api/v1/start?level=1&subjectId=5&quantity=3') + .expect(422); + }); + + it('should return code 422 in case of wrong quantity', async function() { + Questions.$queryInterface.$useHandler(function(query, queryOptions) { + return arrayOfQuestions; + }); + await request(app) + .get('/api/v1/start?level=1&subjectId=1&quantity=800') + .expect(422); + }); +}); diff --git a/validation/query.js b/validation/query.js index adf6a3d..3c46514 100644 --- a/validation/query.js +++ b/validation/query.js @@ -1,5 +1,4 @@ const Joi = require('joi'); -const SUBJECTS_AMOUNT = process.env.SUBJECTS_AMOUNT; module.exports = { query: { @@ -13,7 +12,7 @@ module.exports = { subjectId: Joi.number() .integer() .min(1) - .max(Number(SUBJECTS_AMOUNT)), + .max(3), start: Joi.number() .integer() .min(0), diff --git a/validation/question.js b/validation/question.js index 61655c3..e6f4d0d 100644 --- a/validation/question.js +++ b/validation/question.js @@ -1,5 +1,4 @@ const Joi = require('joi'); -const SUBJECTS_AMOUNT = process.env.SUBJECTS_AMOUNT; module.exports = { body: { @@ -10,7 +9,7 @@ module.exports = { .integer() .required() .min(1) - .max(Number(SUBJECTS_AMOUNT)), + .max(3), level: Joi.number() .integer() .required() diff --git a/validation/start.js b/validation/start.js index d311469..f6cc203 100644 --- a/validation/start.js +++ b/validation/start.js @@ -1,5 +1,4 @@ const Joi = require('joi'); -const SUBJECTS_AMOUNT = process.env.SUBJECTS_AMOUNT; module.exports = { query: { @@ -17,6 +16,6 @@ module.exports = { .integer() .required() .min(1) - .max(Number(SUBJECTS_AMOUNT)), + .max(3), }, }; diff --git a/validation/update.js b/validation/update.js index 210285e..50d2f43 100644 --- a/validation/update.js +++ b/validation/update.js @@ -1,5 +1,4 @@ const Joi = require('joi'); -const SUBJECTS_AMOUNT = process.env.SUBJECTS_AMOUNT; module.exports = { body: { @@ -11,7 +10,7 @@ module.exports = { subjectId: Joi.number() .integer() .min(1) - .max(Number(SUBJECTS_AMOUNT)), + .max(3), level: Joi.number() .integer() .min(1)