From d3ea89759104e6c14b00443526f2c2a0a13aeb97 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 22 Dec 2017 12:10:40 +0100 Subject: [PATCH] Begin unit tests --- server/controllers/api/videos/comment.ts | 8 +- .../lib/activitypub/process/process-create.ts | 4 +- server/lib/video-comment.ts | 24 ++-- .../middlewares/validators/video-comments.ts | 21 +-- server/models/video/video-comment.ts | 30 ++-- server/tests/api/index-slow.ts | 1 + server/tests/api/video-comments.ts | 135 ++++++++++++++++++ server/tests/utils/video-comments.ts | 64 +++++++++ shared/models/videos/video-comment.model.ts | 7 +- 9 files changed, 252 insertions(+), 42 deletions(-) create mode 100644 server/tests/api/video-comments.ts create mode 100644 server/tests/utils/video-comments.ts diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index 81c9e7d16..ac64f0154 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts @@ -78,9 +78,9 @@ function addVideoCommentThread (req: express.Request, res: express.Response) { return sequelizeTypescript.transaction(async t => { return createVideoComment({ text: videoCommentInfo.text, - inReplyToComment: null, + inReplyToCommentId: null, video: res.locals.video, - actorId: res.locals.oauth.token.User.Account.Actor.id + accountId: res.locals.oauth.token.User.Account.id }, t) }) } @@ -106,9 +106,9 @@ function addVideoCommentReply (req: express.Request, res: express.Response, next return sequelizeTypescript.transaction(async t => { return createVideoComment({ text: videoCommentInfo.text, - inReplyToComment: res.locals.videoComment.id, + inReplyToCommentId: res.locals.videoComment.id, video: res.locals.video, - actorId: res.locals.oauth.token.User.Account.Actor.id + accountId: res.locals.oauth.token.User.Account.id }, t) }) } diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 102e54b19..6c2ee97eb 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts @@ -267,7 +267,7 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) { originCommentId: null, inReplyToComment: null, videoId: video.id, - actorId: byActor.id + accountId: byAccount.id }, { transaction: t }) } @@ -281,7 +281,7 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) { originCommentId, inReplyToCommentId: inReplyToComment.id, videoId: inReplyToComment.videoId, - actorId: byActor.id + accountId: byAccount.id }, { transaction: t }) }) } diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index edb72d4e2..e3fe26e35 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts @@ -1,19 +1,20 @@ import * as Sequelize from 'sequelize' import { ResultList } from '../../shared/models' -import { VideoCommentThread } from '../../shared/models/videos/video-comment.model' +import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' import { VideoModel } from '../models/video/video' import { VideoCommentModel } from '../models/video/video-comment' import { getVideoCommentActivityPubUrl } from './activitypub' async function createVideoComment (obj: { text: string, - inReplyToComment: number, + inReplyToCommentId: number, video: VideoModel - actorId: number + accountId: number }, t: Sequelize.Transaction) { let originCommentId: number = null - if (obj.inReplyToComment) { - const repliedComment = await VideoCommentModel.loadById(obj.inReplyToComment) + + if (obj.inReplyToCommentId) { + const repliedComment = await VideoCommentModel.loadById(obj.inReplyToCommentId) if (!repliedComment) throw new Error('Unknown replied comment.') originCommentId = repliedComment.originCommentId || repliedComment.id @@ -22,22 +23,23 @@ async function createVideoComment (obj: { const comment = await VideoCommentModel.create({ text: obj.text, originCommentId, - inReplyToComment: obj.inReplyToComment, + inReplyToCommentId: obj.inReplyToCommentId, videoId: obj.video.id, - actorId: obj.actorId - }, { transaction: t }) + accountId: obj.accountId, + url: 'fake url' + }, { transaction: t, validate: false }) comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment)) return comment.save({ transaction: t }) } -function buildFormattedCommentTree (resultList: ResultList): VideoCommentThread { +function buildFormattedCommentTree (resultList: ResultList): VideoCommentThreadTree { // Comments are sorted by id ASC const comments = resultList.data const comment = comments.shift() - const thread: VideoCommentThread = { + const thread: VideoCommentThreadTree = { comment: comment.toFormattedJSON(), children: [] } @@ -48,7 +50,7 @@ function buildFormattedCommentTree (resultList: ResultList): while (comments.length !== 0) { const childComment = comments.shift() - const childCommentThread: VideoCommentThread = { + const childCommentThread: VideoCommentThreadTree = { comment: childComment.toFormattedJSON(), children: [] } diff --git a/server/middlewares/validators/video-comments.ts b/server/middlewares/validators/video-comments.ts index 5e1be00f2..1d19fac58 100644 --- a/server/middlewares/validators/video-comments.ts +++ b/server/middlewares/validators/video-comments.ts @@ -4,6 +4,7 @@ import { logger } from '../../helpers' import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' import { isValidVideoCommentText } from '../../helpers/custom-validators/video-comments' import { isVideoExist } from '../../helpers/custom-validators/videos' +import { VideoModel } from '../../models/video/video' import { VideoCommentModel } from '../../models/video/video-comment' import { areValidationErrors } from './utils' @@ -11,7 +12,7 @@ const listVideoCommentThreadsValidator = [ param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) + logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params }) if (areValidationErrors(req, res)) return if (!await isVideoExist(req.params.videoId, res)) return @@ -25,11 +26,11 @@ const listVideoThreadCommentsValidator = [ param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) + logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params }) if (areValidationErrors(req, res)) return if (!await isVideoExist(req.params.videoId, res)) return - if (!await isVideoCommentThreadExist(req.params.threadId, req.params.videoId, res)) return + if (!await isVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return return next() } @@ -40,7 +41,7 @@ const addVideoCommentThreadValidator = [ body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) + logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params }) if (areValidationErrors(req, res)) return if (!await isVideoExist(req.params.videoId, res)) return @@ -55,11 +56,11 @@ const addVideoCommentReplyValidator = [ body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) + logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params }) if (areValidationErrors(req, res)) return if (!await isVideoExist(req.params.videoId, res)) return - if (!await isVideoCommentExist(req.params.commentId, req.params.videoId, res)) return + if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return return next() } @@ -76,7 +77,7 @@ export { // --------------------------------------------------------------------------- -async function isVideoCommentThreadExist (id: number, videoId: number, res: express.Response) { +async function isVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) { const videoComment = await VideoCommentModel.loadById(id) if (!videoComment) { @@ -87,7 +88,7 @@ async function isVideoCommentThreadExist (id: number, videoId: number, res: expr return false } - if (videoComment.videoId !== videoId) { + if (videoComment.videoId !== video.id) { res.status(400) .json({ error: 'Video comment is associated to this video.' }) .end() @@ -107,7 +108,7 @@ async function isVideoCommentThreadExist (id: number, videoId: number, res: expr return true } -async function isVideoCommentExist (id: number, videoId: number, res: express.Response) { +async function isVideoCommentExist (id: number, video: VideoModel, res: express.Response) { const videoComment = await VideoCommentModel.loadById(id) if (!videoComment) { @@ -118,7 +119,7 @@ async function isVideoCommentExist (id: number, videoId: number, res: express.Re return false } - if (videoComment.videoId !== videoId) { + if (videoComment.videoId !== video.id) { res.status(400) .json({ error: 'Video comment is associated to this video.' }) .end() diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index d66f933ee..8e84bfc06 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts @@ -6,18 +6,18 @@ import { import { VideoComment } from '../../../shared/models/videos/video-comment.model' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' import { CONSTRAINTS_FIELDS } from '../../initializers' -import { ActorModel } from '../activitypub/actor' +import { AccountModel } from '../account/account' import { getSort, throwIfNotValid } from '../utils' import { VideoModel } from './video' enum ScopeNames { - WITH_ACTOR = 'WITH_ACTOR' + WITH_ACCOUNT = 'WITH_ACCOUNT' } @Scopes({ - [ScopeNames.WITH_ACTOR]: { + [ScopeNames.WITH_ACCOUNT]: { include: [ - () => ActorModel + () => AccountModel ] } }) @@ -84,17 +84,17 @@ export class VideoCommentModel extends Model { }) Video: VideoModel - @ForeignKey(() => ActorModel) + @ForeignKey(() => AccountModel) @Column - actorId: number + accountId: number - @BelongsTo(() => ActorModel, { + @BelongsTo(() => AccountModel, { foreignKey: { allowNull: false }, onDelete: 'CASCADE' }) - Actor: ActorModel + Account: AccountModel @AfterDestroy static sendDeleteIfOwned (instance: VideoCommentModel) { @@ -132,12 +132,13 @@ export class VideoCommentModel extends Model { limit: count, order: [ getSort(sort) ], where: { - videoId + videoId, + inReplyToCommentId: null } } return VideoCommentModel - .scope([ ScopeNames.WITH_ACTOR ]) + .scope([ ScopeNames.WITH_ACCOUNT ]) .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } @@ -146,7 +147,7 @@ export class VideoCommentModel extends Model { static listThreadCommentsForApi (videoId: number, threadId: number) { const query = { - order: [ 'id', 'ASC' ], + order: [ [ 'id', 'ASC' ] ], where: { videoId, [ Sequelize.Op.or ]: [ @@ -157,7 +158,7 @@ export class VideoCommentModel extends Model { } return VideoCommentModel - .scope([ ScopeNames.WITH_ACTOR ]) + .scope([ ScopeNames.WITH_ACCOUNT ]) .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } @@ -173,7 +174,10 @@ export class VideoCommentModel extends Model { inReplyToCommentId: this.inReplyToCommentId, videoId: this.videoId, createdAt: this.createdAt, - updatedAt: this.updatedAt + updatedAt: this.updatedAt, + account: { + name: this.Account.name + } } as VideoComment } } diff --git a/server/tests/api/index-slow.ts b/server/tests/api/index-slow.ts index 4cd5b09a2..b525d6f01 100644 --- a/server/tests/api/index-slow.ts +++ b/server/tests/api/index-slow.ts @@ -4,3 +4,4 @@ import './video-transcoder' import './multiple-servers' import './follows' import './jobs' +import './video-comments' diff --git a/server/tests/api/video-comments.ts b/server/tests/api/video-comments.ts new file mode 100644 index 000000000..fbc1a0a20 --- /dev/null +++ b/server/tests/api/video-comments.ts @@ -0,0 +1,135 @@ +/* tslint:disable:no-unused-expression */ + +import * as chai from 'chai' +import 'mocha' +import { VideoComment, VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' +import { dateIsValid, flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../utils' +import { addVideoCommentReply, addVideoCommentThread, getVideoCommentThreads, getVideoThreadComments } from '../utils/video-comments' + +const expect = chai.expect + +describe('Test a video comments', function () { + let server: ServerInfo + let videoId + let videoUUID + let threadId + + before(async function () { + this.timeout(10000) + + await flushTests() + + server = await runServer(1) + + await setAccessTokensToServers([ server ]) + + const res = await uploadVideo(server.url, server.accessToken, {}) + videoUUID = res.body.video.uuid + videoId = res.body.video.id + }) + + it('Should not have threads on this video', async function () { + const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) + + expect(res.body.total).to.equal(0) + expect(res.body.data).to.be.an('array') + expect(res.body.data).to.have.lengthOf(0) + }) + + it('Should create a thread in this video', async function () { + const text = 'my super first comment' + + await addVideoCommentThread(server.url, server.accessToken, videoUUID, text) + }) + + it('Should list threads of this video', async function () { + const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5) + + expect(res.body.total).to.equal(1) + expect(res.body.data).to.be.an('array') + expect(res.body.data).to.have.lengthOf(1) + + const comment: VideoComment = res.body.data[0] + expect(comment.inReplyToCommentId).to.be.null + expect(comment.text).equal('my super first comment') + expect(comment.videoId).to.equal(videoId) + expect(comment.id).to.equal(comment.threadId) + expect(comment.account.name).to.equal('root') + expect(dateIsValid(comment.createdAt as string)).to.be.true + expect(dateIsValid(comment.updatedAt as string)).to.be.true + + threadId = comment.threadId + }) + + it('Should get all the thread created', async function () { + const res = await getVideoThreadComments(server.url, videoUUID, threadId) + + const rootComment = res.body.comment + expect(rootComment.inReplyToCommentId).to.be.null + expect(rootComment.text).equal('my super first comment') + expect(rootComment.videoId).to.equal(videoId) + expect(dateIsValid(rootComment.createdAt as string)).to.be.true + expect(dateIsValid(rootComment.updatedAt as string)).to.be.true + }) + + it('Should create multiple replies in this thread', async function () { + const text1 = 'my super answer to thread 1' + const childCommentRes = await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text1) + const childCommentId = childCommentRes.body.comment.id + + const text2 = 'my super answer to answer of thread 1' + await addVideoCommentReply(server.url, server.accessToken, videoId, childCommentId, text2) + + const text3 = 'my second answer to thread 1' + await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text3) + }) + + it('Should get correctly the replies', async function () { + const res = await getVideoThreadComments(server.url, videoUUID, threadId) + + const tree: VideoCommentThreadTree = res.body + expect(tree.comment.text).equal('my super first comment') + expect(tree.children).to.have.lengthOf(2) + + const firstChild = tree.children[0] + expect(firstChild.comment.text).to.equal('my super answer to thread 1') + expect(firstChild.children).to.have.lengthOf(1) + + const childOfFirstChild = firstChild.children[0] + expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') + expect(childOfFirstChild.children).to.have.lengthOf(0) + + const secondChild = tree.children[1] + expect(secondChild.comment.text).to.equal('my second answer to thread 1') + expect(secondChild.children).to.have.lengthOf(0) + }) + + it('Should create other threads', async function () { + const text1 = 'super thread 2' + await addVideoCommentThread(server.url, server.accessToken, videoUUID, text1) + + const text2 = 'super thread 3' + await addVideoCommentThread(server.url, server.accessToken, videoUUID, text2) + }) + + it('Should list the threads', async function () { + const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt') + + expect(res.body.total).to.equal(3) + expect(res.body.data).to.be.an('array') + expect(res.body.data).to.have.lengthOf(3) + + expect(res.body.data[0].text).to.equal('my super first comment') + expect(res.body.data[1].text).to.equal('super thread 2') + expect(res.body.data[2].text).to.equal('super thread 3') + }) + + after(async function () { + killallServers([ server ]) + + // Keep the logs if the test failed + if (this['ok']) { + await flushTests() + } + }) +}) diff --git a/server/tests/utils/video-comments.ts b/server/tests/utils/video-comments.ts new file mode 100644 index 000000000..be062f815 --- /dev/null +++ b/server/tests/utils/video-comments.ts @@ -0,0 +1,64 @@ +import * as request from 'supertest' + +function getVideoCommentThreads (url: string, videoId: number, start: number, count: number, sort?: string) { + const path = '/api/v1/videos/' + videoId + '/comment-threads' + + const req = request(url) + .get(path) + .query({ start: start }) + .query({ count: count }) + + if (sort) req.query({ sort }) + + return req.set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function getVideoThreadComments (url: string, videoId: number, threadId: number) { + const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId + + return request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) +} + +function addVideoCommentThread (url: string, token: string, videoId: number, text: string, expectedStatus = 200) { + const path = '/api/v1/videos/' + videoId + '/comment-threads' + + return request(url) + .post(path) + .send({ text }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +function addVideoCommentReply ( + url: string, + token: string, + videoId: number, + inReplyToCommentId: number, + text: string, + expectedStatus = 200 +) { + const path = '/api/v1/videos/' + videoId + '/comments/' + inReplyToCommentId + + return request(url) + .post(path) + .send({ text }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) +} + +// --------------------------------------------------------------------------- + +export { + getVideoCommentThreads, + getVideoThreadComments, + addVideoCommentThread, + addVideoCommentReply +} diff --git a/shared/models/videos/video-comment.model.ts b/shared/models/videos/video-comment.model.ts index bdeb30d28..69884782f 100644 --- a/shared/models/videos/video-comment.model.ts +++ b/shared/models/videos/video-comment.model.ts @@ -7,11 +7,14 @@ export interface VideoComment { videoId: number createdAt: Date | string updatedAt: Date | string + account: { + name: string + } } -export interface VideoCommentThread { +export interface VideoCommentThreadTree { comment: VideoComment - children: VideoCommentThread[] + children: VideoCommentThreadTree[] } export interface VideoCommentCreate {