1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import * as chai from 'chai'
4 import { checkPlaylistFilesWereRemoved, testImage } from '@server/tests/shared'
5 import { wait } from '@shared/core-utils'
9 VideoPlaylistCreateResult,
10 VideoPlaylistElementType,
14 } from '@shared/models'
17 createMultipleServers,
21 setAccessTokensToServers,
22 setDefaultAccountAvatar,
23 setDefaultVideoChannel,
25 } from '@shared/server-commands'
27 const expect = chai.expect
29 async function checkPlaylistElementType (
30 servers: PeerTubeServer[],
32 type: VideoPlaylistElementType,
37 for (const server of servers) {
38 const body = await server.playlists.listVideos({ token: server.accessToken, playlistId, start: 0, count: 10 })
39 expect(body.total).to.equal(total)
41 const videoElement = body.data.find(e => e.position === position)
42 expect(videoElement.type).to.equal(type, 'On server ' + server.url)
44 if (type === VideoPlaylistElementType.REGULAR) {
45 expect(videoElement.video).to.not.be.null
46 expect(videoElement.video.name).to.equal(name)
48 expect(videoElement.video).to.be.null
53 describe('Test video playlists', function () {
54 let servers: PeerTubeServer[] = []
56 let playlistServer2Id1: number
57 let playlistServer2Id2: number
58 let playlistServer2UUID2: string
60 let playlistServer1Id: number
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 playlist = await commands[0].create({
496 displayName: 'playlist 4',
497 privacy: VideoPlaylistPrivacy.PUBLIC,
498 videoChannelId: servers[0].store.channel.id
502 playlistServer1Id = playlist.id
503 playlistServer1UUID = playlist.uuid
505 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
506 await addVideo({ videoId: servers[2].store.videos[1].uuid, startTimestamp: 35 })
507 await addVideo({ videoId: servers[2].store.videos[2].uuid })
509 const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 })
510 playlistElementServer1Video4 = element.id
514 const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
515 playlistElementServer1Video5 = element.id
519 const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
520 playlistElementNSFW = element.id
522 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
523 await addVideo({ videoId: nsfwVideoServer1 })
526 await waitJobs(servers)
529 it('Should correctly list playlist videos', async function () {
532 for (const server of servers) {
534 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
536 expect(body.total).to.equal(8)
538 const videoElements = body.data
539 expect(videoElements).to.have.lengthOf(8)
541 expect(videoElements[0].video.name).to.equal('video 0 server 1')
542 expect(videoElements[0].position).to.equal(1)
543 expect(videoElements[0].startTimestamp).to.equal(15)
544 expect(videoElements[0].stopTimestamp).to.equal(28)
546 expect(videoElements[1].video.name).to.equal('video 1 server 3')
547 expect(videoElements[1].position).to.equal(2)
548 expect(videoElements[1].startTimestamp).to.equal(35)
549 expect(videoElements[1].stopTimestamp).to.be.null
551 expect(videoElements[2].video.name).to.equal('video 2 server 3')
552 expect(videoElements[2].position).to.equal(3)
553 expect(videoElements[2].startTimestamp).to.be.null
554 expect(videoElements[2].stopTimestamp).to.be.null
556 expect(videoElements[3].video.name).to.equal('video 3 server 1')
557 expect(videoElements[3].position).to.equal(4)
558 expect(videoElements[3].startTimestamp).to.be.null
559 expect(videoElements[3].stopTimestamp).to.equal(35)
561 expect(videoElements[4].video.name).to.equal('video 4 server 1')
562 expect(videoElements[4].position).to.equal(5)
563 expect(videoElements[4].startTimestamp).to.equal(45)
564 expect(videoElements[4].stopTimestamp).to.equal(60)
566 expect(videoElements[5].video.name).to.equal('NSFW video')
567 expect(videoElements[5].position).to.equal(6)
568 expect(videoElements[5].startTimestamp).to.equal(5)
569 expect(videoElements[5].stopTimestamp).to.be.null
571 expect(videoElements[6].video.name).to.equal('NSFW video')
572 expect(videoElements[6].position).to.equal(7)
573 expect(videoElements[6].startTimestamp).to.equal(4)
574 expect(videoElements[6].stopTimestamp).to.be.null
576 expect(videoElements[7].video.name).to.equal('NSFW video')
577 expect(videoElements[7].position).to.equal(8)
578 expect(videoElements[7].startTimestamp).to.be.null
579 expect(videoElements[7].stopTimestamp).to.be.null
583 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 })
584 expect(body.data).to.have.lengthOf(2)
590 describe('Element type', function () {
591 let groupUser1: PeerTubeServer[]
592 let groupWithoutToken1: PeerTubeServer[]
593 let group1: PeerTubeServer[]
594 let group2: PeerTubeServer[]
600 before(async function () {
603 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userTokenServer1 }) ]
604 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
605 group1 = [ servers[0] ]
606 group2 = [ servers[1], servers[2] ]
608 const playlist = await commands[0].create({
609 token: userTokenServer1,
611 displayName: 'playlist 56',
612 privacy: VideoPlaylistPrivacy.PUBLIC,
613 videoChannelId: servers[0].store.channel.id
617 const playlistServer1Id2 = playlist.id
618 playlistServer1UUID2 = playlist.uuid
620 const addVideo = (attributes: any) => {
621 return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes })
624 video1 = (await servers[0].videos.quickUpload({ name: 'video 89', token: userTokenServer1 })).uuid
625 video2 = (await servers[1].videos.quickUpload({ name: 'video 90' })).uuid
626 video3 = (await servers[0].videos.quickUpload({ name: 'video 91', nsfw: true })).uuid
628 await waitJobs(servers)
630 await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
631 await addVideo({ videoId: video2, startTimestamp: 35 })
632 await addVideo({ videoId: video3 })
634 await waitJobs(servers)
637 it('Should update the element type if the video is private', async function () {
640 const name = 'video 89'
644 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } })
645 await waitJobs(servers)
647 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
648 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
649 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
650 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
654 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
655 await waitJobs(servers)
657 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
658 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
659 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
660 // We deleted the video, so even if we recreated it, the old entry is still deleted
661 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
665 it('Should update the element type if the video is blacklisted', async function () {
668 const name = 'video 89'
672 await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true })
673 await waitJobs(servers)
675 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
676 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
677 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
678 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
682 await servers[0].blacklist.remove({ videoId: video1 })
683 await waitJobs(servers)
685 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
686 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
687 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
688 // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
689 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
693 it('Should update the element type if the account or server of the video is blocked', async function () {
696 const command = servers[0].blocklist
698 const name = 'video 90'
702 await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
703 await waitJobs(servers)
705 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
706 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
708 await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
709 await waitJobs(servers)
711 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
715 await command.addToMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
716 await waitJobs(servers)
718 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
719 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
721 await command.removeFromMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
722 await waitJobs(servers)
724 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
728 await command.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port })
729 await waitJobs(servers)
731 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
732 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
734 await command.removeFromServerBlocklist({ account: 'root@localhost:' + servers[1].port })
735 await waitJobs(servers)
737 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
741 await command.addToServerBlocklist({ server: 'localhost:' + servers[1].port })
742 await waitJobs(servers)
744 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
745 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
747 await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port })
748 await waitJobs(servers)
750 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
754 it('Should hide the video if it is NSFW', async function () {
755 const body = await commands[0].listVideos({ token: userTokenServer1, playlistId: playlistServer1UUID2, query: { nsfw: 'false' } })
756 expect(body.total).to.equal(3)
758 const elements = body.data
759 const element = elements.find(e => e.position === 3)
761 expect(element).to.exist
762 expect(element.video).to.be.null
763 expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
768 describe('Managing playlist elements', function () {
770 it('Should reorder the playlist', async function () {
774 await commands[0].reorderElements({
775 playlistId: playlistServer1Id,
778 insertAfterPosition: 3
782 await waitJobs(servers)
784 for (const server of servers) {
785 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
786 const names = body.data.map(v => v.video.name)
788 expect(names).to.deep.equal([
802 await commands[0].reorderElements({
803 playlistId: playlistServer1Id,
807 insertAfterPosition: 4
811 await waitJobs(servers)
813 for (const server of servers) {
814 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
815 const names = body.data.map(v => v.video.name)
817 expect(names).to.deep.equal([
831 await commands[0].reorderElements({
832 playlistId: playlistServer1Id,
835 insertAfterPosition: 3
839 await waitJobs(servers)
841 for (const server of servers) {
842 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
843 const names = elements.map(v => v.video.name)
845 expect(names).to.deep.equal([
856 for (let i = 1; i <= elements.length; i++) {
857 expect(elements[i - 1].position).to.equal(i)
863 it('Should update startTimestamp/endTimestamp of some elements', async function () {
866 await commands[0].updateElement({
867 playlistId: playlistServer1Id,
868 elementId: playlistElementServer1Video4,
874 await commands[0].updateElement({
875 playlistId: playlistServer1Id,
876 elementId: playlistElementServer1Video5,
882 await waitJobs(servers)
884 for (const server of servers) {
885 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
887 expect(elements[0].video.name).to.equal('video 3 server 1')
888 expect(elements[0].position).to.equal(1)
889 expect(elements[0].startTimestamp).to.equal(1)
890 expect(elements[0].stopTimestamp).to.equal(35)
892 expect(elements[5].video.name).to.equal('video 4 server 1')
893 expect(elements[5].position).to.equal(6)
894 expect(elements[5].startTimestamp).to.equal(45)
895 expect(elements[5].stopTimestamp).to.be.null
899 it('Should check videos existence in my playlist', async function () {
901 servers[0].store.videos[0].id,
903 servers[0].store.videos[3].id,
905 servers[0].store.videos[4].id
907 const obj = await commands[0].videosExist({ videoIds })
910 const elem = obj[servers[0].store.videos[0].id]
911 expect(elem).to.have.lengthOf(1)
912 expect(elem[0].playlistElementId).to.exist
913 expect(elem[0].playlistId).to.equal(playlistServer1Id)
914 expect(elem[0].startTimestamp).to.equal(15)
915 expect(elem[0].stopTimestamp).to.equal(28)
919 const elem = obj[servers[0].store.videos[3].id]
920 expect(elem).to.have.lengthOf(1)
921 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
922 expect(elem[0].playlistId).to.equal(playlistServer1Id)
923 expect(elem[0].startTimestamp).to.equal(1)
924 expect(elem[0].stopTimestamp).to.equal(35)
928 const elem = obj[servers[0].store.videos[4].id]
929 expect(elem).to.have.lengthOf(1)
930 expect(elem[0].playlistId).to.equal(playlistServer1Id)
931 expect(elem[0].startTimestamp).to.equal(45)
932 expect(elem[0].stopTimestamp).to.equal(null)
935 expect(obj[42000]).to.have.lengthOf(0)
936 expect(obj[43000]).to.have.lengthOf(0)
939 it('Should automatically update updatedAt field of playlists', async function () {
940 const server = servers[1]
941 const videoId = servers[1].store.videos[5].id
943 async function getPlaylistNames () {
944 const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' })
946 return data.map(p => p.displayName)
949 const attributes = { videoId }
950 const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes })
951 const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes })
953 const names1 = await getPlaylistNames()
954 expect(names1[0]).to.equal('playlist 3 updated')
955 expect(names1[1]).to.equal('playlist 2')
957 await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id })
959 const names2 = await getPlaylistNames()
960 expect(names2[0]).to.equal('playlist 2')
961 expect(names2[1]).to.equal('playlist 3 updated')
963 await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id })
965 const names3 = await getPlaylistNames()
966 expect(names3[0]).to.equal('playlist 3 updated')
967 expect(names3[1]).to.equal('playlist 2')
970 it('Should delete some elements', async function () {
973 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 })
974 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW })
976 await waitJobs(servers)
978 for (const server of servers) {
979 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
980 expect(body.total).to.equal(6)
982 const elements = body.data
983 expect(elements).to.have.lengthOf(6)
985 expect(elements[0].video.name).to.equal('video 0 server 1')
986 expect(elements[0].position).to.equal(1)
988 expect(elements[1].video.name).to.equal('video 2 server 3')
989 expect(elements[1].position).to.equal(2)
991 expect(elements[2].video.name).to.equal('video 1 server 3')
992 expect(elements[2].position).to.equal(3)
994 expect(elements[3].video.name).to.equal('video 4 server 1')
995 expect(elements[3].position).to.equal(4)
997 expect(elements[4].video.name).to.equal('NSFW video')
998 expect(elements[4].position).to.equal(5)
1000 expect(elements[5].video.name).to.equal('NSFW video')
1001 expect(elements[5].position).to.equal(6)
1005 it('Should be able to create a public playlist, and set it to private', async function () {
1008 const videoPlaylistIds = await commands[0].create({
1010 displayName: 'my super public playlist',
1011 privacy: VideoPlaylistPrivacy.PUBLIC,
1012 videoChannelId: servers[0].store.channel.id
1016 await waitJobs(servers)
1018 for (const server of servers) {
1019 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1022 const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE }
1023 await commands[0].update({ playlistId: videoPlaylistIds.id, attributes })
1025 await waitJobs(servers)
1027 for (const server of [ servers[1], servers[2] ]) {
1028 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1031 await commands[0].get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
1032 await commands[0].get({ token: servers[0].accessToken, playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1036 describe('Playlist deletion', function () {
1038 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1041 await commands[0].delete({ playlistId: playlistServer1Id })
1043 await waitJobs(servers)
1045 for (const server of servers) {
1046 await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1050 it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1053 for (const server of servers) {
1054 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
1058 it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1061 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist')
1064 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1065 expect(body.total).to.equal(3)
1067 expect(finder(body.data)).to.not.be.undefined
1070 await servers[2].follows.unfollow({ target: servers[0] })
1073 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1074 expect(body.total).to.equal(1)
1076 expect(finder(body.data)).to.be.undefined
1080 it('Should delete a channel and put the associated playlist in private mode', async function () {
1083 const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } })
1085 const playlistCreated = await commands[0].create({
1087 displayName: 'channel playlist',
1088 privacy: VideoPlaylistPrivacy.PUBLIC,
1089 videoChannelId: channel.id
1093 await waitJobs(servers)
1095 await servers[0].channels.delete({ channelName: 'super_channel' })
1097 await waitJobs(servers)
1099 const body = await commands[0].get({ token: servers[0].accessToken, playlistId: playlistCreated.uuid })
1100 expect(body.displayName).to.equal('channel playlist')
1101 expect(body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1103 await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1106 it('Should delete an account and delete its playlists', async function () {
1109 const { userId, token } = await servers[0].users.generate('user_1')
1111 const { videoChannels } = await servers[0].users.getMyInfo({ token })
1112 const userChannel = videoChannels[0]
1114 await commands[0].create({
1116 displayName: 'playlist to be deleted',
1117 privacy: VideoPlaylistPrivacy.PUBLIC,
1118 videoChannelId: userChannel.id
1122 await waitJobs(servers)
1124 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted')
1127 for (const server of [ servers[0], servers[1] ]) {
1128 const body = await server.playlists.list({ start: 0, count: 15 })
1130 expect(finder(body.data)).to.not.be.undefined
1134 await servers[0].users.remove({ userId })
1135 await waitJobs(servers)
1138 for (const server of [ servers[0], servers[1] ]) {
1139 const body = await server.playlists.list({ start: 0, count: 15 })
1141 expect(finder(body.data)).to.be.undefined
1147 after(async function () {
1148 await cleanupTests(servers)