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