Automatically update playlist thumbnails
[oweals/peertube.git] / server / tests / api / videos / video-playlists.ts
1 /* tslint:disable:no-unused-expression */
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 (let 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     it('Should not list unlisted or private playlists', async function () {
413       this.timeout(30000)
414
415       await createVideoPlaylist({
416         url: servers[ 1 ].url,
417         token: servers[ 1 ].accessToken,
418         playlistAttrs: {
419           displayName: 'playlist unlisted',
420           privacy: VideoPlaylistPrivacy.UNLISTED
421         }
422       })
423
424       await createVideoPlaylist({
425         url: servers[ 1 ].url,
426         token: servers[ 1 ].accessToken,
427         playlistAttrs: {
428           displayName: 'playlist private',
429           privacy: VideoPlaylistPrivacy.PRIVATE
430         }
431       })
432
433       await waitJobs(servers)
434
435       for (const server of servers) {
436         const results = [
437           await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[ 1 ].port, 0, 5, '-createdAt'),
438           await getVideoPlaylistsList(server.url, 0, 2, '-createdAt')
439         ]
440
441         expect(results[ 0 ].body.total).to.equal(2)
442         expect(results[ 1 ].body.total).to.equal(3)
443
444         for (const res of results) {
445           const data: VideoPlaylist[] = res.body.data
446           expect(data).to.have.lengthOf(2)
447           expect(data[ 0 ].displayName).to.equal('playlist 3')
448           expect(data[ 1 ].displayName).to.equal('playlist 2')
449         }
450       }
451     })
452   })
453
454   describe('Update playlists', function () {
455
456     it('Should update a playlist', async function () {
457       this.timeout(30000)
458
459       await updateVideoPlaylist({
460         url: servers[1].url,
461         token: servers[1].accessToken,
462         playlistAttrs: {
463           displayName: 'playlist 3 updated',
464           description: 'description updated',
465           privacy: VideoPlaylistPrivacy.UNLISTED,
466           thumbnailfile: 'thumbnail.jpg',
467           videoChannelId: servers[1].videoChannel.id
468         },
469         playlistId: playlistServer2Id2
470       })
471
472       await waitJobs(servers)
473
474       for (const server of servers) {
475         const res = await getVideoPlaylist(server.url, playlistServer2UUID2)
476         const playlist: VideoPlaylist = res.body
477
478         expect(playlist.displayName).to.equal('playlist 3 updated')
479         expect(playlist.description).to.equal('description updated')
480
481         expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
482         expect(playlist.privacy.label).to.equal('Unlisted')
483
484         expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
485         expect(playlist.type.label).to.equal('Regular')
486
487         expect(playlist.videosLength).to.equal(2)
488
489         expect(playlist.ownerAccount.name).to.equal('root')
490         expect(playlist.ownerAccount.displayName).to.equal('root')
491         expect(playlist.videoChannel.name).to.equal('root_channel')
492         expect(playlist.videoChannel.displayName).to.equal('Main root channel')
493       }
494     })
495   })
496
497   describe('Element timestamps', function () {
498
499     it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
500       this.timeout(30000)
501
502       const addVideo = (elementAttrs: any) => {
503         return addVideoInPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: playlistServer1Id, elementAttrs })
504       }
505
506       const res = await createVideoPlaylist({
507         url: servers[ 0 ].url,
508         token: servers[ 0 ].accessToken,
509         playlistAttrs: {
510           displayName: 'playlist 4',
511           privacy: VideoPlaylistPrivacy.PUBLIC,
512           videoChannelId: servers[ 0 ].videoChannel.id
513         }
514       })
515
516       playlistServer1Id = res.body.videoPlaylist.id
517       playlistServer1UUID = res.body.videoPlaylist.uuid
518
519       await addVideo({ videoId: servers[ 0 ].videos[ 0 ].uuid, startTimestamp: 15, stopTimestamp: 28 })
520       await addVideo({ videoId: servers[ 2 ].videos[ 1 ].uuid, startTimestamp: 35 })
521       await addVideo({ videoId: servers[ 2 ].videos[ 2 ].uuid })
522       {
523         const res = await addVideo({ videoId: servers[ 0 ].videos[ 3 ].uuid, stopTimestamp: 35 })
524         playlistElementServer1Video4 = res.body.videoPlaylistElement.id
525       }
526
527       {
528         const res = await addVideo({ videoId: servers[ 0 ].videos[ 4 ].uuid, startTimestamp: 45, stopTimestamp: 60 })
529         playlistElementServer1Video5 = res.body.videoPlaylistElement.id
530       }
531
532       {
533         const res = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
534         playlistElementNSFW = res.body.videoPlaylistElement.id
535       }
536
537       await waitJobs(servers)
538     })
539
540     it('Should correctly list playlist videos', async function () {
541       this.timeout(30000)
542
543       for (const server of servers) {
544         const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
545
546         expect(res.body.total).to.equal(6)
547
548         const videoElements: VideoPlaylistElement[] = res.body.data
549         expect(videoElements).to.have.lengthOf(6)
550
551         expect(videoElements[ 0 ].video.name).to.equal('video 0 server 1')
552         expect(videoElements[ 0 ].position).to.equal(1)
553         expect(videoElements[ 0 ].startTimestamp).to.equal(15)
554         expect(videoElements[ 0 ].stopTimestamp).to.equal(28)
555
556         expect(videoElements[ 1 ].video.name).to.equal('video 1 server 3')
557         expect(videoElements[ 1 ].position).to.equal(2)
558         expect(videoElements[ 1 ].startTimestamp).to.equal(35)
559         expect(videoElements[ 1 ].stopTimestamp).to.be.null
560
561         expect(videoElements[ 2 ].video.name).to.equal('video 2 server 3')
562         expect(videoElements[ 2 ].position).to.equal(3)
563         expect(videoElements[ 2 ].startTimestamp).to.be.null
564         expect(videoElements[ 2 ].stopTimestamp).to.be.null
565
566         expect(videoElements[ 3 ].video.name).to.equal('video 3 server 1')
567         expect(videoElements[ 3 ].position).to.equal(4)
568         expect(videoElements[ 3 ].startTimestamp).to.be.null
569         expect(videoElements[ 3 ].stopTimestamp).to.equal(35)
570
571         expect(videoElements[ 4 ].video.name).to.equal('video 4 server 1')
572         expect(videoElements[ 4 ].position).to.equal(5)
573         expect(videoElements[ 4 ].startTimestamp).to.equal(45)
574         expect(videoElements[ 4 ].stopTimestamp).to.equal(60)
575
576         expect(videoElements[ 5 ].video.name).to.equal('NSFW video')
577         expect(videoElements[ 5 ].position).to.equal(6)
578         expect(videoElements[ 5 ].startTimestamp).to.equal(5)
579         expect(videoElements[ 5 ].stopTimestamp).to.be.null
580
581         const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2)
582         expect(res3.body.data).to.have.lengthOf(2)
583       }
584     })
585   })
586
587   describe('Element type', function () {
588     let groupUser1: ServerInfo[]
589     let groupWithoutToken1: ServerInfo[]
590     let group1: ServerInfo[]
591     let group2: ServerInfo[]
592
593     let video1: string
594     let video2: string
595     let video3: string
596
597     before(async function () {
598       this.timeout(30000)
599
600       groupUser1 = [ Object.assign({}, servers[ 0 ], { accessToken: userAccessTokenServer1 }) ]
601       groupWithoutToken1 = [ Object.assign({}, servers[ 0 ], { accessToken: undefined }) ]
602       group1 = [ servers[ 0 ] ]
603       group2 = [ servers[ 1 ], servers[ 2 ] ]
604
605       const res = await createVideoPlaylist({
606         url: servers[ 0 ].url,
607         token: userAccessTokenServer1,
608         playlistAttrs: {
609           displayName: 'playlist 56',
610           privacy: VideoPlaylistPrivacy.PUBLIC,
611           videoChannelId: servers[ 0 ].videoChannel.id
612         }
613       })
614
615       const playlistServer1Id2 = res.body.videoPlaylist.id
616       playlistServer1UUID2 = res.body.videoPlaylist.uuid
617
618       const addVideo = (elementAttrs: any) => {
619         return addVideoInPlaylist({ url: servers[ 0 ].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs })
620       }
621
622       video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid
623       video2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 90' })).uuid
624       video3 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 91', nsfw: true })).uuid
625
626       await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
627       await addVideo({ videoId: video2, startTimestamp: 35 })
628       await addVideo({ videoId: video3 })
629
630       await waitJobs(servers)
631     })
632
633     it('Should update the element type if the video is private', async function () {
634       this.timeout(20000)
635
636       const name = 'video 89'
637       const position = 1
638
639       {
640         await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PRIVATE })
641         await waitJobs(servers)
642
643         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
644         await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
645         await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
646         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
647       }
648
649       {
650         await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PUBLIC })
651         await waitJobs(servers)
652
653         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
654         await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
655         await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
656         // We deleted the video, so even if we recreated it, the old entry is still deleted
657         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
658       }
659     })
660
661     it('Should update the element type if the video is blacklisted', async function () {
662       this.timeout(20000)
663
664       const name = 'video 89'
665       const position = 1
666
667       {
668         await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1, 'reason', true)
669         await waitJobs(servers)
670
671         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
672         await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
673         await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
674         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
675       }
676
677       {
678         await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1)
679         await waitJobs(servers)
680
681         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
682         await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
683         await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
684         // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
685         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
686       }
687     })
688
689     it('Should update the element type if the account or server of the video is blocked', async function () {
690       this.timeout(90000)
691
692       const name = 'video 90'
693       const position = 2
694
695       {
696         await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port)
697         await waitJobs(servers)
698
699         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
700         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
701
702         await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port)
703         await waitJobs(servers)
704
705         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
706       }
707
708       {
709         await addServerToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port)
710         await waitJobs(servers)
711
712         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
713         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
714
715         await removeServerFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port)
716         await waitJobs(servers)
717
718         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
719       }
720
721       {
722         await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port)
723         await waitJobs(servers)
724
725         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
726         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
727
728         await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port)
729         await waitJobs(servers)
730
731         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
732       }
733
734       {
735         await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
736         await waitJobs(servers)
737
738         await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
739         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
740
741         await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
742         await waitJobs(servers)
743
744         await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
745       }
746     })
747
748     it('Should hide the video if it is NSFW', async function () {
749       const res = await getPlaylistVideos(servers[0].url, userAccessTokenServer1, playlistServer1UUID2, 0, 10, { nsfw: false })
750       expect(res.body.total).to.equal(3)
751
752       const elements: VideoPlaylistElement[] = res.body.data
753       const element = elements.find(e => e.position === 3)
754
755       expect(element).to.exist
756       expect(element.video).to.be.null
757       expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
758     })
759
760   })
761
762   describe('Managing playlist elements', function () {
763
764     it('Should reorder the playlist', async function () {
765       this.timeout(30000)
766
767       {
768         await reorderVideosPlaylist({
769           url: servers[ 0 ].url,
770           token: servers[ 0 ].accessToken,
771           playlistId: playlistServer1Id,
772           elementAttrs: {
773             startPosition: 2,
774             insertAfterPosition: 3
775           }
776         })
777
778         await waitJobs(servers)
779
780         for (const server of servers) {
781           const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
782           const names = (res.body.data as VideoPlaylistElement[]).map(v => v.video.name)
783
784           expect(names).to.deep.equal([
785             'video 0 server 1',
786             'video 2 server 3',
787             'video 1 server 3',
788             'video 3 server 1',
789             'video 4 server 1',
790             'NSFW video'
791           ])
792         }
793       }
794
795       {
796         await reorderVideosPlaylist({
797           url: servers[ 0 ].url,
798           token: servers[ 0 ].accessToken,
799           playlistId: playlistServer1Id,
800           elementAttrs: {
801             startPosition: 1,
802             reorderLength: 3,
803             insertAfterPosition: 4
804           }
805         })
806
807         await waitJobs(servers)
808
809         for (const server of servers) {
810           const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
811           const names = (res.body.data as VideoPlaylistElement[]).map(v => v.video.name)
812
813           expect(names).to.deep.equal([
814             'video 3 server 1',
815             'video 0 server 1',
816             'video 2 server 3',
817             'video 1 server 3',
818             'video 4 server 1',
819             'NSFW video'
820           ])
821         }
822       }
823
824       {
825         await reorderVideosPlaylist({
826           url: servers[ 0 ].url,
827           token: servers[ 0 ].accessToken,
828           playlistId: playlistServer1Id,
829           elementAttrs: {
830             startPosition: 6,
831             insertAfterPosition: 3
832           }
833         })
834
835         await waitJobs(servers)
836
837         for (const server of servers) {
838           const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
839           const elements: VideoPlaylistElement[] = res.body.data
840           const names = elements.map(v => v.video.name)
841
842           expect(names).to.deep.equal([
843             'video 3 server 1',
844             'video 0 server 1',
845             'video 2 server 3',
846             'NSFW video',
847             'video 1 server 3',
848             'video 4 server 1'
849           ])
850
851           for (let i = 1; i <= elements.length; i++) {
852             expect(elements[ i - 1 ].position).to.equal(i)
853           }
854         }
855       }
856     })
857
858     it('Should update startTimestamp/endTimestamp of some elements', async function () {
859       this.timeout(30000)
860
861       await updateVideoPlaylistElement({
862         url: servers[ 0 ].url,
863         token: servers[ 0 ].accessToken,
864         playlistId: playlistServer1Id,
865         playlistElementId: playlistElementServer1Video4,
866         elementAttrs: {
867           startTimestamp: 1
868         }
869       })
870
871       await updateVideoPlaylistElement({
872         url: servers[ 0 ].url,
873         token: servers[ 0 ].accessToken,
874         playlistId: playlistServer1Id,
875         playlistElementId: playlistElementServer1Video5,
876         elementAttrs: {
877           stopTimestamp: null
878         }
879       })
880
881       await waitJobs(servers)
882
883       for (const server of servers) {
884         const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
885         const elements: VideoPlaylistElement[] = res.body.data
886
887         expect(elements[ 0 ].video.name).to.equal('video 3 server 1')
888         expect(elements[ 0 ].position).to.equal(1)
889         expect(elements[ 0 ].startTimestamp).to.equal(1)
890         expect(elements[ 0 ].stopTimestamp).to.equal(35)
891
892         expect(elements[ 5 ].video.name).to.equal('video 4 server 1')
893         expect(elements[ 5 ].position).to.equal(6)
894         expect(elements[ 5 ].startTimestamp).to.equal(45)
895         expect(elements[ 5 ].stopTimestamp).to.be.null
896       }
897     })
898
899     it('Should check videos existence in my playlist', async function () {
900       const videoIds = [
901         servers[ 0 ].videos[ 0 ].id,
902         42000,
903         servers[ 0 ].videos[ 3 ].id,
904         43000,
905         servers[ 0 ].videos[ 4 ].id
906       ]
907       const res = await doVideosExistInMyPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, videoIds)
908       const obj = res.body as VideoExistInPlaylist
909
910       {
911         const elem = obj[ servers[ 0 ].videos[ 0 ].id ]
912         expect(elem).to.have.lengthOf(1)
913         expect(elem[ 0 ].playlistElementId).to.exist
914         expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id)
915         expect(elem[ 0 ].startTimestamp).to.equal(15)
916         expect(elem[ 0 ].stopTimestamp).to.equal(28)
917       }
918
919       {
920         const elem = obj[ servers[ 0 ].videos[ 3 ].id ]
921         expect(elem).to.have.lengthOf(1)
922         expect(elem[ 0 ].playlistElementId).to.equal(playlistElementServer1Video4)
923         expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id)
924         expect(elem[ 0 ].startTimestamp).to.equal(1)
925         expect(elem[ 0 ].stopTimestamp).to.equal(35)
926       }
927
928       {
929         const elem = obj[ servers[ 0 ].videos[ 4 ].id ]
930         expect(elem).to.have.lengthOf(1)
931         expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id)
932         expect(elem[ 0 ].startTimestamp).to.equal(45)
933         expect(elem[ 0 ].stopTimestamp).to.equal(null)
934       }
935
936       expect(obj[ 42000 ]).to.have.lengthOf(0)
937       expect(obj[ 43000 ]).to.have.lengthOf(0)
938     })
939
940     it('Should automatically update updatedAt field of playlists', async function () {
941       const server = servers[ 1 ]
942       const videoId = servers[ 1 ].videos[ 5 ].id
943
944       async function getPlaylistNames () {
945         const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt')
946
947         return (res.body.data as VideoPlaylist[]).map(p => p.displayName)
948       }
949
950       const elementAttrs = { videoId }
951       const res1 = await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id1, elementAttrs })
952       const res2 = await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id2, elementAttrs })
953
954       const element1 = res1.body.videoPlaylistElement.id
955       const element2 = res2.body.videoPlaylistElement.id
956
957       const names1 = await getPlaylistNames()
958       expect(names1[ 0 ]).to.equal('playlist 3 updated')
959       expect(names1[ 1 ]).to.equal('playlist 2')
960
961       await removeVideoFromPlaylist({
962         url: server.url,
963         token: server.accessToken,
964         playlistId: playlistServer2Id1,
965         playlistElementId: element1
966       })
967
968       const names2 = await getPlaylistNames()
969       expect(names2[ 0 ]).to.equal('playlist 2')
970       expect(names2[ 1 ]).to.equal('playlist 3 updated')
971
972       await removeVideoFromPlaylist({
973         url: server.url,
974         token: server.accessToken,
975         playlistId: playlistServer2Id2,
976         playlistElementId: element2
977       })
978
979       const names3 = await getPlaylistNames()
980       expect(names3[ 0 ]).to.equal('playlist 3 updated')
981       expect(names3[ 1 ]).to.equal('playlist 2')
982     })
983
984     it('Should delete some elements', async function () {
985       this.timeout(30000)
986
987       await removeVideoFromPlaylist({
988         url: servers[ 0 ].url,
989         token: servers[ 0 ].accessToken,
990         playlistId: playlistServer1Id,
991         playlistElementId: playlistElementServer1Video4
992       })
993
994       await removeVideoFromPlaylist({
995         url: servers[ 0 ].url,
996         token: servers[ 0 ].accessToken,
997         playlistId: playlistServer1Id,
998         playlistElementId: playlistElementNSFW
999       })
1000
1001       await waitJobs(servers)
1002
1003       for (const server of servers) {
1004         const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
1005
1006         expect(res.body.total).to.equal(4)
1007
1008         const elements: VideoPlaylistElement[] = res.body.data
1009         expect(elements).to.have.lengthOf(4)
1010
1011         expect(elements[ 0 ].video.name).to.equal('video 0 server 1')
1012         expect(elements[ 0 ].position).to.equal(1)
1013
1014         expect(elements[ 1 ].video.name).to.equal('video 2 server 3')
1015         expect(elements[ 1 ].position).to.equal(2)
1016
1017         expect(elements[ 2 ].video.name).to.equal('video 1 server 3')
1018         expect(elements[ 2 ].position).to.equal(3)
1019
1020         expect(elements[ 3 ].video.name).to.equal('video 4 server 1')
1021         expect(elements[ 3 ].position).to.equal(4)
1022       }
1023     })
1024
1025     it('Should be able to create a public playlist, and set it to private', async function () {
1026       this.timeout(30000)
1027
1028       const res = await createVideoPlaylist({
1029         url: servers[ 0 ].url,
1030         token: servers[ 0 ].accessToken,
1031         playlistAttrs: {
1032           displayName: 'my super public playlist',
1033           privacy: VideoPlaylistPrivacy.PUBLIC,
1034           videoChannelId: servers[ 0 ].videoChannel.id
1035         }
1036       })
1037       const videoPlaylistIds = res.body.videoPlaylist
1038
1039       await waitJobs(servers)
1040
1041       for (const server of servers) {
1042         await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 200)
1043       }
1044
1045       const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE }
1046       await updateVideoPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs })
1047
1048       await waitJobs(servers)
1049
1050       for (const server of [ servers[ 1 ], servers[ 2 ] ]) {
1051         await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404)
1052       }
1053       await getVideoPlaylist(servers[ 0 ].url, videoPlaylistIds.uuid, 401)
1054
1055       await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistIds.uuid, 200)
1056     })
1057   })
1058
1059   describe('Playlist deletion', function () {
1060
1061     it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1062       this.timeout(30000)
1063
1064       await deleteVideoPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, playlistServer1Id)
1065
1066       await waitJobs(servers)
1067
1068       for (const server of servers) {
1069         await getVideoPlaylist(server.url, playlistServer1UUID, 404)
1070       }
1071     })
1072
1073     it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1074       this.timeout(30000)
1075
1076       for (const server of servers) {
1077         await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
1078       }
1079     })
1080
1081     it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1082       this.timeout(30000)
1083
1084       const finder = data => data.find(p => p.displayName === 'my super playlist')
1085
1086       {
1087         const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5)
1088         expect(res.body.total).to.equal(3)
1089         expect(finder(res.body.data)).to.not.be.undefined
1090       }
1091
1092       await unfollow(servers[ 2 ].url, servers[ 2 ].accessToken, servers[ 0 ])
1093
1094       {
1095         const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5)
1096         expect(res.body.total).to.equal(1)
1097
1098         expect(finder(res.body.data)).to.be.undefined
1099       }
1100     })
1101
1102     it('Should delete a channel and put the associated playlist in private mode', async function () {
1103       this.timeout(30000)
1104
1105       const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'super_channel', displayName: 'super channel' })
1106       const videoChannelId = res.body.videoChannel.id
1107
1108       const res2 = await createVideoPlaylist({
1109         url: servers[ 0 ].url,
1110         token: servers[ 0 ].accessToken,
1111         playlistAttrs: {
1112           displayName: 'channel playlist',
1113           privacy: VideoPlaylistPrivacy.PUBLIC,
1114           videoChannelId
1115         }
1116       })
1117       const videoPlaylistUUID = res2.body.videoPlaylist.uuid
1118
1119       await waitJobs(servers)
1120
1121       await deleteVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, 'super_channel')
1122
1123       await waitJobs(servers)
1124
1125       const res3 = await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistUUID)
1126       expect(res3.body.displayName).to.equal('channel playlist')
1127       expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1128
1129       await getVideoPlaylist(servers[ 1 ].url, videoPlaylistUUID, 404)
1130     })
1131
1132     it('Should delete an account and delete its playlists', async function () {
1133       this.timeout(30000)
1134
1135       const user = { username: 'user_1', password: 'password' }
1136       const res = await createUser({
1137         url: servers[ 0 ].url,
1138         accessToken: servers[ 0 ].accessToken,
1139         username: user.username,
1140         password: user.password
1141       })
1142
1143       const userId = res.body.user.id
1144       const userAccessToken = await userLogin(servers[ 0 ], user)
1145
1146       const resChannel = await getMyUserInformation(servers[ 0 ].url, userAccessToken)
1147       const userChannel = (resChannel.body as User).videoChannels[ 0 ]
1148
1149       await createVideoPlaylist({
1150         url: servers[ 0 ].url,
1151         token: userAccessToken,
1152         playlistAttrs: {
1153           displayName: 'playlist to be deleted',
1154           privacy: VideoPlaylistPrivacy.PUBLIC,
1155           videoChannelId: userChannel.id
1156         }
1157       })
1158
1159       await waitJobs(servers)
1160
1161       const finder = data => data.find(p => p.displayName === 'playlist to be deleted')
1162
1163       {
1164         for (const server of [ servers[ 0 ], servers[ 1 ] ]) {
1165           const res = await getVideoPlaylistsList(server.url, 0, 15)
1166           expect(finder(res.body.data)).to.not.be.undefined
1167         }
1168       }
1169
1170       await removeUser(servers[ 0 ].url, userId, servers[ 0 ].accessToken)
1171       await waitJobs(servers)
1172
1173       {
1174         for (const server of [ servers[ 0 ], servers[ 1 ] ]) {
1175           const res = await getVideoPlaylistsList(server.url, 0, 15)
1176           expect(finder(res.body.data)).to.be.undefined
1177         }
1178       }
1179     })
1180   })
1181
1182   after(async function () {
1183     await cleanupTests(servers)
1184   })
1185 })