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 0cdc927..231ba76 100644 --- a/__mocks__/db.js +++ b/__mocks__/db.js @@ -1,55 +1,13 @@ var SequelizeMock = require('sequelize-mock'); var dbMock = new SequelizeMock(); -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.hasMany(Answers); +Answers.belongsTo(Questions, { + foreignKey: 'questionId', +}); module.exports.db = dbMock; module.exports.Questions = Questions; 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/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/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/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/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/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 new file mode 100644 index 0000000..da09f94 --- /dev/null +++ b/test/get.test.js @@ -0,0 +1,147 @@ +const request = require('supertest'); +const app = require('../app'); +const _ = require('lodash'); +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 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/97') + .expect(404); + }); + + 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(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 04fefa6..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,7 +76,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 in case of db error', async function() { + Questions.$queryInterface.$queueFailure('Error in posting a question'); + await request(app) + .post('/api/v1/questions') + .send(question) + .expect(500); }); }); diff --git a/test/put.test.js b/test/put.test.js new file mode 100644 index 0000000..df67892 --- /dev/null +++ b/test/put.test.js @@ -0,0 +1,122 @@ +const request = require('supertest'); +const app = require('../app'); +const _ = require('lodash'); +const { Questions, Answers } = require('../db'); +const { putQuestion: goodQuestion } = require('./mockObjects'); + +let question; + +const test = async (question, code) => { + await request(app) + .put('/api/v1/questions') + .send(question) + .expect(code); +}; + +beforeEach(() => { + Questions.$queryInterface.$clearQueue(); + Questions.$queryInterface.$clearResults(); + Questions.$queryInterface.$clearHandlers(); + question = _.cloneDeep(goodQuestion); +}); + +describe('PUTing a question...', function() { + 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)