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'
6 import { uuidToShort } from '@shared/extra-utils'
10 VideoPlaylistCreateResult,
11 VideoPlaylistElementType,
15 } from '@shared/models'
18 createMultipleServers,
22 setAccessTokensToServers,
23 setDefaultAccountAvatar,
24 setDefaultVideoChannel,
26 } from '@shared/server-commands'
28 async function checkPlaylistElementType (
29 servers: PeerTubeServer[],
31 type: VideoPlaylistElementType,
36 for (const server of servers) {
37 const body = await server.playlists.listVideos({ token: server.accessToken, playlistId, start: 0, count: 10 })
38 expect(body.total).to.equal(total)
40 const videoElement = body.data.find(e => e.position === position)
41 expect(videoElement.type).to.equal(type, 'On server ' + server.url)
43 if (type === VideoPlaylistElementType.REGULAR) {
44 expect(videoElement.video).to.not.be.null
45 expect(videoElement.video.name).to.equal(name)
47 expect(videoElement.video).to.be.null
52 describe('Test video playlists', function () {
53 let servers: PeerTubeServer[] = []
55 let playlistServer2Id1: number
56 let playlistServer2Id2: number
57 let playlistServer2UUID2: string
59 let playlistServer1Id: number
60 let playlistServer1DisplayName: string
61 let playlistServer1UUID: string
62 let playlistServer1UUID2: string
64 let playlistElementServer1Video4: number
65 let playlistElementServer1Video5: number
66 let playlistElementNSFW: number
68 let nsfwVideoServer1: number
70 let userTokenServer1: string
72 let commands: PlaylistsCommand[]
74 before(async function () {
77 servers = await createMultipleServers(3)
79 // Get the access tokens
80 await setAccessTokensToServers(servers)
81 await setDefaultVideoChannel(servers)
82 await setDefaultAccountAvatar(servers)
84 for (const server of servers) {
85 await server.config.disableTranscoding()
88 // Server 1 and server 2 follow each other
89 await doubleFollow(servers[0], servers[1])
90 // Server 1 and server 3 follow each other
91 await doubleFollow(servers[0], servers[2])
93 commands = servers.map(s => s.playlists)
96 servers[0].store.videos = []
97 servers[1].store.videos = []
98 servers[2].store.videos = []
100 for (const server of servers) {
101 for (let i = 0; i < 7; i++) {
102 const name = `video ${i} server ${server.serverNumber}`
103 const video = await server.videos.upload({ attributes: { name, nsfw: false } })
105 server.store.videos.push(video)
110 nsfwVideoServer1 = (await servers[0].videos.quickUpload({ name: 'NSFW video', nsfw: true })).id
112 userTokenServer1 = await servers[0].users.generateUserAndToken('user1')
114 await waitJobs(servers)
117 describe('Get default playlists', function () {
119 it('Should list video playlist privacies', async function () {
120 const privacies = await commands[0].getPrivacies()
122 expect(Object.keys(privacies)).to.have.length.at.least(3)
123 expect(privacies[3]).to.equal('Private')
126 it('Should list watch later playlist', async function () {
127 const token = servers[0].accessToken
130 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.WATCH_LATER })
132 expect(body.total).to.equal(1)
133 expect(body.data).to.have.lengthOf(1)
135 const playlist = body.data[0]
136 expect(playlist.displayName).to.equal('Watch later')
137 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
138 expect(playlist.type.label).to.equal('Watch later')
142 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.REGULAR })
144 expect(body.total).to.equal(0)
145 expect(body.data).to.have.lengthOf(0)
149 const body = await commands[0].listByAccount({ handle: 'root' })
150 expect(body.total).to.equal(0)
151 expect(body.data).to.have.lengthOf(0)
155 it('Should get private playlist for a classic user', async function () {
156 const token = await servers[0].users.generateUserAndToken('toto')
158 const body = await commands[0].listByAccount({ token, handle: 'toto' })
160 expect(body.total).to.equal(1)
161 expect(body.data).to.have.lengthOf(1)
163 const playlistId = body.data[0].id
164 await commands[0].listVideos({ token, playlistId })
168 describe('Create and federate playlists', function () {
170 it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
173 await commands[0].create({
175 displayName: 'my super playlist',
176 privacy: VideoPlaylistPrivacy.PUBLIC,
177 description: 'my super description',
178 thumbnailfile: 'thumbnail.jpg',
179 videoChannelId: servers[0].store.channel.id
183 await waitJobs(servers)
184 // Processing a playlist by the receiver could be long
187 for (const server of servers) {
188 const body = await server.playlists.list({ start: 0, count: 5 })
189 expect(body.total).to.equal(1)
190 expect(body.data).to.have.lengthOf(1)
192 const playlistFromList = body.data[0]
194 const playlistFromGet = await server.playlists.get({ playlistId: playlistFromList.uuid })
196 for (const playlist of [ playlistFromGet, playlistFromList ]) {
197 expect(playlist.id).to.be.a('number')
198 expect(playlist.uuid).to.be.a('string')
200 expect(playlist.isLocal).to.equal(server.serverNumber === 1)
202 expect(playlist.displayName).to.equal('my super playlist')
203 expect(playlist.description).to.equal('my super description')
204 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
205 expect(playlist.privacy.label).to.equal('Public')
206 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
207 expect(playlist.type.label).to.equal('Regular')
208 expect(playlist.embedPath).to.equal('/video-playlists/embed/' + playlist.uuid)
210 expect(playlist.videosLength).to.equal(0)
212 expect(playlist.ownerAccount.name).to.equal('root')
213 expect(playlist.ownerAccount.displayName).to.equal('root')
214 expect(playlist.videoChannel.name).to.equal('root_channel')
215 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
220 it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
224 const playlist = await servers[1].playlists.create({
226 displayName: 'playlist 2',
227 privacy: VideoPlaylistPrivacy.PUBLIC,
228 videoChannelId: servers[1].store.channel.id
231 playlistServer2Id1 = playlist.id
235 const playlist = await servers[1].playlists.create({
237 displayName: 'playlist 3',
238 privacy: VideoPlaylistPrivacy.PUBLIC,
239 thumbnailfile: 'thumbnail.jpg',
240 videoChannelId: servers[1].store.channel.id
244 playlistServer2Id2 = playlist.id
245 playlistServer2UUID2 = playlist.uuid
248 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
249 await servers[1].playlists.addElement({
251 attributes: { videoId: servers[1].store.videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
253 await servers[1].playlists.addElement({
255 attributes: { videoId: servers[1].store.videos[1].id }
259 await waitJobs(servers)
262 for (const server of [ servers[0], servers[1] ]) {
263 const body = await server.playlists.list({ start: 0, count: 5 })
265 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
266 expect(playlist2).to.not.be.undefined
267 await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
269 const playlist3 = body.data.find(p => p.displayName === 'playlist 3')
270 expect(playlist3).to.not.be.undefined
271 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
274 const body = await servers[2].playlists.list({ start: 0, count: 5 })
275 expect(body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
276 expect(body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
279 it('Should have the playlist on server 3 after a new follow', async function () {
282 // Server 2 and server 3 follow each other
283 await doubleFollow(servers[1], servers[2])
285 const body = await servers[2].playlists.list({ start: 0, count: 5 })
287 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
288 expect(playlist2).to.not.be.undefined
289 await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
291 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
295 describe('List playlists', function () {
297 it('Should correctly list the playlists', async function () {
301 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: 'createdAt' })
302 expect(body.total).to.equal(3)
304 const data = body.data
305 expect(data).to.have.lengthOf(2)
306 expect(data[0].displayName).to.equal('playlist 2')
307 expect(data[1].displayName).to.equal('playlist 3')
311 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: '-createdAt' })
312 expect(body.total).to.equal(3)
314 const data = body.data
315 expect(data).to.have.lengthOf(2)
316 expect(data[0].displayName).to.equal('playlist 2')
317 expect(data[1].displayName).to.equal('my super playlist')
321 it('Should list video channel playlists', async function () {
325 const body = await commands[0].listByChannel({ handle: 'root_channel', start: 0, count: 2, sort: '-createdAt' })
326 expect(body.total).to.equal(1)
328 const data = body.data
329 expect(data).to.have.lengthOf(1)
330 expect(data[0].displayName).to.equal('my super playlist')
334 it('Should list account playlists', async function () {
338 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: '-createdAt' })
339 expect(body.total).to.equal(2)
341 const data = body.data
342 expect(data).to.have.lengthOf(1)
343 expect(data[0].displayName).to.equal('playlist 2')
347 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: 'createdAt' })
348 expect(body.total).to.equal(2)
350 const data = body.data
351 expect(data).to.have.lengthOf(1)
352 expect(data[0].displayName).to.equal('playlist 3')
356 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '3' })
357 expect(body.total).to.equal(1)
359 const data = body.data
360 expect(data).to.have.lengthOf(1)
361 expect(data[0].displayName).to.equal('playlist 3')
365 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '4' })
366 expect(body.total).to.equal(0)
368 const data = body.data
369 expect(data).to.have.lengthOf(0)
374 describe('Playlist rights', function () {
375 let unlistedPlaylist: VideoPlaylistCreateResult
376 let privatePlaylist: VideoPlaylistCreateResult
378 before(async function () {
382 unlistedPlaylist = await servers[1].playlists.create({
384 displayName: 'playlist unlisted',
385 privacy: VideoPlaylistPrivacy.UNLISTED,
386 videoChannelId: servers[1].store.channel.id
392 privatePlaylist = await servers[1].playlists.create({
394 displayName: 'playlist private',
395 privacy: VideoPlaylistPrivacy.PRIVATE
400 await waitJobs(servers)
404 it('Should not list unlisted or private playlists', async function () {
405 for (const server of servers) {
407 await server.playlists.listByAccount({ handle: 'root@' + servers[1].host, sort: '-createdAt' }),
408 await server.playlists.list({ start: 0, count: 2, sort: '-createdAt' })
411 expect(results[0].total).to.equal(2)
412 expect(results[1].total).to.equal(3)
414 for (const body of results) {
415 const data = body.data
416 expect(data).to.have.lengthOf(2)
417 expect(data[0].displayName).to.equal('playlist 3')
418 expect(data[1].displayName).to.equal('playlist 2')
423 it('Should not get unlisted playlist using only the id', async function () {
424 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 })
427 it('Should get unlisted plyaylist using uuid or shortUUID', async function () {
428 await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid })
429 await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID })
432 it('Should not get private playlist without token', async function () {
433 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
434 await servers[1].playlists.get({ playlistId: id, expectedStatus: 401 })
438 it('Should get private playlist with a token', async function () {
439 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
440 await servers[1].playlists.get({ token: servers[1].accessToken, playlistId: id })
445 describe('Update playlists', function () {
447 it('Should update a playlist', async function () {
450 await servers[1].playlists.update({
452 displayName: 'playlist 3 updated',
453 description: 'description updated',
454 privacy: VideoPlaylistPrivacy.UNLISTED,
455 thumbnailfile: 'thumbnail.jpg',
456 videoChannelId: servers[1].store.channel.id
458 playlistId: playlistServer2Id2
461 await waitJobs(servers)
463 for (const server of servers) {
464 const playlist = await server.playlists.get({ playlistId: playlistServer2UUID2 })
466 expect(playlist.displayName).to.equal('playlist 3 updated')
467 expect(playlist.description).to.equal('description updated')
469 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
470 expect(playlist.privacy.label).to.equal('Unlisted')
472 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
473 expect(playlist.type.label).to.equal('Regular')
475 expect(playlist.videosLength).to.equal(2)
477 expect(playlist.ownerAccount.name).to.equal('root')
478 expect(playlist.ownerAccount.displayName).to.equal('root')
479 expect(playlist.videoChannel.name).to.equal('root_channel')
480 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
485 describe('Element timestamps', function () {
487 it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
490 const addVideo = (attributes: any) => {
491 return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
494 const playlistDisplayName = 'playlist 4'
495 const playlist = await commands[0].create({
497 displayName: playlistDisplayName,
498 privacy: VideoPlaylistPrivacy.PUBLIC,
499 videoChannelId: servers[0].store.channel.id
503 playlistServer1Id = playlist.id
504 playlistServer1DisplayName = playlistDisplayName
505 playlistServer1UUID = playlist.uuid
507 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
508 await addVideo({ videoId: servers[2].store.videos[1].uuid, startTimestamp: 35 })
509 await addVideo({ videoId: servers[2].store.videos[2].uuid })
511 const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 })
512 playlistElementServer1Video4 = element.id
516 const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
517 playlistElementServer1Video5 = element.id
521 const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
522 playlistElementNSFW = element.id
524 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
525 await addVideo({ videoId: nsfwVideoServer1 })
528 await waitJobs(servers)
531 it('Should correctly list playlist videos', async function () {
534 for (const server of servers) {
536 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
538 expect(body.total).to.equal(8)
540 const videoElements = body.data
541 expect(videoElements).to.have.lengthOf(8)
543 expect(videoElements[0].video.name).to.equal('video 0 server 1')
544 expect(videoElements[0].position).to.equal(1)
545 expect(videoElements[0].startTimestamp).to.equal(15)
546 expect(videoElements[0].stopTimestamp).to.equal(28)
548 expect(videoElements[1].video.name).to.equal('video 1 server 3')
549 expect(videoElements[1].position).to.equal(2)
550 expect(videoElements[1].startTimestamp).to.equal(35)
551 expect(videoElements[1].stopTimestamp).to.be.null
553 expect(videoElements[2].video.name).to.equal('video 2 server 3')
554 expect(videoElements[2].position).to.equal(3)
555 expect(videoElements[2].startTimestamp).to.be.null
556 expect(videoElements[2].stopTimestamp).to.be.null
558 expect(videoElements[3].video.name).to.equal('video 3 server 1')
559 expect(videoElements[3].position).to.equal(4)
560 expect(videoElements[3].startTimestamp).to.be.null
561 expect(videoElements[3].stopTimestamp).to.equal(35)
563 expect(videoElements[4].video.name).to.equal('video 4 server 1')
564 expect(videoElements[4].position).to.equal(5)
565 expect(videoElements[4].startTimestamp).to.equal(45)
566 expect(videoElements[4].stopTimestamp).to.equal(60)
568 expect(videoElements[5].video.name).to.equal('NSFW video')
569 expect(videoElements[5].position).to.equal(6)
570 expect(videoElements[5].startTimestamp).to.equal(5)
571 expect(videoElements[5].stopTimestamp).to.be.null
573 expect(videoElements[6].video.name).to.equal('NSFW video')
574 expect(videoElements[6].position).to.equal(7)
575 expect(videoElements[6].startTimestamp).to.equal(4)
576 expect(videoElements[6].stopTimestamp).to.be.null
578 expect(videoElements[7].video.name).to.equal('NSFW video')
579 expect(videoElements[7].position).to.equal(8)
580 expect(videoElements[7].startTimestamp).to.be.null
581 expect(videoElements[7].stopTimestamp).to.be.null
585 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 })
586 expect(body.data).to.have.lengthOf(2)
592 describe('Element type', function () {
593 let groupUser1: PeerTubeServer[]
594 let groupWithoutToken1: PeerTubeServer[]
595 let group1: PeerTubeServer[]
596 let group2: PeerTubeServer[]
602 before(async function () {
605 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userTokenServer1 }) ]
606 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
607 group1 = [ servers[0] ]
608 group2 = [ servers[1], servers[2] ]
610 const playlist = await commands[0].create({
611 token: userTokenServer1,
613 displayName: 'playlist 56',
614 privacy: VideoPlaylistPrivacy.PUBLIC,
615 videoChannelId: servers[0].store.channel.id
619 const playlistServer1Id2 = playlist.id
620 playlistServer1UUID2 = playlist.uuid
622 const addVideo = (attributes: any) => {
623 return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes })
626 video1 = (await servers[0].videos.quickUpload({ name: 'video 89', token: userTokenServer1 })).uuid
627 video2 = (await servers[1].videos.quickUpload({ name: 'video 90' })).uuid
628 video3 = (await servers[0].videos.quickUpload({ name: 'video 91', nsfw: true })).uuid
630 await waitJobs(servers)
632 await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
633 await addVideo({ videoId: video2, startTimestamp: 35 })
634 await addVideo({ videoId: video3 })
636 await waitJobs(servers)
639 it('Should update the element type if the video is private', async function () {
642 const name = 'video 89'
646 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } })
647 await waitJobs(servers)
649 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
650 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
651 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
652 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
656 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
657 await waitJobs(servers)
659 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
660 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
661 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
662 // We deleted the video, so even if we recreated it, the old entry is still deleted
663 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
667 it('Should update the element type if the video is blacklisted', async function () {
670 const name = 'video 89'
674 await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true })
675 await waitJobs(servers)
677 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
678 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
679 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
680 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
684 await servers[0].blacklist.remove({ videoId: video1 })
685 await waitJobs(servers)
687 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
688 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
689 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
690 // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
691 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
695 it('Should update the element type if the account or server of the video is blocked', async function () {
698 const command = servers[0].blocklist
700 const name = 'video 90'
704 await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@' + servers[1].host })
705 await waitJobs(servers)
707 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
708 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
710 await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@' + servers[1].host })
711 await waitJobs(servers)
713 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
717 await command.addToMyBlocklist({ token: userTokenServer1, server: servers[1].host })
718 await waitJobs(servers)
720 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
721 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
723 await command.removeFromMyBlocklist({ token: userTokenServer1, server: servers[1].host })
724 await waitJobs(servers)
726 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
730 await command.addToServerBlocklist({ account: 'root@' + servers[1].host })
731 await waitJobs(servers)
733 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
734 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
736 await command.removeFromServerBlocklist({ account: 'root@' + servers[1].host })
737 await waitJobs(servers)
739 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
743 await command.addToServerBlocklist({ server: servers[1].host })
744 await waitJobs(servers)
746 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
747 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
749 await command.removeFromServerBlocklist({ server: servers[1].host })
750 await waitJobs(servers)
752 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
757 describe('Managing playlist elements', function () {
759 it('Should reorder the playlist', async function () {
763 await commands[0].reorderElements({
764 playlistId: playlistServer1Id,
767 insertAfterPosition: 3
771 await waitJobs(servers)
773 for (const server of servers) {
774 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
775 const names = body.data.map(v => v.video.name)
777 expect(names).to.deep.equal([
791 await commands[0].reorderElements({
792 playlistId: playlistServer1Id,
796 insertAfterPosition: 4
800 await waitJobs(servers)
802 for (const server of servers) {
803 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
804 const names = body.data.map(v => v.video.name)
806 expect(names).to.deep.equal([
820 await commands[0].reorderElements({
821 playlistId: playlistServer1Id,
824 insertAfterPosition: 3
828 await waitJobs(servers)
830 for (const server of servers) {
831 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
832 const names = elements.map(v => v.video.name)
834 expect(names).to.deep.equal([
845 for (let i = 1; i <= elements.length; i++) {
846 expect(elements[i - 1].position).to.equal(i)
852 it('Should update startTimestamp/endTimestamp of some elements', async function () {
855 await commands[0].updateElement({
856 playlistId: playlistServer1Id,
857 elementId: playlistElementServer1Video4,
863 await commands[0].updateElement({
864 playlistId: playlistServer1Id,
865 elementId: playlistElementServer1Video5,
871 await waitJobs(servers)
873 for (const server of servers) {
874 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
876 expect(elements[0].video.name).to.equal('video 3 server 1')
877 expect(elements[0].position).to.equal(1)
878 expect(elements[0].startTimestamp).to.equal(1)
879 expect(elements[0].stopTimestamp).to.equal(35)
881 expect(elements[5].video.name).to.equal('video 4 server 1')
882 expect(elements[5].position).to.equal(6)
883 expect(elements[5].startTimestamp).to.equal(45)
884 expect(elements[5].stopTimestamp).to.be.null
888 it('Should check videos existence in my playlist', async function () {
890 servers[0].store.videos[0].id,
892 servers[0].store.videos[3].id,
894 servers[0].store.videos[4].id
896 const obj = await commands[0].videosExist({ videoIds })
899 const elem = obj[servers[0].store.videos[0].id]
900 expect(elem).to.have.lengthOf(1)
901 expect(elem[0].playlistElementId).to.exist
902 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
903 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
904 expect(elem[0].playlistId).to.equal(playlistServer1Id)
905 expect(elem[0].startTimestamp).to.equal(15)
906 expect(elem[0].stopTimestamp).to.equal(28)
910 const elem = obj[servers[0].store.videos[3].id]
911 expect(elem).to.have.lengthOf(1)
912 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
913 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
914 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
915 expect(elem[0].playlistId).to.equal(playlistServer1Id)
916 expect(elem[0].startTimestamp).to.equal(1)
917 expect(elem[0].stopTimestamp).to.equal(35)
921 const elem = obj[servers[0].store.videos[4].id]
922 expect(elem).to.have.lengthOf(1)
923 expect(elem[0].playlistId).to.equal(playlistServer1Id)
924 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
925 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
926 expect(elem[0].startTimestamp).to.equal(45)
927 expect(elem[0].stopTimestamp).to.equal(null)
930 expect(obj[42000]).to.have.lengthOf(0)
931 expect(obj[43000]).to.have.lengthOf(0)
934 it('Should automatically update updatedAt field of playlists', async function () {
935 const server = servers[1]
936 const videoId = servers[1].store.videos[5].id
938 async function getPlaylistNames () {
939 const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' })
941 return data.map(p => p.displayName)
944 const attributes = { videoId }
945 const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes })
946 const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes })
948 const names1 = await getPlaylistNames()
949 expect(names1[0]).to.equal('playlist 3 updated')
950 expect(names1[1]).to.equal('playlist 2')
952 await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id })
954 const names2 = await getPlaylistNames()
955 expect(names2[0]).to.equal('playlist 2')
956 expect(names2[1]).to.equal('playlist 3 updated')
958 await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id })
960 const names3 = await getPlaylistNames()
961 expect(names3[0]).to.equal('playlist 3 updated')
962 expect(names3[1]).to.equal('playlist 2')
965 it('Should delete some elements', async function () {
968 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 })
969 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW })
971 await waitJobs(servers)
973 for (const server of servers) {
974 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
975 expect(body.total).to.equal(6)
977 const elements = body.data
978 expect(elements).to.have.lengthOf(6)
980 expect(elements[0].video.name).to.equal('video 0 server 1')
981 expect(elements[0].position).to.equal(1)
983 expect(elements[1].video.name).to.equal('video 2 server 3')
984 expect(elements[1].position).to.equal(2)
986 expect(elements[2].video.name).to.equal('video 1 server 3')
987 expect(elements[2].position).to.equal(3)
989 expect(elements[3].video.name).to.equal('video 4 server 1')
990 expect(elements[3].position).to.equal(4)
992 expect(elements[4].video.name).to.equal('NSFW video')
993 expect(elements[4].position).to.equal(5)
995 expect(elements[5].video.name).to.equal('NSFW video')
996 expect(elements[5].position).to.equal(6)
1000 it('Should be able to create a public playlist, and set it to private', async function () {
1003 const videoPlaylistIds = await commands[0].create({
1005 displayName: 'my super public playlist',
1006 privacy: VideoPlaylistPrivacy.PUBLIC,
1007 videoChannelId: servers[0].store.channel.id
1011 await waitJobs(servers)
1013 for (const server of servers) {
1014 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1017 const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE }
1018 await commands[0].update({ playlistId: videoPlaylistIds.id, attributes })
1020 await waitJobs(servers)
1022 for (const server of [ servers[1], servers[2] ]) {
1023 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1026 await commands[0].get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
1027 await commands[0].get({ token: servers[0].accessToken, playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1031 describe('Playlist deletion', function () {
1033 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1036 await commands[0].delete({ playlistId: playlistServer1Id })
1038 await waitJobs(servers)
1040 for (const server of servers) {
1041 await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1045 it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1048 for (const server of servers) {
1049 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server)
1053 it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1056 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist')
1059 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1060 expect(body.total).to.equal(3)
1062 expect(finder(body.data)).to.not.be.undefined
1065 await servers[2].follows.unfollow({ target: servers[0] })
1068 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1069 expect(body.total).to.equal(1)
1071 expect(finder(body.data)).to.be.undefined
1075 it('Should delete a channel and put the associated playlist in private mode', async function () {
1078 const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } })
1080 const playlistCreated = await commands[0].create({
1082 displayName: 'channel playlist',
1083 privacy: VideoPlaylistPrivacy.PUBLIC,
1084 videoChannelId: channel.id
1088 await waitJobs(servers)
1090 await servers[0].channels.delete({ channelName: 'super_channel' })
1092 await waitJobs(servers)
1094 const body = await commands[0].get({ token: servers[0].accessToken, playlistId: playlistCreated.uuid })
1095 expect(body.displayName).to.equal('channel playlist')
1096 expect(body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1098 await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1101 it('Should delete an account and delete its playlists', async function () {
1104 const { userId, token } = await servers[0].users.generate('user_1')
1106 const { videoChannels } = await servers[0].users.getMyInfo({ token })
1107 const userChannel = videoChannels[0]
1109 await commands[0].create({
1111 displayName: 'playlist to be deleted',
1112 privacy: VideoPlaylistPrivacy.PUBLIC,
1113 videoChannelId: userChannel.id
1117 await waitJobs(servers)
1119 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted')
1122 for (const server of [ servers[0], servers[1] ]) {
1123 const body = await server.playlists.list({ start: 0, count: 15 })
1125 expect(finder(body.data)).to.not.be.undefined
1129 await servers[0].users.remove({ userId })
1130 await waitJobs(servers)
1133 for (const server of [ servers[0], servers[1] ]) {
1134 const body = await server.playlists.list({ start: 0, count: 15 })
1136 expect(finder(body.data)).to.be.undefined
1142 after(async function () {
1143 await cleanupTests(servers)