aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/controllers/api/videos/comment.ts8
-rw-r--r--server/lib/activitypub/process/process-create.ts4
-rw-r--r--server/lib/video-comment.ts24
-rw-r--r--server/middlewares/validators/video-comments.ts21
-rw-r--r--server/models/video/video-comment.ts30
-rw-r--r--server/tests/api/index-slow.ts1
-rw-r--r--server/tests/api/video-comments.ts135
-rw-r--r--server/tests/utils/video-comments.ts64
-rw-r--r--shared/models/videos/video-comment.model.ts7
9 files changed, 252 insertions, 42 deletions
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) {
78 return sequelizeTypescript.transaction(async t => { 78 return sequelizeTypescript.transaction(async t => {
79 return createVideoComment({ 79 return createVideoComment({
80 text: videoCommentInfo.text, 80 text: videoCommentInfo.text,
81 inReplyToComment: null, 81 inReplyToCommentId: null,
82 video: res.locals.video, 82 video: res.locals.video,
83 actorId: res.locals.oauth.token.User.Account.Actor.id 83 accountId: res.locals.oauth.token.User.Account.id
84 }, t) 84 }, t)
85 }) 85 })
86} 86}
@@ -106,9 +106,9 @@ function addVideoCommentReply (req: express.Request, res: express.Response, next
106 return sequelizeTypescript.transaction(async t => { 106 return sequelizeTypescript.transaction(async t => {
107 return createVideoComment({ 107 return createVideoComment({
108 text: videoCommentInfo.text, 108 text: videoCommentInfo.text,
109 inReplyToComment: res.locals.videoComment.id, 109 inReplyToCommentId: res.locals.videoComment.id,
110 video: res.locals.video, 110 video: res.locals.video,
111 actorId: res.locals.oauth.token.User.Account.Actor.id 111 accountId: res.locals.oauth.token.User.Account.id
112 }, t) 112 }, t)
113 }) 113 })
114} 114}
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) {
267 originCommentId: null, 267 originCommentId: null,
268 inReplyToComment: null, 268 inReplyToComment: null,
269 videoId: video.id, 269 videoId: video.id,
270 actorId: byActor.id 270 accountId: byAccount.id
271 }, { transaction: t }) 271 }, { transaction: t })
272 } 272 }
273 273
@@ -281,7 +281,7 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) {
281 originCommentId, 281 originCommentId,
282 inReplyToCommentId: inReplyToComment.id, 282 inReplyToCommentId: inReplyToComment.id,
283 videoId: inReplyToComment.videoId, 283 videoId: inReplyToComment.videoId,
284 actorId: byActor.id 284 accountId: byAccount.id
285 }, { transaction: t }) 285 }, { transaction: t })
286 }) 286 })
287} 287}
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 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { ResultList } from '../../shared/models' 2import { ResultList } from '../../shared/models'
3import { VideoCommentThread } from '../../shared/models/videos/video-comment.model' 3import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model'
4import { VideoModel } from '../models/video/video' 4import { VideoModel } from '../models/video/video'
5import { VideoCommentModel } from '../models/video/video-comment' 5import { VideoCommentModel } from '../models/video/video-comment'
6import { getVideoCommentActivityPubUrl } from './activitypub' 6import { getVideoCommentActivityPubUrl } from './activitypub'
7 7
8async function createVideoComment (obj: { 8async function createVideoComment (obj: {
9 text: string, 9 text: string,
10 inReplyToComment: number, 10 inReplyToCommentId: number,
11 video: VideoModel 11 video: VideoModel
12 actorId: number 12 accountId: number
13}, t: Sequelize.Transaction) { 13}, t: Sequelize.Transaction) {
14 let originCommentId: number = null 14 let originCommentId: number = null
15 if (obj.inReplyToComment) { 15
16 const repliedComment = await VideoCommentModel.loadById(obj.inReplyToComment) 16 if (obj.inReplyToCommentId) {
17 const repliedComment = await VideoCommentModel.loadById(obj.inReplyToCommentId)
17 if (!repliedComment) throw new Error('Unknown replied comment.') 18 if (!repliedComment) throw new Error('Unknown replied comment.')
18 19
19 originCommentId = repliedComment.originCommentId || repliedComment.id 20 originCommentId = repliedComment.originCommentId || repliedComment.id
@@ -22,22 +23,23 @@ async function createVideoComment (obj: {
22 const comment = await VideoCommentModel.create({ 23 const comment = await VideoCommentModel.create({
23 text: obj.text, 24 text: obj.text,
24 originCommentId, 25 originCommentId,
25 inReplyToComment: obj.inReplyToComment, 26 inReplyToCommentId: obj.inReplyToCommentId,
26 videoId: obj.video.id, 27 videoId: obj.video.id,
27 actorId: obj.actorId 28 accountId: obj.accountId,
28 }, { transaction: t }) 29 url: 'fake url'
30 }, { transaction: t, validate: false })
29 31
30 comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment)) 32 comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment))
31 33
32 return comment.save({ transaction: t }) 34 return comment.save({ transaction: t })
33} 35}
34 36
35function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThread { 37function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThreadTree {
36 // Comments are sorted by id ASC 38 // Comments are sorted by id ASC
37 const comments = resultList.data 39 const comments = resultList.data
38 40
39 const comment = comments.shift() 41 const comment = comments.shift()
40 const thread: VideoCommentThread = { 42 const thread: VideoCommentThreadTree = {
41 comment: comment.toFormattedJSON(), 43 comment: comment.toFormattedJSON(),
42 children: [] 44 children: []
43 } 45 }
@@ -48,7 +50,7 @@ function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>):
48 while (comments.length !== 0) { 50 while (comments.length !== 0) {
49 const childComment = comments.shift() 51 const childComment = comments.shift()
50 52
51 const childCommentThread: VideoCommentThread = { 53 const childCommentThread: VideoCommentThreadTree = {
52 comment: childComment.toFormattedJSON(), 54 comment: childComment.toFormattedJSON(),
53 children: [] 55 children: []
54 } 56 }
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'
4import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' 4import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
5import { isValidVideoCommentText } from '../../helpers/custom-validators/video-comments' 5import { isValidVideoCommentText } from '../../helpers/custom-validators/video-comments'
6import { isVideoExist } from '../../helpers/custom-validators/videos' 6import { isVideoExist } from '../../helpers/custom-validators/videos'
7import { VideoModel } from '../../models/video/video'
7import { VideoCommentModel } from '../../models/video/video-comment' 8import { VideoCommentModel } from '../../models/video/video-comment'
8import { areValidationErrors } from './utils' 9import { areValidationErrors } from './utils'
9 10
@@ -11,7 +12,7 @@ const listVideoCommentThreadsValidator = [
11 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 12 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
12 13
13 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 14 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
14 logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) 15 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
15 16
16 if (areValidationErrors(req, res)) return 17 if (areValidationErrors(req, res)) return
17 if (!await isVideoExist(req.params.videoId, res)) return 18 if (!await isVideoExist(req.params.videoId, res)) return
@@ -25,11 +26,11 @@ const listVideoThreadCommentsValidator = [
25 param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'), 26 param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
26 27
27 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 28 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
28 logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) 29 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
29 30
30 if (areValidationErrors(req, res)) return 31 if (areValidationErrors(req, res)) return
31 if (!await isVideoExist(req.params.videoId, res)) return 32 if (!await isVideoExist(req.params.videoId, res)) return
32 if (!await isVideoCommentThreadExist(req.params.threadId, req.params.videoId, res)) return 33 if (!await isVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return
33 34
34 return next() 35 return next()
35 } 36 }
@@ -40,7 +41,7 @@ const addVideoCommentThreadValidator = [
40 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), 41 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
41 42
42 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 43 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
43 logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) 44 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params })
44 45
45 if (areValidationErrors(req, res)) return 46 if (areValidationErrors(req, res)) return
46 if (!await isVideoExist(req.params.videoId, res)) return 47 if (!await isVideoExist(req.params.videoId, res)) return
@@ -55,11 +56,11 @@ const addVideoCommentReplyValidator = [
55 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), 56 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
56 57
57 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 58 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
58 logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) 59 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params })
59 60
60 if (areValidationErrors(req, res)) return 61 if (areValidationErrors(req, res)) return
61 if (!await isVideoExist(req.params.videoId, res)) return 62 if (!await isVideoExist(req.params.videoId, res)) return
62 if (!await isVideoCommentExist(req.params.commentId, req.params.videoId, res)) return 63 if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return
63 64
64 return next() 65 return next()
65 } 66 }
@@ -76,7 +77,7 @@ export {
76 77
77// --------------------------------------------------------------------------- 78// ---------------------------------------------------------------------------
78 79
79async function isVideoCommentThreadExist (id: number, videoId: number, res: express.Response) { 80async function isVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) {
80 const videoComment = await VideoCommentModel.loadById(id) 81 const videoComment = await VideoCommentModel.loadById(id)
81 82
82 if (!videoComment) { 83 if (!videoComment) {
@@ -87,7 +88,7 @@ async function isVideoCommentThreadExist (id: number, videoId: number, res: expr
87 return false 88 return false
88 } 89 }
89 90
90 if (videoComment.videoId !== videoId) { 91 if (videoComment.videoId !== video.id) {
91 res.status(400) 92 res.status(400)
92 .json({ error: 'Video comment is associated to this video.' }) 93 .json({ error: 'Video comment is associated to this video.' })
93 .end() 94 .end()
@@ -107,7 +108,7 @@ async function isVideoCommentThreadExist (id: number, videoId: number, res: expr
107 return true 108 return true
108} 109}
109 110
110async function isVideoCommentExist (id: number, videoId: number, res: express.Response) { 111async function isVideoCommentExist (id: number, video: VideoModel, res: express.Response) {
111 const videoComment = await VideoCommentModel.loadById(id) 112 const videoComment = await VideoCommentModel.loadById(id)
112 113
113 if (!videoComment) { 114 if (!videoComment) {
@@ -118,7 +119,7 @@ async function isVideoCommentExist (id: number, videoId: number, res: express.Re
118 return false 119 return false
119 } 120 }
120 121
121 if (videoComment.videoId !== videoId) { 122 if (videoComment.videoId !== video.id) {
122 res.status(400) 123 res.status(400)
123 .json({ error: 'Video comment is associated to this video.' }) 124 .json({ error: 'Video comment is associated to this video.' })
124 .end() 125 .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 {
6import { VideoComment } from '../../../shared/models/videos/video-comment.model' 6import { VideoComment } from '../../../shared/models/videos/video-comment.model'
7import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' 7import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub'
8import { CONSTRAINTS_FIELDS } from '../../initializers' 8import { CONSTRAINTS_FIELDS } from '../../initializers'
9import { ActorModel } from '../activitypub/actor' 9import { AccountModel } from '../account/account'
10import { getSort, throwIfNotValid } from '../utils' 10import { getSort, throwIfNotValid } from '../utils'
11import { VideoModel } from './video' 11import { VideoModel } from './video'
12 12
13enum ScopeNames { 13enum ScopeNames {
14 WITH_ACTOR = 'WITH_ACTOR' 14 WITH_ACCOUNT = 'WITH_ACCOUNT'
15} 15}
16 16
17@Scopes({ 17@Scopes({
18 [ScopeNames.WITH_ACTOR]: { 18 [ScopeNames.WITH_ACCOUNT]: {
19 include: [ 19 include: [
20 () => ActorModel 20 () => AccountModel
21 ] 21 ]
22 } 22 }
23}) 23})
@@ -84,17 +84,17 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
84 }) 84 })
85 Video: VideoModel 85 Video: VideoModel
86 86
87 @ForeignKey(() => ActorModel) 87 @ForeignKey(() => AccountModel)
88 @Column 88 @Column
89 actorId: number 89 accountId: number
90 90
91 @BelongsTo(() => ActorModel, { 91 @BelongsTo(() => AccountModel, {
92 foreignKey: { 92 foreignKey: {
93 allowNull: false 93 allowNull: false
94 }, 94 },
95 onDelete: 'CASCADE' 95 onDelete: 'CASCADE'
96 }) 96 })
97 Actor: ActorModel 97 Account: AccountModel
98 98
99 @AfterDestroy 99 @AfterDestroy
100 static sendDeleteIfOwned (instance: VideoCommentModel) { 100 static sendDeleteIfOwned (instance: VideoCommentModel) {
@@ -132,12 +132,13 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
132 limit: count, 132 limit: count,
133 order: [ getSort(sort) ], 133 order: [ getSort(sort) ],
134 where: { 134 where: {
135 videoId 135 videoId,
136 inReplyToCommentId: null
136 } 137 }
137 } 138 }
138 139
139 return VideoCommentModel 140 return VideoCommentModel
140 .scope([ ScopeNames.WITH_ACTOR ]) 141 .scope([ ScopeNames.WITH_ACCOUNT ])
141 .findAndCountAll(query) 142 .findAndCountAll(query)
142 .then(({ rows, count }) => { 143 .then(({ rows, count }) => {
143 return { total: count, data: rows } 144 return { total: count, data: rows }
@@ -146,7 +147,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
146 147
147 static listThreadCommentsForApi (videoId: number, threadId: number) { 148 static listThreadCommentsForApi (videoId: number, threadId: number) {
148 const query = { 149 const query = {
149 order: [ 'id', 'ASC' ], 150 order: [ [ 'id', 'ASC' ] ],
150 where: { 151 where: {
151 videoId, 152 videoId,
152 [ Sequelize.Op.or ]: [ 153 [ Sequelize.Op.or ]: [
@@ -157,7 +158,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
157 } 158 }
158 159
159 return VideoCommentModel 160 return VideoCommentModel
160 .scope([ ScopeNames.WITH_ACTOR ]) 161 .scope([ ScopeNames.WITH_ACCOUNT ])
161 .findAndCountAll(query) 162 .findAndCountAll(query)
162 .then(({ rows, count }) => { 163 .then(({ rows, count }) => {
163 return { total: count, data: rows } 164 return { total: count, data: rows }
@@ -173,7 +174,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
173 inReplyToCommentId: this.inReplyToCommentId, 174 inReplyToCommentId: this.inReplyToCommentId,
174 videoId: this.videoId, 175 videoId: this.videoId,
175 createdAt: this.createdAt, 176 createdAt: this.createdAt,
176 updatedAt: this.updatedAt 177 updatedAt: this.updatedAt,
178 account: {
179 name: this.Account.name
180 }
177 } as VideoComment 181 } as VideoComment
178 } 182 }
179} 183}
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'
4import './multiple-servers' 4import './multiple-servers'
5import './follows' 5import './follows'
6import './jobs' 6import './jobs'
7import './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 @@
1/* tslint:disable:no-unused-expression */
2
3import * as chai from 'chai'
4import 'mocha'
5import { VideoComment, VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
6import { dateIsValid, flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../utils'
7import { addVideoCommentReply, addVideoCommentThread, getVideoCommentThreads, getVideoThreadComments } from '../utils/video-comments'
8
9const expect = chai.expect
10
11describe('Test a video comments', function () {
12 let server: ServerInfo
13 let videoId
14 let videoUUID
15 let threadId
16
17 before(async function () {
18 this.timeout(10000)
19
20 await flushTests()
21
22 server = await runServer(1)
23
24 await setAccessTokensToServers([ server ])
25
26 const res = await uploadVideo(server.url, server.accessToken, {})
27 videoUUID = res.body.video.uuid
28 videoId = res.body.video.id
29 })
30
31 it('Should not have threads on this video', async function () {
32 const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
33
34 expect(res.body.total).to.equal(0)
35 expect(res.body.data).to.be.an('array')
36 expect(res.body.data).to.have.lengthOf(0)
37 })
38
39 it('Should create a thread in this video', async function () {
40 const text = 'my super first comment'
41
42 await addVideoCommentThread(server.url, server.accessToken, videoUUID, text)
43 })
44
45 it('Should list threads of this video', async function () {
46 const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
47
48 expect(res.body.total).to.equal(1)
49 expect(res.body.data).to.be.an('array')
50 expect(res.body.data).to.have.lengthOf(1)
51
52 const comment: VideoComment = res.body.data[0]
53 expect(comment.inReplyToCommentId).to.be.null
54 expect(comment.text).equal('my super first comment')
55 expect(comment.videoId).to.equal(videoId)
56 expect(comment.id).to.equal(comment.threadId)
57 expect(comment.account.name).to.equal('root')
58 expect(dateIsValid(comment.createdAt as string)).to.be.true
59 expect(dateIsValid(comment.updatedAt as string)).to.be.true
60
61 threadId = comment.threadId
62 })
63
64 it('Should get all the thread created', async function () {
65 const res = await getVideoThreadComments(server.url, videoUUID, threadId)
66
67 const rootComment = res.body.comment
68 expect(rootComment.inReplyToCommentId).to.be.null
69 expect(rootComment.text).equal('my super first comment')
70 expect(rootComment.videoId).to.equal(videoId)
71 expect(dateIsValid(rootComment.createdAt as string)).to.be.true
72 expect(dateIsValid(rootComment.updatedAt as string)).to.be.true
73 })
74
75 it('Should create multiple replies in this thread', async function () {
76 const text1 = 'my super answer to thread 1'
77 const childCommentRes = await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text1)
78 const childCommentId = childCommentRes.body.comment.id
79
80 const text2 = 'my super answer to answer of thread 1'
81 await addVideoCommentReply(server.url, server.accessToken, videoId, childCommentId, text2)
82
83 const text3 = 'my second answer to thread 1'
84 await addVideoCommentReply(server.url, server.accessToken, videoId, threadId, text3)
85 })
86
87 it('Should get correctly the replies', async function () {
88 const res = await getVideoThreadComments(server.url, videoUUID, threadId)
89
90 const tree: VideoCommentThreadTree = res.body
91 expect(tree.comment.text).equal('my super first comment')
92 expect(tree.children).to.have.lengthOf(2)
93
94 const firstChild = tree.children[0]
95 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
96 expect(firstChild.children).to.have.lengthOf(1)
97
98 const childOfFirstChild = firstChild.children[0]
99 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
100 expect(childOfFirstChild.children).to.have.lengthOf(0)
101
102 const secondChild = tree.children[1]
103 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
104 expect(secondChild.children).to.have.lengthOf(0)
105 })
106
107 it('Should create other threads', async function () {
108 const text1 = 'super thread 2'
109 await addVideoCommentThread(server.url, server.accessToken, videoUUID, text1)
110
111 const text2 = 'super thread 3'
112 await addVideoCommentThread(server.url, server.accessToken, videoUUID, text2)
113 })
114
115 it('Should list the threads', async function () {
116 const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5, 'createdAt')
117
118 expect(res.body.total).to.equal(3)
119 expect(res.body.data).to.be.an('array')
120 expect(res.body.data).to.have.lengthOf(3)
121
122 expect(res.body.data[0].text).to.equal('my super first comment')
123 expect(res.body.data[1].text).to.equal('super thread 2')
124 expect(res.body.data[2].text).to.equal('super thread 3')
125 })
126
127 after(async function () {
128 killallServers([ server ])
129
130 // Keep the logs if the test failed
131 if (this['ok']) {
132 await flushTests()
133 }
134 })
135})
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 @@
1import * as request from 'supertest'
2
3function getVideoCommentThreads (url: string, videoId: number, start: number, count: number, sort?: string) {
4 const path = '/api/v1/videos/' + videoId + '/comment-threads'
5
6 const req = request(url)
7 .get(path)
8 .query({ start: start })
9 .query({ count: count })
10
11 if (sort) req.query({ sort })
12
13 return req.set('Accept', 'application/json')
14 .expect(200)
15 .expect('Content-Type', /json/)
16}
17
18function getVideoThreadComments (url: string, videoId: number, threadId: number) {
19 const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId
20
21 return request(url)
22 .get(path)
23 .set('Accept', 'application/json')
24 .expect(200)
25 .expect('Content-Type', /json/)
26}
27
28function addVideoCommentThread (url: string, token: string, videoId: number, text: string, expectedStatus = 200) {
29 const path = '/api/v1/videos/' + videoId + '/comment-threads'
30
31 return request(url)
32 .post(path)
33 .send({ text })
34 .set('Accept', 'application/json')
35 .set('Authorization', 'Bearer ' + token)
36 .expect(expectedStatus)
37}
38
39function addVideoCommentReply (
40 url: string,
41 token: string,
42 videoId: number,
43 inReplyToCommentId: number,
44 text: string,
45 expectedStatus = 200
46) {
47 const path = '/api/v1/videos/' + videoId + '/comments/' + inReplyToCommentId
48
49 return request(url)
50 .post(path)
51 .send({ text })
52 .set('Accept', 'application/json')
53 .set('Authorization', 'Bearer ' + token)
54 .expect(expectedStatus)
55}
56
57// ---------------------------------------------------------------------------
58
59export {
60 getVideoCommentThreads,
61 getVideoThreadComments,
62 addVideoCommentThread,
63 addVideoCommentReply
64}
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 {
7 videoId: number 7 videoId: number
8 createdAt: Date | string 8 createdAt: Date | string
9 updatedAt: Date | string 9 updatedAt: Date | string
10 account: {
11 name: string
12 }
10} 13}
11 14
12export interface VideoCommentThread { 15export interface VideoCommentThreadTree {
13 comment: VideoComment 16 comment: VideoComment
14 children: VideoCommentThread[] 17 children: VideoCommentThreadTree[]
15} 18}
16 19
17export interface VideoCommentCreate { 20export interface VideoCommentCreate {