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, { transcoding: { enabled: false } })
80 // Get the access tokens
81 await setAccessTokensToServers(servers)
82 await setDefaultVideoChannel(servers)
83 await setDefaultAccountAvatar(servers)
85 // Server 1 and server 2 follow each other
86 await doubleFollow(servers[0], servers[1])
87 // Server 1 and server 3 follow each other
88 await doubleFollow(servers[0], servers[2])
90 commands = servers.map(s => s.playlists)
93 servers[0].store.videos = []
94 servers[1].store.videos = []
95 servers[2].store.videos = []
97 for (const server of servers) {
98 for (let i = 0; i < 7; i++) {
99 const name = `video ${i} server ${server.serverNumber}`
100 const video = await server.videos.upload({ attributes: { name, nsfw: false } })
102 server.store.videos.push(video)
107 nsfwVideoServer1 = (await servers[0].videos.quickUpload({ name: 'NSFW video', nsfw: true })).id
109 userTokenServer1 = await servers[0].users.generateUserAndToken('user1')
111 await waitJobs(servers)
114 describe('Get default playlists', function () {
116 it('Should list video playlist privacies', async function () {
117 const privacies = await commands[0].getPrivacies()
119 expect(Object.keys(privacies)).to.have.length.at.least(3)
120 expect(privacies[3]).to.equal('Private')
123 it('Should list watch later playlist', async function () {
124 const token = servers[0].accessToken
127 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.WATCH_LATER })
129 expect(body.total).to.equal(1)
130 expect(body.data).to.have.lengthOf(1)
132 const playlist = body.data[0]
133 expect(playlist.displayName).to.equal('Watch later')
134 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
135 expect(playlist.type.label).to.equal('Watch later')
139 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.REGULAR })
141 expect(body.total).to.equal(0)
142 expect(body.data).to.have.lengthOf(0)
146 const body = await commands[0].listByAccount({ handle: 'root' })
147 expect(body.total).to.equal(0)
148 expect(body.data).to.have.lengthOf(0)
152 it('Should get private playlist for a classic user', async function () {
153 const token = await servers[0].users.generateUserAndToken('toto')
155 const body = await commands[0].listByAccount({ token, handle: 'toto' })
157 expect(body.total).to.equal(1)
158 expect(body.data).to.have.lengthOf(1)
160 const playlistId = body.data[0].id
161 await commands[0].listVideos({ token, playlistId })
165 describe('Create and federate playlists', function () {
167 it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
170 await commands[0].create({
172 displayName: 'my super playlist',
173 privacy: VideoPlaylistPrivacy.PUBLIC,
174 description: 'my super description',
175 thumbnailfile: 'thumbnail.jpg',
176 videoChannelId: servers[0].store.channel.id
180 await waitJobs(servers)
181 // Processing a playlist by the receiver could be long
184 for (const server of servers) {
185 const body = await server.playlists.list({ start: 0, count: 5 })
186 expect(body.total).to.equal(1)
187 expect(body.data).to.have.lengthOf(1)
189 const playlistFromList = body.data[0]
191 const playlistFromGet = await server.playlists.get({ playlistId: playlistFromList.uuid })
193 for (const playlist of [ playlistFromGet, playlistFromList ]) {
194 expect(playlist.id).to.be.a('number')
195 expect(playlist.uuid).to.be.a('string')
197 expect(playlist.isLocal).to.equal(server.serverNumber === 1)
199 expect(playlist.displayName).to.equal('my super playlist')
200 expect(playlist.description).to.equal('my super description')
201 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
202 expect(playlist.privacy.label).to.equal('Public')
203 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
204 expect(playlist.type.label).to.equal('Regular')
205 expect(playlist.embedPath).to.equal('/video-playlists/embed/' + playlist.uuid)
207 expect(playlist.videosLength).to.equal(0)
209 expect(playlist.ownerAccount.name).to.equal('root')
210 expect(playlist.ownerAccount.displayName).to.equal('root')
211 expect(playlist.videoChannel.name).to.equal('root_channel')
212 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
217 it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
221 const playlist = await servers[1].playlists.create({
223 displayName: 'playlist 2',
224 privacy: VideoPlaylistPrivacy.PUBLIC,
225 videoChannelId: servers[1].store.channel.id
228 playlistServer2Id1 = playlist.id
232 const playlist = await servers[1].playlists.create({
234 displayName: 'playlist 3',
235 privacy: VideoPlaylistPrivacy.PUBLIC,
236 thumbnailfile: 'thumbnail.jpg',
237 videoChannelId: servers[1].store.channel.id
241 playlistServer2Id2 = playlist.id
242 playlistServer2UUID2 = playlist.uuid
245 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
246 await servers[1].playlists.addElement({
248 attributes: { videoId: servers[1].store.videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
250 await servers[1].playlists.addElement({
252 attributes: { videoId: servers[1].store.videos[1].id }
256 await waitJobs(servers)
259 for (const server of [ servers[0], servers[1] ]) {
260 const body = await server.playlists.list({ start: 0, count: 5 })
262 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
263 expect(playlist2).to.not.be.undefined
264 await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
266 const playlist3 = body.data.find(p => p.displayName === 'playlist 3')
267 expect(playlist3).to.not.be.undefined
268 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
271 const body = await servers[2].playlists.list({ start: 0, count: 5 })
272 expect(body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
273 expect(body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
276 it('Should have the playlist on server 3 after a new follow', async function () {
279 // Server 2 and server 3 follow each other
280 await doubleFollow(servers[1], servers[2])
282 const body = await servers[2].playlists.list({ start: 0, count: 5 })
284 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
285 expect(playlist2).to.not.be.undefined
286 await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
288 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
292 describe('List playlists', function () {
294 it('Should correctly list the playlists', async function () {
298 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: 'createdAt' })
299 expect(body.total).to.equal(3)
301 const data = body.data
302 expect(data).to.have.lengthOf(2)
303 expect(data[0].displayName).to.equal('playlist 2')
304 expect(data[1].displayName).to.equal('playlist 3')
308 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: '-createdAt' })
309 expect(body.total).to.equal(3)
311 const data = body.data
312 expect(data).to.have.lengthOf(2)
313 expect(data[0].displayName).to.equal('playlist 2')
314 expect(data[1].displayName).to.equal('my super playlist')
318 it('Should list video channel playlists', async function () {
322 const body = await commands[0].listByChannel({ handle: 'root_channel', start: 0, count: 2, sort: '-createdAt' })
323 expect(body.total).to.equal(1)
325 const data = body.data
326 expect(data).to.have.lengthOf(1)
327 expect(data[0].displayName).to.equal('my super playlist')
331 it('Should list account playlists', async function () {
335 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: '-createdAt' })
336 expect(body.total).to.equal(2)
338 const data = body.data
339 expect(data).to.have.lengthOf(1)
340 expect(data[0].displayName).to.equal('playlist 2')
344 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: 'createdAt' })
345 expect(body.total).to.equal(2)
347 const data = body.data
348 expect(data).to.have.lengthOf(1)
349 expect(data[0].displayName).to.equal('playlist 3')
353 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '3' })
354 expect(body.total).to.equal(1)
356 const data = body.data
357 expect(data).to.have.lengthOf(1)
358 expect(data[0].displayName).to.equal('playlist 3')
362 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '4' })
363 expect(body.total).to.equal(0)
365 const data = body.data
366 expect(data).to.have.lengthOf(0)
371 describe('Playlist rights', function () {
372 let unlistedPlaylist: VideoPlaylistCreateResult
373 let privatePlaylist: VideoPlaylistCreateResult
375 before(async function () {
379 unlistedPlaylist = await servers[1].playlists.create({
381 displayName: 'playlist unlisted',
382 privacy: VideoPlaylistPrivacy.UNLISTED,
383 videoChannelId: servers[1].store.channel.id
389 privatePlaylist = await servers[1].playlists.create({
391 displayName: 'playlist private',
392 privacy: VideoPlaylistPrivacy.PRIVATE
397 await waitJobs(servers)
401 it('Should not list unlisted or private playlists', async function () {
402 for (const server of servers) {
404 await server.playlists.listByAccount({ handle: 'root@localhost:' + servers[1].port, sort: '-createdAt' }),
405 await server.playlists.list({ start: 0, count: 2, sort: '-createdAt' })
408 expect(results[0].total).to.equal(2)
409 expect(results[1].total).to.equal(3)
411 for (const body of results) {
412 const data = body.data
413 expect(data).to.have.lengthOf(2)
414 expect(data[0].displayName).to.equal('playlist 3')
415 expect(data[1].displayName).to.equal('playlist 2')
420 it('Should not get unlisted playlist using only the id', async function () {
421 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 })
424 it('Should get unlisted plyaylist using uuid or shortUUID', async function () {
425 await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid })
426 await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID })
429 it('Should not get private playlist without token', async function () {
430 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
431 await servers[1].playlists.get({ playlistId: id, expectedStatus: 401 })
435 it('Should get private playlist with a token', async function () {
436 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
437 await servers[1].playlists.get({ token: servers[1].accessToken, playlistId: id })
442 describe('Update playlists', function () {
444 it('Should update a playlist', async function () {
447 await servers[1].playlists.update({
449 displayName: 'playlist 3 updated',
450 description: 'description updated',
451 privacy: VideoPlaylistPrivacy.UNLISTED,
452 thumbnailfile: 'thumbnail.jpg',
453 videoChannelId: servers[1].store.channel.id
455 playlistId: playlistServer2Id2
458 await waitJobs(servers)
460 for (const server of servers) {
461 const playlist = await server.playlists.get({ playlistId: playlistServer2UUID2 })
463 expect(playlist.displayName).to.equal('playlist 3 updated')
464 expect(playlist.description).to.equal('description updated')
466 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
467 expect(playlist.privacy.label).to.equal('Unlisted')
469 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
470 expect(playlist.type.label).to.equal('Regular')
472 expect(playlist.videosLength).to.equal(2)
474 expect(playlist.ownerAccount.name).to.equal('root')
475 expect(playlist.ownerAccount.displayName).to.equal('root')
476 expect(playlist.videoChannel.name).to.equal('root_channel')
477 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
482 describe('Element timestamps', function () {
484 it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
487 const addVideo = (attributes: any) => {
488 return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
491 const playlist = await commands[0].create({
493 displayName: 'playlist 4',
494 privacy: VideoPlaylistPrivacy.PUBLIC,
495 videoChannelId: servers[0].store.channel.id
499 playlistServer1Id = playlist.id
500 playlistServer1UUID = playlist.uuid
502 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
503 await addVideo({ videoId: servers[2].store.videos[1].uuid, startTimestamp: 35 })
504 await addVideo({ videoId: servers[2].store.videos[2].uuid })
506 const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 })
507 playlistElementServer1Video4 = element.id
511 const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
512 playlistElementServer1Video5 = element.id
516 const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
517 playlistElementNSFW = element.id
519 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
520 await addVideo({ videoId: nsfwVideoServer1 })
523 await waitJobs(servers)
526 it('Should correctly list playlist videos', async function () {
529 for (const server of servers) {
531 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
533 expect(body.total).to.equal(8)
535 const videoElements = body.data
536 expect(videoElements).to.have.lengthOf(8)
538 expect(videoElements[0].video.name).to.equal('video 0 server 1')
539 expect(videoElements[0].position).to.equal(1)
540 expect(videoElements[0].startTimestamp).to.equal(15)
541 expect(videoElements[0].stopTimestamp).to.equal(28)
543 expect(videoElements[1].video.name).to.equal('video 1 server 3')
544 expect(videoElements[1].position).to.equal(2)
545 expect(videoElements[1].startTimestamp).to.equal(35)
546 expect(videoElements[1].stopTimestamp).to.be.null
548 expect(videoElements[2].video.name).to.equal('video 2 server 3')
549 expect(videoElements[2].position).to.equal(3)
550 expect(videoElements[2].startTimestamp).to.be.null
551 expect(videoElements[2].stopTimestamp).to.be.null
553 expect(videoElements[3].video.name).to.equal('video 3 server 1')
554 expect(videoElements[3].position).to.equal(4)
555 expect(videoElements[3].startTimestamp).to.be.null
556 expect(videoElements[3].stopTimestamp).to.equal(35)
558 expect(videoElements[4].video.name).to.equal('video 4 server 1')
559 expect(videoElements[4].position).to.equal(5)
560 expect(videoElements[4].startTimestamp).to.equal(45)
561 expect(videoElements[4].stopTimestamp).to.equal(60)
563 expect(videoElements[5].video.name).to.equal('NSFW video')
564 expect(videoElements[5].position).to.equal(6)
565 expect(videoElements[5].startTimestamp).to.equal(5)
566 expect(videoElements[5].stopTimestamp).to.be.null
568 expect(videoElements[6].video.name).to.equal('NSFW video')
569 expect(videoElements[6].position).to.equal(7)
570 expect(videoElements[6].startTimestamp).to.equal(4)
571 expect(videoElements[6].stopTimestamp).to.be.null
573 expect(videoElements[7].video.name).to.equal('NSFW video')
574 expect(videoElements[7].position).to.equal(8)
575 expect(videoElements[7].startTimestamp).to.be.null
576 expect(videoElements[7].stopTimestamp).to.be.null
580 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 })
581 expect(body.data).to.have.lengthOf(2)
587 describe('Element type', function () {
588 let groupUser1: PeerTubeServer[]
589 let groupWithoutToken1: PeerTubeServer[]
590 let group1: PeerTubeServer[]
591 let group2: PeerTubeServer[]
597 before(async function () {
600 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userTokenServer1 }) ]
601 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
602 group1 = [ servers[0] ]
603 group2 = [ servers[1], servers[2] ]
605 const playlist = await commands[0].create({
606 token: userTokenServer1,
608 displayName: 'playlist 56',
609 privacy: VideoPlaylistPrivacy.PUBLIC,
610 videoChannelId: servers[0].store.channel.id
614 const playlistServer1Id2 = playlist.id
615 playlistServer1UUID2 = playlist.uuid
617 const addVideo = (attributes: any) => {
618 return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes })
621 video1 = (await servers[0].videos.quickUpload({ name: 'video 89', token: userTokenServer1 })).uuid
622 video2 = (await servers[1].videos.quickUpload({ name: 'video 90' })).uuid
623 video3 = (await servers[0].videos.quickUpload({ name: 'video 91', nsfw: true })).uuid
625 await waitJobs(servers)
627 await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
628 await addVideo({ videoId: video2, startTimestamp: 35 })
629 await addVideo({ videoId: video3 })
631 await waitJobs(servers)
634 it('Should update the element type if the video is private', async function () {
637 const name = 'video 89'
641 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } })
642 await waitJobs(servers)
644 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
645 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
646 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
647 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
651 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
652 await waitJobs(servers)
654 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
655 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
656 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
657 // We deleted the video, so even if we recreated it, the old entry is still deleted
658 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
662 it('Should update the element type if the video is blacklisted', async function () {
665 const name = 'video 89'
669 await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true })
670 await waitJobs(servers)
672 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
673 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
674 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
675 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
679 await servers[0].blacklist.remove({ videoId: video1 })
680 await waitJobs(servers)
682 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
683 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
684 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
685 // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
686 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
690 it('Should update the element type if the account or server of the video is blocked', async function () {
693 const command = servers[0].blocklist
695 const name = 'video 90'
699 await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
700 await waitJobs(servers)
702 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
703 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
705 await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
706 await waitJobs(servers)
708 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
712 await command.addToMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
713 await waitJobs(servers)
715 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
716 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
718 await command.removeFromMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
719 await waitJobs(servers)
721 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
725 await command.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port })
726 await waitJobs(servers)
728 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
729 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
731 await command.removeFromServerBlocklist({ account: 'root@localhost:' + servers[1].port })
732 await waitJobs(servers)
734 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
738 await command.addToServerBlocklist({ server: 'localhost:' + servers[1].port })
739 await waitJobs(servers)
741 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
742 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
744 await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port })
745 await waitJobs(servers)
747 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
751 it('Should hide the video if it is NSFW', async function () {
752 const body = await commands[0].listVideos({ token: userTokenServer1, playlistId: playlistServer1UUID2, query: { nsfw: 'false' } })
753 expect(body.total).to.equal(3)
755 const elements = body.data
756 const element = elements.find(e => e.position === 3)
758 expect(element).to.exist
759 expect(element.video).to.be.null
760 expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
765 describe('Managing playlist elements', function () {
767 it('Should reorder the playlist', async function () {
771 await commands[0].reorderElements({
772 playlistId: playlistServer1Id,
775 insertAfterPosition: 3
779 await waitJobs(servers)
781 for (const server of servers) {
782 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
783 const names = body.data.map(v => v.video.name)
785 expect(names).to.deep.equal([
799 await commands[0].reorderElements({
800 playlistId: playlistServer1Id,
804 insertAfterPosition: 4
808 await waitJobs(servers)
810 for (const server of servers) {
811 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
812 const names = body.data.map(v => v.video.name)
814 expect(names).to.deep.equal([
828 await commands[0].reorderElements({
829 playlistId: playlistServer1Id,
832 insertAfterPosition: 3
836 await waitJobs(servers)
838 for (const server of servers) {
839 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
840 const names = elements.map(v => v.video.name)
842 expect(names).to.deep.equal([
853 for (let i = 1; i <= elements.length; i++) {
854 expect(elements[i - 1].position).to.equal(i)
860 it('Should update startTimestamp/endTimestamp of some elements', async function () {
863 await commands[0].updateElement({
864 playlistId: playlistServer1Id,
865 elementId: playlistElementServer1Video4,
871 await commands[0].updateElement({
872 playlistId: playlistServer1Id,
873 elementId: playlistElementServer1Video5,
879 await waitJobs(servers)
881 for (const server of servers) {
882 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
884 expect(elements[0].video.name).to.equal('video 3 server 1')
885 expect(elements[0].position).to.equal(1)
886 expect(elements[0].startTimestamp).to.equal(1)
887 expect(elements[0].stopTimestamp).to.equal(35)
889 expect(elements[5].video.name).to.equal('video 4 server 1')
890 expect(elements[5].position).to.equal(6)
891 expect(elements[5].startTimestamp).to.equal(45)
892 expect(elements[5].stopTimestamp).to.be.null
896 it('Should check videos existence in my playlist', async function () {
898 servers[0].store.videos[0].id,
900 servers[0].store.videos[3].id,
902 servers[0].store.videos[4].id
904 const obj = await commands[0].videosExist({ videoIds })
907 const elem = obj[servers[0].store.videos[0].id]
908 expect(elem).to.have.lengthOf(1)
909 expect(elem[0].playlistElementId).to.exist
910 expect(elem[0].playlistId).to.equal(playlistServer1Id)
911 expect(elem[0].startTimestamp).to.equal(15)
912 expect(elem[0].stopTimestamp).to.equal(28)
916 const elem = obj[servers[0].store.videos[3].id]
917 expect(elem).to.have.lengthOf(1)
918 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
919 expect(elem[0].playlistId).to.equal(playlistServer1Id)
920 expect(elem[0].startTimestamp).to.equal(1)
921 expect(elem[0].stopTimestamp).to.equal(35)
925 const elem = obj[servers[0].store.videos[4].id]
926 expect(elem).to.have.lengthOf(1)
927 expect(elem[0].playlistId).to.equal(playlistServer1Id)
928 expect(elem[0].startTimestamp).to.equal(45)
929 expect(elem[0].stopTimestamp).to.equal(null)
932 expect(obj[42000]).to.have.lengthOf(0)
933 expect(obj[43000]).to.have.lengthOf(0)
936 it('Should automatically update updatedAt field of playlists', async function () {
937 const server = servers[1]
938 const videoId = servers[1].store.videos[5].id
940 async function getPlaylistNames () {
941 const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' })
943 return data.map(p => p.displayName)
946 const attributes = { videoId }
947 const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes })
948 const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes })
950 const names1 = await getPlaylistNames()
951 expect(names1[0]).to.equal('playlist 3 updated')
952 expect(names1[1]).to.equal('playlist 2')
954 await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id })
956 const names2 = await getPlaylistNames()
957 expect(names2[0]).to.equal('playlist 2')
958 expect(names2[1]).to.equal('playlist 3 updated')
960 await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id })
962 const names3 = await getPlaylistNames()
963 expect(names3[0]).to.equal('playlist 3 updated')
964 expect(names3[1]).to.equal('playlist 2')
967 it('Should delete some elements', async function () {
970 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 })
971 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW })
973 await waitJobs(servers)
975 for (const server of servers) {
976 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
977 expect(body.total).to.equal(6)
979 const elements = body.data
980 expect(elements).to.have.lengthOf(6)
982 expect(elements[0].video.name).to.equal('video 0 server 1')
983 expect(elements[0].position).to.equal(1)
985 expect(elements[1].video.name).to.equal('video 2 server 3')
986 expect(elements[1].position).to.equal(2)
988 expect(elements[2].video.name).to.equal('video 1 server 3')
989 expect(elements[2].position).to.equal(3)
991 expect(elements[3].video.name).to.equal('video 4 server 1')
992 expect(elements[3].position).to.equal(4)
994 expect(elements[4].video.name).to.equal('NSFW video')
995 expect(elements[4].position).to.equal(5)
997 expect(elements[5].video.name).to.equal('NSFW video')
998 expect(elements[5].position).to.equal(6)
1002 it('Should be able to create a public playlist, and set it to private', async function () {
1005 const videoPlaylistIds = await commands[0].create({
1007 displayName: 'my super public playlist',
1008 privacy: VideoPlaylistPrivacy.PUBLIC,
1009 videoChannelId: servers[0].store.channel.id
1013 await waitJobs(servers)
1015 for (const server of servers) {
1016 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1019 const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE }
1020 await commands[0].update({ playlistId: videoPlaylistIds.id, attributes })
1022 await waitJobs(servers)
1024 for (const server of [ servers[1], servers[2] ]) {
1025 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1028 await commands[0].get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
1029 await commands[0].get({ token: servers[0].accessToken, playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1033 describe('Playlist deletion', function () {
1035 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1038 await commands[0].delete({ playlistId: playlistServer1Id })
1040 await waitJobs(servers)
1042 for (const server of servers) {
1043 await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1047 it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1050 for (const server of servers) {
1051 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
1055 it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1058 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist')
1061 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1062 expect(body.total).to.equal(3)
1064 expect(finder(body.data)).to.not.be.undefined
1067 await servers[2].follows.unfollow({ target: servers[0] })
1070 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1071 expect(body.total).to.equal(1)
1073 expect(finder(body.data)).to.be.undefined
1077 it('Should delete a channel and put the associated playlist in private mode', async function () {
1080 const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } })
1082 const playlistCreated = await commands[0].create({
1084 displayName: 'channel playlist',
1085 privacy: VideoPlaylistPrivacy.PUBLIC,
1086 videoChannelId: channel.id
1090 await waitJobs(servers)
1092 await servers[0].channels.delete({ channelName: 'super_channel' })
1094 await waitJobs(servers)
1096 const body = await commands[0].get({ token: servers[0].accessToken, playlistId: playlistCreated.uuid })
1097 expect(body.displayName).to.equal('channel playlist')
1098 expect(body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1100 await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1103 it('Should delete an account and delete its playlists', async function () {
1106 const { userId, token } = await servers[0].users.generate('user_1')
1108 const { videoChannels } = await servers[0].users.getMyInfo({ token })
1109 const userChannel = videoChannels[0]
1111 await commands[0].create({
1113 displayName: 'playlist to be deleted',
1114 privacy: VideoPlaylistPrivacy.PUBLIC,
1115 videoChannelId: userChannel.id
1119 await waitJobs(servers)
1121 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted')
1124 for (const server of [ servers[0], servers[1] ]) {
1125 const body = await server.playlists.list({ start: 0, count: 15 })
1127 expect(finder(body.data)).to.not.be.undefined
1131 await servers[0].users.remove({ userId })
1132 await waitJobs(servers)
1135 for (const server of [ servers[0], servers[1] ]) {
1136 const body = await server.playlists.list({ start: 0, count: 15 })
1138 expect(finder(body.data)).to.be.undefined
1144 after(async function () {
1145 await cleanupTests(servers)