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