1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { checkPlaylistFilesWereRemoved, testImage } from '@server/tests/shared'
5 import { wait } from '@shared/core-utils'
9 VideoPlaylistCreateResult,
10 VideoPlaylistElementType,
14 } from '@shared/models'
17 createMultipleServers,
21 setAccessTokensToServers,
22 setDefaultAccountAvatar,
23 setDefaultVideoChannel,
25 } from '@shared/server-commands'
26 import { uuidToShort } from '@shared/extra-utils'
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@localhost:' + servers[1].port, 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@localhost:' + servers[1].port })
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@localhost:' + servers[1].port })
711 await waitJobs(servers)
713 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
717 await command.addToMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
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: 'localhost:' + servers[1].port })
724 await waitJobs(servers)
726 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
730 await command.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port })
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@localhost:' + servers[1].port })
737 await waitJobs(servers)
739 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
743 await command.addToServerBlocklist({ server: 'localhost:' + servers[1].port })
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: 'localhost:' + servers[1].port })
750 await waitJobs(servers)
752 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
756 it('Should hide the video if it is NSFW', async function () {
757 const body = await commands[0].listVideos({ token: userTokenServer1, playlistId: playlistServer1UUID2, query: { nsfw: 'false' } })
758 expect(body.total).to.equal(3)
760 const elements = body.data
761 const element = elements.find(e => e.position === 3)
763 expect(element).to.exist
764 expect(element.video).to.be.null
765 expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
770 describe('Managing playlist elements', function () {
772 it('Should reorder the playlist', async function () {
776 await commands[0].reorderElements({
777 playlistId: playlistServer1Id,
780 insertAfterPosition: 3
784 await waitJobs(servers)
786 for (const server of servers) {
787 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
788 const names = body.data.map(v => v.video.name)
790 expect(names).to.deep.equal([
804 await commands[0].reorderElements({
805 playlistId: playlistServer1Id,
809 insertAfterPosition: 4
813 await waitJobs(servers)
815 for (const server of servers) {
816 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
817 const names = body.data.map(v => v.video.name)
819 expect(names).to.deep.equal([
833 await commands[0].reorderElements({
834 playlistId: playlistServer1Id,
837 insertAfterPosition: 3
841 await waitJobs(servers)
843 for (const server of servers) {
844 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
845 const names = elements.map(v => v.video.name)
847 expect(names).to.deep.equal([
858 for (let i = 1; i <= elements.length; i++) {
859 expect(elements[i - 1].position).to.equal(i)
865 it('Should update startTimestamp/endTimestamp of some elements', async function () {
868 await commands[0].updateElement({
869 playlistId: playlistServer1Id,
870 elementId: playlistElementServer1Video4,
876 await commands[0].updateElement({
877 playlistId: playlistServer1Id,
878 elementId: playlistElementServer1Video5,
884 await waitJobs(servers)
886 for (const server of servers) {
887 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
889 expect(elements[0].video.name).to.equal('video 3 server 1')
890 expect(elements[0].position).to.equal(1)
891 expect(elements[0].startTimestamp).to.equal(1)
892 expect(elements[0].stopTimestamp).to.equal(35)
894 expect(elements[5].video.name).to.equal('video 4 server 1')
895 expect(elements[5].position).to.equal(6)
896 expect(elements[5].startTimestamp).to.equal(45)
897 expect(elements[5].stopTimestamp).to.be.null
901 it('Should check videos existence in my playlist', async function () {
903 servers[0].store.videos[0].id,
905 servers[0].store.videos[3].id,
907 servers[0].store.videos[4].id
909 const obj = await commands[0].videosExist({ videoIds })
912 const elem = obj[servers[0].store.videos[0].id]
913 expect(elem).to.have.lengthOf(1)
914 expect(elem[0].playlistElementId).to.exist
915 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
916 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
917 expect(elem[0].playlistId).to.equal(playlistServer1Id)
918 expect(elem[0].startTimestamp).to.equal(15)
919 expect(elem[0].stopTimestamp).to.equal(28)
923 const elem = obj[servers[0].store.videos[3].id]
924 expect(elem).to.have.lengthOf(1)
925 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
926 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
927 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
928 expect(elem[0].playlistId).to.equal(playlistServer1Id)
929 expect(elem[0].startTimestamp).to.equal(1)
930 expect(elem[0].stopTimestamp).to.equal(35)
934 const elem = obj[servers[0].store.videos[4].id]
935 expect(elem).to.have.lengthOf(1)
936 expect(elem[0].playlistId).to.equal(playlistServer1Id)
937 expect(elem[0].playlistDisplayName).to.equal(playlistServer1DisplayName)
938 expect(elem[0].playlistShortUUID).to.equal(uuidToShort(playlistServer1UUID))
939 expect(elem[0].startTimestamp).to.equal(45)
940 expect(elem[0].stopTimestamp).to.equal(null)
943 expect(obj[42000]).to.have.lengthOf(0)
944 expect(obj[43000]).to.have.lengthOf(0)
947 it('Should automatically update updatedAt field of playlists', async function () {
948 const server = servers[1]
949 const videoId = servers[1].store.videos[5].id
951 async function getPlaylistNames () {
952 const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' })
954 return data.map(p => p.displayName)
957 const attributes = { videoId }
958 const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes })
959 const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes })
961 const names1 = await getPlaylistNames()
962 expect(names1[0]).to.equal('playlist 3 updated')
963 expect(names1[1]).to.equal('playlist 2')
965 await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id })
967 const names2 = await getPlaylistNames()
968 expect(names2[0]).to.equal('playlist 2')
969 expect(names2[1]).to.equal('playlist 3 updated')
971 await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id })
973 const names3 = await getPlaylistNames()
974 expect(names3[0]).to.equal('playlist 3 updated')
975 expect(names3[1]).to.equal('playlist 2')
978 it('Should delete some elements', async function () {
981 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 })
982 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW })
984 await waitJobs(servers)
986 for (const server of servers) {
987 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
988 expect(body.total).to.equal(6)
990 const elements = body.data
991 expect(elements).to.have.lengthOf(6)
993 expect(elements[0].video.name).to.equal('video 0 server 1')
994 expect(elements[0].position).to.equal(1)
996 expect(elements[1].video.name).to.equal('video 2 server 3')
997 expect(elements[1].position).to.equal(2)
999 expect(elements[2].video.name).to.equal('video 1 server 3')
1000 expect(elements[2].position).to.equal(3)
1002 expect(elements[3].video.name).to.equal('video 4 server 1')
1003 expect(elements[3].position).to.equal(4)
1005 expect(elements[4].video.name).to.equal('NSFW video')
1006 expect(elements[4].position).to.equal(5)
1008 expect(elements[5].video.name).to.equal('NSFW video')
1009 expect(elements[5].position).to.equal(6)
1013 it('Should be able to create a public playlist, and set it to private', async function () {
1016 const videoPlaylistIds = await commands[0].create({
1018 displayName: 'my super public playlist',
1019 privacy: VideoPlaylistPrivacy.PUBLIC,
1020 videoChannelId: servers[0].store.channel.id
1024 await waitJobs(servers)
1026 for (const server of servers) {
1027 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1030 const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE }
1031 await commands[0].update({ playlistId: videoPlaylistIds.id, attributes })
1033 await waitJobs(servers)
1035 for (const server of [ servers[1], servers[2] ]) {
1036 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1039 await commands[0].get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
1040 await commands[0].get({ token: servers[0].accessToken, playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1044 describe('Playlist deletion', function () {
1046 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1049 await commands[0].delete({ playlistId: playlistServer1Id })
1051 await waitJobs(servers)
1053 for (const server of servers) {
1054 await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1058 it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1061 for (const server of servers) {
1062 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server)
1066 it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1069 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist')
1072 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1073 expect(body.total).to.equal(3)
1075 expect(finder(body.data)).to.not.be.undefined
1078 await servers[2].follows.unfollow({ target: servers[0] })
1081 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1082 expect(body.total).to.equal(1)
1084 expect(finder(body.data)).to.be.undefined
1088 it('Should delete a channel and put the associated playlist in private mode', async function () {
1091 const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } })
1093 const playlistCreated = await commands[0].create({
1095 displayName: 'channel playlist',
1096 privacy: VideoPlaylistPrivacy.PUBLIC,
1097 videoChannelId: channel.id
1101 await waitJobs(servers)
1103 await servers[0].channels.delete({ channelName: 'super_channel' })
1105 await waitJobs(servers)
1107 const body = await commands[0].get({ token: servers[0].accessToken, playlistId: playlistCreated.uuid })
1108 expect(body.displayName).to.equal('channel playlist')
1109 expect(body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1111 await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1114 it('Should delete an account and delete its playlists', async function () {
1117 const { userId, token } = await servers[0].users.generate('user_1')
1119 const { videoChannels } = await servers[0].users.getMyInfo({ token })
1120 const userChannel = videoChannels[0]
1122 await commands[0].create({
1124 displayName: 'playlist to be deleted',
1125 privacy: VideoPlaylistPrivacy.PUBLIC,
1126 videoChannelId: userChannel.id
1130 await waitJobs(servers)
1132 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted')
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.not.be.undefined
1142 await servers[0].users.remove({ userId })
1143 await waitJobs(servers)
1146 for (const server of [ servers[0], servers[1] ]) {
1147 const body = await server.playlists.list({ start: 0, count: 15 })
1149 expect(finder(body.data)).to.be.undefined
1155 after(async function () {
1156 await cleanupTests(servers)