1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
7 VideoPlaylistCreateResult,
8 VideoPlaylistElementCreate,
9 VideoPlaylistElementUpdate,
13 } from '@shared/models'
20 setAccessTokensToServers,
21 setDefaultVideoChannel
22 } from '@shared/server-commands'
24 describe('Test video playlists API validator', function () {
25 let server: PeerTubeServer
26 let userAccessToken: string
28 let playlist: VideoPlaylistCreateResult
29 let privatePlaylistUUID: string
31 let watchLaterPlaylistId: number
35 let command: PlaylistsCommand
37 // ---------------------------------------------------------------
39 before(async function () {
42 server = await createSingleServer(1)
44 await setAccessTokensToServers([ server ])
45 await setDefaultVideoChannel([ server ])
47 userAccessToken = await server.users.generateUserAndToken('user1')
48 videoId = (await server.videos.quickUpload({ name: 'video 1' })).id
50 command = server.playlists
53 const { data } = await command.listByAccount({
54 token: server.accessToken,
58 playlistType: VideoPlaylistType.WATCH_LATER
60 watchLaterPlaylistId = data[0].id
64 playlist = await command.create({
66 displayName: 'super playlist',
67 privacy: VideoPlaylistPrivacy.PUBLIC,
68 videoChannelId: server.store.channel.id
74 const created = await command.create({
76 displayName: 'private',
77 privacy: VideoPlaylistPrivacy.PRIVATE
80 privatePlaylistUUID = created.uuid
84 describe('When listing playlists', function () {
85 const globalPath = '/api/v1/video-playlists'
86 const accountPath = '/api/v1/accounts/root/video-playlists'
87 const videoChannelPath = '/api/v1/video-channels/root_channel/video-playlists'
89 it('Should fail with a bad start pagination', async function () {
90 await checkBadStartPagination(server.url, globalPath, server.accessToken)
91 await checkBadStartPagination(server.url, accountPath, server.accessToken)
92 await checkBadStartPagination(server.url, videoChannelPath, server.accessToken)
95 it('Should fail with a bad count pagination', async function () {
96 await checkBadCountPagination(server.url, globalPath, server.accessToken)
97 await checkBadCountPagination(server.url, accountPath, server.accessToken)
98 await checkBadCountPagination(server.url, videoChannelPath, server.accessToken)
101 it('Should fail with an incorrect sort', async function () {
102 await checkBadSortPagination(server.url, globalPath, server.accessToken)
103 await checkBadSortPagination(server.url, accountPath, server.accessToken)
104 await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
107 it('Should fail with a bad playlist type', async function () {
108 await makeGetRequest({ url: server.url, path: globalPath, query: { playlistType: 3 } })
109 await makeGetRequest({ url: server.url, path: accountPath, query: { playlistType: 3 } })
110 await makeGetRequest({ url: server.url, path: videoChannelPath, query: { playlistType: 3 } })
113 it('Should fail with a bad account parameter', async function () {
114 const accountPath = '/api/v1/accounts/root2/video-playlists'
116 await makeGetRequest({
119 expectedStatus: HttpStatusCode.NOT_FOUND_404,
120 token: server.accessToken
124 it('Should fail with a bad video channel parameter', async function () {
125 const accountPath = '/api/v1/video-channels/bad_channel/video-playlists'
127 await makeGetRequest({
130 expectedStatus: HttpStatusCode.NOT_FOUND_404,
131 token: server.accessToken
135 it('Should success with the correct parameters', async function () {
136 await makeGetRequest({ url: server.url, path: globalPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
137 await makeGetRequest({ url: server.url, path: accountPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
138 await makeGetRequest({
140 path: videoChannelPath,
141 expectedStatus: HttpStatusCode.OK_200,
142 token: server.accessToken
147 describe('When listing videos of a playlist', function () {
148 const path = '/api/v1/video-playlists/'
150 it('Should fail with a bad start pagination', async function () {
151 await checkBadStartPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
154 it('Should fail with a bad count pagination', async function () {
155 await checkBadCountPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
158 it('Should success with the correct parameters', async function () {
159 await makeGetRequest({ url: server.url, path: path + playlist.shortUUID + '/videos', expectedStatus: HttpStatusCode.OK_200 })
163 describe('When getting a video playlist', function () {
164 it('Should fail with a bad id or uuid', async function () {
165 await command.get({ playlistId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
168 it('Should fail with an unknown playlist', async function () {
169 await command.get({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
172 it('Should fail to get an unlisted playlist with the number id', async function () {
173 const playlist = await command.create({
175 displayName: 'super playlist',
176 videoChannelId: server.store.channel.id,
177 privacy: VideoPlaylistPrivacy.UNLISTED
181 await command.get({ playlistId: playlist.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
182 await command.get({ playlistId: playlist.uuid, expectedStatus: HttpStatusCode.OK_200 })
185 it('Should succeed with the correct params', async function () {
186 await command.get({ playlistId: playlist.uuid, expectedStatus: HttpStatusCode.OK_200 })
190 describe('When creating/updating a video playlist', function () {
192 attributes?: Partial<VideoPlaylistCreate>,
193 wrapper?: Partial<Parameters<PlaylistsCommand['create']>[0]>
197 displayName: 'display name',
198 privacy: VideoPlaylistPrivacy.UNLISTED,
199 thumbnailfile: 'thumbnail.jpg',
200 videoChannelId: server.store.channel.id,
205 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
210 const getUpdate = (params: any, playlistId: number | string) => {
211 return { ...params, playlistId }
214 it('Should fail with an unauthenticated user', async function () {
215 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
217 await command.create(params)
218 await command.update(getUpdate(params, playlist.shortUUID))
221 it('Should fail without displayName', async function () {
222 const params = getBase({ displayName: undefined })
224 await command.create(params)
227 it('Should fail with an incorrect display name', async function () {
228 const params = getBase({ displayName: 's'.repeat(300) })
230 await command.create(params)
231 await command.update(getUpdate(params, playlist.shortUUID))
234 it('Should fail with an incorrect description', async function () {
235 const params = getBase({ description: 't' })
237 await command.create(params)
238 await command.update(getUpdate(params, playlist.shortUUID))
241 it('Should fail with an incorrect privacy', async function () {
242 const params = getBase({ privacy: 45 as any })
244 await command.create(params)
245 await command.update(getUpdate(params, playlist.shortUUID))
248 it('Should fail with an unknown video channel id', async function () {
249 const params = getBase({ videoChannelId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
251 await command.create(params)
252 await command.update(getUpdate(params, playlist.shortUUID))
255 it('Should fail with an incorrect thumbnail file', async function () {
256 const params = getBase({ thumbnailfile: 'video_short.mp4' })
258 await command.create(params)
259 await command.update(getUpdate(params, playlist.shortUUID))
262 it('Should fail with a thumbnail file too big', async function () {
263 const params = getBase({ thumbnailfile: 'preview-big.png' })
265 await command.create(params)
266 await command.update(getUpdate(params, playlist.shortUUID))
269 it('Should fail to set "public" a playlist not assigned to a channel', async function () {
270 const params = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: undefined })
271 const params2 = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: 'null' as any })
272 const params3 = getBase({ privacy: undefined, videoChannelId: 'null' as any })
274 await command.create(params)
275 await command.create(params2)
276 await command.update(getUpdate(params, privatePlaylistUUID))
277 await command.update(getUpdate(params2, playlist.shortUUID))
278 await command.update(getUpdate(params3, playlist.shortUUID))
281 it('Should fail with an unknown playlist to update', async function () {
282 await command.update(getUpdate(
283 getBase({}, { expectedStatus: HttpStatusCode.NOT_FOUND_404 }),
288 it('Should fail to update a playlist of another user', async function () {
289 await command.update(getUpdate(
290 getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }),
295 it('Should fail to update the watch later playlist', async function () {
296 await command.update(getUpdate(
297 getBase({}, { expectedStatus: HttpStatusCode.BAD_REQUEST_400 }),
302 it('Should succeed with the correct params', async function () {
304 const params = getBase({}, { expectedStatus: HttpStatusCode.OK_200 })
305 await command.create(params)
309 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
310 await command.update(getUpdate(params, playlist.shortUUID))
315 describe('When adding an element in a playlist', function () {
317 attributes?: Partial<VideoPlaylistElementCreate>,
318 wrapper?: Partial<Parameters<PlaylistsCommand['addElement']>[0]>
329 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
330 playlistId: playlist.id,
336 it('Should fail with an unauthenticated user', async function () {
337 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
338 await command.addElement(params)
341 it('Should fail with the playlist of another user', async function () {
342 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
343 await command.addElement(params)
346 it('Should fail with an unknown or incorrect playlist id', async function () {
348 const params = getBase({}, { playlistId: 'toto' })
349 await command.addElement(params)
353 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
354 await command.addElement(params)
358 it('Should fail with an unknown or incorrect video id', async function () {
359 const params = getBase({ videoId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
360 await command.addElement(params)
363 it('Should fail with a bad start/stop timestamp', async function () {
365 const params = getBase({ startTimestamp: -42 })
366 await command.addElement(params)
370 const params = getBase({ stopTimestamp: 'toto' as any })
371 await command.addElement(params)
375 it('Succeed with the correct params', async function () {
376 const params = getBase({}, { expectedStatus: HttpStatusCode.OK_200 })
377 const created = await command.addElement(params)
378 elementId = created.id
382 describe('When updating an element in a playlist', function () {
384 attributes?: Partial<VideoPlaylistElementUpdate>,
385 wrapper?: Partial<Parameters<PlaylistsCommand['updateElement']>[0]>
396 playlistId: playlist.id,
397 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
403 it('Should fail with an unauthenticated user', async function () {
404 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
405 await command.updateElement(params)
408 it('Should fail with the playlist of another user', async function () {
409 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
410 await command.updateElement(params)
413 it('Should fail with an unknown or incorrect playlist id', async function () {
415 const params = getBase({}, { playlistId: 'toto' })
416 await command.updateElement(params)
420 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
421 await command.updateElement(params)
425 it('Should fail with an unknown or incorrect playlistElement id', async function () {
427 const params = getBase({}, { elementId: 'toto' })
428 await command.updateElement(params)
432 const params = getBase({}, { elementId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
433 await command.updateElement(params)
437 it('Should fail with a bad start/stop timestamp', async function () {
439 const params = getBase({ startTimestamp: 'toto' as any })
440 await command.updateElement(params)
444 const params = getBase({ stopTimestamp: -42 })
445 await command.updateElement(params)
449 it('Should fail with an unknown element', async function () {
450 const params = getBase({}, { elementId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
451 await command.updateElement(params)
454 it('Succeed with the correct params', async function () {
455 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
456 await command.updateElement(params)
460 describe('When reordering elements of a playlist', function () {
465 attributes?: Partial<VideoPlaylistReorder>,
466 wrapper?: Partial<Parameters<PlaylistsCommand['reorderElements']>[0]>
471 insertAfterPosition: 2,
477 playlistId: playlist.shortUUID,
478 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
484 before(async function () {
485 videoId3 = (await server.videos.quickUpload({ name: 'video 3' })).id
486 videoId4 = (await server.videos.quickUpload({ name: 'video 4' })).id
488 for (const id of [ videoId3, videoId4 ]) {
489 await command.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: id } })
493 it('Should fail with an unauthenticated user', async function () {
494 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
495 await command.reorderElements(params)
498 it('Should fail with the playlist of another user', async function () {
499 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
500 await command.reorderElements(params)
503 it('Should fail with an invalid playlist', async function () {
505 const params = getBase({}, { playlistId: 'toto' })
506 await command.reorderElements(params)
510 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
511 await command.reorderElements(params)
515 it('Should fail with an invalid start position', async function () {
517 const params = getBase({ startPosition: -1 })
518 await command.reorderElements(params)
522 const params = getBase({ startPosition: 'toto' as any })
523 await command.reorderElements(params)
527 const params = getBase({ startPosition: 42 })
528 await command.reorderElements(params)
532 it('Should fail with an invalid insert after position', async function () {
534 const params = getBase({ insertAfterPosition: 'toto' as any })
535 await command.reorderElements(params)
539 const params = getBase({ insertAfterPosition: -2 })
540 await command.reorderElements(params)
544 const params = getBase({ insertAfterPosition: 42 })
545 await command.reorderElements(params)
549 it('Should fail with an invalid reorder length', async function () {
551 const params = getBase({ reorderLength: 'toto' as any })
552 await command.reorderElements(params)
556 const params = getBase({ reorderLength: -2 })
557 await command.reorderElements(params)
561 const params = getBase({ reorderLength: 42 })
562 await command.reorderElements(params)
566 it('Succeed with the correct params', async function () {
567 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
568 await command.reorderElements(params)
572 describe('When checking exists in playlist endpoint', function () {
573 const path = '/api/v1/users/me/video-playlists/videos-exist'
575 it('Should fail with an unauthenticated user', async function () {
576 await makeGetRequest({
579 query: { videoIds: [ 1, 2 ] },
580 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
584 it('Should fail with invalid video ids', async function () {
585 await makeGetRequest({
587 token: server.accessToken,
589 query: { videoIds: 'toto' }
592 await makeGetRequest({
594 token: server.accessToken,
596 query: { videoIds: [ 'toto' ] }
599 await makeGetRequest({
601 token: server.accessToken,
603 query: { videoIds: [ 1, 'toto' ] }
607 it('Should succeed with the correct params', async function () {
608 await makeGetRequest({
610 token: server.accessToken,
612 query: { videoIds: [ 1, 2 ] },
613 expectedStatus: HttpStatusCode.OK_200
618 describe('When deleting an element in a playlist', function () {
619 const getBase = (wrapper: Partial<Parameters<PlaylistsCommand['removeElement']>[0]>) => {
622 playlistId: playlist.uuid,
623 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
629 it('Should fail with an unauthenticated user', async function () {
630 const params = getBase({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
631 await command.removeElement(params)
634 it('Should fail with the playlist of another user', async function () {
635 const params = getBase({ token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
636 await command.removeElement(params)
639 it('Should fail with an unknown or incorrect playlist id', async function () {
641 const params = getBase({ playlistId: 'toto' })
642 await command.removeElement(params)
646 const params = getBase({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
647 await command.removeElement(params)
651 it('Should fail with an unknown or incorrect video id', async function () {
653 const params = getBase({ elementId: 'toto' as any })
654 await command.removeElement(params)
658 const params = getBase({ elementId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
659 await command.removeElement(params)
663 it('Should fail with an unknown element', async function () {
664 const params = getBase({ elementId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
665 await command.removeElement(params)
668 it('Succeed with the correct params', async function () {
669 const params = getBase({ expectedStatus: HttpStatusCode.NO_CONTENT_204 })
670 await command.removeElement(params)
674 describe('When deleting a playlist', function () {
675 it('Should fail with an unknown playlist', async function () {
676 await command.delete({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
679 it('Should fail with a playlist of another user', async function () {
680 await command.delete({ token: userAccessToken, playlistId: playlist.uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
683 it('Should fail with the watch later playlist', async function () {
684 await command.delete({ playlistId: watchLaterPlaylistId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
687 it('Should succeed with the correct params', async function () {
688 await command.delete({ playlistId: playlist.uuid })
692 after(async function () {
693 await cleanupTests([ server ])