1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
8 VideoPlaylistCreateResult,
9 VideoPlaylistElementCreate,
10 VideoPlaylistElementUpdate,
14 } from '@shared/models'
21 setAccessTokensToServers,
22 setDefaultVideoChannel
23 } from '@shared/server-commands'
25 describe('Test video playlists API validator', function () {
26 let server: PeerTubeServer
27 let userAccessToken: string
29 let playlist: VideoPlaylistCreateResult
30 let privatePlaylistUUID: string
32 let watchLaterPlaylistId: number
36 let command: PlaylistsCommand
38 // ---------------------------------------------------------------
40 before(async function () {
43 server = await createSingleServer(1)
45 await setAccessTokensToServers([ server ])
46 await setDefaultVideoChannel([ server ])
48 userAccessToken = await server.users.generateUserAndToken('user1')
49 videoId = (await server.videos.quickUpload({ name: 'video 1' })).id
51 command = server.playlists
54 const { data } = await command.listByAccount({
55 token: server.accessToken,
59 playlistType: VideoPlaylistType.WATCH_LATER
61 watchLaterPlaylistId = data[0].id
65 playlist = await command.create({
67 displayName: 'super playlist',
68 privacy: VideoPlaylistPrivacy.PUBLIC,
69 videoChannelId: server.store.channel.id
75 const created = await command.create({
77 displayName: 'private',
78 privacy: VideoPlaylistPrivacy.PRIVATE
81 privatePlaylistUUID = created.uuid
85 describe('When listing playlists', function () {
86 const globalPath = '/api/v1/video-playlists'
87 const accountPath = '/api/v1/accounts/root/video-playlists'
88 const videoChannelPath = '/api/v1/video-channels/root_channel/video-playlists'
90 it('Should fail with a bad start pagination', async function () {
91 await checkBadStartPagination(server.url, globalPath, server.accessToken)
92 await checkBadStartPagination(server.url, accountPath, server.accessToken)
93 await checkBadStartPagination(server.url, videoChannelPath, server.accessToken)
96 it('Should fail with a bad count pagination', async function () {
97 await checkBadCountPagination(server.url, globalPath, server.accessToken)
98 await checkBadCountPagination(server.url, accountPath, server.accessToken)
99 await checkBadCountPagination(server.url, videoChannelPath, server.accessToken)
102 it('Should fail with an incorrect sort', async function () {
103 await checkBadSortPagination(server.url, globalPath, server.accessToken)
104 await checkBadSortPagination(server.url, accountPath, server.accessToken)
105 await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
108 it('Should fail with a bad playlist type', async function () {
109 await makeGetRequest({ url: server.url, path: globalPath, query: { playlistType: 3 } })
110 await makeGetRequest({ url: server.url, path: accountPath, query: { playlistType: 3 } })
111 await makeGetRequest({ url: server.url, path: videoChannelPath, query: { playlistType: 3 } })
114 it('Should fail with a bad account parameter', async function () {
115 const accountPath = '/api/v1/accounts/root2/video-playlists'
117 await makeGetRequest({
120 expectedStatus: HttpStatusCode.NOT_FOUND_404,
121 token: server.accessToken
125 it('Should fail with a bad video channel parameter', async function () {
126 const accountPath = '/api/v1/video-channels/bad_channel/video-playlists'
128 await makeGetRequest({
131 expectedStatus: HttpStatusCode.NOT_FOUND_404,
132 token: server.accessToken
136 it('Should success with the correct parameters', async function () {
137 await makeGetRequest({ url: server.url, path: globalPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
138 await makeGetRequest({ url: server.url, path: accountPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
139 await makeGetRequest({
141 path: videoChannelPath,
142 expectedStatus: HttpStatusCode.OK_200,
143 token: server.accessToken
148 describe('When listing videos of a playlist', function () {
149 const path = '/api/v1/video-playlists/'
151 it('Should fail with a bad start pagination', async function () {
152 await checkBadStartPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
155 it('Should fail with a bad count pagination', async function () {
156 await checkBadCountPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
159 it('Should success with the correct parameters', async function () {
160 await makeGetRequest({ url: server.url, path: path + playlist.shortUUID + '/videos', expectedStatus: HttpStatusCode.OK_200 })
164 describe('When getting a video playlist', function () {
165 it('Should fail with a bad id or uuid', async function () {
166 await command.get({ playlistId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
169 it('Should fail with an unknown playlist', async function () {
170 await command.get({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
173 it('Should fail to get an unlisted playlist with the number id', async function () {
174 const playlist = await command.create({
176 displayName: 'super playlist',
177 videoChannelId: server.store.channel.id,
178 privacy: VideoPlaylistPrivacy.UNLISTED
182 await command.get({ playlistId: playlist.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
183 await command.get({ playlistId: playlist.uuid, expectedStatus: HttpStatusCode.OK_200 })
186 it('Should succeed with the correct params', async function () {
187 await command.get({ playlistId: playlist.uuid, expectedStatus: HttpStatusCode.OK_200 })
191 describe('When creating/updating a video playlist', function () {
193 attributes?: Partial<VideoPlaylistCreate>,
194 wrapper?: Partial<Parameters<PlaylistsCommand['create']>[0]>
198 displayName: 'display name',
199 privacy: VideoPlaylistPrivacy.UNLISTED,
200 thumbnailfile: 'thumbnail.jpg',
201 videoChannelId: server.store.channel.id,
206 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
211 const getUpdate = (params: any, playlistId: number | string) => {
212 return { ...params, playlistId }
215 it('Should fail with an unauthenticated user', async function () {
216 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
218 await command.create(params)
219 await command.update(getUpdate(params, playlist.shortUUID))
222 it('Should fail without displayName', async function () {
223 const params = getBase({ displayName: undefined })
225 await command.create(params)
228 it('Should fail with an incorrect display name', async function () {
229 const params = getBase({ displayName: 's'.repeat(300) })
231 await command.create(params)
232 await command.update(getUpdate(params, playlist.shortUUID))
235 it('Should fail with an incorrect description', async function () {
236 const params = getBase({ description: 't' })
238 await command.create(params)
239 await command.update(getUpdate(params, playlist.shortUUID))
242 it('Should fail with an incorrect privacy', async function () {
243 const params = getBase({ privacy: 45 })
245 await command.create(params)
246 await command.update(getUpdate(params, playlist.shortUUID))
249 it('Should fail with an unknown video channel id', async function () {
250 const params = getBase({ videoChannelId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
252 await command.create(params)
253 await command.update(getUpdate(params, playlist.shortUUID))
256 it('Should fail with an incorrect thumbnail file', async function () {
257 const params = getBase({ thumbnailfile: 'video_short.mp4' })
259 await command.create(params)
260 await command.update(getUpdate(params, playlist.shortUUID))
263 it('Should fail with a thumbnail file too big', async function () {
264 const params = getBase({ thumbnailfile: 'preview-big.png' })
266 await command.create(params)
267 await command.update(getUpdate(params, playlist.shortUUID))
270 it('Should fail to set "public" a playlist not assigned to a channel', async function () {
271 const params = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: undefined })
272 const params2 = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: 'null' as any })
273 const params3 = getBase({ privacy: undefined, videoChannelId: 'null' as any })
275 await command.create(params)
276 await command.create(params2)
277 await command.update(getUpdate(params, privatePlaylistUUID))
278 await command.update(getUpdate(params2, playlist.shortUUID))
279 await command.update(getUpdate(params3, playlist.shortUUID))
282 it('Should fail with an unknown playlist to update', async function () {
283 await command.update(getUpdate(
284 getBase({}, { expectedStatus: HttpStatusCode.NOT_FOUND_404 }),
289 it('Should fail to update a playlist of another user', async function () {
290 await command.update(getUpdate(
291 getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }),
296 it('Should fail to update the watch later playlist', async function () {
297 await command.update(getUpdate(
298 getBase({}, { expectedStatus: HttpStatusCode.BAD_REQUEST_400 }),
303 it('Should succeed with the correct params', async function () {
305 const params = getBase({}, { expectedStatus: HttpStatusCode.OK_200 })
306 await command.create(params)
310 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
311 await command.update(getUpdate(params, playlist.shortUUID))
316 describe('When adding an element in a playlist', function () {
318 attributes?: Partial<VideoPlaylistElementCreate>,
319 wrapper?: Partial<Parameters<PlaylistsCommand['addElement']>[0]>
330 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
331 playlistId: playlist.id,
337 it('Should fail with an unauthenticated user', async function () {
338 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
339 await command.addElement(params)
342 it('Should fail with the playlist of another user', async function () {
343 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
344 await command.addElement(params)
347 it('Should fail with an unknown or incorrect playlist id', async function () {
349 const params = getBase({}, { playlistId: 'toto' })
350 await command.addElement(params)
354 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
355 await command.addElement(params)
359 it('Should fail with an unknown or incorrect video id', async function () {
360 const params = getBase({ videoId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
361 await command.addElement(params)
364 it('Should fail with a bad start/stop timestamp', async function () {
366 const params = getBase({ startTimestamp: -42 })
367 await command.addElement(params)
371 const params = getBase({ stopTimestamp: 'toto' as any })
372 await command.addElement(params)
376 it('Succeed with the correct params', async function () {
377 const params = getBase({}, { expectedStatus: HttpStatusCode.OK_200 })
378 const created = await command.addElement(params)
379 elementId = created.id
383 describe('When updating an element in a playlist', function () {
385 attributes?: Partial<VideoPlaylistElementUpdate>,
386 wrapper?: Partial<Parameters<PlaylistsCommand['updateElement']>[0]>
397 playlistId: playlist.id,
398 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
404 it('Should fail with an unauthenticated user', async function () {
405 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
406 await command.updateElement(params)
409 it('Should fail with the playlist of another user', async function () {
410 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
411 await command.updateElement(params)
414 it('Should fail with an unknown or incorrect playlist id', async function () {
416 const params = getBase({}, { playlistId: 'toto' })
417 await command.updateElement(params)
421 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
422 await command.updateElement(params)
426 it('Should fail with an unknown or incorrect playlistElement id', async function () {
428 const params = getBase({}, { elementId: 'toto' })
429 await command.updateElement(params)
433 const params = getBase({}, { elementId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
434 await command.updateElement(params)
438 it('Should fail with a bad start/stop timestamp', async function () {
440 const params = getBase({ startTimestamp: 'toto' as any })
441 await command.updateElement(params)
445 const params = getBase({ stopTimestamp: -42 })
446 await command.updateElement(params)
450 it('Should fail with an unknown element', async function () {
451 const params = getBase({}, { elementId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
452 await command.updateElement(params)
455 it('Succeed with the correct params', async function () {
456 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
457 await command.updateElement(params)
461 describe('When reordering elements of a playlist', function () {
466 attributes?: Partial<VideoPlaylistReorder>,
467 wrapper?: Partial<Parameters<PlaylistsCommand['reorderElements']>[0]>
472 insertAfterPosition: 2,
478 playlistId: playlist.shortUUID,
479 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
485 before(async function () {
486 videoId3 = (await server.videos.quickUpload({ name: 'video 3' })).id
487 videoId4 = (await server.videos.quickUpload({ name: 'video 4' })).id
489 for (const id of [ videoId3, videoId4 ]) {
490 await command.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: id } })
494 it('Should fail with an unauthenticated user', async function () {
495 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
496 await command.reorderElements(params)
499 it('Should fail with the playlist of another user', async function () {
500 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
501 await command.reorderElements(params)
504 it('Should fail with an invalid playlist', async function () {
506 const params = getBase({}, { playlistId: 'toto' })
507 await command.reorderElements(params)
511 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
512 await command.reorderElements(params)
516 it('Should fail with an invalid start position', async function () {
518 const params = getBase({ startPosition: -1 })
519 await command.reorderElements(params)
523 const params = getBase({ startPosition: 'toto' as any })
524 await command.reorderElements(params)
528 const params = getBase({ startPosition: 42 })
529 await command.reorderElements(params)
533 it('Should fail with an invalid insert after position', async function () {
535 const params = getBase({ insertAfterPosition: 'toto' as any })
536 await command.reorderElements(params)
540 const params = getBase({ insertAfterPosition: -2 })
541 await command.reorderElements(params)
545 const params = getBase({ insertAfterPosition: 42 })
546 await command.reorderElements(params)
550 it('Should fail with an invalid reorder length', async function () {
552 const params = getBase({ reorderLength: 'toto' as any })
553 await command.reorderElements(params)
557 const params = getBase({ reorderLength: -2 })
558 await command.reorderElements(params)
562 const params = getBase({ reorderLength: 42 })
563 await command.reorderElements(params)
567 it('Succeed with the correct params', async function () {
568 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
569 await command.reorderElements(params)
573 describe('When checking exists in playlist endpoint', function () {
574 const path = '/api/v1/users/me/video-playlists/videos-exist'
576 it('Should fail with an unauthenticated user', async function () {
577 await makeGetRequest({
580 query: { videoIds: [ 1, 2 ] },
581 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
585 it('Should fail with invalid video ids', async function () {
586 await makeGetRequest({
588 token: server.accessToken,
590 query: { videoIds: 'toto' }
593 await makeGetRequest({
595 token: server.accessToken,
597 query: { videoIds: [ 'toto' ] }
600 await makeGetRequest({
602 token: server.accessToken,
604 query: { videoIds: [ 1, 'toto' ] }
608 it('Should succeed with the correct params', async function () {
609 await makeGetRequest({
611 token: server.accessToken,
613 query: { videoIds: [ 1, 2 ] },
614 expectedStatus: HttpStatusCode.OK_200
619 describe('When deleting an element in a playlist', function () {
620 const getBase = (wrapper: Partial<Parameters<PlaylistsCommand['removeElement']>[0]>) => {
623 playlistId: playlist.uuid,
624 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
630 it('Should fail with an unauthenticated user', async function () {
631 const params = getBase({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
632 await command.removeElement(params)
635 it('Should fail with the playlist of another user', async function () {
636 const params = getBase({ token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
637 await command.removeElement(params)
640 it('Should fail with an unknown or incorrect playlist id', async function () {
642 const params = getBase({ playlistId: 'toto' })
643 await command.removeElement(params)
647 const params = getBase({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
648 await command.removeElement(params)
652 it('Should fail with an unknown or incorrect video id', async function () {
654 const params = getBase({ elementId: 'toto' as any })
655 await command.removeElement(params)
659 const params = getBase({ elementId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
660 await command.removeElement(params)
664 it('Should fail with an unknown element', async function () {
665 const params = getBase({ elementId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
666 await command.removeElement(params)
669 it('Succeed with the correct params', async function () {
670 const params = getBase({ expectedStatus: HttpStatusCode.NO_CONTENT_204 })
671 await command.removeElement(params)
675 describe('When deleting a playlist', function () {
676 it('Should fail with an unknown playlist', async function () {
677 await command.delete({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
680 it('Should fail with a playlist of another user', async function () {
681 await command.delete({ token: userAccessToken, playlistId: playlist.uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
684 it('Should fail with the watch later playlist', async function () {
685 await command.delete({ playlistId: watchLaterPlaylistId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
688 it('Should succeed with the correct params', async function () {
689 await command.delete({ playlistId: playlist.uuid })
693 after(async function () {
694 await cleanupTests([ server ])