diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2689c66..e007def 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -286,6 +286,41 @@ paths: application/json: schema: $ref: "#/components/schemas/notFoundResponse" + '/novel/{slug}': + get: + tags: + - Users + summary: View a novel details. + security: + - ApiKeyAuth: [] + parameters: + - name: slug + in: path + required: true + schema: + type: string + responses: + 200: + description: novel details + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NovelInput' + 404: + description: novel not found + content: + application/json: + schema: + type: object + properties: + error: + type: object + properties: + message: + type: string + example: 'novel not found' 500: description: server error content: diff --git a/src/controllers/novelController.js b/src/controllers/novelController.js index 487ec4a..3fa28e8 100644 --- a/src/controllers/novelController.js +++ b/src/controllers/novelController.js @@ -7,7 +7,9 @@ const { } = helpers; const { Novel } = models; const { - novelServices: { addNovel, findGenre, findAllNovels }, + novelServices: { + addNovel, findGenre, findNovel, findAllNovels + }, notificationServices: { addNotification } } = services; @@ -113,4 +115,26 @@ const createGenre = async (request, response) => { } }; -export default { createNovel, getNovels, createGenre }; +/** + * @description returns novel with slug + * @param {object} request express request object + * @param {object} response express response object + * @param {object} next express next argument + * @returns {json} json + */ +const getNovel = async (request, response) => { + try { + const { slug } = request.params; + const novel = await findNovel(slug); + if (!novel) { + return responseMessage(response, 404, { error: 'novel not found' }); + } + return responseMessage(response, 200, { novel }); + } catch (error) { + responseMessage(response, 500, { error: error.message }); + } +}; + +export default { + createNovel, getNovel, getNovels, createGenre +}; diff --git a/src/database/migrations/20190729135752-create-novel.js b/src/database/migrations/20190729135752-create-novel.js index acf81b0..a90b04b 100644 --- a/src/database/migrations/20190729135752-create-novel.js +++ b/src/database/migrations/20190729135752-create-novel.js @@ -38,6 +38,10 @@ const up = (queryInterface, Sequelize) => queryInterface.createTable('Novels', { allowNull: false, type: Sequelize.TEXT }, + readTime: { + allowNull: false, + type: Sequelize.INTEGER + }, createdAt: { allowNull: false, type: Sequelize.DATE diff --git a/src/database/models/novel.js b/src/database/models/novel.js index f1b6660..848d59e 100644 --- a/src/database/models/novel.js +++ b/src/database/models/novel.js @@ -37,6 +37,10 @@ export default (Sequelize, DataTypes) => { type: DataTypes.BOOLEAN, defaultValue: false }, + readTime: { + allowNull: false, + type: DataTypes.INTEGER + }, createdAt: { allowNull: false, type: DataTypes.DATE diff --git a/src/database/seeders/20190806144830-novel.js b/src/database/seeders/20190806144830-novel.js index bcff045..2551329 100644 --- a/src/database/seeders/20190806144830-novel.js +++ b/src/database/seeders/20190806144830-novel.js @@ -6,6 +6,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'abcd', genreId: 'ffe299b9-889b-4ad3-86cf-138cd57d5aab', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -17,6 +18,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'abcd', genreId: 'ffe299b9-889b-4ad3-86cf-138cd57d5aab', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -28,6 +30,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Religion and Christianity proposes that all the answers can be found in the Bible, while Christianity is all bout searching for anwsers.', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -39,6 +42,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -50,6 +54,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -61,6 +66,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -72,6 +78,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -83,6 +90,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -94,6 +102,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: '09add0fe-d063-48ce-8e18-0dc590d04dcf', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -105,6 +114,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -116,6 +126,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -127,6 +138,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -138,6 +150,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -149,6 +162,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'e4427fcd-d1dd-480d-98a6-bb08d5e4d4aa', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -160,6 +174,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -171,6 +186,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -182,6 +198,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: '09add0fe-d063-48ce-8e18-0dc590d04dcf', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -193,6 +210,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -204,6 +222,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -215,6 +234,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', genreId: 'da86915a-6d4d-455f-8b44-5c4b8221ebf6', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -226,6 +246,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: 'fb94de4d-47ff-4079-89e8-b0186c0a3be8', genreId: '09add0fe-d063-48ce-8e18-0dc590d04dcf', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }, @@ -237,6 +258,7 @@ export const up = queryInterface => queryInterface.bulkInsert('Novels', [{ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris arcu ipsum, sagittis eu dapibus sed, pulvinar suscipit magna. Mauris iaculis rutrum ipsum in lobortis. Quisque ullamcorper at odio ac tristique. Vivamus vel risus vitae lorem varius consequat at quis urna.', authorId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456', genreId: 'ceb59aa0-b10d-4f37-a0d5-925b38876db4', + readTime: 1, createdAt: new Date(), updatedAt: new Date() }], {}); diff --git a/src/helpers/generateReadTime.js b/src/helpers/generateReadTime.js new file mode 100644 index 0000000..a8a22b2 --- /dev/null +++ b/src/helpers/generateReadTime.js @@ -0,0 +1,12 @@ +/** + * @description returns read time of string in minutes + * @param {string} string a string + * @returns {number} number in minutes + */ +const generateReadTime = (string) => { + const wordCount = string.split(' ').length; + const readTimeMinutes = Math.round(wordCount / 200); + return readTimeMinutes; +}; + +export default generateReadTime; diff --git a/src/helpers/index.js b/src/helpers/index.js index 5692e43..34750f2 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -8,6 +8,7 @@ import verifyUser from './verifyUser'; import notificationConfig from './notificationConfig'; import novelHelpers from './novelHelpers'; import reportHelper from './reportHelper'; +import generateReadTime from './generateReadTime'; const { forgotPasswordMessage, emailNotificationMessage } = emailMessages; @@ -23,5 +24,6 @@ export default { emailNotificationMessage, notificationConfig, novelHelpers, - reportHelper + reportHelper, + generateReadTime }; diff --git a/src/middlewares/novelValidator.js b/src/middlewares/novelValidator.js index c1941d6..b72375b 100644 --- a/src/middlewares/novelValidator.js +++ b/src/middlewares/novelValidator.js @@ -2,7 +2,7 @@ import validator from '../helpers/validator'; import errorHandler from './errorHandler'; const { - isNotEmpty, isValidGenre, isValidInt, isValidName + isNotEmpty, isValidGenre, isValidInt, isValidName, isNotEmptySlug } = validator; const { validatorError } = errorHandler; @@ -27,6 +27,10 @@ const novelValidator = { genreValidator: [ isValidName('name'), validatorError + ], + getNovelBySlugValidator: [ + isNotEmptySlug(), + validatorError ] }; diff --git a/src/routes/novel.js b/src/routes/novel.js index bef964f..a13c131 100644 --- a/src/routes/novel.js +++ b/src/routes/novel.js @@ -3,17 +3,18 @@ import middlewares from '../middlewares'; import updateLikes from '../controllers/likesController'; import novelController from '../controllers/novelController'; -const novel = express.Router(); -const NOVEL_URL = '/novels'; - const { novelValidator: { - createNovelValidator, getNovelValidator, genreValidator + createNovelValidator, getNovelBySlugValidator, getNovelValidator, genreValidator }, verifyToken, authorizeUser } = middlewares; -const { createNovel, getNovels, createGenre } = novelController; +const { + createNovel, getNovel, getNovels, createGenre +} = novelController; +const novel = express.Router(); +const NOVEL_URL = '/novels'; // Route to create a novel novel.post(`${NOVEL_URL}`, verifyToken, authorizeUser(['author', 'admin', 'superadmin']), createNovelValidator, createNovel); @@ -24,7 +25,10 @@ novel.post(`${NOVEL_URL}/:slug/like`, verifyToken, authorizeUser(['author', 'adm // Route to create a genre novel.post('/genres', verifyToken, authorizeUser(['author', 'admin', 'superadmin']), genreValidator, createGenre); -// Route to get novels +// Route to get all novels novel.get(`${NOVEL_URL}`, verifyToken, getNovelValidator, getNovels); +// Route to get novel by slug +novel.get(`${NOVEL_URL}/:slug`, verifyToken, authorizeUser(['reader', 'author', 'admin', 'superadmin']), getNovelBySlugValidator, getNovel); + export default novel; diff --git a/src/services/novelService.js b/src/services/novelService.js index abf8f22..5c41195 100644 --- a/src/services/novelService.js +++ b/src/services/novelService.js @@ -1,9 +1,11 @@ import { Op } from 'sequelize'; import models from '../database/models'; +import helpers from '../helpers'; const { - Genre, Novel, Like + Genre, Novel, Like, User } = models; +const { generateReadTime } = helpers; /** * Finds a novel from the database by slug @@ -13,7 +15,8 @@ const { const findNovel = async (param) => { const novel = await Novel.findOne({ - where: { slug: param } + where: { slug: param }, + include: [{ model: Genre, attributes: ['name'] }, { model: User, attributes: ['id', 'firstName', 'lastName', 'bio', 'avatarUrl'] }], }); return novel; }; @@ -71,6 +74,8 @@ const addNovel = async (novel, author) => { const getGenre = await Genre.findOne({ where: { name: genre } }); const slug = `${title.toLowerCase().split(' ').join('-')}-${author.id}`; const foundNovel = await Novel.findOne({ where: { slug } }); + const generatedReadTime = generateReadTime(body); + const readTime = (generatedReadTime > 0) ? generatedReadTime : 1; if (foundNovel) { return { status: 409, @@ -84,7 +89,8 @@ const addNovel = async (novel, author) => { slug, title, description, - body + body, + readTime }); return { id: createdNovel.id, @@ -94,6 +100,7 @@ const addNovel = async (novel, author) => { body: createdNovel.body, genre, author: `${author.firstName} ${author.lastName}`, + readTime, createdAt: createdNovel.createdAt, updatedAt: createdNovel.updatedAt }; diff --git a/tests/mockData/novelMock.js b/tests/mockData/novelMock.js index 60c4f55..0998a11 100644 --- a/tests/mockData/novelMock.js +++ b/tests/mockData/novelMock.js @@ -2,7 +2,13 @@ const novelMock = { validNovel: { title: 'This is the first', description: 'The very first description', - body: 'The body contains text that is really long', + body: 'The body contains text that is really long, it is a long text, it is very long, but it would not be too long to read', + genre: 'action' + }, + validNovel2: { + title: 'This is the second', + description: 'The very second description', + body: 'The body contains text that is really long, it is a long text, it is very long, but it would not be too long to read, The body contains text that is really long, it is a long text, it is very long, but it would not be too long to read, The body contains text that is really long, it is a long text, it is very long, but it would not be too long to read, The body contains text that is really long, it is a long text, it is very long, but it would not be too long to read, The body contains text that is really long, it is a long text, it is very long, but it would not be too long to read, The body contains text that is really long, it is a long text, it is very long, but it would not be too long to read', genre: 'action' }, validGenre: { diff --git a/tests/novel.spec.js b/tests/novel.spec.js index 5f2d1f8..66063a5 100644 --- a/tests/novel.spec.js +++ b/tests/novel.spec.js @@ -3,8 +3,8 @@ import jwt from 'jsonwebtoken'; import chaiHttp from 'chai-http'; import sinon from 'sinon'; import server from '../src'; -import models from '../src/database/models'; import mockData from './mockData'; +import models from '../src/database/models'; import helpers from '../src/helpers'; chai.use(chaiHttp); @@ -17,13 +17,11 @@ const { Genre } = models; const { userMock: { validProfileLogin, validReaderProfileLogin }, novelMock: { - validNovel, validGenre, invalidGenre, emptyGenre, existingGenre + validNovel, validNovel2, validGenre, invalidGenre, emptyGenre, existingGenre } } = mockData; const { novelHelpers: { extractNovels } } = helpers; -let authToken, authReaderToken; - const API_VERSION = '/api/v1'; const LOGIN_URL = `${API_VERSION}/users/login`; const NOVEL_URL = `${API_VERSION}/novels`; @@ -31,6 +29,9 @@ const GENRE_URL = `${API_VERSION}/genres`; const nonexistNovelEndpoint = `${NOVEL_URL}/3c3b6226-b691-472e-babf-a96c6eb373f0/like`; const invalidToken = 'ksjbvksvkerlgvdsbv.ergrpewgjperger.gergnkerl'; const nonExistUserToken = jwt.sign({ id: '8b031dd76-7348-425c-98ea-7b4bd5812c6f' }, process.env.SECRET_KEY); +const validSlug = 'hancock'; +const invalidSlug = 'this-is-the-first-9a5f3850-c53b-4450-8ce4-d560aa2ca736'; +let authToken, authReaderToken; let endpoint; // token of logged in user Eden Hazard in the database @@ -63,6 +64,18 @@ describe('Test for novel CRUD', () => { .post(NOVEL_URL) .send(validNovel) .set('authorization', authToken) + .end((err, res) => { + expect(res).to.have.status(201); + expect(res.body).to.have.property('novel'); + done(); + }); + }); + + it('should create another novel if all fields are valid', (done) => { + chai.request(server) + .post(NOVEL_URL) + .send(validNovel2) + .set('authorization', authToken) .end((err, res) => { const { novel: { slug } } = res.body; endpoint = `/api/v1/novels/${slug}/like`; @@ -120,6 +133,71 @@ describe('Test for novel CRUD', () => { }); }); + describe('GETs /api/v1/novels/:slug', () => { + it('should be able to view a novel details', (done) => { + chai.request(server) + .get(`${NOVEL_URL}/${validSlug}`) + .set('authorization', authToken) + .end((error, response) => { + expect(response).to.have.status(200); + expect(response.body).to.be.an('object'); + expect(response.body).to.have.property('novel'); + done(); + }); + }); + + it('should not be able to view a novel details if the novel does exist in the database', (done) => { + chai.request(server) + .get(`${NOVEL_URL}/${invalidSlug}`) + .set('authorization', authToken) + .end((error, response) => { + expect(response).to.have.status(404); + expect(response.body).to.be.an('object'); + expect(response.body.error).to.equal('novel not found'); + done(); + }); + }); + + it('should not be able to view a novel details if the token is missing', (done) => { + chai.request(server) + .get(`${NOVEL_URL}/${validSlug}`) + .set('authorization', '') + .end((error, response) => { + expect(response).to.have.status(401); + expect(response.body).to.be.an('object'); + done(); + }); + }); + + it('should not be able to view a novel details if the token is invalid', (done) => { + chai.request(server) + .get(`${NOVEL_URL}/${validSlug}`) + .set('authorization', '29327y37grug9') + .end((error, response) => { + expect(response).to.have.status(401); + expect(response.body).to.be.an('object'); + done(); + }); + }); + + it('should return an error message if a server error occurs', (done) => { + const stub = sinon.stub(Novel, 'findOne'); + stub.throws(new Error('error occurred!')); + + chai.request(server) + .get(`${NOVEL_URL}/${validSlug}`) + .set('authorization', authToken) + .end((error, response) => { + expect(response).to.have.status(500); + expect(response.body).to.haveOwnProperty('error'); + expect(response.body.error).to.be.a('string'); + expect(response.body.error).to.equal('error occurred!'); + stub.restore(); + done(); + }); + }); + }); + describe('like and dislike functionalities', () => { it('should not be able to like or dislike without token', (done) => { chai.request(server)