1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import { checkPlaylistFilesWereRemoved, testImage } from '@server/tests/shared'
6 import { wait } from '@shared/core-utils'
10 VideoPlaylistCreateResult,
11 VideoPlaylistElementType,
15 } from '@shared/models'
18 createMultipleServers,
22 setAccessTokensToServers,
23 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, { transcoding: { enabled: false } })
79 // Get the access tokens
80 await setAccessTokensToServers(servers)
81 await setDefaultVideoChannel(servers)
83 // Server 1 and server 2 follow each other
84 await doubleFollow(servers[0], servers[1])
85 // Server 1 and server 3 follow each other
86 await doubleFollow(servers[0], servers[2])
88 commands = servers.map(s => s.playlists)
91 servers[0].store.videos = []
92 servers[1].store.videos = []
93 servers[2].store.videos = []
95 for (const server of servers) {
96 for (let i = 0; i < 7; i++) {
97 const name = `video ${i} server ${server.serverNumber}`
98 const video = await server.videos.upload({ attributes: { name, nsfw: false } })
100 server.store.videos.push(video)
105 nsfwVideoServer1 = (await servers[0].videos.quickUpload({ name: 'NSFW video', nsfw: true })).id
107 userTokenServer1 = await servers[0].users.generateUserAndToken('user1')
109 await waitJobs(servers)
112 describe('Get default playlists', function () {
114 it('Should list video playlist privacies', async function () {
115 const privacies = await commands[0].getPrivacies()
117 expect(Object.keys(privacies)).to.have.length.at.least(3)
118 expect(privacies[3]).to.equal('Private')
121 it('Should list watch later playlist', async function () {
122 const token = servers[0].accessToken
125 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.WATCH_LATER })
127 expect(body.total).to.equal(1)
128 expect(body.data).to.have.lengthOf(1)
130 const playlist = body.data[0]
131 expect(playlist.displayName).to.equal('Watch later')
132 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
133 expect(playlist.type.label).to.equal('Watch later')
137 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.REGULAR })
139 expect(body.total).to.equal(0)
140 expect(body.data).to.have.lengthOf(0)
144 const body = await commands[0].listByAccount({ handle: 'root' })
145 expect(body.total).to.equal(0)
146 expect(body.data).to.have.lengthOf(0)
150 it('Should get private playlist for a classic user', async function () {
151 const token = await servers[0].users.generateUserAndToken('toto')
153 const body = await commands[0].listByAccount({ token, handle: 'toto' })
155 expect(body.total).to.equal(1)
156 expect(body.data).to.have.lengthOf(1)
158 const playlistId = body.data[0].id
159 await commands[0].listVideos({ token, playlistId })
163 describe('Create and federate playlists', function () {
165 it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
168 await commands[0].create({
170 displayName: 'my super playlist',
171 privacy: VideoPlaylistPrivacy.PUBLIC,
172 description: 'my super description',
173 thumbnailfile: 'thumbnail.jpg',
174 videoChannelId: servers[0].store.channel.id
178 await waitJobs(servers)
179 // Processing a playlist by the receiver could be long
182 for (const server of servers) {
183 const body = await server.playlists.list({ start: 0, count: 5 })
184 expect(body.total).to.equal(1)
185 expect(body.data).to.have.lengthOf(1)
187 const playlistFromList = body.data[0]
189 const playlistFromGet = await server.playlists.get({ playlistId: playlistFromList.uuid })
191 for (const playlist of [ playlistFromGet, playlistFromList ]) {
192 expect(playlist.id).to.be.a('number')
193 expect(playlist.uuid).to.be.a('string')
195 expect(playlist.isLocal).to.equal(server.serverNumber === 1)
197 expect(playlist.displayName).to.equal('my super playlist')
198 expect(playlist.description).to.equal('my super description')
199 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
200 expect(playlist.privacy.label).to.equal('Public')
201 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
202 expect(playlist.type.label).to.equal('Regular')
203 expect(playlist.embedPath).to.equal('/video-playlists/embed/' + playlist.uuid)
205 expect(playlist.videosLength).to.equal(0)
207 expect(playlist.ownerAccount.name).to.equal('root')
208 expect(playlist.ownerAccount.displayName).to.equal('root')
209 expect(playlist.videoChannel.name).to.equal('root_channel')
210 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
215 it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
219 const playlist = await servers[1].playlists.create({
221 displayName: 'playlist 2',
222 privacy: VideoPlaylistPrivacy.PUBLIC,
223 videoChannelId: servers[1].store.channel.id
226 playlistServer2Id1 = playlist.id
230 const playlist = await servers[1].playlists.create({
232 displayName: 'playlist 3',
233 privacy: VideoPlaylistPrivacy.PUBLIC,
234 thumbnailfile: 'thumbnail.jpg',
235 videoChannelId: servers[1].store.channel.id
239 playlistServer2Id2 = playlist.id
240 playlistServer2UUID2 = playlist.uuid
243 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
244 await servers[1].playlists.addElement({
246 attributes: { videoId: servers[1].store.videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
248 await servers[1].playlists.addElement({
250 attributes: { videoId: servers[1].store.videos[1].id }
254 await waitJobs(servers)
257 for (const server of [ servers[0], servers[1] ]) {
258 const body = await server.playlists.list({ start: 0, count: 5 })
260 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
261 expect(playlist2).to.not.be.undefined
262 await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
264 const playlist3 = body.data.find(p => p.displayName === 'playlist 3')
265 expect(playlist3).to.not.be.undefined
266 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
269 const body = await servers[2].playlists.list({ start: 0, count: 5 })
270 expect(body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
271 expect(body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
274 it('Should have the playlist on server 3 after a new follow', async function () {
277 // Server 2 and server 3 follow each other
278 await doubleFollow(servers[1], servers[2])
280 const body = await servers[2].playlists.list({ start: 0, count: 5 })
282 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
283 expect(playlist2).to.not.be.undefined
284 await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
286 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
290 describe('List playlists', function () {
292 it('Should correctly list the playlists', async function () {
296 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: 'createdAt' })
297 expect(body.total).to.equal(3)
299 const data = body.data
300 expect(data).to.have.lengthOf(2)
301 expect(data[0].displayName).to.equal('playlist 2')
302 expect(data[1].displayName).to.equal('playlist 3')
306 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: '-createdAt' })
307 expect(body.total).to.equal(3)
309 const data = body.data
310 expect(data).to.have.lengthOf(2)
311 expect(data[0].displayName).to.equal('playlist 2')
312 expect(data[1].displayName).to.equal('my super playlist')
316 it('Should list video channel playlists', async function () {
320 const body = await commands[0].listByChannel({ handle: 'root_channel', start: 0, count: 2, sort: '-createdAt' })
321 expect(body.total).to.equal(1)
323 const data = body.data
324 expect(data).to.have.lengthOf(1)
325 expect(data[0].displayName).to.equal('my super playlist')
329 it('Should list account playlists', async function () {
333 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: '-createdAt' })
334 expect(body.total).to.equal(2)
336 const data = body.data
337 expect(data).to.have.lengthOf(1)
338 expect(data[0].displayName).to.equal('playlist 2')
342 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: 'createdAt' })
343 expect(body.total).to.equal(2)
345 const data = body.data
346 expect(data).to.have.lengthOf(1)
347 expect(data[0].displayName).to.equal('playlist 3')
351 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '3' })
352 expect(body.total).to.equal(1)
354 const data = body.data
355 expect(data).to.have.lengthOf(1)
356 expect(data[0].displayName).to.equal('playlist 3')
360 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '4' })
361 expect(body.total).to.equal(0)
363 const data = body.data
364 expect(data).to.have.lengthOf(0)
369 describe('Playlist rights', function () {
370 let unlistedPlaylist: VideoPlaylistCreateResult
371 let privatePlaylist: VideoPlaylistCreateResult
373 before(async function () {
377 unlistedPlaylist = await servers[1].playlists.create({
379 displayName: 'playlist unlisted',
380 privacy: VideoPlaylistPrivacy.UNLISTED,
381 videoChannelId: servers[1].store.channel.id
387 privatePlaylist = await servers[1].playlists.create({
389 displayName: 'playlist private',
390 privacy: VideoPlaylistPrivacy.PRIVATE
395 await waitJobs(servers)
399 it('Should not list unlisted or private playlists', async function () {
400 for (const server of servers) {
402 await server.playlists.listByAccount({ handle: 'root@localhost:' + servers[1].port, sort: '-createdAt' }),
403 await server.playlists.list({ start: 0, count: 2, sort: '-createdAt' })
406 expect(results[0].total).to.equal(2)
407 expect(results[1].total).to.equal(3)
409 for (const body of results) {
410 const data = body.data
411 expect(data).to.have.lengthOf(2)
412 expect(data[0].displayName).to.equal('playlist 3')
413 expect(data[1].displayName).to.equal('playlist 2')
418 it('Should not get unlisted playlist using only the id', async function () {
419 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 })
422 it('Should get unlisted plyaylist using uuid or shortUUID', async function () {
423 await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid })
424 await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID })
427 it('Should not get private playlist without token', async function () {
428 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
429 await servers[1].playlists.get({ playlistId: id, expectedStatus: 401 })
433 it('Should get private playlist with a token', async function () {
434 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
435 await servers[1].playlists.get({ token: servers[1].accessToken, playlistId: id })
440 describe('Update playlists', function () {
442 it('Should update a playlist', async function () {
445 await servers[1].playlists.update({
447 displayName: 'playlist 3 updated',
448 description: 'description updated',
449 privacy: VideoPlaylistPrivacy.UNLISTED,
450 thumbnailfile: 'thumbnail.jpg',
451 videoChannelId: servers[1].store.channel.id
453 playlistId: playlistServer2Id2
456 await waitJobs(servers)
458 for (const server of servers) {
459 const playlist = await server.playlists.get({ playlistId: playlistServer2UUID2 })
461 expect(playlist.displayName).to.equal('playlist 3 updated')
462 expect(playlist.description).to.equal('description updated')
464 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
465 expect(playlist.privacy.label).to.equal('Unlisted')
467 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
468 expect(playlist.type.label).to.equal('Regular')
470 expect(playlist.videosLength).to.equal(2)
472 expect(playlist.ownerAccount.name).to.equal('root')
473 expect(playlist.ownerAccount.displayName).to.equal('root')
474 expect(playlist.videoChannel.name).to.equal('root_channel')
475 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
480 describe('Element timestamps', function () {
482 it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
485 const addVideo = (attributes: any) => {
486 return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
489 const playlist = await commands[0].create({
491 displayName: 'playlist 4',
492 privacy: VideoPlaylistPrivacy.PUBLIC,
493 videoChannelId: servers[0].store.channel.id
497 playlistServer1Id = playlist.id
498 playlistServer1UUID = playlist.uuid
500 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
501 await addVideo({ videoId: servers[2].store.videos[1].uuid, startTimestamp: 35 })
502 await addVideo({ videoId: servers[2].store.videos[2].uuid })
504 const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 })
505 playlistElementServer1Video4 = element.id
509 const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
510 playlistElementServer1Video5 = element.id
514 const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
515 playlistElementNSFW = element.id
517 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
518 await addVideo({ videoId: nsfwVideoServer1 })
521 await waitJobs(servers)
524 it('Should correctly list playlist videos', async function () {
527 for (const server of servers) {
529 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
531 expect(body.total).to.equal(8)
533 const videoElements = body.data
534 expect(videoElements).to.have.lengthOf(8)
536 expect(videoElements[0].video.name).to.equal('video 0 server 1')
537 expect(videoElements[0].position).to.equal(1)
538 expect(videoElements[0].startTimestamp).to.equal(15)
539 expect(videoElements[0].stopTimestamp).to.equal(28)
541 expect(videoElements[1].video.name).to.equal('video 1 server 3')
542 expect(videoElements[1].position).to.equal(2)
543 expect(videoElements[1].startTimestamp).to.equal(35)
544 expect(videoElements[1].stopTimestamp).to.be.null
546 expect(videoElements[2].video.name).to.equal('video 2 server 3')
547 expect(videoElements[2].position).to.equal(3)
548 expect(videoElements[2].startTimestamp).to.be.null
549 expect(videoElements[2].stopTimestamp).to.be.null
551 expect(videoElements[3].video.name).to.equal('video 3 server 1')
552 expect(videoElements[3].position).to.equal(4)
553 expect(videoElements[3].startTimestamp).to.be.null
554 expect(videoElements[3].stopTimestamp).to.equal(35)
556 expect(videoElements[4].video.name).to.equal('video 4 server 1')
557 expect(videoElements[4].position).to.equal(5)
558 expect(videoElements[4].startTimestamp).to.equal(45)
559 expect(videoElements[4].stopTimestamp).to.equal(60)
561 expect(videoElements[5].video.name).to.equal('NSFW video')
562 expect(videoElements[5].position).to.equal(6)
563 expect(videoElements[5].startTimestamp).to.equal(5)
564 expect(videoElements[5].stopTimestamp).to.be.null
566 expect(videoElements[6].video.name).to.equal('NSFW video')
567 expect(videoElements[6].position).to.equal(7)
568 expect(videoElements[6].startTimestamp).to.equal(4)
569 expect(videoElements[6].stopTimestamp).to.be.null
571 expect(videoElements[7].video.name).to.equal('NSFW video')
572 expect(videoElements[7].position).to.equal(8)
573 expect(videoElements[7].startTimestamp).to.be.null
574 expect(videoElements[7].stopTimestamp).to.be.null
578 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 })
579 expect(body.data).to.have.lengthOf(2)
585 describe('Element type', function () {
586 let groupUser1: PeerTubeServer[]
587 let groupWithoutToken1: PeerTubeServer[]
588 let group1: PeerTubeServer[]
589 let group2: PeerTubeServer[]
595 before(async function () {
598 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userTokenServer1 }) ]
599 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
600 group1 = [ servers[0] ]
601 group2 = [ servers[1], servers[2] ]
603 const playlist = await commands[0].create({
604 token: userTokenServer1,
606 displayName: 'playlist 56',
607 privacy: VideoPlaylistPrivacy.PUBLIC,
608 videoChannelId: servers[0].store.channel.id
612 const playlistServer1Id2 = playlist.id
613 playlistServer1UUID2 = playlist.uuid
615 const addVideo = (attributes: any) => {
616 return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes })
619 video1 = (await servers[0].videos.quickUpload({ name: 'video 89', token: userTokenServer1 })).uuid
620 video2 = (await servers[1].videos.quickUpload({ name: 'video 90' })).uuid
621 video3 = (await servers[0].videos.quickUpload({ name: 'video 91', nsfw: true })).uuid
623 await waitJobs(servers)
625 await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
626 await addVideo({ videoId: video2, startTimestamp: 35 })
627 await addVideo({ videoId: video3 })
629 await waitJobs(servers)
632 it('Should update the element type if the video is private', async function () {
635 const name = 'video 89'
639 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } })
640 await waitJobs(servers)
642 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
643 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
644 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
645 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
649 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
650 await waitJobs(servers)
652 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
653 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
654 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
655 // We deleted the video, so even if we recreated it, the old entry is still deleted
656 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
660 it('Should update the element type if the video is blacklisted', async function () {
663 const name = 'video 89'
667 await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true })
668 await waitJobs(servers)
670 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
671 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
672 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
673 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
677 await servers[0].blacklist.remove({ videoId: video1 })
678 await waitJobs(servers)
680 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
681 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
682 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
683 // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
684 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
688 it('Should update the element type if the account or server of the video is blocked', async function () {
691 const command = servers[0].blocklist
693 const name = 'video 90'
697 await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
698 await waitJobs(servers)
700 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
701 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
703 await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
704 await waitJobs(servers)
706 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
710 await command.addToMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
711 await waitJobs(servers)
713 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
714 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
716 await command.removeFromMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
717 await waitJobs(servers)
719 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
723 await command.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port })
724 await waitJobs(servers)
726 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
727 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
729 await command.removeFromServerBlocklist({ account: 'root@localhost:' + servers[1].port })
730 await waitJobs(servers)
732 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
736 await command.addToServerBlocklist({ server: 'localhost:' + servers[1].port })
737 await waitJobs(servers)
739 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
740 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
742 await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port })
743 await waitJobs(servers)
745 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
749 it('Should hide the video if it is NSFW', async function () {
750 const body = await commands[0].listVideos({ token: userTokenServer1, playlistId: playlistServer1UUID2, query: { nsfw: 'false' } })
751 expect(body.total).to.equal(3)
753 const elements = body.data
754 const element = elements.find(e => e.position === 3)
756 expect(element).to.exist
757 expect(element.video).to.be.null
758 expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
763 describe('Managing playlist elements', function () {
765 it('Should reorder the playlist', async function () {
769 await commands[0].reorderElements({
770 playlistId: playlistServer1Id,
773 insertAfterPosition: 3
777 await waitJobs(servers)
779 for (const server of servers) {
780 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
781 const names = body.data.map(v => v.video.name)
783 expect(names).to.deep.equal([
797 await commands[0].reorderElements({
798 playlistId: playlistServer1Id,
802 insertAfterPosition: 4
806 await waitJobs(servers)
808 for (const server of servers) {
809 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
810 const names = body.data.map(v => v.video.name)
812 expect(names).to.deep.equal([
826 await commands[0].reorderElements({
827 playlistId: playlistServer1Id,
830 insertAfterPosition: 3
834 await waitJobs(servers)
836 for (const server of servers) {
837 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
838 const names = elements.map(v => v.video.name)
840 expect(names).to.deep.equal([
851 for (let i = 1; i <= elements.length; i++) {
852 expect(elements[i - 1].position).to.equal(i)
858 it('Should update startTimestamp/endTimestamp of some elements', async function () {
861 await commands[0].updateElement({
862 playlistId: playlistServer1Id,
863 elementId: playlistElementServer1Video4,
869 await commands[0].updateElement({
870 playlistId: playlistServer1Id,
871 elementId: playlistElementServer1Video5,
877 await waitJobs(servers)
879 for (const server of servers) {
880 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
882 expect(elements[0].video.name).to.equal('video 3 server 1')
883 expect(elements[0].position).to.equal(1)
884 expect(elements[0].startTimestamp).to.equal(1)
885 expect(elements[0].stopTimestamp).to.equal(35)
887 expect(elements[5].video.name).to.equal('video 4 server 1')
888 expect(elements[5].position).to.equal(6)
889 expect(elements[5].startTimestamp).to.equal(45)
890 expect(elements[5].stopTimestamp).to.be.null
894 it('Should check videos existence in my playlist', async function () {
896 servers[0].store.videos[0].id,
898 servers[0].store.videos[3].id,
900 servers[0].store.videos[4].id
902 const obj = await commands[0].videosExist({ videoIds })
905 const elem = obj[servers[0].store.videos[0].id]
906 expect(elem).to.have.lengthOf(1)
907 expect(elem[0].playlistElementId).to.exist
908 expect(elem[0].playlistId).to.equal(playlistServer1Id)
909 expect(elem[0].startTimestamp).to.equal(15)
910 expect(elem[0].stopTimestamp).to.equal(28)
914 const elem = obj[servers[0].store.videos[3].id]
915 expect(elem).to.have.lengthOf(1)
916 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
917 expect(elem[0].playlistId).to.equal(playlistServer1Id)
918 expect(elem[0].startTimestamp).to.equal(1)
919 expect(elem[0].stopTimestamp).to.equal(35)
923 const elem = obj[servers[0].store.videos[4].id]
924 expect(elem).to.have.lengthOf(1)
925 expect(elem[0].playlistId).to.equal(playlistServer1Id)
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.internalServerNumber)
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)