2bb97d7a8b05ae7dcecb902d09e405e4e685621f
[oweals/peertube.git] / server / tests / api / videos / video-playlists.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import * as chai from 'chai'
4 import 'mocha'
5 import {
6   addVideoChannel,
7   addVideoInPlaylist,
8   addVideoToBlacklist,
9   checkPlaylistFilesWereRemoved,
10   cleanupTests,
11   createUser,
12   createVideoPlaylist,
13   deleteVideoChannel,
14   deleteVideoPlaylist,
15   doubleFollow,
16   doVideosExistInMyPlaylist,
17   flushAndRunMultipleServers,
18   generateUserAccessToken,
19   getAccessToken,
20   getAccountPlaylistsList,
21   getAccountPlaylistsListWithToken,
22   getMyUserInformation,
23   getPlaylistVideos,
24   getVideoChannelPlaylistsList,
25   getVideoPlaylist,
26   getVideoPlaylistPrivacies,
27   getVideoPlaylistsList,
28   getVideoPlaylistWithToken,
29   removeUser,
30   removeVideoFromBlacklist,
31   removeVideoFromPlaylist,
32   reorderVideosPlaylist,
33   ServerInfo,
34   setAccessTokensToServers,
35   setDefaultVideoChannel,
36   testImage,
37   unfollow,
38   updateVideo,
39   updateVideoPlaylist,
40   updateVideoPlaylistElement,
41   uploadVideo,
42   uploadVideoAndGetId,
43   userLogin,
44   waitJobs
45 } from '../../../../shared/extra-utils'
46 import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
47 import { VideoPlaylist } from '../../../../shared/models/videos/playlist/video-playlist.model'
48 import { VideoPrivacy } from '../../../../shared/models/videos'
49 import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
50 import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
51 import { User } from '../../../../shared/models/users'
52 import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../../shared/models/videos/playlist/video-playlist-element.model'
53 import {
54   addAccountToAccountBlocklist,
55   addAccountToServerBlocklist,
56   addServerToAccountBlocklist,
57   addServerToServerBlocklist,
58   removeAccountFromAccountBlocklist,
59   removeAccountFromServerBlocklist,
60   removeServerFromAccountBlocklist,
61   removeServerFromServerBlocklist
62 } from '../../../../shared/extra-utils/users/blocklist'
63
64 const expect = chai.expect
65
66 async function checkPlaylistElementType (
67   servers: ServerInfo[],
68   playlistId: string,
69   type: VideoPlaylistElementType,
70   position: number,
71   name: string,
72   total: number
73 ) {
74   for (const server of servers) {
75     const res = await getPlaylistVideos(server.url, server.accessToken, playlistId, 0, 10)
76     expect(res.body.total).to.equal(total)
77
78     const videoElement: VideoPlaylistElement = res.body.data.find((e: VideoPlaylistElement) => e.position === position)
79     expect(videoElement.type).to.equal(type, 'On server ' + server.url)
80
81     if (type === VideoPlaylistElementType.REGULAR) {
82       expect(videoElement.video).to.not.be.null
83       expect(videoElement.video.name).to.equal(name)
84     } else {
85       expect(videoElement.video).to.be.null
86     }
87   }
88 }
89
90 describe('Test video playlists', function () {
91   let servers: ServerInfo[] = []
92
93   let playlistServer2Id1: number
94   let playlistServer2Id2: number
95   let playlistServer2UUID2: number
96
97   let playlistServer1Id: number
98   let playlistServer1UUID: string
99   let playlistServer1UUID2: string
100
101   let playlistElementServer1Video4: number
102   let playlistElementServer1Video5: number
103   let playlistElementNSFW: number
104
105   let nsfwVideoServer1: number
106
107   let userAccessTokenServer1: string
108
109   before(async function () {
110     this.timeout(120000)
111
112     servers = await flushAndRunMultipleServers(3, { transcoding: { enabled: false } })
113
114     // Get the access tokens
115     await setAccessTokensToServers(servers)
116     await setDefaultVideoChannel(servers)
117
118     // Server 1 and server 2 follow each other
119     await doubleFollow(servers[0], servers[1])
120     // Server 1 and server 3 follow each other
121     await doubleFollow(servers[0], servers[2])
122
123     {
124       const serverPromises: Promise<any>[][] = []
125
126       for (const server of servers) {
127         const videoPromises: Promise<any>[] = []
128
129         for (let i = 0; i < 7; i++) {
130           videoPromises.push(
131             uploadVideo(server.url, server.accessToken, { name: `video ${i} server ${server.serverNumber}`, nsfw: false })
132               .then(res => res.body.video)
133           )
134         }
135
136         serverPromises.push(videoPromises)
137       }
138
139       servers[0].videos = await Promise.all(serverPromises[0])
140       servers[1].videos = await Promise.all(serverPromises[1])
141       servers[2].videos = await Promise.all(serverPromises[2])
142     }
143
144     nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'NSFW video', nsfw: true })).id
145
146     {
147       await createUser({
148         url: servers[0].url,
149         accessToken: servers[0].accessToken,
150         username: 'user1',
151         password: 'password'
152       })
153       userAccessTokenServer1 = await getAccessToken(servers[0].url, 'user1', 'password')
154     }
155
156     await waitJobs(servers)
157   })
158
159   describe('Get default playlists', function () {
160     it('Should list video playlist privacies', async function () {
161       const res = await getVideoPlaylistPrivacies(servers[0].url)
162
163       const privacies = res.body
164       expect(Object.keys(privacies)).to.have.length.at.least(3)
165
166       expect(privacies[3]).to.equal('Private')
167     })
168
169     it('Should list watch later playlist', async function () {
170       const url = servers[0].url
171       const accessToken = servers[0].accessToken
172
173       {
174         const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER)
175
176         expect(res.body.total).to.equal(1)
177         expect(res.body.data).to.have.lengthOf(1)
178
179         const playlist: VideoPlaylist = res.body.data[0]
180         expect(playlist.displayName).to.equal('Watch later')
181         expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
182         expect(playlist.type.label).to.equal('Watch later')
183       }
184
185       {
186         const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.REGULAR)
187
188         expect(res.body.total).to.equal(0)
189         expect(res.body.data).to.have.lengthOf(0)
190       }
191
192       {
193         const res = await getAccountPlaylistsList(url, 'root', 0, 5)
194         expect(res.body.total).to.equal(0)
195         expect(res.body.data).to.have.lengthOf(0)
196       }
197     })
198
199     it('Should get private playlist for a classic user', async function () {
200       const token = await generateUserAccessToken(servers[0], 'toto')
201
202       const res = await getAccountPlaylistsListWithToken(servers[0].url, token, 'toto', 0, 5)
203
204       expect(res.body.total).to.equal(1)
205       expect(res.body.data).to.have.lengthOf(1)
206
207       const playlistId = res.body.data[0].id
208       await getPlaylistVideos(servers[0].url, token, playlistId, 0, 5)
209     })
210   })
211
212   describe('Create and federate playlists', function () {
213
214     it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
215       this.timeout(30000)
216
217       await createVideoPlaylist({
218         url: servers[0].url,
219         token: servers[0].accessToken,
220         playlistAttrs: {
221           displayName: 'my super playlist',
222           privacy: VideoPlaylistPrivacy.PUBLIC,
223           description: 'my super description',
224           thumbnailfile: 'thumbnail.jpg',
225           videoChannelId: servers[0].videoChannel.id
226         }
227       })
228
229       await waitJobs(servers)
230
231       for (const server of servers) {
232         const res = await getVideoPlaylistsList(server.url, 0, 5)
233         expect(res.body.total).to.equal(1)
234         expect(res.body.data).to.have.lengthOf(1)
235
236         const playlistFromList = res.body.data[0] as VideoPlaylist
237
238         const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid)
239         const playlistFromGet = res2.body
240
241         for (const playlist of [ playlistFromGet, playlistFromList ]) {
242           expect(playlist.id).to.be.a('number')
243           expect(playlist.uuid).to.be.a('string')
244
245           expect(playlist.isLocal).to.equal(server.serverNumber === 1)
246
247           expect(playlist.displayName).to.equal('my super playlist')
248           expect(playlist.description).to.equal('my super description')
249           expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
250           expect(playlist.privacy.label).to.equal('Public')
251           expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
252           expect(playlist.type.label).to.equal('Regular')
253
254           expect(playlist.videosLength).to.equal(0)
255
256           expect(playlist.ownerAccount.name).to.equal('root')
257           expect(playlist.ownerAccount.displayName).to.equal('root')
258           expect(playlist.videoChannel.name).to.equal('root_channel')
259           expect(playlist.videoChannel.displayName).to.equal('Main root channel')
260         }
261       }
262     })
263
264     it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
265       this.timeout(30000)
266
267       {
268         const res = await createVideoPlaylist({
269           url: servers[1].url,
270           token: servers[1].accessToken,
271           playlistAttrs: {
272             displayName: 'playlist 2',
273             privacy: VideoPlaylistPrivacy.PUBLIC,
274             videoChannelId: servers[1].videoChannel.id
275           }
276         })
277         playlistServer2Id1 = res.body.videoPlaylist.id
278       }
279
280       {
281         const res = await createVideoPlaylist({
282           url: servers[1].url,
283           token: servers[1].accessToken,
284           playlistAttrs: {
285             displayName: 'playlist 3',
286             privacy: VideoPlaylistPrivacy.PUBLIC,
287             thumbnailfile: 'thumbnail.jpg',
288             videoChannelId: servers[1].videoChannel.id
289           }
290         })
291
292         playlistServer2Id2 = res.body.videoPlaylist.id
293         playlistServer2UUID2 = res.body.videoPlaylist.uuid
294       }
295
296       for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
297         await addVideoInPlaylist({
298           url: servers[1].url,
299           token: servers[1].accessToken,
300           playlistId: id,
301           elementAttrs: { videoId: servers[1].videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
302         })
303         await addVideoInPlaylist({
304           url: servers[1].url,
305           token: servers[1].accessToken,
306           playlistId: id,
307           elementAttrs: { videoId: servers[1].videos[1].id }
308         })
309       }
310
311       await waitJobs(servers)
312
313       for (const server of [ servers[0], servers[1] ]) {
314         const res = await getVideoPlaylistsList(server.url, 0, 5)
315
316         const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2')
317         expect(playlist2).to.not.be.undefined
318         await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
319
320         const playlist3 = res.body.data.find(p => p.displayName === 'playlist 3')
321         expect(playlist3).to.not.be.undefined
322         await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
323       }
324
325       const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
326       expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
327       expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
328     })
329
330     it('Should have the playlist on server 3 after a new follow', async function () {
331       this.timeout(30000)
332
333       // Server 2 and server 3 follow each other
334       await doubleFollow(servers[1], servers[2])
335
336       const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
337
338       const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2')
339       expect(playlist2).to.not.be.undefined
340       await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
341
342       expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
343     })
344   })
345
346   describe('List playlists', function () {
347
348     it('Should correctly list the playlists', async function () {
349       this.timeout(30000)
350
351       {
352         const res = await getVideoPlaylistsList(servers[2].url, 1, 2, 'createdAt')
353
354         expect(res.body.total).to.equal(3)
355
356         const data: VideoPlaylist[] = res.body.data
357         expect(data).to.have.lengthOf(2)
358         expect(data[0].displayName).to.equal('playlist 2')
359         expect(data[1].displayName).to.equal('playlist 3')
360       }
361
362       {
363         const res = await getVideoPlaylistsList(servers[2].url, 1, 2, '-createdAt')
364
365         expect(res.body.total).to.equal(3)
366
367         const data: VideoPlaylist[] = res.body.data
368         expect(data).to.have.lengthOf(2)
369         expect(data[0].displayName).to.equal('playlist 2')
370         expect(data[1].displayName).to.equal('my super playlist')
371       }
372     })
373
374     it('Should list video channel playlists', async function () {
375       this.timeout(30000)
376
377       {
378         const res = await getVideoChannelPlaylistsList(servers[0].url, 'root_channel', 0, 2, '-createdAt')
379
380         expect(res.body.total).to.equal(1)
381
382         const data: VideoPlaylist[] = res.body.data
383         expect(data).to.have.lengthOf(1)
384         expect(data[0].displayName).to.equal('my super playlist')
385       }
386     })
387
388     it('Should list account playlists', async function () {
389       this.timeout(30000)
390
391       {
392         const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, '-createdAt')
393
394         expect(res.body.total).to.equal(2)
395
396         const data: VideoPlaylist[] = res.body.data
397         expect(data).to.have.lengthOf(1)
398         expect(data[0].displayName).to.equal('playlist 2')
399       }
400
401       {
402         const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, 'createdAt')
403
404         expect(res.body.total).to.equal(2)
405
406         const data: VideoPlaylist[] = res.body.data
407         expect(data).to.have.lengthOf(1)
408         expect(data[0].displayName).to.equal('playlist 3')
409       }
410
411       {
412         const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '3')
413
414         expect(res.body.total).to.equal(1)
415
416         const data: VideoPlaylist[] = res.body.data
417         expect(data).to.have.lengthOf(1)
418         expect(data[0].displayName).to.equal('playlist 3')
419       }
420
421       {
422         const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '4')
423
424         expect(res.body.total).to.equal(0)
425
426         const data: VideoPlaylist[] = res.body.data
427         expect(data).to.have.lengthOf(0)
428       }
429     })
430
431     it('Should not list unlisted or private playlists', async function () {
432       this.timeout(30000)
433
434       await createVideoPlaylist({
435         url: servers[1].url,
436         token: servers[1].accessToken,
437         playlistAttrs: {
438           displayName: 'playlist unlisted',
439           privacy: VideoPlaylistPrivacy.UNLISTED
440         }
441       })
442
443       await createVideoPlaylist({
444         url: servers[1].url,
445         token: servers[1].accessToken,
446         playlistAttrs: {
447           displayName: 'playlist private',
448           privacy: VideoPlaylistPrivacy.PRIVATE
449         }
450       })
451
452       await waitJobs(servers)
453
454       for (const server of servers) {
455         const results = [
456           await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'),
457           await getVideoPlaylistsList(server.url, 0, 2, '-createdAt')
458         ]
459
460         expect(results[0].body.total).to.equal(2)
461         expect(results[1].body.total).to.equal(3)
462
463         for (const res of results) {
464           const data: VideoPlaylist[] = res.body.data
465           expect(data).to.have.lengthOf(2)
466           expect(data[0].displayName).to.equal('playlist 3')
467           expect(data[1].displayName).to.equal('playlist 2')
468         }
469       }
470     })
471   })
472
473   describe('Update playlists', function () {
474
475     it('Should update a playlist', async function () {
476       this.timeout(30000)
477
478       await updateVideoPlaylist({
479         url: servers[1].url,
480         token: servers[1].accessToken,
481         playlistAttrs: {
482           displayName: 'playlist 3 updated',
483           description: 'description updated',
484           privacy: VideoPlaylistPrivacy.UNLISTED,
485           thumbnailfile: 'thumbnail.jpg',
486           videoChannelId: servers[1].videoChannel.id
487         },
488         playlistId: playlistServer2Id2
489       })
490
491       await waitJobs(servers)
492
493       for (const server of servers) {
494         const res = await getVideoPlaylist(server.url, playlistServer2UUID2)
495         const playlist: VideoPlaylist = res.body
496
497         expect(playlist.displayName).to.equal('playlist 3 updated')
498         expect(playlist.description).to.equal('description updated')
499
500         expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
501         expect(playlist.privacy.label).to.equal('Unlisted')
502
503         expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
504         expect(playlist.type.label).to.equal('Regular')
505
506         expect(playlist.videosLength).to.equal(2)
507
508         expect(playlist.ownerAccount.name).to.equal('root')
509         expect(playlist.ownerAccount.displayName).to.equal('root')
510         expect(playlist.videoChannel.name).to.equal('root_channel')
511         expect(playlist.videoChannel.displayName).to.equal('Main root channel')
512       }
513     })
514   })
515
516   describe('Element timestamps', function () {
517
518     it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
519       this.timeout(30000)
520
521       const addVideo = (elementAttrs: any) => {
522         return addVideoInPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: playlistServer1Id, elementAttrs })
523       }
524
525       const res = await createVideoPlaylist({
526         url: servers[0].url,
527         token: servers[0].accessToken,
528         playlistAttrs: {
529           displayName: 'playlist 4',
530           privacy: VideoPlaylistPrivacy.PUBLIC,
531           videoChannelId: servers[0].videoChannel.id
532         }
533       })
534
535       playlistServer1Id = res.body.videoPlaylist.id
536       playlistServer1UUID = res.body.videoPlaylist.uuid
537
538       await addVideo({ videoId: servers[0].videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
539       await addVideo({ videoId: servers[2].videos[1].uuid, startTimestamp: 35 })
540       await addVideo({ videoId: servers[2].videos[2].uuid })
541       {
542         const res = await addVideo({ videoId: servers[0].videos[3].uuid, stopTimestamp: 35 })
543         playlistElementServer1Video4 = res.body.videoPlaylistElement.id
544       }
545
546       {
547         const res = await addVideo({ videoId: servers[0].videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
548         playlistElementServer1Video5 = res.body.videoPlaylistElement.id
549       }
550
551       {
552         const res = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
553         playlistElementNSFW = res.body.videoPlaylistElement.id
554       }
555
556       await waitJobs(servers)
557     })
558
559     it('Should correctly list playlist videos', async function () {
560       this.timeout(30000)
561
562       for (const server of servers) {
563         const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
564
565         expect(res.body.total).to.equal(6)
566
567         const videoElements: VideoPlaylistElement[] = res.body.data
568         expect(videoElements).to.have.lengthOf(6)
569
570         expect(videoElements[0].video.name).to.equal('video 0 server 1')
571         expect(videoElements[0].position).to.equal(1)
572         expect(videoElements[0].startTimestamp).to.equal(15)
573         expect(videoElements[0].stopTimestamp).to.equal(28)
574
575         expect(videoElements[1].video.name).to.equal('video 1 server 3')
576         expect(videoElements[1].position).to.equal(2)
577         expect(videoElements[1].startTimestamp).to.equal(35)
578         expect(videoElements[1].stopTimestamp).to.be.null
579
580         expect(videoElements[2].video.name).to.equal('video 2 server 3')
581         expect(videoElements[2].position).to.equal(3)
582         expect(videoElements[2].startTimestamp).to.be.null
583         expect(videoElements[2].stopTimestamp).to.be.null
584
585         expect(videoElements[3].video.name).to.equal('video 3 server 1')
586         expect(videoElements[3].position).to.equal(4)
587         expect(videoElements[3].startTimestamp).to.be.null
588         expect(videoElements[3].stopTimestamp).to.equal(35)
589
590         expect(videoElements[4].video.name).to.equal('video 4 server 1')
591         expect(videoElements[4].position).to.equal(5)
592         expect(videoElements[4].startTimestamp).to.equal(45)
593         expect(videoElements[4].stopTimestamp).to.equal(60)
594
595         expect(videoElements[5].video.name).to.equal('NSFW video')
596         expect(videoElements[5].position).to.equal(6)
597         expect(videoElements[5].startTimestamp).to.equal(5)
598         expect(videoElements[5].stopTimestamp).to.be.null
599
600         const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2)
601         expect(res3.body.data).to.have.lengthOf(2)
602       }
603     })
604   })
605
606   describe('Element type', function () {
607     let groupUser1: ServerInfo[]
608     let groupWithoutToken1: ServerInfo[]
609     let group1: ServerInfo[]
610     let group2: ServerInfo[]
611
612     let video1: string
613     let video2: string
614     let video3: string
615
616     before(async function () {
617       this.timeout(30000)
618
619       groupUser1 = [ Object.assign({}, servers[0], { accessToken: userAccessTokenServer1 }) ]
620       groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
621       group1 = [ servers[0] ]
622       group2 = [ servers[1], servers[2] ]
623
624       const res = await createVideoPlaylist({
625         url: servers[0].url,
626         token: userAccessTokenServer1,
627         playlistAttrs: {
628           displayName: 'playlist 56',
629           privacy: VideoPlaylistPrivacy.PUBLIC,
630           videoChannelId: servers[0].videoChannel.id
631         }
632       })
633
634       const playlistServer1Id2 = res.body.videoPlaylist.id
635       playlistServer1UUID2 = res.body.videoPlaylist.uuid
636
637       const addVideo = (elementAttrs: any) => {
638         return addVideoInPlaylist({ url: servers[0].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs })
639       }
640
641       video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid
642       video2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 90' })).uuid
643       video3 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 91', nsfw: true })).uuid
644
645       await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
646       await addVideo({ videoId: video2, startTimestamp: 35 })
647       await addVideo({ videoId: video3 })
648
649       await waitJobs(servers)
650     })
651
652     it('Should update the element type if the video is private', async function () {
653       this.timeout(20000)
654
655       const name = 'video 89'
656       const position = 1
657
658       {
659         await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PRIVATE })
660         await waitJobs(servers)
661
662         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
663         await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
664         await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
665         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
666       }
667
668       {
669         await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PUBLIC })
670         await waitJobs(servers)
671
672         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
673         await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
674         await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
675         // We deleted the video, so even if we recreated it, the old entry is still deleted
676         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
677       }
678     })
679
680     it('Should update the element type if the video is blacklisted', async function () {
681       this.timeout(20000)
682
683       const name = 'video 89'
684       const position = 1
685
686       {
687         await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video1, 'reason', true)
688         await waitJobs(servers)
689
690         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
691         await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
692         await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
693         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
694       }
695
696       {
697         await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video1)
698         await waitJobs(servers)
699
700         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
701         await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
702         await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
703         // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
704         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
705       }
706     })
707
708     it('Should update the element type if the account or server of the video is blocked', async function () {
709       this.timeout(90000)
710
711       const name = 'video 90'
712       const position = 2
713
714       {
715         await addAccountToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port)
716         await waitJobs(servers)
717
718         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
719         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
720
721         await removeAccountFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port)
722         await waitJobs(servers)
723
724         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
725       }
726
727       {
728         await addServerToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port)
729         await waitJobs(servers)
730
731         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
732         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
733
734         await removeServerFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port)
735         await waitJobs(servers)
736
737         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
738       }
739
740       {
741         await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port)
742         await waitJobs(servers)
743
744         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
745         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
746
747         await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port)
748         await waitJobs(servers)
749
750         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
751       }
752
753       {
754         await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
755         await waitJobs(servers)
756
757         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
758         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
759
760         await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
761         await waitJobs(servers)
762
763         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
764       }
765     })
766
767     it('Should hide the video if it is NSFW', async function () {
768       const res = await getPlaylistVideos(servers[0].url, userAccessTokenServer1, playlistServer1UUID2, 0, 10, { nsfw: false })
769       expect(res.body.total).to.equal(3)
770
771       const elements: VideoPlaylistElement[] = res.body.data
772       const element = elements.find(e => e.position === 3)
773
774       expect(element).to.exist
775       expect(element.video).to.be.null
776       expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
777     })
778
779   })
780
781   describe('Managing playlist elements', function () {
782
783     it('Should reorder the playlist', async function () {
784       this.timeout(30000)
785
786       {
787         await reorderVideosPlaylist({
788           url: servers[0].url,
789           token: servers[0].accessToken,
790           playlistId: playlistServer1Id,
791           elementAttrs: {
792             startPosition: 2,
793             insertAfterPosition: 3
794           }
795         })
796
797         await waitJobs(servers)
798
799         for (const server of servers) {
800           const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
801           const names = (res.body.data as VideoPlaylistElement[]).map(v => v.video.name)
802
803           expect(names).to.deep.equal([
804             'video 0 server 1',
805             'video 2 server 3',
806             'video 1 server 3',
807             'video 3 server 1',
808             'video 4 server 1',
809             'NSFW video'
810           ])
811         }
812       }
813
814       {
815         await reorderVideosPlaylist({
816           url: servers[0].url,
817           token: servers[0].accessToken,
818           playlistId: playlistServer1Id,
819           elementAttrs: {
820             startPosition: 1,
821             reorderLength: 3,
822             insertAfterPosition: 4
823           }
824         })
825
826         await waitJobs(servers)
827
828         for (const server of servers) {
829           const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
830           const names = (res.body.data as VideoPlaylistElement[]).map(v => v.video.name)
831
832           expect(names).to.deep.equal([
833             'video 3 server 1',
834             'video 0 server 1',
835             'video 2 server 3',
836             'video 1 server 3',
837             'video 4 server 1',
838             'NSFW video'
839           ])
840         }
841       }
842
843       {
844         await reorderVideosPlaylist({
845           url: servers[0].url,
846           token: servers[0].accessToken,
847           playlistId: playlistServer1Id,
848           elementAttrs: {
849             startPosition: 6,
850             insertAfterPosition: 3
851           }
852         })
853
854         await waitJobs(servers)
855
856         for (const server of servers) {
857           const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
858           const elements: VideoPlaylistElement[] = res.body.data
859           const names = elements.map(v => v.video.name)
860
861           expect(names).to.deep.equal([
862             'video 3 server 1',
863             'video 0 server 1',
864             'video 2 server 3',
865             'NSFW video',
866             'video 1 server 3',
867             'video 4 server 1'
868           ])
869
870           for (let i = 1; i <= elements.length; i++) {
871             expect(elements[i - 1].position).to.equal(i)
872           }
873         }
874       }
875     })
876
877     it('Should update startTimestamp/endTimestamp of some elements', async function () {
878       this.timeout(30000)
879
880       await updateVideoPlaylistElement({
881         url: servers[0].url,
882         token: servers[0].accessToken,
883         playlistId: playlistServer1Id,
884         playlistElementId: playlistElementServer1Video4,
885         elementAttrs: {
886           startTimestamp: 1
887         }
888       })
889
890       await updateVideoPlaylistElement({
891         url: servers[0].url,
892         token: servers[0].accessToken,
893         playlistId: playlistServer1Id,
894         playlistElementId: playlistElementServer1Video5,
895         elementAttrs: {
896           stopTimestamp: null
897         }
898       })
899
900       await waitJobs(servers)
901
902       for (const server of servers) {
903         const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
904         const elements: VideoPlaylistElement[] = res.body.data
905
906         expect(elements[0].video.name).to.equal('video 3 server 1')
907         expect(elements[0].position).to.equal(1)
908         expect(elements[0].startTimestamp).to.equal(1)
909         expect(elements[0].stopTimestamp).to.equal(35)
910
911         expect(elements[5].video.name).to.equal('video 4 server 1')
912         expect(elements[5].position).to.equal(6)
913         expect(elements[5].startTimestamp).to.equal(45)
914         expect(elements[5].stopTimestamp).to.be.null
915       }
916     })
917
918     it('Should check videos existence in my playlist', async function () {
919       const videoIds = [
920         servers[0].videos[0].id,
921         42000,
922         servers[0].videos[3].id,
923         43000,
924         servers[0].videos[4].id
925       ]
926       const res = await doVideosExistInMyPlaylist(servers[0].url, servers[0].accessToken, videoIds)
927       const obj = res.body as VideoExistInPlaylist
928
929       {
930         const elem = obj[servers[0].videos[0].id]
931         expect(elem).to.have.lengthOf(1)
932         expect(elem[0].playlistElementId).to.exist
933         expect(elem[0].playlistId).to.equal(playlistServer1Id)
934         expect(elem[0].startTimestamp).to.equal(15)
935         expect(elem[0].stopTimestamp).to.equal(28)
936       }
937
938       {
939         const elem = obj[servers[0].videos[3].id]
940         expect(elem).to.have.lengthOf(1)
941         expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
942         expect(elem[0].playlistId).to.equal(playlistServer1Id)
943         expect(elem[0].startTimestamp).to.equal(1)
944         expect(elem[0].stopTimestamp).to.equal(35)
945       }
946
947       {
948         const elem = obj[servers[0].videos[4].id]
949         expect(elem).to.have.lengthOf(1)
950         expect(elem[0].playlistId).to.equal(playlistServer1Id)
951         expect(elem[0].startTimestamp).to.equal(45)
952         expect(elem[0].stopTimestamp).to.equal(null)
953       }
954
955       expect(obj[42000]).to.have.lengthOf(0)
956       expect(obj[43000]).to.have.lengthOf(0)
957     })
958
959     it('Should automatically update updatedAt field of playlists', async function () {
960       const server = servers[1]
961       const videoId = servers[1].videos[5].id
962
963       async function getPlaylistNames () {
964         const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt')
965
966         return (res.body.data as VideoPlaylist[]).map(p => p.displayName)
967       }
968
969       const elementAttrs = { videoId }
970       const res1 = await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id1, elementAttrs })
971       const res2 = await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id2, elementAttrs })
972
973       const element1 = res1.body.videoPlaylistElement.id
974       const element2 = res2.body.videoPlaylistElement.id
975
976       const names1 = await getPlaylistNames()
977       expect(names1[0]).to.equal('playlist 3 updated')
978       expect(names1[1]).to.equal('playlist 2')
979
980       await removeVideoFromPlaylist({
981         url: server.url,
982         token: server.accessToken,
983         playlistId: playlistServer2Id1,
984         playlistElementId: element1
985       })
986
987       const names2 = await getPlaylistNames()
988       expect(names2[0]).to.equal('playlist 2')
989       expect(names2[1]).to.equal('playlist 3 updated')
990
991       await removeVideoFromPlaylist({
992         url: server.url,
993         token: server.accessToken,
994         playlistId: playlistServer2Id2,
995         playlistElementId: element2
996       })
997
998       const names3 = await getPlaylistNames()
999       expect(names3[0]).to.equal('playlist 3 updated')
1000       expect(names3[1]).to.equal('playlist 2')
1001     })
1002
1003     it('Should delete some elements', async function () {
1004       this.timeout(30000)
1005
1006       await removeVideoFromPlaylist({
1007         url: servers[0].url,
1008         token: servers[0].accessToken,
1009         playlistId: playlistServer1Id,
1010         playlistElementId: playlistElementServer1Video4
1011       })
1012
1013       await removeVideoFromPlaylist({
1014         url: servers[0].url,
1015         token: servers[0].accessToken,
1016         playlistId: playlistServer1Id,
1017         playlistElementId: playlistElementNSFW
1018       })
1019
1020       await waitJobs(servers)
1021
1022       for (const server of servers) {
1023         const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
1024
1025         expect(res.body.total).to.equal(4)
1026
1027         const elements: VideoPlaylistElement[] = res.body.data
1028         expect(elements).to.have.lengthOf(4)
1029
1030         expect(elements[0].video.name).to.equal('video 0 server 1')
1031         expect(elements[0].position).to.equal(1)
1032
1033         expect(elements[1].video.name).to.equal('video 2 server 3')
1034         expect(elements[1].position).to.equal(2)
1035
1036         expect(elements[2].video.name).to.equal('video 1 server 3')
1037         expect(elements[2].position).to.equal(3)
1038
1039         expect(elements[3].video.name).to.equal('video 4 server 1')
1040         expect(elements[3].position).to.equal(4)
1041       }
1042     })
1043
1044     it('Should be able to create a public playlist, and set it to private', async function () {
1045       this.timeout(30000)
1046
1047       const res = await createVideoPlaylist({
1048         url: servers[0].url,
1049         token: servers[0].accessToken,
1050         playlistAttrs: {
1051           displayName: 'my super public playlist',
1052           privacy: VideoPlaylistPrivacy.PUBLIC,
1053           videoChannelId: servers[0].videoChannel.id
1054         }
1055       })
1056       const videoPlaylistIds = res.body.videoPlaylist
1057
1058       await waitJobs(servers)
1059
1060       for (const server of servers) {
1061         await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 200)
1062       }
1063
1064       const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE }
1065       await updateVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs })
1066
1067       await waitJobs(servers)
1068
1069       for (const server of [ servers[1], servers[2] ]) {
1070         await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404)
1071       }
1072       await getVideoPlaylist(servers[0].url, videoPlaylistIds.uuid, 401)
1073
1074       await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistIds.uuid, 200)
1075     })
1076   })
1077
1078   describe('Playlist deletion', function () {
1079
1080     it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1081       this.timeout(30000)
1082
1083       await deleteVideoPlaylist(servers[0].url, servers[0].accessToken, playlistServer1Id)
1084
1085       await waitJobs(servers)
1086
1087       for (const server of servers) {
1088         await getVideoPlaylist(server.url, playlistServer1UUID, 404)
1089       }
1090     })
1091
1092     it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1093       this.timeout(30000)
1094
1095       for (const server of servers) {
1096         await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
1097       }
1098     })
1099
1100     it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1101       this.timeout(30000)
1102
1103       const finder = data => data.find(p => p.displayName === 'my super playlist')
1104
1105       {
1106         const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
1107         expect(res.body.total).to.equal(3)
1108         expect(finder(res.body.data)).to.not.be.undefined
1109       }
1110
1111       await unfollow(servers[2].url, servers[2].accessToken, servers[0])
1112
1113       {
1114         const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
1115         expect(res.body.total).to.equal(1)
1116
1117         expect(finder(res.body.data)).to.be.undefined
1118       }
1119     })
1120
1121     it('Should delete a channel and put the associated playlist in private mode', async function () {
1122       this.timeout(30000)
1123
1124       const res = await addVideoChannel(servers[0].url, servers[0].accessToken, { name: 'super_channel', displayName: 'super channel' })
1125       const videoChannelId = res.body.videoChannel.id
1126
1127       const res2 = await createVideoPlaylist({
1128         url: servers[0].url,
1129         token: servers[0].accessToken,
1130         playlistAttrs: {
1131           displayName: 'channel playlist',
1132           privacy: VideoPlaylistPrivacy.PUBLIC,
1133           videoChannelId
1134         }
1135       })
1136       const videoPlaylistUUID = res2.body.videoPlaylist.uuid
1137
1138       await waitJobs(servers)
1139
1140       await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'super_channel')
1141
1142       await waitJobs(servers)
1143
1144       const res3 = await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistUUID)
1145       expect(res3.body.displayName).to.equal('channel playlist')
1146       expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1147
1148       await getVideoPlaylist(servers[1].url, videoPlaylistUUID, 404)
1149     })
1150
1151     it('Should delete an account and delete its playlists', async function () {
1152       this.timeout(30000)
1153
1154       const user = { username: 'user_1', password: 'password' }
1155       const res = await createUser({
1156         url: servers[0].url,
1157         accessToken: servers[0].accessToken,
1158         username: user.username,
1159         password: user.password
1160       })
1161
1162       const userId = res.body.user.id
1163       const userAccessToken = await userLogin(servers[0], user)
1164
1165       const resChannel = await getMyUserInformation(servers[0].url, userAccessToken)
1166       const userChannel = (resChannel.body as User).videoChannels[0]
1167
1168       await createVideoPlaylist({
1169         url: servers[0].url,
1170         token: userAccessToken,
1171         playlistAttrs: {
1172           displayName: 'playlist to be deleted',
1173           privacy: VideoPlaylistPrivacy.PUBLIC,
1174           videoChannelId: userChannel.id
1175         }
1176       })
1177
1178       await waitJobs(servers)
1179
1180       const finder = data => data.find(p => p.displayName === 'playlist to be deleted')
1181
1182       {
1183         for (const server of [ servers[0], servers[1] ]) {
1184           const res = await getVideoPlaylistsList(server.url, 0, 15)
1185           expect(finder(res.body.data)).to.not.be.undefined
1186         }
1187       }
1188
1189       await removeUser(servers[0].url, userId, servers[0].accessToken)
1190       await waitJobs(servers)
1191
1192       {
1193         for (const server of [ servers[0], servers[1] ]) {
1194           const res = await getVideoPlaylistsList(server.url, 0, 15)
1195           expect(finder(res.body.data)).to.be.undefined
1196         }
1197       }
1198     })
1199   })
1200
1201   after(async function () {
1202     await cleanupTests(servers)
1203   })
1204 })