Implement video channel views
[oweals/peertube.git] / server / tests / api / videos / multiple-servers.ts
1 /* tslint:disable:no-unused-expression */
2
3 import * as chai from 'chai'
4 import 'mocha'
5 import { join } from 'path'
6 import * as request from 'supertest'
7 import { VideoPrivacy } from '../../../../shared/models/videos'
8 import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
9
10 import {
11   addVideoChannel,
12   checkVideoFilesWereRemoved,
13   completeVideoCheck,
14   createUser,
15   dateIsValid,
16   doubleFollow,
17   flushAndRunMultipleServers,
18   flushTests, getLocalVideos,
19   getVideo,
20   getVideoChannelsList,
21   getVideosList,
22   killallServers,
23   rateVideo,
24   removeVideo,
25   ServerInfo,
26   setAccessTokensToServers,
27   testImage,
28   updateVideo,
29   uploadVideo,
30   userLogin,
31   viewVideo,
32   wait,
33   webtorrentAdd
34 } from '../../utils'
35 import {
36   addVideoCommentReply,
37   addVideoCommentThread,
38   deleteVideoComment,
39   getVideoCommentThreads,
40   getVideoThreadComments
41 } from '../../utils/videos/video-comments'
42 import { getAccountsList } from '../../utils/users/accounts'
43
44 const expect = chai.expect
45
46 describe('Test multiple servers', function () {
47   let servers: ServerInfo[] = []
48   const toRemove = []
49   let videoUUID = ''
50   let videoChannelId: number
51
52   before(async function () {
53     this.timeout(120000)
54
55     servers = await flushAndRunMultipleServers(3)
56
57     // Get the access tokens
58     await setAccessTokensToServers(servers)
59
60     {
61       const videoChannel = {
62         name: 'my channel',
63         description: 'super channel'
64       }
65       await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel)
66       const channelRes = await getVideoChannelsList(servers[ 0 ].url, 0, 1)
67       videoChannelId = channelRes.body.data[ 0 ].id
68     }
69
70     // Server 1 and server 2 follow each other
71     await doubleFollow(servers[0], servers[1])
72     // Server 1 and server 3 follow each other
73     await doubleFollow(servers[0], servers[2])
74     // Server 2 and server 3 follow each other
75     await doubleFollow(servers[1], servers[2])
76   })
77
78   it('Should not have videos for all servers', async function () {
79     for (const server of servers) {
80       const res = await getVideosList(server.url)
81       const videos = res.body.data
82       expect(videos).to.be.an('array')
83       expect(videos.length).to.equal(0)
84     }
85   })
86
87   describe('Should upload the video and propagate on each server', function () {
88     it('Should upload the video on server 1 and propagate on each server', async function () {
89       this.timeout(25000)
90
91       const videoAttributes = {
92         name: 'my super name for server 1',
93         category: 5,
94         licence: 4,
95         language: 'ja',
96         nsfw: true,
97         description: 'my super description for server 1',
98         support: 'my super support text for server 1',
99         tags: [ 'tag1p1', 'tag2p1' ],
100         channelId: videoChannelId,
101         fixture: 'video_short1.webm'
102       }
103       await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
104
105       await wait(10000)
106
107       // All servers should have this video
108       for (const server of servers) {
109         const isLocal = server.url === 'http://localhost:9001'
110         const checkAttributes = {
111           name: 'my super name for server 1',
112           category: 5,
113           licence: 4,
114           language: 'ja',
115           nsfw: true,
116           description: 'my super description for server 1',
117           support: 'my super support text for server 1',
118           account: {
119             name: 'root',
120             host: 'localhost:9001'
121           },
122           isLocal,
123           duration: 10,
124           tags: [ 'tag1p1', 'tag2p1' ],
125           privacy: VideoPrivacy.PUBLIC,
126           commentsEnabled: true,
127           channel: {
128             name: 'my channel',
129             description: 'super channel',
130             isLocal
131           },
132           fixture: 'video_short1.webm',
133           files: [
134             {
135               resolution: 720,
136               size: 572456
137             }
138           ]
139         }
140
141         const res = await getVideosList(server.url)
142         const videos = res.body.data
143         expect(videos).to.be.an('array')
144         expect(videos.length).to.equal(1)
145         const video = videos[0]
146
147         await completeVideoCheck(server.url, video, checkAttributes)
148       }
149     })
150
151     it('Should upload the video on server 2 and propagate on each server', async function () {
152       this.timeout(50000)
153
154       const user = {
155         username: 'user1',
156         password: 'super_password'
157       }
158       await createUser(servers[1].url, servers[1].accessToken, user.username, user.password)
159       const userAccessToken = await userLogin(servers[1], user)
160
161       const videoAttributes = {
162         name: 'my super name for server 2',
163         category: 4,
164         licence: 3,
165         language: 'de',
166         nsfw: true,
167         description: 'my super description for server 2',
168         support: 'my super support text for server 2',
169         tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
170         fixture: 'video_short2.webm',
171         thumbnailfile: 'thumbnail.jpg',
172         previewfile: 'preview.jpg'
173       }
174       await uploadVideo(servers[1].url, userAccessToken, videoAttributes)
175
176       // Transcoding
177       await wait(30000)
178
179       // All servers should have this video
180       for (const server of servers) {
181         const isLocal = server.url === 'http://localhost:9002'
182         const checkAttributes = {
183           name: 'my super name for server 2',
184           category: 4,
185           licence: 3,
186           language: 'de',
187           nsfw: true,
188           description: 'my super description for server 2',
189           support: 'my super support text for server 2',
190           account: {
191             name: 'user1',
192             host: 'localhost:9002'
193           },
194           isLocal,
195           commentsEnabled: true,
196           duration: 5,
197           tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
198           privacy: VideoPrivacy.PUBLIC,
199           channel: {
200             name: 'Default user1 channel',
201             description: 'super channel',
202             isLocal
203           },
204           fixture: 'video_short2.webm',
205           files: [
206             {
207               resolution: 240,
208               size: 190000
209             },
210             {
211               resolution: 360,
212               size: 280000
213             },
214             {
215               resolution: 480,
216               size: 390000
217             },
218             {
219               resolution: 720,
220               size: 710000
221             }
222           ],
223           thumbnailfile: 'thumbnail',
224           previewfile: 'preview'
225         }
226
227         const res = await getVideosList(server.url)
228         const videos = res.body.data
229         expect(videos).to.be.an('array')
230         expect(videos.length).to.equal(2)
231         const video = videos[1]
232
233         await completeVideoCheck(server.url, video, checkAttributes)
234       }
235     })
236
237     it('Should upload two videos on server 3 and propagate on each server', async function () {
238       this.timeout(45000)
239
240       const videoAttributes1 = {
241         name: 'my super name for server 3',
242         category: 6,
243         licence: 5,
244         language: 'de',
245         nsfw: true,
246         description: 'my super description for server 3',
247         support: 'my super support text for server 3',
248         tags: [ 'tag1p3' ],
249         fixture: 'video_short3.webm'
250       }
251       await uploadVideo(servers[2].url, servers[2].accessToken, videoAttributes1)
252
253       const videoAttributes2 = {
254         name: 'my super name for server 3-2',
255         category: 7,
256         licence: 6,
257         language: 'ko',
258         nsfw: false,
259         description: 'my super description for server 3-2',
260         support: 'my super support text for server 3-2',
261         tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
262         fixture: 'video_short.webm'
263       }
264       await uploadVideo(servers[2].url, servers[2].accessToken, videoAttributes2)
265
266       await wait(10000)
267
268       // All servers should have this video
269       for (const server of servers) {
270         const isLocal = server.url === 'http://localhost:9003'
271         const res = await getVideosList(server.url)
272
273         const videos = res.body.data
274         expect(videos).to.be.an('array')
275         expect(videos.length).to.equal(4)
276
277         // We not sure about the order of the two last uploads
278         let video1 = null
279         let video2 = null
280         if (videos[2].name === 'my super name for server 3') {
281           video1 = videos[2]
282           video2 = videos[3]
283         } else {
284           video1 = videos[3]
285           video2 = videos[2]
286         }
287
288         const checkAttributesVideo1 = {
289           name: 'my super name for server 3',
290           category: 6,
291           licence: 5,
292           language: 'de',
293           nsfw: true,
294           description: 'my super description for server 3',
295           support: 'my super support text for server 3',
296           account: {
297             name: 'root',
298             host: 'localhost:9003'
299           },
300           isLocal,
301           duration: 5,
302           commentsEnabled: true,
303           tags: [ 'tag1p3' ],
304           privacy: VideoPrivacy.PUBLIC,
305           channel: {
306             name: 'Default root channel',
307             description: '',
308             isLocal
309           },
310           fixture: 'video_short3.webm',
311           files: [
312             {
313               resolution: 720,
314               size: 292677
315             }
316           ]
317         }
318         await completeVideoCheck(server.url, video1, checkAttributesVideo1)
319
320         const checkAttributesVideo2 = {
321           name: 'my super name for server 3-2',
322           category: 7,
323           licence: 6,
324           language: 'ko',
325           nsfw: false,
326           description: 'my super description for server 3-2',
327           support: 'my super support text for server 3-2',
328           account: {
329             name: 'root',
330             host: 'localhost:9003'
331           },
332           commentsEnabled: true,
333           isLocal,
334           duration: 5,
335           tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
336           privacy: VideoPrivacy.PUBLIC,
337           channel: {
338             name: 'Default root channel',
339             description: '',
340             isLocal
341           },
342           fixture: 'video_short.webm',
343           files: [
344             {
345               resolution: 720,
346               size: 218910
347             }
348           ]
349         }
350         await completeVideoCheck(server.url, video2, checkAttributesVideo2)
351       }
352     })
353   })
354
355   describe('It should list local videos', function () {
356     it('Should list only local videos on server 1', async function () {
357       const { body } = await getLocalVideos(servers[0].url)
358
359       expect(body.total).to.equal(1)
360       expect(body.data).to.be.an('array')
361       expect(body.data.length).to.equal(1)
362       expect(body.data[0].name).to.equal('my super name for server 1')
363     })
364
365     it('Should list only local videos on server 2', async function () {
366       const { body } = await getLocalVideos(servers[1].url)
367
368       expect(body.total).to.equal(1)
369       expect(body.data).to.be.an('array')
370       expect(body.data.length).to.equal(1)
371       expect(body.data[0].name).to.equal('my super name for server 2')
372     })
373
374     it('Should list only local videos on server 3', async function () {
375       const { body } = await getLocalVideos(servers[2].url)
376
377       expect(body.total).to.equal(2)
378       expect(body.data).to.be.an('array')
379       expect(body.data.length).to.equal(2)
380       expect(body.data[0].name).to.equal('my super name for server 3')
381       expect(body.data[1].name).to.equal('my super name for server 3-2')
382     })
383   })
384
385   describe('Should seed the uploaded video', function () {
386     it('Should add the file 1 by asking server 3', async function () {
387       this.timeout(10000)
388
389       const res = await getVideosList(servers[2].url)
390
391       const video = res.body.data[0]
392       toRemove.push(res.body.data[2])
393       toRemove.push(res.body.data[3])
394
395       const res2 = await getVideo(servers[2].url, video.id)
396       const videoDetails = res2.body
397
398       const torrent = await webtorrentAdd(videoDetails.files[0].magnetUri, true)
399       expect(torrent.files).to.be.an('array')
400       expect(torrent.files.length).to.equal(1)
401       expect(torrent.files[0].path).to.exist.and.to.not.equal('')
402     })
403
404     it('Should add the file 2 by asking server 1', async function () {
405       this.timeout(10000)
406
407       const res = await getVideosList(servers[0].url)
408
409       const video = res.body.data[1]
410       const res2 = await getVideo(servers[0].url, video.id)
411       const videoDetails = res2.body
412
413       const torrent = await webtorrentAdd(videoDetails.files[0].magnetUri, true)
414       expect(torrent.files).to.be.an('array')
415       expect(torrent.files.length).to.equal(1)
416       expect(torrent.files[0].path).to.exist.and.to.not.equal('')
417     })
418
419     it('Should add the file 3 by asking server 2', async function () {
420       this.timeout(10000)
421
422       const res = await getVideosList(servers[1].url)
423
424       const video = res.body.data[2]
425       const res2 = await getVideo(servers[1].url, video.id)
426       const videoDetails = res2.body
427
428       const torrent = await webtorrentAdd(videoDetails.files[0].magnetUri, true)
429       expect(torrent.files).to.be.an('array')
430       expect(torrent.files.length).to.equal(1)
431       expect(torrent.files[0].path).to.exist.and.to.not.equal('')
432     })
433
434     it('Should add the file 3-2 by asking server 1', async function () {
435       this.timeout(10000)
436
437       const res = await getVideosList(servers[0].url)
438
439       const video = res.body.data[3]
440       const res2 = await getVideo(servers[0].url, video.id)
441       const videoDetails = res2.body
442
443       const torrent = await webtorrentAdd(videoDetails.files[0].magnetUri)
444       expect(torrent.files).to.be.an('array')
445       expect(torrent.files.length).to.equal(1)
446       expect(torrent.files[0].path).to.exist.and.to.not.equal('')
447     })
448
449     it('Should add the file 2 in 360p by asking server 1', async function () {
450       this.timeout(10000)
451
452       const res = await getVideosList(servers[0].url)
453
454       const video = res.body.data.find(v => v.name === 'my super name for server 2')
455       const res2 = await getVideo(servers[0].url, video.id)
456       const videoDetails = res2.body
457
458       const file = videoDetails.files.find(f => f.resolution.id === 360)
459       expect(file).not.to.be.undefined
460
461       const torrent = await webtorrentAdd(file.magnetUri)
462       expect(torrent.files).to.be.an('array')
463       expect(torrent.files.length).to.equal(1)
464       expect(torrent.files[0].path).to.exist.and.to.not.equal('')
465     })
466   })
467
468   describe('Should update video views, likes and dislikes', function () {
469     let localVideosServer3 = []
470     let remoteVideosServer1 = []
471     let remoteVideosServer2 = []
472     let remoteVideosServer3 = []
473
474     before(async function () {
475       const res1 = await getVideosList(servers[0].url)
476       remoteVideosServer1 = res1.body.data.filter(video => video.isLocal === false).map(video => video.uuid)
477
478       const res2 = await getVideosList(servers[1].url)
479       remoteVideosServer2 = res2.body.data.filter(video => video.isLocal === false).map(video => video.uuid)
480
481       const res3 = await getVideosList(servers[2].url)
482       localVideosServer3 = res3.body.data.filter(video => video.isLocal === true).map(video => video.uuid)
483       remoteVideosServer3 = res3.body.data.filter(video => video.isLocal === false).map(video => video.uuid)
484     })
485
486     it('Should view multiple videos on owned servers', async function () {
487       this.timeout(15000)
488
489       const tasks: Promise<any>[] = []
490       await viewVideo(servers[2].url, localVideosServer3[0])
491       await viewVideo(servers[2].url, localVideosServer3[0])
492       await viewVideo(servers[2].url, localVideosServer3[0])
493       await viewVideo(servers[2].url, localVideosServer3[1])
494
495       await Promise.all(tasks)
496       await wait(1500)
497
498       await viewVideo(servers[2].url, localVideosServer3[0])
499
500       await wait(1500)
501
502       await viewVideo(servers[2].url, localVideosServer3[0])
503
504       await wait(5000)
505
506       for (const server of servers) {
507         const res = await getVideosList(server.url)
508
509         const videos = res.body.data
510         const video0 = videos.find(v => v.uuid === localVideosServer3[0])
511         const video1 = videos.find(v => v.uuid === localVideosServer3[1])
512
513         expect(video0.views).to.equal(3)
514         expect(video1.views).to.equal(1)
515       }
516     })
517
518     it('Should view multiple videos on each servers', async function () {
519       this.timeout(15000)
520
521       const tasks: Promise<any>[] = []
522       tasks.push(viewVideo(servers[0].url, remoteVideosServer1[0]))
523       tasks.push(viewVideo(servers[1].url, remoteVideosServer2[0]))
524       tasks.push(viewVideo(servers[1].url, remoteVideosServer2[0]))
525       tasks.push(viewVideo(servers[2].url, remoteVideosServer3[0]))
526       tasks.push(viewVideo(servers[2].url, remoteVideosServer3[1]))
527       tasks.push(viewVideo(servers[2].url, remoteVideosServer3[1]))
528       tasks.push(viewVideo(servers[2].url, remoteVideosServer3[1]))
529       tasks.push(viewVideo(servers[2].url, localVideosServer3[1]))
530       tasks.push(viewVideo(servers[2].url, localVideosServer3[1]))
531       tasks.push(viewVideo(servers[2].url, localVideosServer3[1]))
532
533       await Promise.all(tasks)
534
535       await wait(10000)
536
537       let baseVideos = null
538
539       for (const server of servers) {
540         const res = await getVideosList(server.url)
541
542         const videos = res.body.data
543
544         // Initialize base videos for future comparisons
545         if (baseVideos === null) {
546           baseVideos = videos
547           continue
548         }
549
550         for (const baseVideo of baseVideos) {
551           const sameVideo = videos.find(video => video.name === baseVideo.name)
552           expect(baseVideo.views).to.equal(sameVideo.views)
553         }
554       }
555     })
556
557     it('Should like and dislikes videos on different services', async function () {
558       this.timeout(20000)
559
560       await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like')
561       await wait(200)
562       await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'dislike')
563       await wait(200)
564       await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like')
565       await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'like')
566       await wait(200)
567       await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'dislike')
568       await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[1], 'dislike')
569       await wait(200)
570       await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[0], 'like')
571
572       await wait(10000)
573
574       let baseVideos = null
575       for (const server of servers) {
576         const res = await getVideosList(server.url)
577
578         const videos = res.body.data
579
580         // Initialize base videos for future comparisons
581         if (baseVideos === null) {
582           baseVideos = videos
583           continue
584         }
585
586         for (const baseVideo of baseVideos) {
587           const sameVideo = videos.find(video => video.name === baseVideo.name)
588           expect(baseVideo.likes).to.equal(sameVideo.likes)
589           expect(baseVideo.dislikes).to.equal(sameVideo.dislikes)
590         }
591       }
592     })
593   })
594
595   describe('Should manipulate these videos', function () {
596     it('Should update the video 3 by asking server 3', async function () {
597       this.timeout(10000)
598
599       const attributes = {
600         name: 'my super video updated',
601         category: 10,
602         licence: 7,
603         language: 'fr',
604         nsfw: true,
605         description: 'my super description updated',
606         support: 'my super support text updated',
607         tags: [ 'tag_up_1', 'tag_up_2' ],
608         thumbnailfile: 'thumbnail.jpg',
609         previewfile: 'preview.jpg'
610       }
611
612       await updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, attributes)
613
614       await wait(5000)
615     })
616
617     it('Should have the video 3 updated on each server', async function () {
618       this.timeout(10000)
619
620       for (const server of servers) {
621         const res = await getVideosList(server.url)
622
623         const videos = res.body.data
624         const videoUpdated = videos.find(video => video.name === 'my super video updated')
625         expect(!!videoUpdated).to.be.true
626
627         const isLocal = server.url === 'http://localhost:9003'
628         const checkAttributes = {
629           name: 'my super video updated',
630           category: 10,
631           licence: 7,
632           language: 'fr',
633           nsfw: true,
634           description: 'my super description updated',
635           support: 'my super support text updated',
636           account: {
637             name: 'root',
638             host: 'localhost:9003'
639           },
640           isLocal,
641           duration: 5,
642           commentsEnabled: true,
643           tags: [ 'tag_up_1', 'tag_up_2' ],
644           privacy: VideoPrivacy.PUBLIC,
645           channel: {
646             name: 'Default root channel',
647             description: '',
648             isLocal
649           },
650           fixture: 'video_short3.webm',
651           files: [
652             {
653               resolution: 720,
654               size: 292677
655             }
656           ],
657           thumbnailfile: 'thumbnail',
658           previewfile: 'preview'
659         }
660         await completeVideoCheck(server.url, videoUpdated, checkAttributes)
661       }
662     })
663
664     it('Should remove the videos 3 and 3-2 by asking server 3', async function () {
665       this.timeout(10000)
666
667       await removeVideo(servers[2].url, servers[2].accessToken, toRemove[0].id)
668       await removeVideo(servers[2].url, servers[2].accessToken, toRemove[1].id)
669
670       await wait(5000)
671     })
672
673     it('Should not have files of videos 3 and 3-2 on each server', async function () {
674       for (const server of servers) {
675         await checkVideoFilesWereRemoved(toRemove[0].uuid, server.serverNumber)
676         await checkVideoFilesWereRemoved(toRemove[1].uuid, server.serverNumber)
677       }
678     })
679
680     it('Should have videos 1 and 3 on each server', async function () {
681       for (const server of servers) {
682         const res = await getVideosList(server.url)
683
684         const videos = res.body.data
685         expect(videos).to.be.an('array')
686         expect(videos.length).to.equal(2)
687         expect(videos[0].name).not.to.equal(videos[1].name)
688         expect(videos[0].name).not.to.equal(toRemove[0].name)
689         expect(videos[1].name).not.to.equal(toRemove[0].name)
690         expect(videos[0].name).not.to.equal(toRemove[1].name)
691         expect(videos[1].name).not.to.equal(toRemove[1].name)
692
693         videoUUID = videos.find(video => video.name === 'my super name for server 1').uuid
694       }
695     })
696
697     it('Should get the same video by UUID on each server', async function () {
698       let baseVideo = null
699       for (const server of servers) {
700         const res = await getVideo(server.url, videoUUID)
701
702         const video = res.body
703
704         if (baseVideo === null) {
705           baseVideo = video
706           continue
707         }
708
709         expect(baseVideo.name).to.equal(video.name)
710         expect(baseVideo.uuid).to.equal(video.uuid)
711         expect(baseVideo.category.id).to.equal(video.category.id)
712         expect(baseVideo.language.id).to.equal(video.language.id)
713         expect(baseVideo.licence.id).to.equal(video.licence.id)
714         expect(baseVideo.nsfw).to.equal(video.nsfw)
715         expect(baseVideo.account.name).to.equal(video.account.name)
716         expect(baseVideo.account.displayName).to.equal(video.account.displayName)
717         expect(baseVideo.account.url).to.equal(video.account.url)
718         expect(baseVideo.account.host).to.equal(video.account.host)
719         expect(baseVideo.tags).to.deep.equal(video.tags)
720       }
721     })
722
723     it('Should get the preview from each server', async function () {
724       for (const server of servers) {
725         const res = await getVideo(server.url, videoUUID)
726         const video = res.body
727
728         await testImage(server.url, 'video_short1-preview.webm', video.previewPath)
729       }
730     })
731   })
732
733   describe('Should comment these videos', function () {
734     let childOfFirstChild: VideoCommentThreadTree
735
736     it('Should add comment (threads and replies)', async function () {
737       this.timeout(25000)
738
739       {
740         const text = 'my super first comment'
741         await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, text)
742       }
743
744       {
745         const text = 'my super second comment'
746         await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, text)
747       }
748
749       await wait(5000)
750
751       {
752         const res = await getVideoCommentThreads(servers[1].url, videoUUID, 0, 5)
753         const threadId = res.body.data.find(c => c.text === 'my super first comment').id
754
755         const text = 'my super answer to thread 1'
756         await addVideoCommentReply(servers[ 1 ].url, servers[ 1 ].accessToken, videoUUID, threadId, text)
757       }
758
759       await wait(5000)
760
761       {
762         const res1 = await getVideoCommentThreads(servers[2].url, videoUUID, 0, 5)
763         const threadId = res1.body.data.find(c => c.text === 'my super first comment').id
764
765         const res2 = await getVideoThreadComments(servers[2].url, videoUUID, threadId)
766         const childCommentId = res2.body.children[0].comment.id
767
768         const text3 = 'my second answer to thread 1'
769         await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, threadId, text3)
770
771         const text2 = 'my super answer to answer of thread 1'
772         await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, childCommentId, text2)
773       }
774
775       await wait(5000)
776     })
777
778     it('Should have these threads', async function () {
779       for (const server of servers) {
780         const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
781
782         expect(res.body.total).to.equal(2)
783         expect(res.body.data).to.be.an('array')
784         expect(res.body.data).to.have.lengthOf(2)
785
786         {
787           const comment: VideoComment = res.body.data.find(c => c.text === 'my super first comment')
788           expect(comment).to.not.be.undefined
789           expect(comment.inReplyToCommentId).to.be.null
790           expect(comment.account.name).to.equal('root')
791           expect(comment.account.host).to.equal('localhost:9001')
792           expect(comment.totalReplies).to.equal(3)
793           expect(dateIsValid(comment.createdAt as string)).to.be.true
794           expect(dateIsValid(comment.updatedAt as string)).to.be.true
795         }
796
797         {
798           const comment: VideoComment = res.body.data.find(c => c.text === 'my super second comment')
799           expect(comment).to.not.be.undefined
800           expect(comment.inReplyToCommentId).to.be.null
801           expect(comment.account.name).to.equal('root')
802           expect(comment.account.host).to.equal('localhost:9003')
803           expect(comment.totalReplies).to.equal(0)
804           expect(dateIsValid(comment.createdAt as string)).to.be.true
805           expect(dateIsValid(comment.updatedAt as string)).to.be.true
806         }
807       }
808     })
809
810     it('Should have these comments', async function () {
811       for (const server of servers) {
812         const res1 = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
813         const threadId = res1.body.data.find(c => c.text === 'my super first comment').id
814
815         const res2 = await getVideoThreadComments(server.url, videoUUID, threadId)
816
817         const tree: VideoCommentThreadTree = res2.body
818         expect(tree.comment.text).equal('my super first comment')
819         expect(tree.comment.account.name).equal('root')
820         expect(tree.comment.account.host).equal('localhost:9001')
821         expect(tree.children).to.have.lengthOf(2)
822
823         const firstChild = tree.children[0]
824         expect(firstChild.comment.text).to.equal('my super answer to thread 1')
825         expect(firstChild.comment.account.name).equal('root')
826         expect(firstChild.comment.account.host).equal('localhost:9002')
827         expect(firstChild.children).to.have.lengthOf(1)
828
829         childOfFirstChild = firstChild.children[0]
830         expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
831         expect(childOfFirstChild.comment.account.name).equal('root')
832         expect(childOfFirstChild.comment.account.host).equal('localhost:9003')
833         expect(childOfFirstChild.children).to.have.lengthOf(0)
834
835         const secondChild = tree.children[1]
836         expect(secondChild.comment.text).to.equal('my second answer to thread 1')
837         expect(secondChild.comment.account.name).equal('root')
838         expect(secondChild.comment.account.host).equal('localhost:9003')
839         expect(secondChild.children).to.have.lengthOf(0)
840       }
841     })
842
843     it('Should delete a reply', async function () {
844       this.timeout(10000)
845
846       await deleteVideoComment(servers[2].url, servers[2].accessToken, videoUUID, childOfFirstChild.comment.id)
847
848       await wait(5000)
849     })
850
851     it('Should not have this comment anymore', async function () {
852       for (const server of servers) {
853         const res1 = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
854         const threadId = res1.body.data.find(c => c.text === 'my super first comment').id
855
856         const res2 = await getVideoThreadComments(server.url, videoUUID, threadId)
857
858         const tree: VideoCommentThreadTree = res2.body
859         expect(tree.comment.text).equal('my super first comment')
860
861         const firstChild = tree.children[0]
862         expect(firstChild.comment.text).to.equal('my super answer to thread 1')
863         expect(firstChild.children).to.have.lengthOf(0)
864
865         const secondChild = tree.children[1]
866         expect(secondChild.comment.text).to.equal('my second answer to thread 1')
867       }
868     })
869
870     it('Should delete the thread comments', async function () {
871       this.timeout(10000)
872
873       const res1 = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5)
874       const threadId = res1.body.data.find(c => c.text === 'my super first comment').id
875       await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId)
876
877       await wait(5000)
878     })
879
880     it('Should have the thread comments deleted on other servers too', async function () {
881       for (const server of servers) {
882         const res = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
883
884         expect(res.body.total).to.equal(1)
885         expect(res.body.data).to.be.an('array')
886         expect(res.body.data).to.have.lengthOf(1)
887
888         {
889           const comment: VideoComment = res.body.data[0]
890           expect(comment).to.not.be.undefined
891           expect(comment.inReplyToCommentId).to.be.null
892           expect(comment.account.name).to.equal('root')
893           expect(comment.account.host).to.equal('localhost:9003')
894           expect(comment.totalReplies).to.equal(0)
895           expect(dateIsValid(comment.createdAt as string)).to.be.true
896           expect(dateIsValid(comment.updatedAt as string)).to.be.true
897         }
898       }
899     })
900
901     it('Should disable comments', async function () {
902       this.timeout(20000)
903
904       const attributes = {
905         commentsEnabled: false
906       }
907
908       await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, attributes)
909
910       await wait(5000)
911
912       for (const server of servers) {
913         const res = await getVideo(server.url, videoUUID)
914         expect(res.body.commentsEnabled).to.be.false
915
916         const text = 'my super forbidden comment'
917         await addVideoCommentThread(server.url, server.accessToken, videoUUID, text, 409)
918       }
919     })
920   })
921
922   describe('With minimum parameters', function () {
923     it('Should upload and propagate the video', async function () {
924       this.timeout(50000)
925
926       const path = '/api/v1/videos/upload'
927
928       const req = request(servers[1].url)
929         .post(path)
930         .set('Accept', 'application/json')
931         .set('Authorization', 'Bearer ' + servers[1].accessToken)
932         .field('name', 'minimum parameters')
933         .field('privacy', '1')
934         .field('nsfw', 'false')
935         .field('channelId', '1')
936         .field('commentsEnabled', 'true')
937
938       const filePath = join(__dirname, '..', '..', 'api', 'fixtures', 'video_short.webm')
939
940       await req.attach('videofile', filePath)
941         .expect(200)
942
943       await wait(25000)
944
945       for (const server of servers) {
946         const res = await getVideosList(server.url)
947         const video = res.body.data.find(v => v.name === 'minimum parameters')
948
949         const isLocal = server.url === 'http://localhost:9002'
950         const checkAttributes = {
951           name: 'minimum parameters',
952           category: null,
953           licence: null,
954           language: null,
955           nsfw: false,
956           description: null,
957           support: null,
958           account: {
959             name: 'root',
960             host: 'localhost:9002'
961           },
962           isLocal,
963           duration: 5,
964           commentsEnabled: true,
965           tags: [ ],
966           privacy: VideoPrivacy.PUBLIC,
967           channel: {
968             name: 'Default root channel',
969             description: '',
970             isLocal
971           },
972           fixture: 'video_short.webm',
973           files: [
974             {
975               resolution: 720,
976               size: 40315
977             },
978             {
979               resolution: 480,
980               size: 22808
981             },
982             {
983               resolution: 360,
984               size: 18617
985             },
986             {
987               resolution: 240,
988               size: 15217
989             }
990           ]
991         }
992         await completeVideoCheck(server.url, video, checkAttributes)
993       }
994     })
995   })
996
997   after(async function () {
998     killallServers(servers)
999
1000     // Keep the logs if the test failed
1001     if (this['ok']) {
1002       await flushTests()
1003     }
1004   })
1005 })