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