Begin unit tests
authorChocobozzz <me@florianbigard.com>
Fri, 22 Dec 2017 11:10:40 +0000 (12:10 +0100)
committerChocobozzz <me@florianbigard.com>
Fri, 22 Dec 2017 11:12:33 +0000 (12:12 +0100)
server/controllers/api/videos/comment.ts
server/lib/activitypub/process/process-create.ts
server/lib/video-comment.ts
server/middlewares/validators/video-comments.ts
server/models/video/video-comment.ts
server/tests/api/index-slow.ts
server/tests/api/video-comments.ts [new file with mode: 0644]
server/tests/utils/video-comments.ts [new file with mode: 0644]
shared/models/videos/video-comment.model.ts

index 81c9e7d161b689d6ff4b064a427982bd716edef5..ac64f0154598dc10873c558c2c58196ff42a2ed8 100644 (file)
@@ -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)
   })
 }
index 102e54b19c34ad8dba729ddacf62c5958a2306bf..6c2ee97ebe09405bb004a27586c1135f77b038f7 100644 (file)
@@ -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 })
   })
 }
index edb72d4e2c9698f7ed16c3a30be77d781d957128..e3fe26e355d2d8851b0bdf3a5697ed0ab5a0263c 100644 (file)
@@ -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<VideoCommentModel>): VideoCommentThread {
+function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): 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<VideoCommentModel>):
   while (comments.length !== 0) {
     const childComment = comments.shift()
 
-    const childCommentThread: VideoCommentThread = {
+    const childCommentThread: VideoCommentThreadTree = {
       comment: childComment.toFormattedJSON(),
       children: []
     }
index 5e1be00f23c187791114e817b0d6bcdb6092283b..1d19fac58fd9c79d5b7c610feba2e512529ccc24 100644 (file)
@@ -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()
index d66f933ee714fd7ebffe7083882d6bf1f9aeea0b..8e84bfc06ab0603429b4811c8837a73c8f93136f 100644 (file)
@@ -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<VideoCommentModel> {
   })
   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<VideoCommentModel> {
       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<VideoCommentModel> {
 
   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<VideoCommentModel> {
     }
 
     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<VideoCommentModel> {
       inReplyToCommentId: this.inReplyToCommentId,
       videoId: this.videoId,
       createdAt: this.createdAt,
-      updatedAt: this.updatedAt
+      updatedAt: this.updatedAt,
+      account: {
+        name: this.Account.name
+      }
     } as VideoComment
   }
 }
index 4cd5b09a2bc52d6eadd31cff62818f1ecc9f553e..b525d6f01277878ef8285075761ef3a61ac58e63 100644 (file)
@@ -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 (file)
index 0000000..fbc1a0a
--- /dev/null
@@ -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 (file)
index 0000000..be062f8
--- /dev/null
@@ -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
+}
index bdeb30d28cdd1df5d0be87eb22cc228dad79c217..69884782fbf6264d17b83b5e4f6c6ca3d268243c 100644 (file)
@@ -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 {