1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import { HttpStatusCode } from '@shared/models'
7 checkPlaylistFilesWereRemoved,
10 createMultipleServers,
13 setAccessTokensToServers,
14 setDefaultVideoChannel,
18 } from '@shared/extra-utils'
21 VideoPlaylistCreateResult,
22 VideoPlaylistElementType,
26 } from '@shared/models'
28 const expect = chai.expect
30 async function checkPlaylistElementType (
31 servers: PeerTubeServer[],
33 type: VideoPlaylistElementType,
38 for (const server of servers) {
39 const body = await server.playlists.listVideos({ token: server.accessToken, playlistId, start: 0, count: 10 })
40 expect(body.total).to.equal(total)
42 const videoElement = body.data.find(e => e.position === position)
43 expect(videoElement.type).to.equal(type, 'On server ' + server.url)
45 if (type === VideoPlaylistElementType.REGULAR) {
46 expect(videoElement.video).to.not.be.null
47 expect(videoElement.video.name).to.equal(name)
49 expect(videoElement.video).to.be.null
54 describe('Test video playlists', function () {
55 let servers: PeerTubeServer[] = []
57 let playlistServer2Id1: number
58 let playlistServer2Id2: number
59 let playlistServer2UUID2: string
61 let playlistServer1Id: number
62 let playlistServer1UUID: string
63 let playlistServer1UUID2: string
65 let playlistElementServer1Video4: number
66 let playlistElementServer1Video5: number
67 let playlistElementNSFW: number
69 let nsfwVideoServer1: number
71 let userTokenServer1: string
73 let commands: PlaylistsCommand[]
75 before(async function () {
78 servers = await createMultipleServers(3, { transcoding: { enabled: false } })
80 // Get the access tokens
81 await setAccessTokensToServers(servers)
82 await setDefaultVideoChannel(servers)
84 // Server 1 and server 2 follow each other
85 await doubleFollow(servers[0], servers[1])
86 // Server 1 and server 3 follow each other
87 await doubleFollow(servers[0], servers[2])
89 commands = servers.map(s => s.playlists)
92 servers[0].store.videos = []
93 servers[1].store.videos = []
94 servers[2].store.videos = []
96 for (const server of servers) {
97 for (let i = 0; i < 7; i++) {
98 const name = `video ${i} server ${server.serverNumber}`
99 const video = await server.videos.upload({ attributes: { name, nsfw: false } })
101 server.store.videos.push(video)
106 nsfwVideoServer1 = (await servers[0].videos.quickUpload({ name: 'NSFW video', nsfw: true })).id
108 userTokenServer1 = await servers[0].users.generateUserAndToken('user1')
110 await waitJobs(servers)
113 describe('Get default playlists', function () {
115 it('Should list video playlist privacies', async function () {
116 const privacies = await commands[0].getPrivacies()
118 expect(Object.keys(privacies)).to.have.length.at.least(3)
119 expect(privacies[3]).to.equal('Private')
122 it('Should list watch later playlist', async function () {
123 const token = servers[0].accessToken
126 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.WATCH_LATER })
128 expect(body.total).to.equal(1)
129 expect(body.data).to.have.lengthOf(1)
131 const playlist = body.data[0]
132 expect(playlist.displayName).to.equal('Watch later')
133 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
134 expect(playlist.type.label).to.equal('Watch later')
138 const body = await commands[0].listByAccount({ token, handle: 'root', playlistType: VideoPlaylistType.REGULAR })
140 expect(body.total).to.equal(0)
141 expect(body.data).to.have.lengthOf(0)
145 const body = await commands[0].listByAccount({ handle: 'root' })
146 expect(body.total).to.equal(0)
147 expect(body.data).to.have.lengthOf(0)
151 it('Should get private playlist for a classic user', async function () {
152 const token = await servers[0].users.generateUserAndToken('toto')
154 const body = await commands[0].listByAccount({ token, handle: 'toto' })
156 expect(body.total).to.equal(1)
157 expect(body.data).to.have.lengthOf(1)
159 const playlistId = body.data[0].id
160 await commands[0].listVideos({ token, playlistId })
164 describe('Create and federate playlists', function () {
166 it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
169 await commands[0].create({
171 displayName: 'my super playlist',
172 privacy: VideoPlaylistPrivacy.PUBLIC,
173 description: 'my super description',
174 thumbnailfile: 'thumbnail.jpg',
175 videoChannelId: servers[0].store.channel.id
179 await waitJobs(servers)
180 // Processing a playlist by the receiver could be long
183 for (const server of servers) {
184 const body = await server.playlists.list({ start: 0, count: 5 })
185 expect(body.total).to.equal(1)
186 expect(body.data).to.have.lengthOf(1)
188 const playlistFromList = body.data[0]
190 const playlistFromGet = await server.playlists.get({ playlistId: playlistFromList.uuid })
192 for (const playlist of [ playlistFromGet, playlistFromList ]) {
193 expect(playlist.id).to.be.a('number')
194 expect(playlist.uuid).to.be.a('string')
196 expect(playlist.isLocal).to.equal(server.serverNumber === 1)
198 expect(playlist.displayName).to.equal('my super playlist')
199 expect(playlist.description).to.equal('my super description')
200 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
201 expect(playlist.privacy.label).to.equal('Public')
202 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
203 expect(playlist.type.label).to.equal('Regular')
204 expect(playlist.embedPath).to.equal('/video-playlists/embed/' + playlist.uuid)
206 expect(playlist.videosLength).to.equal(0)
208 expect(playlist.ownerAccount.name).to.equal('root')
209 expect(playlist.ownerAccount.displayName).to.equal('root')
210 expect(playlist.videoChannel.name).to.equal('root_channel')
211 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
216 it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
220 const playlist = await servers[1].playlists.create({
222 displayName: 'playlist 2',
223 privacy: VideoPlaylistPrivacy.PUBLIC,
224 videoChannelId: servers[1].store.channel.id
227 playlistServer2Id1 = playlist.id
231 const playlist = await servers[1].playlists.create({
233 displayName: 'playlist 3',
234 privacy: VideoPlaylistPrivacy.PUBLIC,
235 thumbnailfile: 'thumbnail.jpg',
236 videoChannelId: servers[1].store.channel.id
240 playlistServer2Id2 = playlist.id
241 playlistServer2UUID2 = playlist.uuid
244 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
245 await servers[1].playlists.addElement({
247 attributes: { videoId: servers[1].store.videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
249 await servers[1].playlists.addElement({
251 attributes: { videoId: servers[1].store.videos[1].id }
255 await waitJobs(servers)
258 for (const server of [ servers[0], servers[1] ]) {
259 const body = await server.playlists.list({ start: 0, count: 5 })
261 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
262 expect(playlist2).to.not.be.undefined
263 await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
265 const playlist3 = body.data.find(p => p.displayName === 'playlist 3')
266 expect(playlist3).to.not.be.undefined
267 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
270 const body = await servers[2].playlists.list({ start: 0, count: 5 })
271 expect(body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
272 expect(body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
275 it('Should have the playlist on server 3 after a new follow', async function () {
278 // Server 2 and server 3 follow each other
279 await doubleFollow(servers[1], servers[2])
281 const body = await servers[2].playlists.list({ start: 0, count: 5 })
283 const playlist2 = body.data.find(p => p.displayName === 'playlist 2')
284 expect(playlist2).to.not.be.undefined
285 await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
287 expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
291 describe('List playlists', function () {
293 it('Should correctly list the playlists', async function () {
297 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: 'createdAt' })
298 expect(body.total).to.equal(3)
300 const data = body.data
301 expect(data).to.have.lengthOf(2)
302 expect(data[0].displayName).to.equal('playlist 2')
303 expect(data[1].displayName).to.equal('playlist 3')
307 const body = await servers[2].playlists.list({ start: 1, count: 2, sort: '-createdAt' })
308 expect(body.total).to.equal(3)
310 const data = body.data
311 expect(data).to.have.lengthOf(2)
312 expect(data[0].displayName).to.equal('playlist 2')
313 expect(data[1].displayName).to.equal('my super playlist')
317 it('Should list video channel playlists', async function () {
321 const body = await commands[0].listByChannel({ handle: 'root_channel', start: 0, count: 2, sort: '-createdAt' })
322 expect(body.total).to.equal(1)
324 const data = body.data
325 expect(data).to.have.lengthOf(1)
326 expect(data[0].displayName).to.equal('my super playlist')
330 it('Should list account playlists', async function () {
334 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: '-createdAt' })
335 expect(body.total).to.equal(2)
337 const data = body.data
338 expect(data).to.have.lengthOf(1)
339 expect(data[0].displayName).to.equal('playlist 2')
343 const body = await servers[1].playlists.listByAccount({ handle: 'root', start: 1, count: 2, sort: 'createdAt' })
344 expect(body.total).to.equal(2)
346 const data = body.data
347 expect(data).to.have.lengthOf(1)
348 expect(data[0].displayName).to.equal('playlist 3')
352 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '3' })
353 expect(body.total).to.equal(1)
355 const data = body.data
356 expect(data).to.have.lengthOf(1)
357 expect(data[0].displayName).to.equal('playlist 3')
361 const body = await servers[1].playlists.listByAccount({ handle: 'root', sort: 'createdAt', search: '4' })
362 expect(body.total).to.equal(0)
364 const data = body.data
365 expect(data).to.have.lengthOf(0)
370 describe('Playlist rights', function () {
371 let unlistedPlaylist: VideoPlaylistCreateResult
372 let privatePlaylist: VideoPlaylistCreateResult
374 before(async function () {
378 unlistedPlaylist = await servers[1].playlists.create({
380 displayName: 'playlist unlisted',
381 privacy: VideoPlaylistPrivacy.UNLISTED,
382 videoChannelId: servers[1].store.channel.id
388 privatePlaylist = await servers[1].playlists.create({
390 displayName: 'playlist private',
391 privacy: VideoPlaylistPrivacy.PRIVATE
396 await waitJobs(servers)
400 it('Should not list unlisted or private playlists', async function () {
401 for (const server of servers) {
403 await server.playlists.listByAccount({ handle: 'root@localhost:' + servers[1].port, sort: '-createdAt' }),
404 await server.playlists.list({ start: 0, count: 2, sort: '-createdAt' })
407 expect(results[0].total).to.equal(2)
408 expect(results[1].total).to.equal(3)
410 for (const body of results) {
411 const data = body.data
412 expect(data).to.have.lengthOf(2)
413 expect(data[0].displayName).to.equal('playlist 3')
414 expect(data[1].displayName).to.equal('playlist 2')
419 it('Should not get unlisted playlist using only the id', async function () {
420 await servers[1].playlists.get({ playlistId: unlistedPlaylist.id, expectedStatus: 404 })
423 it('Should get unlisted plyaylist using uuid or shortUUID', async function () {
424 await servers[1].playlists.get({ playlistId: unlistedPlaylist.uuid })
425 await servers[1].playlists.get({ playlistId: unlistedPlaylist.shortUUID })
428 it('Should not get private playlist without token', async function () {
429 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
430 await servers[1].playlists.get({ playlistId: id, expectedStatus: 401 })
434 it('Should get private playlist with a token', async function () {
435 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
436 await servers[1].playlists.get({ token: servers[1].accessToken, playlistId: id })
441 describe('Update playlists', function () {
443 it('Should update a playlist', async function () {
446 await servers[1].playlists.update({
448 displayName: 'playlist 3 updated',
449 description: 'description updated',
450 privacy: VideoPlaylistPrivacy.UNLISTED,
451 thumbnailfile: 'thumbnail.jpg',
452 videoChannelId: servers[1].store.channel.id
454 playlistId: playlistServer2Id2
457 await waitJobs(servers)
459 for (const server of servers) {
460 const playlist = await server.playlists.get({ playlistId: playlistServer2UUID2 })
462 expect(playlist.displayName).to.equal('playlist 3 updated')
463 expect(playlist.description).to.equal('description updated')
465 expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
466 expect(playlist.privacy.label).to.equal('Unlisted')
468 expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
469 expect(playlist.type.label).to.equal('Regular')
471 expect(playlist.videosLength).to.equal(2)
473 expect(playlist.ownerAccount.name).to.equal('root')
474 expect(playlist.ownerAccount.displayName).to.equal('root')
475 expect(playlist.videoChannel.name).to.equal('root_channel')
476 expect(playlist.videoChannel.displayName).to.equal('Main root channel')
481 describe('Element timestamps', function () {
483 it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
486 const addVideo = (attributes: any) => {
487 return commands[0].addElement({ playlistId: playlistServer1Id, attributes })
490 const playlist = await commands[0].create({
492 displayName: 'playlist 4',
493 privacy: VideoPlaylistPrivacy.PUBLIC,
494 videoChannelId: servers[0].store.channel.id
498 playlistServer1Id = playlist.id
499 playlistServer1UUID = playlist.uuid
501 await addVideo({ videoId: servers[0].store.videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
502 await addVideo({ videoId: servers[2].store.videos[1].uuid, startTimestamp: 35 })
503 await addVideo({ videoId: servers[2].store.videos[2].uuid })
505 const element = await addVideo({ videoId: servers[0].store.videos[3].uuid, stopTimestamp: 35 })
506 playlistElementServer1Video4 = element.id
510 const element = await addVideo({ videoId: servers[0].store.videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
511 playlistElementServer1Video5 = element.id
515 const element = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
516 playlistElementNSFW = element.id
518 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
519 await addVideo({ videoId: nsfwVideoServer1 })
522 await waitJobs(servers)
525 it('Should correctly list playlist videos', async function () {
528 for (const server of servers) {
530 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
532 expect(body.total).to.equal(8)
534 const videoElements = body.data
535 expect(videoElements).to.have.lengthOf(8)
537 expect(videoElements[0].video.name).to.equal('video 0 server 1')
538 expect(videoElements[0].position).to.equal(1)
539 expect(videoElements[0].startTimestamp).to.equal(15)
540 expect(videoElements[0].stopTimestamp).to.equal(28)
542 expect(videoElements[1].video.name).to.equal('video 1 server 3')
543 expect(videoElements[1].position).to.equal(2)
544 expect(videoElements[1].startTimestamp).to.equal(35)
545 expect(videoElements[1].stopTimestamp).to.be.null
547 expect(videoElements[2].video.name).to.equal('video 2 server 3')
548 expect(videoElements[2].position).to.equal(3)
549 expect(videoElements[2].startTimestamp).to.be.null
550 expect(videoElements[2].stopTimestamp).to.be.null
552 expect(videoElements[3].video.name).to.equal('video 3 server 1')
553 expect(videoElements[3].position).to.equal(4)
554 expect(videoElements[3].startTimestamp).to.be.null
555 expect(videoElements[3].stopTimestamp).to.equal(35)
557 expect(videoElements[4].video.name).to.equal('video 4 server 1')
558 expect(videoElements[4].position).to.equal(5)
559 expect(videoElements[4].startTimestamp).to.equal(45)
560 expect(videoElements[4].stopTimestamp).to.equal(60)
562 expect(videoElements[5].video.name).to.equal('NSFW video')
563 expect(videoElements[5].position).to.equal(6)
564 expect(videoElements[5].startTimestamp).to.equal(5)
565 expect(videoElements[5].stopTimestamp).to.be.null
567 expect(videoElements[6].video.name).to.equal('NSFW video')
568 expect(videoElements[6].position).to.equal(7)
569 expect(videoElements[6].startTimestamp).to.equal(4)
570 expect(videoElements[6].stopTimestamp).to.be.null
572 expect(videoElements[7].video.name).to.equal('NSFW video')
573 expect(videoElements[7].position).to.equal(8)
574 expect(videoElements[7].startTimestamp).to.be.null
575 expect(videoElements[7].stopTimestamp).to.be.null
579 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 2 })
580 expect(body.data).to.have.lengthOf(2)
586 describe('Element type', function () {
587 let groupUser1: PeerTubeServer[]
588 let groupWithoutToken1: PeerTubeServer[]
589 let group1: PeerTubeServer[]
590 let group2: PeerTubeServer[]
596 before(async function () {
599 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userTokenServer1 }) ]
600 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
601 group1 = [ servers[0] ]
602 group2 = [ servers[1], servers[2] ]
604 const playlist = await commands[0].create({
605 token: userTokenServer1,
607 displayName: 'playlist 56',
608 privacy: VideoPlaylistPrivacy.PUBLIC,
609 videoChannelId: servers[0].store.channel.id
613 const playlistServer1Id2 = playlist.id
614 playlistServer1UUID2 = playlist.uuid
616 const addVideo = (attributes: any) => {
617 return commands[0].addElement({ token: userTokenServer1, playlistId: playlistServer1Id2, attributes })
620 video1 = (await servers[0].videos.quickUpload({ name: 'video 89', token: userTokenServer1 })).uuid
621 video2 = (await servers[1].videos.quickUpload({ name: 'video 90' })).uuid
622 video3 = (await servers[0].videos.quickUpload({ name: 'video 91', nsfw: true })).uuid
624 await waitJobs(servers)
626 await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 })
627 await addVideo({ videoId: video2, startTimestamp: 35 })
628 await addVideo({ videoId: video3 })
630 await waitJobs(servers)
633 it('Should update the element type if the video is private', async function () {
636 const name = 'video 89'
640 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PRIVATE } })
641 await waitJobs(servers)
643 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
644 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
645 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3)
646 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
650 await servers[0].videos.update({ id: video1, attributes: { privacy: VideoPrivacy.PUBLIC } })
651 await waitJobs(servers)
653 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
654 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
655 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
656 // We deleted the video, so even if we recreated it, the old entry is still deleted
657 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
661 it('Should update the element type if the video is blacklisted', async function () {
664 const name = 'video 89'
668 await servers[0].blacklist.add({ videoId: video1, reason: 'reason', unfederate: true })
669 await waitJobs(servers)
671 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
672 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
673 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
674 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
678 await servers[0].blacklist.remove({ videoId: video1 })
679 await waitJobs(servers)
681 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
682 await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
683 await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
684 // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted
685 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3)
689 it('Should update the element type if the account or server of the video is blocked', async function () {
692 const command = servers[0].blocklist
694 const name = 'video 90'
698 await command.addToMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
699 await waitJobs(servers)
701 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
702 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
704 await command.removeFromMyBlocklist({ token: userTokenServer1, account: 'root@localhost:' + servers[1].port })
705 await waitJobs(servers)
707 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
711 await command.addToMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
712 await waitJobs(servers)
714 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
715 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
717 await command.removeFromMyBlocklist({ token: userTokenServer1, server: 'localhost:' + servers[1].port })
718 await waitJobs(servers)
720 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
724 await command.addToServerBlocklist({ account: 'root@localhost:' + servers[1].port })
725 await waitJobs(servers)
727 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
728 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
730 await command.removeFromServerBlocklist({ account: 'root@localhost:' + servers[1].port })
731 await waitJobs(servers)
733 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
737 await command.addToServerBlocklist({ server: 'localhost:' + servers[1].port })
738 await waitJobs(servers)
740 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
741 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
743 await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port })
744 await waitJobs(servers)
746 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
750 it('Should hide the video if it is NSFW', async function () {
751 const body = await commands[0].listVideos({ token: userTokenServer1, playlistId: playlistServer1UUID2, query: { nsfw: 'false' } })
752 expect(body.total).to.equal(3)
754 const elements = body.data
755 const element = elements.find(e => e.position === 3)
757 expect(element).to.exist
758 expect(element.video).to.be.null
759 expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE)
764 describe('Managing playlist elements', function () {
766 it('Should reorder the playlist', async function () {
770 await commands[0].reorderElements({
771 playlistId: playlistServer1Id,
774 insertAfterPosition: 3
778 await waitJobs(servers)
780 for (const server of servers) {
781 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
782 const names = body.data.map(v => v.video.name)
784 expect(names).to.deep.equal([
798 await commands[0].reorderElements({
799 playlistId: playlistServer1Id,
803 insertAfterPosition: 4
807 await waitJobs(servers)
809 for (const server of servers) {
810 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
811 const names = body.data.map(v => v.video.name)
813 expect(names).to.deep.equal([
827 await commands[0].reorderElements({
828 playlistId: playlistServer1Id,
831 insertAfterPosition: 3
835 await waitJobs(servers)
837 for (const server of servers) {
838 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
839 const names = elements.map(v => v.video.name)
841 expect(names).to.deep.equal([
852 for (let i = 1; i <= elements.length; i++) {
853 expect(elements[i - 1].position).to.equal(i)
859 it('Should update startTimestamp/endTimestamp of some elements', async function () {
862 await commands[0].updateElement({
863 playlistId: playlistServer1Id,
864 elementId: playlistElementServer1Video4,
870 await commands[0].updateElement({
871 playlistId: playlistServer1Id,
872 elementId: playlistElementServer1Video5,
878 await waitJobs(servers)
880 for (const server of servers) {
881 const { data: elements } = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
883 expect(elements[0].video.name).to.equal('video 3 server 1')
884 expect(elements[0].position).to.equal(1)
885 expect(elements[0].startTimestamp).to.equal(1)
886 expect(elements[0].stopTimestamp).to.equal(35)
888 expect(elements[5].video.name).to.equal('video 4 server 1')
889 expect(elements[5].position).to.equal(6)
890 expect(elements[5].startTimestamp).to.equal(45)
891 expect(elements[5].stopTimestamp).to.be.null
895 it('Should check videos existence in my playlist', async function () {
897 servers[0].store.videos[0].id,
899 servers[0].store.videos[3].id,
901 servers[0].store.videos[4].id
903 const obj = await commands[0].videosExist({ videoIds })
906 const elem = obj[servers[0].store.videos[0].id]
907 expect(elem).to.have.lengthOf(1)
908 expect(elem[0].playlistElementId).to.exist
909 expect(elem[0].playlistId).to.equal(playlistServer1Id)
910 expect(elem[0].startTimestamp).to.equal(15)
911 expect(elem[0].stopTimestamp).to.equal(28)
915 const elem = obj[servers[0].store.videos[3].id]
916 expect(elem).to.have.lengthOf(1)
917 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
918 expect(elem[0].playlistId).to.equal(playlistServer1Id)
919 expect(elem[0].startTimestamp).to.equal(1)
920 expect(elem[0].stopTimestamp).to.equal(35)
924 const elem = obj[servers[0].store.videos[4].id]
925 expect(elem).to.have.lengthOf(1)
926 expect(elem[0].playlistId).to.equal(playlistServer1Id)
927 expect(elem[0].startTimestamp).to.equal(45)
928 expect(elem[0].stopTimestamp).to.equal(null)
931 expect(obj[42000]).to.have.lengthOf(0)
932 expect(obj[43000]).to.have.lengthOf(0)
935 it('Should automatically update updatedAt field of playlists', async function () {
936 const server = servers[1]
937 const videoId = servers[1].store.videos[5].id
939 async function getPlaylistNames () {
940 const { data } = await server.playlists.listByAccount({ token: server.accessToken, handle: 'root', sort: '-updatedAt' })
942 return data.map(p => p.displayName)
945 const attributes = { videoId }
946 const element1 = await server.playlists.addElement({ playlistId: playlistServer2Id1, attributes })
947 const element2 = await server.playlists.addElement({ playlistId: playlistServer2Id2, attributes })
949 const names1 = await getPlaylistNames()
950 expect(names1[0]).to.equal('playlist 3 updated')
951 expect(names1[1]).to.equal('playlist 2')
953 await server.playlists.removeElement({ playlistId: playlistServer2Id1, elementId: element1.id })
955 const names2 = await getPlaylistNames()
956 expect(names2[0]).to.equal('playlist 2')
957 expect(names2[1]).to.equal('playlist 3 updated')
959 await server.playlists.removeElement({ playlistId: playlistServer2Id2, elementId: element2.id })
961 const names3 = await getPlaylistNames()
962 expect(names3[0]).to.equal('playlist 3 updated')
963 expect(names3[1]).to.equal('playlist 2')
966 it('Should delete some elements', async function () {
969 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementServer1Video4 })
970 await commands[0].removeElement({ playlistId: playlistServer1Id, elementId: playlistElementNSFW })
972 await waitJobs(servers)
974 for (const server of servers) {
975 const body = await server.playlists.listVideos({ playlistId: playlistServer1UUID, start: 0, count: 10 })
976 expect(body.total).to.equal(6)
978 const elements = body.data
979 expect(elements).to.have.lengthOf(6)
981 expect(elements[0].video.name).to.equal('video 0 server 1')
982 expect(elements[0].position).to.equal(1)
984 expect(elements[1].video.name).to.equal('video 2 server 3')
985 expect(elements[1].position).to.equal(2)
987 expect(elements[2].video.name).to.equal('video 1 server 3')
988 expect(elements[2].position).to.equal(3)
990 expect(elements[3].video.name).to.equal('video 4 server 1')
991 expect(elements[3].position).to.equal(4)
993 expect(elements[4].video.name).to.equal('NSFW video')
994 expect(elements[4].position).to.equal(5)
996 expect(elements[5].video.name).to.equal('NSFW video')
997 expect(elements[5].position).to.equal(6)
1001 it('Should be able to create a public playlist, and set it to private', async function () {
1004 const videoPlaylistIds = await commands[0].create({
1006 displayName: 'my super public playlist',
1007 privacy: VideoPlaylistPrivacy.PUBLIC,
1008 videoChannelId: servers[0].store.channel.id
1012 await waitJobs(servers)
1014 for (const server of servers) {
1015 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1018 const attributes = { privacy: VideoPlaylistPrivacy.PRIVATE }
1019 await commands[0].update({ playlistId: videoPlaylistIds.id, attributes })
1021 await waitJobs(servers)
1023 for (const server of [ servers[1], servers[2] ]) {
1024 await server.playlists.get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1027 await commands[0].get({ playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
1028 await commands[0].get({ token: servers[0].accessToken, playlistId: videoPlaylistIds.uuid, expectedStatus: HttpStatusCode.OK_200 })
1032 describe('Playlist deletion', function () {
1034 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1037 await commands[0].delete({ playlistId: playlistServer1Id })
1039 await waitJobs(servers)
1041 for (const server of servers) {
1042 await server.playlists.get({ playlistId: playlistServer1UUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1046 it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
1049 for (const server of servers) {
1050 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
1054 it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
1057 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'my super playlist')
1060 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1061 expect(body.total).to.equal(3)
1063 expect(finder(body.data)).to.not.be.undefined
1066 await servers[2].follows.unfollow({ target: servers[0] })
1069 const body = await servers[2].playlists.list({ start: 0, count: 5 })
1070 expect(body.total).to.equal(1)
1072 expect(finder(body.data)).to.be.undefined
1076 it('Should delete a channel and put the associated playlist in private mode', async function () {
1079 const channel = await servers[0].channels.create({ attributes: { name: 'super_channel', displayName: 'super channel' } })
1081 const playlistCreated = await commands[0].create({
1083 displayName: 'channel playlist',
1084 privacy: VideoPlaylistPrivacy.PUBLIC,
1085 videoChannelId: channel.id
1089 await waitJobs(servers)
1091 await servers[0].channels.delete({ channelName: 'super_channel' })
1093 await waitJobs(servers)
1095 const body = await commands[0].get({ token: servers[0].accessToken, playlistId: playlistCreated.uuid })
1096 expect(body.displayName).to.equal('channel playlist')
1097 expect(body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1099 await servers[1].playlists.get({ playlistId: playlistCreated.uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
1102 it('Should delete an account and delete its playlists', async function () {
1105 const { userId, token } = await servers[0].users.generate('user_1')
1107 const { videoChannels } = await servers[0].users.getMyInfo({ token })
1108 const userChannel = videoChannels[0]
1110 await commands[0].create({
1112 displayName: 'playlist to be deleted',
1113 privacy: VideoPlaylistPrivacy.PUBLIC,
1114 videoChannelId: userChannel.id
1118 await waitJobs(servers)
1120 const finder = (data: VideoPlaylist[]) => data.find(p => p.displayName === 'playlist to be deleted')
1123 for (const server of [ servers[0], servers[1] ]) {
1124 const body = await server.playlists.list({ start: 0, count: 15 })
1126 expect(finder(body.data)).to.not.be.undefined
1130 await servers[0].users.remove({ userId })
1131 await waitJobs(servers)
1134 for (const server of [ servers[0], servers[1] ]) {
1135 const body = await server.playlists.list({ start: 0, count: 15 })
1137 expect(finder(body.data)).to.be.undefined
1143 after(async function () {
1144 await cleanupTests(servers)