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