1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { checkPlaylistFilesWereRemoved, testImage } from '@server/tests/shared'
5 import { wait } from '@shared/core-utils'
9 VideoPlaylistCreateResult,
10 VideoPlaylistElementType,
14 } from '@shared/models'
17 createMultipleServers,
21 setAccessTokensToServers,
22 setDefaultAccountAvatar,
23 setDefaultVideoChannel,
25 } from '@shared/server-commands'
27 async function checkPlaylistElementType (
28 servers: PeerTubeServer[],
30 type: VideoPlaylistElementType,
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)
39 const videoElement = body.data.find(e => e.position === position)
40 expect(videoElement.type).to.equal(type, 'On server ' + server.url)
42 if (type === VideoPlaylistElementType.REGULAR) {
43 expect(videoElement.video).to.not.be.null
44 expect(videoElement.video.name).to.equal(name)
46 expect(videoElement.video).to.be.null
51 describe('Test video playlists', function () {
52 let servers: PeerTubeServer[] = []
54 let playlistServer2Id1: number
55 let playlistServer2Id2: number
56 let playlistServer2UUID2: string
58 let playlistServer1Id: number
59 let playlistServer1UUID: string
60 let playlistServer1UUID2: string
62 let playlistElementServer1Video4: number
63 let playlistElementServer1Video5: number
64 let playlistElementNSFW: number
66 let nsfwVideoServer1: number
68 let userTokenServer1: string
70 let commands: PlaylistsCommand[]
72 before(async function () {
75 servers = await createMultipleServers(3)
77 // Get the access tokens
78 await setAccessTokensToServers(servers)
79 await setDefaultVideoChannel(servers)
80 await setDefaultAccountAvatar(servers)
82 for (const server of servers) {
83 await server.config.disableTranscoding()
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])
91 commands = servers.map(s => s.playlists)
94 servers[0].store.videos = []
95 servers[1].store.videos = []
96 servers[2].store.videos = []
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 } })
103 server.store.videos.push(video)
108 nsfwVideoServer1 = (await servers[0].videos.quickUpload({ name: 'NSFW video', nsfw: true })).id
110 userTokenServer1 = await servers[0].users.generateUserAndToken('user1')
112 await waitJobs(servers)
115 describe('Get default playlists', function () {
117 it('Should list video playlist privacies', async function () {
118 const privacies = await commands[0].getPrivacies()
120 expect(Object.keys(privacies)).to.have.length.at.least(3)
121 expect(privacies[3]).to.equal('Private')
124 it('Should list watch later playlist', async function () {
125 const token = servers[0].accessToken
128 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.WATCH_LATER })
130 expect(body.total).to.equal(1)
131 expect(body.data).to.have.lengthOf(1)
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')
140 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.REGULAR })
142 expect(body.total).to.equal(0)
143 expect(body.data).to.have.lengthOf(0)
147 const body = await commands[0].listByAccount({ handle: 'root' })
148 expect(body.total).to.equal(0)
149 expect(body.data).to.have.lengthOf(0)
153 it('Should get private playlist for a classic user', async function () {
154 const token = await servers[0].users.generateUserAndToken('toto')
156 const body = await commands[0].listByAccount({ token, handle: 'toto' })
158 expect(body.total).to.equal(1)
159 expect(body.data).to.have.lengthOf(1)
161 const playlistId = body.data[0].id
162 await commands[0].listVideos({ token, playlistId })
166 describe('Create and federate playlists', function () {
168 it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
171 await commands[0].create({
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
181 await waitJobs(servers)
182 // Processing a playlist by the receiver could be long
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)
190 const playlistFromList = body.data[0]
192 const playlistFromGet = await server.playlists.get({ playlistId: playlistFromList.uuid })
194 for (const playlist of [ playlistFromGet, playlistFromList ]) {
195 expect(playlist.id).to.be.a('number')
196 expect(playlist.uuid).to.be.a('string')
198 expect(playlist.isLocal).to.equal(server.serverNumber === 1)
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)
208 expect(playlist.videosLength).to.equal(0)
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')
218 it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
222 const playlist = await servers[1].playlists.create({
224 displayName: 'playlist 2',
225 privacy: VideoPlaylistPrivacy.PUBLIC,
226 videoChannelId: servers[1].store.channel.id
229 playlistServer2Id1 = playlist.id
233 const playlist = await servers[1].playlists.create({
235 displayName: 'playlist 3',
236 privacy: VideoPlaylistPrivacy.PUBLIC,
237 thumbnailfile: 'thumbnail.jpg',
238 videoChannelId: servers[1].store.channel.id
242 playlistServer2Id2 = playlist.id
243 playlistServer2UUID2 = playlist.uuid
246 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
247 await servers[1].playlists.addElement({
249 attributes: { videoId: servers[1].store.videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
251 await servers[1].playlists.addElement({
253 attributes: { videoId: servers[1].store.videos[1].id }
257 await waitJobs(servers)
260 for (const server of [ servers[0], servers[1] ]) {
261 const body = await server.playlists.list({ start: 0, count: 5 })
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)
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)
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
277 it('Should have the playlist on server 3 after a new follow', async function () {
280 // Server 2 and server 3 follow each other
281 await doubleFollow(servers[1], servers[2])
283 const body = await servers[2].playlists.list({ start: 0, count: 5 })
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)
289 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
293 describe('List playlists', function () {
295 it('Should correctly list the playlists', async function () {
299 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: 'createdAt' })
300 expect(body.total).to.equal(3)
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')
309 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: '-createdAt' })
310 expect(body.total).to.equal(3)
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')
319 it('Should list video channel playlists', async function () {
323 const body = await commands[0].listByChannel({ handle: 'root_channel', start: 0, count: 2, sort: '-createdAt' })
324 expect(body.total).to.equal(1)
326 const data = body.data
327 expect(data).to.have.lengthOf(1)
328 expect(data[0].displayName).to.equal('my super playlist')
332 it('Should list account playlists', async function () {
336 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: '-createdAt' })
337 expect(body.total).to.equal(2)
339 const data = body.data
340 expect(data).to.have.lengthOf(1)
341 expect(data[0].displayName).to.equal('playlist 2')
345 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: 'createdAt' })
346 expect(body.total).to.equal(2)
348 const data = body.data
349 expect(data).to.have.lengthOf(1)
350 expect(data[0].displayName).to.equal('playlist 3')
354 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '3' })
355 expect(body.total).to.equal(1)
357 const data = body.data
358 expect(data).to.have.lengthOf(1)
359 expect(data[0].displayName).to.equal('playlist 3')
363 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '4' })
364 expect(body.total).to.equal(0)
366 const data = body.data
367 expect(data).to.have.lengthOf(0)
372 describe('Playlist rights', function () {
373 let unlistedPlaylist: VideoPlaylistCreateResult
374 let privatePlaylist: VideoPlaylistCreateResult
376 before(async function () {
380 unlistedPlaylist = await servers[1].playlists.create({
382 displayName: 'playlist unlisted',
383 privacy: VideoPlaylistPrivacy.UNLISTED,
384 videoChannelId: servers[1].store.channel.id
390 privatePlaylist = await servers[1].playlists.create({
392 displayName: 'playlist private',
393 privacy: VideoPlaylistPrivacy.PRIVATE
398 await waitJobs(servers)
402 it('Should not list unlisted or private playlists', async function () {
403 for (const server of servers) {
405 await server.playlists.listByAccount({ handle: 'root@localhost:' + servers[1].port, sort: '-createdAt' }),
406 await server.playlists.list({ start: 0, count: 2, sort: '-createdAt' })
409 expect(results[0].total).to.equal(2)
410 expect(results[1].total).to.equal(3)
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')
421 it('Should not get unlisted playlist using only the id', async function () {
422 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 })
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 })
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 })
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 })
443 describe('Update playlists', function () {
445 it('Should update a playlist', async function () {
448 await servers[1].playlists.update({
450 displayName: 'playlist 3 updated',
451 description: 'description updated',
452 privacy: VideoPlaylistPrivacy.UNLISTED,
453 thumbnailfile: 'thumbnail.jpg',
454 videoChannelId: servers[1].store.channel.id
456 playlistId: playlistServer2Id2
459 await waitJobs(servers)
461 for (const server of servers) {
462 const playlist = await server.playlists.get({ playlistId: playlistServer2UUID2 })
464 expect(playlist.displayName).to.equal('playlist 3 updated')
465 expect(playlist.description).to.equal('description updated')
467 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
468 expect(playlist.privacy.label).to.equal('Unlisted')
470 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
471 expect(playlist.type.label).to.equal('Regular')
473 expect(playlist.videosLength).to.equal(2)
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')
483 describe('Element timestamps', function () {
485 it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
488 const addVideo = (attributes: any) => {
489 return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
492 const playlist = await commands[0].create({
494 displayName: 'playlist 4',
495 privacy: VideoPlaylistPrivacy.PUBLIC,
496 videoChannelId: servers[0].store.channel.id
500 playlistServer1Id = playlist.id
501 playlistServer1UUID = playlist.uuid
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 })
507 const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 })
508 playlistElementServer1Video4 = element.id
512 const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
513 playlistElementServer1Video5 = element.id
517 const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
518 playlistElementNSFW = element.id
520 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
521 await addVideo({ videoId: nsfwVideoServer1 })
524 await waitJobs(servers)
527 it('Should correctly list playlist videos', async function () {
530 for (const server of servers) {
532 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
534 expect(body.total).to.equal(8)
536 const videoElements = body.data
537 expect(videoElements).to.have.lengthOf(8)
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)
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
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
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)
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)
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
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
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
581 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 })
582 expect(body.data).to.have.lengthOf(2)
588 describe('Element type', function () {
589 let groupUser1: PeerTubeServer[]
590 let groupWithoutToken1: PeerTubeServer[]
591 let group1: PeerTubeServer[]
592 let group2: PeerTubeServer[]
598 before(async function () {
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] ]
606 const playlist = await commands[0].create({
607 token: userTokenServer1,
609 displayName: 'playlist 56',
610 privacy: VideoPlaylistPrivacy.PUBLIC,
611 videoChannelId: servers[0].store.channel.id
615 const playlistServer1Id2 = playlist.id
616 playlistServer1UUID2 = playlist.uuid
618 const addVideo = (attributes: any) => {
619 return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes })
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
626 await waitJobs(servers)
628 await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
629 await addVideo({ videoId: video2, startTimestamp: 35 })
630 await addVideo({ videoId: video3 })
632 await waitJobs(servers)
635 it('Should update the element type if the video is private', async function () {
638 const name = 'video 89'
642 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } })
643 await waitJobs(servers)
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)
652 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
653 await waitJobs(servers)
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)
663 it('Should update the element type if the video is blacklisted', async function () {
666 const name = 'video 89'
670 await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true })
671 await waitJobs(servers)
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)
680 await servers[0].blacklist.remove({ videoId: video1 })
681 await waitJobs(servers)
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)
691 it('Should update the element type if the account or server of the video is blocked', async function () {
694 const command = servers[0].blocklist
696 const name = 'video 90'
700 await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
701 await waitJobs(servers)
703 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
704 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
706 await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
707 await waitJobs(servers)
709 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
713 await command.addToMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
714 await waitJobs(servers)
716 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
717 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
719 await command.removeFromMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
720 await waitJobs(servers)
722 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
726 await command.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port })
727 await waitJobs(servers)
729 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
730 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
732 await command.removeFromServerBlocklist({ account: 'root@localhost:' + servers[1].port })
733 await waitJobs(servers)
735 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
739 await command.addToServerBlocklist({ server: 'localhost:' + servers[1].port })
740 await waitJobs(servers)
742 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
743 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
745 await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port })
746 await waitJobs(servers)
748 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
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)
756 const elements = body.data
757 const element = elements.find(e => e.position === 3)
759 expect(element).to.exist
760 expect(element.video).to.be.null
761 expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
766 describe('Managing playlist elements', function () {
768 it('Should reorder the playlist', async function () {
772 await commands[0].reorderElements({
773 playlistId: playlistServer1Id,
776 insertAfterPosition: 3
780 await waitJobs(servers)
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)
786 expect(names).to.deep.equal([
800 await commands[0].reorderElements({
801 playlistId: playlistServer1Id,
805 insertAfterPosition: 4
809 await waitJobs(servers)
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)
815 expect(names).to.deep.equal([
829 await commands[0].reorderElements({
830 playlistId: playlistServer1Id,
833 insertAfterPosition: 3
837 await waitJobs(servers)
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)
843 expect(names).to.deep.equal([
854 for (let i = 1; i <= elements.length; i++) {
855 expect(elements[i - 1].position).to.equal(i)
861 it('Should update startTimestamp/endTimestamp of some elements', async function () {
864 await commands[0].updateElement({
865 playlistId: playlistServer1Id,
866 elementId: playlistElementServer1Video4,
872 await commands[0].updateElement({
873 playlistId: playlistServer1Id,
874 elementId: playlistElementServer1Video5,
880 await waitJobs(servers)
882 for (const server of servers) {
883 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
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)
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
897 it('Should check videos existence in my playlist', async function () {
899 servers[0].store.videos[0].id,
901 servers[0].store.videos[3].id,
903 servers[0].store.videos[4].id
905 const obj = await commands[0].videosExist({ videoIds })
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)
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)
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)
933 expect(obj[42000]).to.have.lengthOf(0)
934 expect(obj[43000]).to.have.lengthOf(0)
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
941 async function getPlaylistNames () {
942 const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' })
944 return data.map(p => p.displayName)
947 const attributes = { videoId }
948 const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes })
949 const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes })
951 const names1 = await getPlaylistNames()
952 expect(names1[0]).to.equal('playlist 3 updated')
953 expect(names1[1]).to.equal('playlist 2')
955 await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id })
957 const names2 = await getPlaylistNames()
958 expect(names2[0]).to.equal('playlist 2')
959 expect(names2[1]).to.equal('playlist 3 updated')
961 await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id })
963 const names3 = await getPlaylistNames()
964 expect(names3[0]).to.equal('playlist 3 updated')
965 expect(names3[1]).to.equal('playlist 2')
968 it('Should delete some elements', async function () {
971 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 })
972 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW })
974 await waitJobs(servers)
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)
980 const elements = body.data
981 expect(elements).to.have.lengthOf(6)
983 expect(elements[0].video.name).to.equal('video 0 server 1')
984 expect(elements[0].position).to.equal(1)
986 expect(elements[1].video.name).to.equal('video 2 server 3')
987 expect(elements[1].position).to.equal(2)
989 expect(elements[2].video.name).to.equal('video 1 server 3')
990 expect(elements[2].position).to.equal(3)
992 expect(elements[3].video.name).to.equal('video 4 server 1')
993 expect(elements[3].position).to.equal(4)
995 expect(elements[4].video.name).to.equal('NSFW video')
996 expect(elements[4].position).to.equal(5)
998 expect(elements[5].video.name).to.equal('NSFW video')
999 expect(elements[5].position).to.equal(6)
1003 it('Should be able to create a public playlist, and set it to private', async function () {
1006 const videoPlaylistIds = await commands[0].create({
1008 displayName: 'my super public playlist',
1009 privacy: VideoPlaylistPrivacy.PUBLIC,
1010 videoChannelId: servers[0].store.channel.id
1014 await waitJobs(servers)
1016 for (const server of servers) {
1017 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1020 const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE }
1021 await commands[0].update({ playlistId: videoPlaylistIds.id, attributes })
1023 await waitJobs(servers)
1025 for (const server of [ servers[1], servers[2] ]) {
1026 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
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 })
1034 describe('Playlist deletion', function () {
1036 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1039 await commands[0].delete({ playlistId: playlistServer1Id })
1041 await waitJobs(servers)
1043 for (const server of servers) {
1044 await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1048 it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1051 for (const server of servers) {
1052 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
1056 it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1059 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist')
1062 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1063 expect(body.total).to.equal(3)
1065 expect(finder(body.data)).to.not.be.undefined
1068 await servers[2].follows.unfollow({ target: servers[0] })
1071 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1072 expect(body.total).to.equal(1)
1074 expect(finder(body.data)).to.be.undefined
1078 it('Should delete a channel and put the associated playlist in private mode', async function () {
1081 const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } })
1083 const playlistCreated = await commands[0].create({
1085 displayName: 'channel playlist',
1086 privacy: VideoPlaylistPrivacy.PUBLIC,
1087 videoChannelId: channel.id
1091 await waitJobs(servers)
1093 await servers[0].channels.delete({ channelName: 'super_channel' })
1095 await waitJobs(servers)
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)
1101 await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1104 it('Should delete an account and delete its playlists', async function () {
1107 const { userId, token } = await servers[0].users.generate('user_1')
1109 const { videoChannels } = await servers[0].users.getMyInfo({ token })
1110 const userChannel = videoChannels[0]
1112 await commands[0].create({
1114 displayName: 'playlist to be deleted',
1115 privacy: VideoPlaylistPrivacy.PUBLIC,
1116 videoChannelId: userChannel.id
1120 await waitJobs(servers)
1122 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted')
1125 for (const server of [ servers[0], servers[1] ]) {
1126 const body = await server.playlists.list({ start: 0, count: 15 })
1128 expect(finder(body.data)).to.not.be.undefined
1132 await servers[0].users.remove({ userId })
1133 await waitJobs(servers)
1136 for (const server of [ servers[0], servers[1] ]) {
1137 const body = await server.playlists.list({ start: 0, count: 15 })
1139 expect(finder(body.data)).to.be.undefined
1145 after(async function () {
1146 await cleanupTests(servers)