1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import { checkPlaylistFilesWereRemoved, testImage } from '@server/tests/shared'
6 import { wait } from '@shared/core-utils'
10 VideoPlaylistCreateResult,
11 VideoPlaylistElementType,
15 } from '@shared/models'
18 createMultipleServers,
22 setAccessTokensToServers,
23 setDefaultAccountAvatar,
24 setDefaultVideoChannel,
26 } from '@shared/server-commands'
28 const expect = chai.expect
30 async function checkPlaylistElementType (
31 servers: PeerTubeServer[],
33 type: VideoPlaylistElementType,
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)
42 const videoElement = body.data.find(e => e.position === position)
43 expect(videoElement.type).to.equal(type, 'On server ' + server.url)
45 if (type === VideoPlaylistElementType.REGULAR) {
46 expect(videoElement.video).to.not.be.null
47 expect(videoElement.video.name).to.equal(name)
49 expect(videoElement.video).to.be.null
54 describe('Test video playlists', function () {
55 let servers: PeerTubeServer[] = []
57 let playlistServer2Id1: number
58 let playlistServer2Id2: number
59 let playlistServer2UUID2: string
61 let playlistServer1Id: number
62 let playlistServer1UUID: string
63 let playlistServer1UUID2: string
65 let playlistElementServer1Video4: number
66 let playlistElementServer1Video5: number
67 let playlistElementNSFW: number
69 let nsfwVideoServer1: number
71 let userTokenServer1: string
73 let commands: PlaylistsCommand[]
75 before(async function () {
78 servers = await createMultipleServers(3)
80 // Get the access tokens
81 await setAccessTokensToServers(servers)
82 await setDefaultVideoChannel(servers)
83 await setDefaultAccountAvatar(servers)
85 for (const server of servers) {
86 await server.config.disableTranscoding()
89 // Server 1 and server 2 follow each other
90 await doubleFollow(servers[0], servers[1])
91 // Server 1 and server 3 follow each other
92 await doubleFollow(servers[0], servers[2])
94 commands = servers.map(s => s.playlists)
97 servers[0].store.videos = []
98 servers[1].store.videos = []
99 servers[2].store.videos = []
101 for (const server of servers) {
102 for (let i = 0; i < 7; i++) {
103 const name = `video ${i} server ${server.serverNumber}`
104 const video = await server.videos.upload({ attributes: { name, nsfw: false } })
106 server.store.videos.push(video)
111 nsfwVideoServer1 = (await servers[0].videos.quickUpload({ name: 'NSFW video', nsfw: true })).id
113 userTokenServer1 = await servers[0].users.generateUserAndToken('user1')
115 await waitJobs(servers)
118 describe('Get default playlists', function () {
120 it('Should list video playlist privacies', async function () {
121 const privacies = await commands[0].getPrivacies()
123 expect(Object.keys(privacies)).to.have.length.at.least(3)
124 expect(privacies[3]).to.equal('Private')
127 it('Should list watch later playlist', async function () {
128 const token = servers[0].accessToken
131 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.WATCH_LATER })
133 expect(body.total).to.equal(1)
134 expect(body.data).to.have.lengthOf(1)
136 const playlist = body.data[0]
137 expect(playlist.displayName).to.equal('Watch later')
138 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
139 expect(playlist.type.label).to.equal('Watch later')
143 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.REGULAR })
145 expect(body.total).to.equal(0)
146 expect(body.data).to.have.lengthOf(0)
150 const body = await commands[0].listByAccount({ handle: 'root' })
151 expect(body.total).to.equal(0)
152 expect(body.data).to.have.lengthOf(0)
156 it('Should get private playlist for a classic user', async function () {
157 const token = await servers[0].users.generateUserAndToken('toto')
159 const body = await commands[0].listByAccount({ token, handle: 'toto' })
161 expect(body.total).to.equal(1)
162 expect(body.data).to.have.lengthOf(1)
164 const playlistId = body.data[0].id
165 await commands[0].listVideos({ token, playlistId })
169 describe('Create and federate playlists', function () {
171 it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
174 await commands[0].create({
176 displayName: 'my super playlist',
177 privacy: VideoPlaylistPrivacy.PUBLIC,
178 description: 'my super description',
179 thumbnailfile: 'thumbnail.jpg',
180 videoChannelId: servers[0].store.channel.id
184 await waitJobs(servers)
185 // Processing a playlist by the receiver could be long
188 for (const server of servers) {
189 const body = await server.playlists.list({ start: 0, count: 5 })
190 expect(body.total).to.equal(1)
191 expect(body.data).to.have.lengthOf(1)
193 const playlistFromList = body.data[0]
195 const playlistFromGet = await server.playlists.get({ playlistId: playlistFromList.uuid })
197 for (const playlist of [ playlistFromGet, playlistFromList ]) {
198 expect(playlist.id).to.be.a('number')
199 expect(playlist.uuid).to.be.a('string')
201 expect(playlist.isLocal).to.equal(server.serverNumber === 1)
203 expect(playlist.displayName).to.equal('my super playlist')
204 expect(playlist.description).to.equal('my super description')
205 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
206 expect(playlist.privacy.label).to.equal('Public')
207 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
208 expect(playlist.type.label).to.equal('Regular')
209 expect(playlist.embedPath).to.equal('/video-playlists/embed/' + playlist.uuid)
211 expect(playlist.videosLength).to.equal(0)
213 expect(playlist.ownerAccount.name).to.equal('root')
214 expect(playlist.ownerAccount.displayName).to.equal('root')
215 expect(playlist.videoChannel.name).to.equal('root_channel')
216 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
221 it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
225 const playlist = await servers[1].playlists.create({
227 displayName: 'playlist 2',
228 privacy: VideoPlaylistPrivacy.PUBLIC,
229 videoChannelId: servers[1].store.channel.id
232 playlistServer2Id1 = playlist.id
236 const playlist = await servers[1].playlists.create({
238 displayName: 'playlist 3',
239 privacy: VideoPlaylistPrivacy.PUBLIC,
240 thumbnailfile: 'thumbnail.jpg',
241 videoChannelId: servers[1].store.channel.id
245 playlistServer2Id2 = playlist.id
246 playlistServer2UUID2 = playlist.uuid
249 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
250 await servers[1].playlists.addElement({
252 attributes: { videoId: servers[1].store.videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
254 await servers[1].playlists.addElement({
256 attributes: { videoId: servers[1].store.videos[1].id }
260 await waitJobs(servers)
263 for (const server of [ servers[0], servers[1] ]) {
264 const body = await server.playlists.list({ start: 0, count: 5 })
266 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
267 expect(playlist2).to.not.be.undefined
268 await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
270 const playlist3 = body.data.find(p => p.displayName === 'playlist 3')
271 expect(playlist3).to.not.be.undefined
272 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
275 const body = await servers[2].playlists.list({ start: 0, count: 5 })
276 expect(body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
277 expect(body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
280 it('Should have the playlist on server 3 after a new follow', async function () {
283 // Server 2 and server 3 follow each other
284 await doubleFollow(servers[1], servers[2])
286 const body = await servers[2].playlists.list({ start: 0, count: 5 })
288 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
289 expect(playlist2).to.not.be.undefined
290 await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
292 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
296 describe('List playlists', function () {
298 it('Should correctly list the playlists', async function () {
302 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: 'createdAt' })
303 expect(body.total).to.equal(3)
305 const data = body.data
306 expect(data).to.have.lengthOf(2)
307 expect(data[0].displayName).to.equal('playlist 2')
308 expect(data[1].displayName).to.equal('playlist 3')
312 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: '-createdAt' })
313 expect(body.total).to.equal(3)
315 const data = body.data
316 expect(data).to.have.lengthOf(2)
317 expect(data[0].displayName).to.equal('playlist 2')
318 expect(data[1].displayName).to.equal('my super playlist')
322 it('Should list video channel playlists', async function () {
326 const body = await commands[0].listByChannel({ handle: 'root_channel', start: 0, count: 2, sort: '-createdAt' })
327 expect(body.total).to.equal(1)
329 const data = body.data
330 expect(data).to.have.lengthOf(1)
331 expect(data[0].displayName).to.equal('my super playlist')
335 it('Should list account playlists', async function () {
339 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: '-createdAt' })
340 expect(body.total).to.equal(2)
342 const data = body.data
343 expect(data).to.have.lengthOf(1)
344 expect(data[0].displayName).to.equal('playlist 2')
348 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: 'createdAt' })
349 expect(body.total).to.equal(2)
351 const data = body.data
352 expect(data).to.have.lengthOf(1)
353 expect(data[0].displayName).to.equal('playlist 3')
357 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '3' })
358 expect(body.total).to.equal(1)
360 const data = body.data
361 expect(data).to.have.lengthOf(1)
362 expect(data[0].displayName).to.equal('playlist 3')
366 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '4' })
367 expect(body.total).to.equal(0)
369 const data = body.data
370 expect(data).to.have.lengthOf(0)
375 describe('Playlist rights', function () {
376 let unlistedPlaylist: VideoPlaylistCreateResult
377 let privatePlaylist: VideoPlaylistCreateResult
379 before(async function () {
383 unlistedPlaylist = await servers[1].playlists.create({
385 displayName: 'playlist unlisted',
386 privacy: VideoPlaylistPrivacy.UNLISTED,
387 videoChannelId: servers[1].store.channel.id
393 privatePlaylist = await servers[1].playlists.create({
395 displayName: 'playlist private',
396 privacy: VideoPlaylistPrivacy.PRIVATE
401 await waitJobs(servers)
405 it('Should not list unlisted or private playlists', async function () {
406 for (const server of servers) {
408 await server.playlists.listByAccount({ handle: 'root@localhost:' + servers[1].port, sort: '-createdAt' }),
409 await server.playlists.list({ start: 0, count: 2, sort: '-createdAt' })
412 expect(results[0].total).to.equal(2)
413 expect(results[1].total).to.equal(3)
415 for (const body of results) {
416 const data = body.data
417 expect(data).to.have.lengthOf(2)
418 expect(data[0].displayName).to.equal('playlist 3')
419 expect(data[1].displayName).to.equal('playlist 2')
424 it('Should not get unlisted playlist using only the id', async function () {
425 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 })
428 it('Should get unlisted plyaylist using uuid or shortUUID', async function () {
429 await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid })
430 await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID })
433 it('Should not get private playlist without token', async function () {
434 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
435 await servers[1].playlists.get({ playlistId: id, expectedStatus: 401 })
439 it('Should get private playlist with a token', async function () {
440 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
441 await servers[1].playlists.get({ token: servers[1].accessToken, playlistId: id })
446 describe('Update playlists', function () {
448 it('Should update a playlist', async function () {
451 await servers[1].playlists.update({
453 displayName: 'playlist 3 updated',
454 description: 'description updated',
455 privacy: VideoPlaylistPrivacy.UNLISTED,
456 thumbnailfile: 'thumbnail.jpg',
457 videoChannelId: servers[1].store.channel.id
459 playlistId: playlistServer2Id2
462 await waitJobs(servers)
464 for (const server of servers) {
465 const playlist = await server.playlists.get({ playlistId: playlistServer2UUID2 })
467 expect(playlist.displayName).to.equal('playlist 3 updated')
468 expect(playlist.description).to.equal('description updated')
470 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
471 expect(playlist.privacy.label).to.equal('Unlisted')
473 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
474 expect(playlist.type.label).to.equal('Regular')
476 expect(playlist.videosLength).to.equal(2)
478 expect(playlist.ownerAccount.name).to.equal('root')
479 expect(playlist.ownerAccount.displayName).to.equal('root')
480 expect(playlist.videoChannel.name).to.equal('root_channel')
481 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
486 describe('Element timestamps', function () {
488 it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
491 const addVideo = (attributes: any) => {
492 return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
495 const playlist = await commands[0].create({
497 displayName: 'playlist 4',
498 privacy: VideoPlaylistPrivacy.PUBLIC,
499 videoChannelId: servers[0].store.channel.id
503 playlistServer1Id = playlist.id
504 playlistServer1UUID = playlist.uuid
506 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
507 await addVideo({ videoId: servers[2].store.videos[1].uuid, startTimestamp: 35 })
508 await addVideo({ videoId: servers[2].store.videos[2].uuid })
510 const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 })
511 playlistElementServer1Video4 = element.id
515 const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
516 playlistElementServer1Video5 = element.id
520 const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
521 playlistElementNSFW = element.id
523 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
524 await addVideo({ videoId: nsfwVideoServer1 })
527 await waitJobs(servers)
530 it('Should correctly list playlist videos', async function () {
533 for (const server of servers) {
535 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
537 expect(body.total).to.equal(8)
539 const videoElements = body.data
540 expect(videoElements).to.have.lengthOf(8)
542 expect(videoElements[0].video.name).to.equal('video 0 server 1')
543 expect(videoElements[0].position).to.equal(1)
544 expect(videoElements[0].startTimestamp).to.equal(15)
545 expect(videoElements[0].stopTimestamp).to.equal(28)
547 expect(videoElements[1].video.name).to.equal('video 1 server 3')
548 expect(videoElements[1].position).to.equal(2)
549 expect(videoElements[1].startTimestamp).to.equal(35)
550 expect(videoElements[1].stopTimestamp).to.be.null
552 expect(videoElements[2].video.name).to.equal('video 2 server 3')
553 expect(videoElements[2].position).to.equal(3)
554 expect(videoElements[2].startTimestamp).to.be.null
555 expect(videoElements[2].stopTimestamp).to.be.null
557 expect(videoElements[3].video.name).to.equal('video 3 server 1')
558 expect(videoElements[3].position).to.equal(4)
559 expect(videoElements[3].startTimestamp).to.be.null
560 expect(videoElements[3].stopTimestamp).to.equal(35)
562 expect(videoElements[4].video.name).to.equal('video 4 server 1')
563 expect(videoElements[4].position).to.equal(5)
564 expect(videoElements[4].startTimestamp).to.equal(45)
565 expect(videoElements[4].stopTimestamp).to.equal(60)
567 expect(videoElements[5].video.name).to.equal('NSFW video')
568 expect(videoElements[5].position).to.equal(6)
569 expect(videoElements[5].startTimestamp).to.equal(5)
570 expect(videoElements[5].stopTimestamp).to.be.null
572 expect(videoElements[6].video.name).to.equal('NSFW video')
573 expect(videoElements[6].position).to.equal(7)
574 expect(videoElements[6].startTimestamp).to.equal(4)
575 expect(videoElements[6].stopTimestamp).to.be.null
577 expect(videoElements[7].video.name).to.equal('NSFW video')
578 expect(videoElements[7].position).to.equal(8)
579 expect(videoElements[7].startTimestamp).to.be.null
580 expect(videoElements[7].stopTimestamp).to.be.null
584 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 })
585 expect(body.data).to.have.lengthOf(2)
591 describe('Element type', function () {
592 let groupUser1: PeerTubeServer[]
593 let groupWithoutToken1: PeerTubeServer[]
594 let group1: PeerTubeServer[]
595 let group2: PeerTubeServer[]
601 before(async function () {
604 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userTokenServer1 }) ]
605 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
606 group1 = [ servers[0] ]
607 group2 = [ servers[1], servers[2] ]
609 const playlist = await commands[0].create({
610 token: userTokenServer1,
612 displayName: 'playlist 56',
613 privacy: VideoPlaylistPrivacy.PUBLIC,
614 videoChannelId: servers[0].store.channel.id
618 const playlistServer1Id2 = playlist.id
619 playlistServer1UUID2 = playlist.uuid
621 const addVideo = (attributes: any) => {
622 return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes })
625 video1 = (await servers[0].videos.quickUpload({ name: 'video 89', token: userTokenServer1 })).uuid
626 video2 = (await servers[1].videos.quickUpload({ name: 'video 90' })).uuid
627 video3 = (await servers[0].videos.quickUpload({ name: 'video 91', nsfw: true })).uuid
629 await waitJobs(servers)
631 await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
632 await addVideo({ videoId: video2, startTimestamp: 35 })
633 await addVideo({ videoId: video3 })
635 await waitJobs(servers)
638 it('Should update the element type if the video is private', async function () {
641 const name = 'video 89'
645 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } })
646 await waitJobs(servers)
648 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
649 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
650 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
651 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
655 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
656 await waitJobs(servers)
658 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
659 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
660 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
661 // We deleted the video, so even if we recreated it, the old entry is still deleted
662 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
666 it('Should update the element type if the video is blacklisted', async function () {
669 const name = 'video 89'
673 await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true })
674 await waitJobs(servers)
676 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
677 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
678 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
679 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
683 await servers[0].blacklist.remove({ videoId: video1 })
684 await waitJobs(servers)
686 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
687 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
688 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
689 // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
690 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
694 it('Should update the element type if the account or server of the video is blocked', async function () {
697 const command = servers[0].blocklist
699 const name = 'video 90'
703 await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
704 await waitJobs(servers)
706 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
707 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
709 await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
710 await waitJobs(servers)
712 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
716 await command.addToMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
717 await waitJobs(servers)
719 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
720 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
722 await command.removeFromMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
723 await waitJobs(servers)
725 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
729 await command.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port })
730 await waitJobs(servers)
732 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
733 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
735 await command.removeFromServerBlocklist({ account: 'root@localhost:' + servers[1].port })
736 await waitJobs(servers)
738 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
742 await command.addToServerBlocklist({ server: 'localhost:' + servers[1].port })
743 await waitJobs(servers)
745 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
746 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
748 await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port })
749 await waitJobs(servers)
751 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
755 it('Should hide the video if it is NSFW', async function () {
756 const body = await commands[0].listVideos({ token: userTokenServer1, playlistId: playlistServer1UUID2, query: { nsfw: 'false' } })
757 expect(body.total).to.equal(3)
759 const elements = body.data
760 const element = elements.find(e => e.position === 3)
762 expect(element).to.exist
763 expect(element.video).to.be.null
764 expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
769 describe('Managing playlist elements', function () {
771 it('Should reorder the playlist', async function () {
775 await commands[0].reorderElements({
776 playlistId: playlistServer1Id,
779 insertAfterPosition: 3
783 await waitJobs(servers)
785 for (const server of servers) {
786 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
787 const names = body.data.map(v => v.video.name)
789 expect(names).to.deep.equal([
803 await commands[0].reorderElements({
804 playlistId: playlistServer1Id,
808 insertAfterPosition: 4
812 await waitJobs(servers)
814 for (const server of servers) {
815 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
816 const names = body.data.map(v => v.video.name)
818 expect(names).to.deep.equal([
832 await commands[0].reorderElements({
833 playlistId: playlistServer1Id,
836 insertAfterPosition: 3
840 await waitJobs(servers)
842 for (const server of servers) {
843 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
844 const names = elements.map(v => v.video.name)
846 expect(names).to.deep.equal([
857 for (let i = 1; i <= elements.length; i++) {
858 expect(elements[i - 1].position).to.equal(i)
864 it('Should update startTimestamp/endTimestamp of some elements', async function () {
867 await commands[0].updateElement({
868 playlistId: playlistServer1Id,
869 elementId: playlistElementServer1Video4,
875 await commands[0].updateElement({
876 playlistId: playlistServer1Id,
877 elementId: playlistElementServer1Video5,
883 await waitJobs(servers)
885 for (const server of servers) {
886 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
888 expect(elements[0].video.name).to.equal('video 3 server 1')
889 expect(elements[0].position).to.equal(1)
890 expect(elements[0].startTimestamp).to.equal(1)
891 expect(elements[0].stopTimestamp).to.equal(35)
893 expect(elements[5].video.name).to.equal('video 4 server 1')
894 expect(elements[5].position).to.equal(6)
895 expect(elements[5].startTimestamp).to.equal(45)
896 expect(elements[5].stopTimestamp).to.be.null
900 it('Should check videos existence in my playlist', async function () {
902 servers[0].store.videos[0].id,
904 servers[0].store.videos[3].id,
906 servers[0].store.videos[4].id
908 const obj = await commands[0].videosExist({ videoIds })
911 const elem = obj[servers[0].store.videos[0].id]
912 expect(elem).to.have.lengthOf(1)
913 expect(elem[0].playlistElementId).to.exist
914 expect(elem[0].playlistId).to.equal(playlistServer1Id)
915 expect(elem[0].startTimestamp).to.equal(15)
916 expect(elem[0].stopTimestamp).to.equal(28)
920 const elem = obj[servers[0].store.videos[3].id]
921 expect(elem).to.have.lengthOf(1)
922 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
923 expect(elem[0].playlistId).to.equal(playlistServer1Id)
924 expect(elem[0].startTimestamp).to.equal(1)
925 expect(elem[0].stopTimestamp).to.equal(35)
929 const elem = obj[servers[0].store.videos[4].id]
930 expect(elem).to.have.lengthOf(1)
931 expect(elem[0].playlistId).to.equal(playlistServer1Id)
932 expect(elem[0].startTimestamp).to.equal(45)
933 expect(elem[0].stopTimestamp).to.equal(null)
936 expect(obj[42000]).to.have.lengthOf(0)
937 expect(obj[43000]).to.have.lengthOf(0)
940 it('Should automatically update updatedAt field of playlists', async function () {
941 const server = servers[1]
942 const videoId = servers[1].store.videos[5].id
944 async function getPlaylistNames () {
945 const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' })
947 return data.map(p => p.displayName)
950 const attributes = { videoId }
951 const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes })
952 const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes })
954 const names1 = await getPlaylistNames()
955 expect(names1[0]).to.equal('playlist 3 updated')
956 expect(names1[1]).to.equal('playlist 2')
958 await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id })
960 const names2 = await getPlaylistNames()
961 expect(names2[0]).to.equal('playlist 2')
962 expect(names2[1]).to.equal('playlist 3 updated')
964 await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id })
966 const names3 = await getPlaylistNames()
967 expect(names3[0]).to.equal('playlist 3 updated')
968 expect(names3[1]).to.equal('playlist 2')
971 it('Should delete some elements', async function () {
974 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 })
975 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW })
977 await waitJobs(servers)
979 for (const server of servers) {
980 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
981 expect(body.total).to.equal(6)
983 const elements = body.data
984 expect(elements).to.have.lengthOf(6)
986 expect(elements[0].video.name).to.equal('video 0 server 1')
987 expect(elements[0].position).to.equal(1)
989 expect(elements[1].video.name).to.equal('video 2 server 3')
990 expect(elements[1].position).to.equal(2)
992 expect(elements[2].video.name).to.equal('video 1 server 3')
993 expect(elements[2].position).to.equal(3)
995 expect(elements[3].video.name).to.equal('video 4 server 1')
996 expect(elements[3].position).to.equal(4)
998 expect(elements[4].video.name).to.equal('NSFW video')
999 expect(elements[4].position).to.equal(5)
1001 expect(elements[5].video.name).to.equal('NSFW video')
1002 expect(elements[5].position).to.equal(6)
1006 it('Should be able to create a public playlist, and set it to private', async function () {
1009 const videoPlaylistIds = await commands[0].create({
1011 displayName: 'my super public playlist',
1012 privacy: VideoPlaylistPrivacy.PUBLIC,
1013 videoChannelId: servers[0].store.channel.id
1017 await waitJobs(servers)
1019 for (const server of servers) {
1020 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1023 const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE }
1024 await commands[0].update({ playlistId: videoPlaylistIds.id, attributes })
1026 await waitJobs(servers)
1028 for (const server of [ servers[1], servers[2] ]) {
1029 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1032 await commands[0].get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
1033 await commands[0].get({ token: servers[0].accessToken, playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1037 describe('Playlist deletion', function () {
1039 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1042 await commands[0].delete({ playlistId: playlistServer1Id })
1044 await waitJobs(servers)
1046 for (const server of servers) {
1047 await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1051 it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1054 for (const server of servers) {
1055 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
1059 it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1062 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist')
1065 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1066 expect(body.total).to.equal(3)
1068 expect(finder(body.data)).to.not.be.undefined
1071 await servers[2].follows.unfollow({ target: servers[0] })
1074 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1075 expect(body.total).to.equal(1)
1077 expect(finder(body.data)).to.be.undefined
1081 it('Should delete a channel and put the associated playlist in private mode', async function () {
1084 const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } })
1086 const playlistCreated = await commands[0].create({
1088 displayName: 'channel playlist',
1089 privacy: VideoPlaylistPrivacy.PUBLIC,
1090 videoChannelId: channel.id
1094 await waitJobs(servers)
1096 await servers[0].channels.delete({ channelName: 'super_channel' })
1098 await waitJobs(servers)
1100 const body = await commands[0].get({ token: servers[0].accessToken, playlistId: playlistCreated.uuid })
1101 expect(body.displayName).to.equal('channel playlist')
1102 expect(body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1104 await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1107 it('Should delete an account and delete its playlists', async function () {
1110 const { userId, token } = await servers[0].users.generate('user_1')
1112 const { videoChannels } = await servers[0].users.getMyInfo({ token })
1113 const userChannel = videoChannels[0]
1115 await commands[0].create({
1117 displayName: 'playlist to be deleted',
1118 privacy: VideoPlaylistPrivacy.PUBLIC,
1119 videoChannelId: userChannel.id
1123 await waitJobs(servers)
1125 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted')
1128 for (const server of [ servers[0], servers[1] ]) {
1129 const body = await server.playlists.list({ start: 0, count: 15 })
1131 expect(finder(body.data)).to.not.be.undefined
1135 await servers[0].users.remove({ userId })
1136 await waitJobs(servers)
1139 for (const server of [ servers[0], servers[1] ]) {
1140 const body = await server.playlists.list({ start: 0, count: 15 })
1142 expect(finder(body.data)).to.be.undefined
1148 after(async function () {
1149 await cleanupTests(servers)