Add playlist check param tests
authorChocobozzz <me@florianbigard.com>
Thu, 28 Feb 2019 10:14:26 +0000 (11:14 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Mon, 18 Mar 2019 10:17:59 +0000 (11:17 +0100)
16 files changed:
server/controllers/api/video-playlist.ts
server/helpers/custom-validators/video-channels.ts
server/lib/activitypub/index.ts
server/lib/activitypub/url.ts
server/middlewares/validators/videos/video-playlists.ts
server/models/video/video-channel.ts
server/models/video/video-playlist-element.ts
server/models/video/video-playlist.ts
server/tests/api/check-params/video-playlists.ts
server/tests/api/check-params/videos-filter.ts
server/tests/api/redundancy/redundancy.ts
shared/models/videos/playlist/video-playlist-create.model.ts
shared/models/videos/playlist/video-playlist-element-create.model.ts
shared/models/videos/playlist/video-playlist-update.model.ts
shared/utils/index.ts
shared/utils/videos/video-playlists.ts

index 709c58beb8d41ca69ee40b0eb39a852c368f5c8f..e026b4d16dc5dbba5b54d2c54715325bc113c8d8 100644 (file)
@@ -4,7 +4,7 @@ import {
   asyncMiddleware,
   asyncRetryTransactionMiddleware,
   authenticate,
-  commonVideosFiltersValidator,
+  commonVideosFiltersValidator, optionalAuthenticate,
   paginationValidator,
   setDefaultPagination,
   setDefaultSort
@@ -31,12 +31,14 @@ import { processImage } from '../../helpers/image-utils'
 import { join } from 'path'
 import { UserModel } from '../../models/account/user'
 import {
-  getVideoPlaylistActivityPubUrl,
-  getVideoPlaylistElementActivityPubUrl,
   sendCreateVideoPlaylist,
   sendDeleteVideoPlaylist,
   sendUpdateVideoPlaylist
-} from '../../lib/activitypub'
+} from '../../lib/activitypub/send'
+import {
+  getVideoPlaylistActivityPubUrl,
+  getVideoPlaylistElementActivityPubUrl
+} from '../../lib/activitypub/url'
 import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model'
 import { VideoModel } from '../../models/video/video'
 import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
@@ -85,6 +87,7 @@ videoPlaylistRouter.get('/:playlistId/videos',
   asyncMiddleware(videoPlaylistsGetValidator),
   paginationValidator,
   setDefaultPagination,
+  optionalAuthenticate,
   commonVideosFiltersValidator,
   asyncMiddleware(getVideoPlaylistVideos)
 )
@@ -95,7 +98,7 @@ videoPlaylistRouter.post('/:playlistId/videos',
   asyncRetryTransactionMiddleware(addVideoInPlaylist)
 )
 
-videoPlaylistRouter.put('/:playlistId/videos',
+videoPlaylistRouter.post('/:playlistId/videos/reorder',
   authenticate,
   asyncMiddleware(videoPlaylistsReorderVideosValidator),
   asyncRetryTransactionMiddleware(reorderVideosPlaylist)
@@ -168,6 +171,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
   const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
     const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
 
+    videoPlaylistCreated.OwnerAccount = user.Account
     await sendCreateVideoPlaylist(videoPlaylistCreated, t)
 
     return videoPlaylistCreated
@@ -349,7 +353,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons
   const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
 
   const start: number = req.body.startPosition
-  const insertAfter: number = req.body.insertAfter
+  const insertAfter: number = req.body.insertAfterPosition
   const reorderLength: number = req.body.reorderLength || 1
 
   if (start === insertAfter) {
index cbf150e53d29bd85ef37912cdcf918fb1c4e5731..3792bbdcc8290a738a5d5ddc08a6f0c82b2c73ec 100644 (file)
@@ -26,12 +26,12 @@ async function isLocalVideoChannelNameExist (name: string, res: express.Response
   return processVideoChannelExist(videoChannel, res)
 }
 
-async function isVideoChannelIdExist (id: string, res: express.Response) {
+async function isVideoChannelIdExist (id: number | string, res: express.Response) {
   let videoChannel: VideoChannelModel
-  if (validator.isInt(id)) {
+  if (validator.isInt('' + id)) {
     videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
   } else { // UUID
-    videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount(id)
+    videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount('' + id)
   }
 
   return processVideoChannelExist(videoChannel, res)
index 6906bf9d3d86302277d506738a7a0cf8f8f8be19..d8c7d83b7e80e5aa6effb527b271f68646d95ad1 100644 (file)
@@ -2,6 +2,7 @@ export * from './process'
 export * from './send'
 export * from './actor'
 export * from './share'
+export * from './playlist'
 export * from './videos'
 export * from './video-comments'
 export * from './video-rates'
index 00bbbba2ddf3ffca48d13bd48ff29c572bf5d2ba..c80b09436c644ac05b342af011b0a3e7d405c36d 100644 (file)
@@ -5,10 +5,8 @@ import { VideoModel } from '../../models/video/video'
 import { VideoAbuseModel } from '../../models/video/video-abuse'
 import { VideoCommentModel } from '../../models/video/video-comment'
 import { VideoFileModel } from '../../models/video/video-file'
-import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model'
 import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
 import { VideoPlaylistModel } from '../../models/video/video-playlist'
-import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
 
 function getVideoActivityPubUrl (video: VideoModel) {
   return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
index ef8d0b8511eeaefe4cf060e43335fbf9fbd75c3b..0e97c8dc02dfdd239efa9562e01ed5cc08bf973c 100644 (file)
@@ -6,7 +6,7 @@ import { UserModel } from '../../../models/account/user'
 import { areValidationErrors } from '../utils'
 import { isVideoExist, isVideoImage } from '../../../helpers/custom-validators/videos'
 import { CONSTRAINTS_FIELDS } from '../../../initializers'
-import { isIdOrUUIDValid, toValueOrNull } from '../../../helpers/custom-validators/misc'
+import { isIdOrUUIDValid, isUUIDValid, toValueOrNull } from '../../../helpers/custom-validators/misc'
 import {
   isVideoPlaylistDescriptionValid,
   isVideoPlaylistExist,
@@ -43,10 +43,19 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
     if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
 
     if (!await isVideoPlaylistExist(req.params.playlistId, res)) return cleanUpReqFiles(req)
+
+    const videoPlaylist = res.locals.videoPlaylist
+
     if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
       return cleanUpReqFiles(req)
     }
 
+    if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PRIVATE && req.body.privacy === VideoPlaylistPrivacy.PRIVATE) {
+      cleanUpReqFiles(req)
+      return res.status(409)
+                .json({ error: 'Cannot set "private" a video playlist that was not private.' })
+    }
+
     if (req.body.videoChannelId && !await isVideoChannelIdExist(req.body.videoChannelId, res)) return cleanUpReqFiles(req)
 
     return next()
@@ -83,6 +92,14 @@ const videoPlaylistsGetValidator = [
     if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
 
     const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
+
+    // Video is unlisted, check we used the uuid to fetch it
+    if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) {
+      if (isUUIDValid(req.params.playlistId)) return next()
+
+      return res.status(404).end()
+    }
+
     if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
       await authenticatePromiseIfNeeded(req, res)
 
@@ -121,7 +138,7 @@ const videoPlaylistsAddVideoValidator = [
     if (areValidationErrors(req, res)) return
 
     if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
-    if (!await isVideoExist(req.body.videoId, res, 'id')) return
+    if (!await isVideoExist(req.body.videoId, res, 'only-video')) return
 
     const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
     const video: VideoModel = res.locals.video
@@ -161,7 +178,7 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [
     if (areValidationErrors(req, res)) return
 
     if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
-    if (!await isVideoExist(req.params.playlistId, res, 'id')) return
+    if (!await isVideoExist(req.params.videoId, res, 'id')) return
 
     const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
     const video: VideoModel = res.locals.video
@@ -233,6 +250,27 @@ const videoPlaylistsReorderVideosValidator = [
     const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
     if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
 
+    const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id)
+    const startPosition: number = req.body.startPosition
+    const insertAfterPosition: number = req.body.insertAfterPosition
+    const reorderLength: number = req.body.reorderLength
+
+    if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) {
+      res.status(400)
+         .json({ error: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` })
+         .end()
+
+      return
+    }
+
+    if (reorderLength && reorderLength + startPosition > nextPosition) {
+      res.status(400)
+         .json({ error: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` })
+         .end()
+
+      return
+    }
+
     return next()
   }
 ]
index 112abf8cfad7150553031898d25940ed1a2e17ff..c077fb5184650461ab49cc48c06905ca1dc5cb55 100644 (file)
@@ -223,7 +223,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
 
   @HasMany(() => VideoPlaylistModel, {
     foreignKey: {
-      allowNull: false
+      allowNull: true
     },
     onDelete: 'cascade',
     hooks: true
index d76149d1225f77e0981162008dbf6827644a550b..5530e0492d6463db06e95361fdc4788abad66e09 100644 (file)
@@ -188,7 +188,8 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
           [Sequelize.Op.lte]: endPosition
         }
       },
-      transaction
+      transaction,
+      validate: false // We use a literal to update the position
     }
 
     return VideoPlaylistElementModel.update({ position: Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) }, query)
index 93b8c2f58e3d81e539e385dc4046a71770a62d2a..397887ebf1a6c793d83e75f27e5711d138c8f8a2 100644 (file)
@@ -197,7 +197,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
 
   @BelongsTo(() => VideoChannelModel, {
     foreignKey: {
-      allowNull: false
+      allowNull: true
     },
     onDelete: 'CASCADE'
   })
@@ -351,7 +351,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
       updatedAt: this.updatedAt,
 
       ownerAccount: this.OwnerAccount.toFormattedSummaryJSON(),
-      videoChannel: this.VideoChannel.toFormattedSummaryJSON()
+      videoChannel: this.VideoChannel ? this.VideoChannel.toFormattedSummaryJSON() : null
     }
   }
 
index 7004badac65c10972799dd61f4def893b18540d4..68fe362e9c3d55c8e696a07be8901ce82153a74f 100644 (file)
@@ -1,35 +1,35 @@
 /* tslint:disable:no-unused-expression */
 
-import { omit } from 'lodash'
 import 'mocha'
-import { join } from 'path'
-import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
 import {
   createUser,
+  createVideoPlaylist,
+  deleteVideoPlaylist,
   flushTests,
-  getMyUserInformation,
+  getVideoPlaylist,
   immutableAssign,
   killallServers,
   makeGetRequest,
-  makePostBodyRequest,
-  makeUploadRequest,
   runServer,
   ServerInfo,
   setAccessTokensToServers,
-  updateCustomSubConfig,
-  userLogin
+  updateVideoPlaylist,
+  userLogin,
+  addVideoInPlaylist, uploadVideo, updateVideoPlaylistElement, removeVideoFromPlaylist, reorderVideosPlaylist
 } from '../../../../shared/utils'
 import {
   checkBadCountPagination,
   checkBadSortPagination,
   checkBadStartPagination
 } from '../../../../shared/utils/requests/check-api-params'
-import { getMagnetURI, getYoutubeVideoUrl } from '../../../../shared/utils/videos/video-imports'
+import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
 
 describe('Test video playlists API validator', function () {
-  const path = '/api/v1/videos/video-playlists'
   let server: ServerInfo
   let userAccessToken = ''
+  let playlistUUID: string
+  let videoId: number
+  let videoId2: number
 
   // ---------------------------------------------------------------
 
@@ -46,9 +46,31 @@ describe('Test video playlists API validator', function () {
     const password = 'my super password'
     await createUser(server.url, server.accessToken, username, password)
     userAccessToken = await userLogin(server, { username, password })
+
+    {
+      const res = await uploadVideo(server.url, server.accessToken, { name: 'video 1' })
+      videoId = res.body.video.id
+    }
+
+    {
+      const res = await uploadVideo(server.url, server.accessToken, { name: 'video 2' })
+      videoId2 = res.body.video.id
+    }
+
+    {
+      const res = await createVideoPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 'super playlist',
+          privacy: VideoPlaylistPrivacy.PUBLIC
+        }
+      })
+      playlistUUID = res.body.videoPlaylist.uuid
+    }
   })
 
-  describe('When listing video playlists', function () {
+  describe('When listing playlists', function () {
     const globalPath = '/api/v1/video-playlists'
     const accountPath = '/api/v1/accounts/root/video-playlists'
     const videoChannelPath = '/api/v1/video-channels/root_channel/video-playlists'
@@ -90,7 +112,7 @@ describe('Test video playlists API validator', function () {
     })
   })
 
-  describe('When listing videos of a playlist', async function () {
+  describe('When listing videos of a playlist', function () {
     const path = '/api/v1/video-playlists'
 
     it('Should fail with a bad start pagination', async function () {
@@ -101,11 +123,743 @@ describe('Test video playlists API validator', function () {
       await checkBadCountPagination(server.url, path, server.accessToken)
     })
 
-    it('Should fail with an incorrect sort', async function () {
+    it('Should fail with a bad filter', async function () {
       await checkBadSortPagination(server.url, path, server.accessToken)
     })
   })
 
+  describe('When getting a video playlist', function () {
+    it('Should fail with a bad id or uuid', async function () {
+      await getVideoPlaylist(server.url, 'toto', 400)
+    })
+
+    it('Should fail with an unknown playlist', async function () {
+      await getVideoPlaylist(server.url, 42, 404)
+    })
+
+    it('Should fail to get an unlisted playlist with the number id', async function () {
+      const res = await createVideoPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 'super playlist',
+          privacy: VideoPlaylistPrivacy.UNLISTED
+        }
+      })
+      const playlist = res.body.videoPlaylist
+
+      await getVideoPlaylist(server.url, playlist.id, 404)
+      await getVideoPlaylist(server.url, playlist.uuid, 200)
+    })
+
+    it('Should succeed with the correct params', async function () {
+      await getVideoPlaylist(server.url, playlistUUID, 200)
+    })
+  })
+
+  describe('When creating/updating a video playlist', function () {
+
+    it('Should fail with an unauthenticated user', async function () {
+      const baseParams = {
+        url: server.url,
+        token: null,
+        playlistAttrs: {
+          displayName: 'super playlist',
+          privacy: VideoPlaylistPrivacy.PUBLIC
+        },
+        expectedStatus: 401
+      }
+
+      await createVideoPlaylist(baseParams)
+      await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+    })
+
+    it('Should fail without displayName', async function () {
+      const baseParams = {
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          privacy: VideoPlaylistPrivacy.PUBLIC
+        } as any,
+        expectedStatus: 400
+      }
+
+      await createVideoPlaylist(baseParams)
+      await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+    })
+
+    it('Should fail with an incorrect display name', async function () {
+      const baseParams = {
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 's'.repeat(300),
+          privacy: VideoPlaylistPrivacy.PUBLIC
+        },
+        expectedStatus: 400
+      }
+
+      await createVideoPlaylist(baseParams)
+      await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+    })
+
+    it('Should fail with an incorrect description', async function () {
+      const baseParams = {
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 'display name',
+          privacy: VideoPlaylistPrivacy.PUBLIC,
+          description: 't'
+        },
+        expectedStatus: 400
+      }
+
+      await createVideoPlaylist(baseParams)
+      await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+    })
+
+    it('Should fail with an incorrect privacy', async function () {
+      const baseParams = {
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 'display name',
+          privacy: 45
+        } as any,
+        expectedStatus: 400
+      }
+
+      await createVideoPlaylist(baseParams)
+      await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+    })
+
+    it('Should fail with an unknown video channel id', async function () {
+      const baseParams = {
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 'display name',
+          privacy: VideoPlaylistPrivacy.PUBLIC,
+          videoChannelId: 42
+        },
+        expectedStatus: 404
+      }
+
+      await createVideoPlaylist(baseParams)
+      await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+    })
+
+    it('Should fail with an incorrect thumbnail file', async function () {
+      const baseParams = {
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 'display name',
+          privacy: VideoPlaylistPrivacy.PUBLIC,
+          thumbnailfile: 'avatar.png'
+        },
+        expectedStatus: 400
+      }
+
+      await createVideoPlaylist(baseParams)
+      await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+    })
+
+    it('Should fail with an unknown playlist to update', async function () {
+      await updateVideoPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: 42,
+        playlistAttrs: {
+          displayName: 'display name',
+          privacy: VideoPlaylistPrivacy.PUBLIC
+        },
+        expectedStatus: 404
+      })
+    })
+
+    it('Should fail to update a playlist of another user', async function () {
+      await updateVideoPlaylist({
+        url: server.url,
+        token: userAccessToken,
+        playlistId: playlistUUID,
+        playlistAttrs: {
+          displayName: 'display name',
+          privacy: VideoPlaylistPrivacy.PUBLIC
+        },
+        expectedStatus: 403
+      })
+    })
+
+    it('Should fail to update to private a public/unlisted playlist', async function () {
+      const res = await createVideoPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 'super playlist',
+          privacy: VideoPlaylistPrivacy.PUBLIC
+        }
+      })
+      const playlist = res.body.videoPlaylist
+
+      await updateVideoPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlist.id,
+        playlistAttrs: {
+          displayName: 'display name',
+          privacy: VideoPlaylistPrivacy.PRIVATE
+        },
+        expectedStatus: 409
+      })
+    })
+
+    it('Should succeed with the correct params', async function () {
+      const baseParams = {
+        url: server.url,
+        token: server.accessToken,
+        playlistAttrs: {
+          displayName: 'display name',
+          privacy: VideoPlaylistPrivacy.UNLISTED,
+          thumbnailfile: 'thumbnail.jpg'
+        }
+      }
+
+      await createVideoPlaylist(baseParams)
+      await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+    })
+  })
+
+  describe('When adding an element in a playlist', function () {
+    it('Should fail with an unauthenticated user', async function () {
+      await addVideoInPlaylist({
+        url: server.url,
+        token: null,
+        elementAttrs: {
+          videoId: videoId
+        },
+        playlistId: playlistUUID,
+        expectedStatus: 401
+      })
+    })
+
+    it('Should fail with the playlist of another user', async function () {
+      await addVideoInPlaylist({
+        url: server.url,
+        token: userAccessToken,
+        elementAttrs: {
+          videoId: videoId
+        },
+        playlistId: playlistUUID,
+        expectedStatus: 403
+      })
+    })
+
+    it('Should fail with an unknown or incorrect playlist id', async function () {
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          videoId: videoId
+        },
+        playlistId: 'toto',
+        expectedStatus: 400
+      })
+
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          videoId: videoId
+        },
+        playlistId: 42,
+        expectedStatus: 404
+      })
+    })
+
+    it('Should fail with an unknown or incorrect video id', async function () {
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          videoId: 'toto' as any
+        },
+        playlistId: playlistUUID,
+        expectedStatus: 400
+      })
+
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          videoId: 42
+        },
+        playlistId: playlistUUID,
+        expectedStatus: 404
+      })
+    })
+
+    it('Should fail with a bad start/stop timestamp', async function () {
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          videoId: videoId,
+          startTimestamp: -42
+        },
+        playlistId: playlistUUID,
+        expectedStatus: 400
+      })
+
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          videoId: videoId,
+          stopTimestamp: 'toto' as any
+        },
+        playlistId: playlistUUID,
+        expectedStatus: 400
+      })
+    })
+
+    it('Succeed with the correct params', async function () {
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          videoId: videoId,
+          stopTimestamp: 3
+        },
+        playlistId: playlistUUID,
+        expectedStatus: 200
+      })
+    })
+
+    it('Should fail if the video was already added in the playlist', async function () {
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          videoId: videoId,
+          stopTimestamp: 3
+        },
+        playlistId: playlistUUID,
+        expectedStatus: 409
+      })
+    })
+  })
+
+  describe('When updating an element in a playlist', function () {
+    it('Should fail with an unauthenticated user', async function () {
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: null,
+        elementAttrs: { },
+        videoId: videoId,
+        playlistId: playlistUUID,
+        expectedStatus: 401
+      })
+    })
+
+    it('Should fail with the playlist of another user', async function () {
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: userAccessToken,
+        elementAttrs: { },
+        videoId: videoId,
+        playlistId: playlistUUID,
+        expectedStatus: 403
+      })
+    })
+
+    it('Should fail with an unknown or incorrect playlist id', async function () {
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: { },
+        videoId: videoId,
+        playlistId: 'toto',
+        expectedStatus: 400
+      })
+
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: { },
+        videoId: videoId,
+        playlistId: 42,
+        expectedStatus: 404
+      })
+    })
+
+    it('Should fail with an unknown or incorrect video id', async function () {
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: { },
+        videoId: 'toto',
+        playlistId: playlistUUID,
+        expectedStatus: 400
+      })
+
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: { },
+        videoId: 42,
+        playlistId: playlistUUID,
+        expectedStatus: 404
+      })
+    })
+
+    it('Should fail with a bad start/stop timestamp', async function () {
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          startTimestamp: 'toto' as any
+        },
+        videoId: videoId,
+        playlistId: playlistUUID,
+        expectedStatus: 400
+      })
+
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          stopTimestamp: -42
+        },
+        videoId: videoId,
+        playlistId: playlistUUID,
+        expectedStatus: 400
+      })
+    })
+
+    it('Should fail with an unknown element', async function () {
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          stopTimestamp: 2
+        },
+        videoId: videoId2,
+        playlistId: playlistUUID,
+        expectedStatus: 404
+      })
+    })
+
+    it('Succeed with the correct params', async function () {
+      await updateVideoPlaylistElement({
+        url: server.url,
+        token: server.accessToken,
+        elementAttrs: {
+          stopTimestamp: 2
+        },
+        videoId: videoId,
+        playlistId: playlistUUID,
+        expectedStatus: 204
+      })
+    })
+  })
+
+  describe('When reordering elements of a playlist', function () {
+    let videoId3: number
+    let videoId4: number
+
+    before(async function () {
+      {
+        const res = await uploadVideo(server.url, server.accessToken, { name: 'video 3' })
+        videoId3 = res.body.video.id
+      }
+
+      {
+        const res = await uploadVideo(server.url, server.accessToken, { name: 'video 4' })
+        videoId4 = res.body.video.id
+      }
+
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: { videoId: videoId3 }
+      })
+
+      await addVideoInPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: { videoId: videoId4 }
+      })
+    })
+
+    it('Should fail with an unauthenticated user', async function () {
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: null,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 2
+        },
+        expectedStatus: 401
+      })
+    })
+
+    it('Should fail with the playlist of another user', async function () {
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: userAccessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 2
+        },
+        expectedStatus: 403
+      })
+    })
+
+    it('Should fail with an invalid playlist', async function () {
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: 'toto',
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 2
+        },
+        expectedStatus: 400
+      })
+
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: 42,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 2
+        },
+        expectedStatus: 404
+      })
+    })
+
+    it('Should fail with an invalid start position', async function () {
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: -1,
+          insertAfterPosition: 2
+        },
+        expectedStatus: 400
+      })
+
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 'toto' as any,
+          insertAfterPosition: 2
+        },
+        expectedStatus: 400
+      })
+
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 42,
+          insertAfterPosition: 2
+        },
+        expectedStatus: 400
+      })
+    })
+
+    it('Should fail with an invalid insert after position', async function () {
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 'toto' as any
+        },
+        expectedStatus: 400
+      })
+
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: -2
+        },
+        expectedStatus: 400
+      })
+
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 42
+        },
+        expectedStatus: 400
+      })
+    })
+
+    it('Should fail with an invalid reorder length', async function () {
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 2,
+          reorderLength: 'toto' as any
+        },
+        expectedStatus: 400
+      })
+
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 2,
+          reorderLength: -1
+        },
+        expectedStatus: 400
+      })
+
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 2,
+          reorderLength: 4
+        },
+        expectedStatus: 400
+      })
+    })
+
+    it('Succeed with the correct params', async function () {
+      await reorderVideosPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        playlistId: playlistUUID,
+        elementAttrs: {
+          startPosition: 1,
+          insertAfterPosition: 2,
+          reorderLength: 3
+        },
+        expectedStatus: 204
+      })
+    })
+  })
+
+  describe('When deleting an element in a playlist', function () {
+    it('Should fail with an unauthenticated user', async function () {
+      await removeVideoFromPlaylist({
+        url: server.url,
+        token: null,
+        videoId,
+        playlistId: playlistUUID,
+        expectedStatus: 401
+      })
+    })
+
+    it('Should fail with the playlist of another user', async function () {
+      await removeVideoFromPlaylist({
+        url: server.url,
+        token: userAccessToken,
+        videoId,
+        playlistId: playlistUUID,
+        expectedStatus: 403
+      })
+    })
+
+    it('Should fail with an unknown or incorrect playlist id', async function () {
+      await removeVideoFromPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        videoId,
+        playlistId: 'toto',
+        expectedStatus: 400
+      })
+
+      await removeVideoFromPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        videoId,
+        playlistId: 42,
+        expectedStatus: 404
+      })
+    })
+
+    it('Should fail with an unknown or incorrect video id', async function () {
+      await removeVideoFromPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        videoId: 'toto',
+        playlistId: playlistUUID,
+        expectedStatus: 400
+      })
+
+      await removeVideoFromPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        videoId: 42,
+        playlistId: playlistUUID,
+        expectedStatus: 404
+      })
+    })
+
+    it('Should fail with an unknown element', async function () {
+      await removeVideoFromPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        videoId: videoId2,
+        playlistId: playlistUUID,
+        expectedStatus: 404
+      })
+    })
+
+    it('Succeed with the correct params', async function () {
+      await removeVideoFromPlaylist({
+        url: server.url,
+        token: server.accessToken,
+        videoId: videoId,
+        playlistId: playlistUUID,
+        expectedStatus: 204
+      })
+    })
+  })
+
+  describe('When deleting a playlist', function () {
+    it('Should fail with an unknown playlist', async function () {
+      await deleteVideoPlaylist(server.url, server.accessToken, 42, 404)
+    })
+
+    it('Should fail with a playlist of another user', async function () {
+      await deleteVideoPlaylist(server.url, userAccessToken, playlistUUID, 403)
+    })
+
+    it('Should succeed with the correct params', async function () {
+      await deleteVideoPlaylist(server.url, server.accessToken, playlistUUID)
+    })
+  })
+
   after(async function () {
     killallServers([ server ])
 
index e998c8a3dafcbb445f0eb2c1abfe1f26c9120353..cc2f350690cec02db866a76680cacced5b4b5433 100644 (file)
@@ -1,9 +1,9 @@
 /* tslint:disable:no-unused-expression */
 
-import * as chai from 'chai'
 import 'mocha'
 import {
   createUser,
+  createVideoPlaylist,
   flushTests,
   killallServers,
   makeGetRequest,
@@ -13,15 +13,15 @@ import {
   userLogin
 } from '../../../../shared/utils'
 import { UserRole } from '../../../../shared/models/users'
+import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
 
-const expect = chai.expect
-
-async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) {
+async function testEndpoints (server: ServerInfo, token: string, filter: string, playlistUUID: string, statusCodeExpected: number) {
   const paths = [
     '/api/v1/video-channels/root_channel/videos',
     '/api/v1/accounts/root/videos',
     '/api/v1/videos',
-    '/api/v1/search/videos'
+    '/api/v1/search/videos',
+    '/api/v1/video-playlists/' + playlistUUID + '/videos'
   ]
 
   for (const path of paths) {
@@ -41,6 +41,7 @@ describe('Test videos filters', function () {
   let server: ServerInfo
   let userAccessToken: string
   let moderatorAccessToken: string
+  let playlistUUID: string
 
   // ---------------------------------------------------------------
 
@@ -68,28 +69,38 @@ describe('Test videos filters', function () {
       UserRole.MODERATOR
     )
     moderatorAccessToken = await userLogin(server, moderator)
+
+    const res = await createVideoPlaylist({
+      url: server.url,
+      token: server.accessToken,
+      playlistAttrs: {
+        displayName: 'super playlist',
+        privacy: VideoPlaylistPrivacy.PUBLIC
+      }
+    })
+    playlistUUID = res.body.videoPlaylist.uuid
   })
 
   describe('When setting a video filter', function () {
 
     it('Should fail with a bad filter', async function () {
-      await testEndpoints(server, server.accessToken, 'bad-filter', 400)
+      await testEndpoints(server, server.accessToken, 'bad-filter', playlistUUID, 400)
     })
 
     it('Should succeed with a good filter', async function () {
-      await testEndpoints(server, server.accessToken,'local', 200)
+      await testEndpoints(server, server.accessToken,'local', playlistUUID, 200)
     })
 
     it('Should fail to list all-local with a simple user', async function () {
-      await testEndpoints(server, userAccessToken, 'all-local', 401)
+      await testEndpoints(server, userAccessToken, 'all-local', playlistUUID, 401)
     })
 
     it('Should succeed to list all-local with a moderator', async function () {
-      await testEndpoints(server, moderatorAccessToken, 'all-local', 200)
+      await testEndpoints(server, moderatorAccessToken, 'all-local', playlistUUID, 200)
     })
 
     it('Should succeed to list all-local with an admin', async function () {
-      await testEndpoints(server, server.accessToken, 'all-local', 200)
+      await testEndpoints(server, server.accessToken, 'all-local', playlistUUID, 200)
     })
 
     // Because we cannot authenticate the user on the RSS endpoint
@@ -104,7 +115,7 @@ describe('Test videos filters', function () {
       })
     })
 
-    it('Should succed on the feeds endpoint with the local filter', async function () {
+    it('Should succeed on the feeds endpoint with the local filter', async function () {
       await makeGetRequest({
         url: server.url,
         path: '/feeds/videos.json',
index 778611fffbab81890a68338a2677383b624af3ac..fc5ffbad7b67f020dedf188b40c18dbf7e6521c7 100644 (file)
@@ -4,20 +4,26 @@ import * as chai from 'chai'
 import 'mocha'
 import { VideoDetails } from '../../../../shared/models/videos'
 import {
+  checkSegmentHash,
+  checkVideoFilesWereRemoved,
   doubleFollow,
   flushAndRunMultipleServers,
   getFollowingListPaginationAndSort,
   getVideo,
+  getVideoWithToken,
   immutableAssign,
-  killallServers, makeGetRequest,
+  killallServers,
+  makeGetRequest,
+  removeVideo,
+  reRunServer,
   root,
   ServerInfo,
-  setAccessTokensToServers, unfollow,
+  setAccessTokensToServers,
+  unfollow,
   uploadVideo,
   viewVideo,
   wait,
-  waitUntilLog,
-  checkVideoFilesWereRemoved, removeVideo, getVideoWithToken, reRunServer, checkSegmentHash
+  waitUntilLog
 } from '../../../../shared/utils'
 import { waitJobs } from '../../../../shared/utils/server/jobs'
 
index 386acbb960e9e47c5deaff2f84c3c6cfcc37365b..67a33fa3599415b6d9fc4822144f14491602629e 100644 (file)
@@ -2,10 +2,10 @@ import { VideoPlaylistPrivacy } from './video-playlist-privacy.model'
 
 export interface VideoPlaylistCreate {
   displayName: string
-  description: string
   privacy: VideoPlaylistPrivacy
 
+  description?: string
   videoChannelId?: number
 
-  thumbnailfile?: Blob
+  thumbnailfile?: any
 }
index 9bd56a8ca412f605c0e2d5bad9a6eb1891104d19..c31702892e0247bc89e35f7c73e274851500afd2 100644 (file)
@@ -1,4 +1,6 @@
 export interface VideoPlaylistElementCreate {
+  videoId: number
+
   startTimestamp?: number
   stopTimestamp?: number
 }
index c7a15c550e6485cd402808e696df0a1744c7dd64..0ff5bcb0fdcd88e782143ab1bca870ab0527f7bd 100644 (file)
@@ -2,9 +2,9 @@ import { VideoPlaylistPrivacy } from './video-playlist-privacy.model'
 
 export interface VideoPlaylistUpdate {
   displayName: string
-  description: string
   privacy: VideoPlaylistPrivacy
 
+  description?: string
   videoChannelId?: number
-  thumbnailfile?: Blob
+  thumbnailfile?: any
 }
index 156901372f03d2a9a015f2a710f4124bbe68ebd3..c09565d95fa53b464fa0b66f63acf75dc27fdc1f 100644 (file)
@@ -13,12 +13,13 @@ export * from './requests/requests'
 export * from './requests/check-api-params'
 export * from './server/servers'
 export * from './videos/services'
+export * from './videos/video-playlists'
 export * from './users/users'
 export * from './videos/video-abuses'
 export * from './videos/video-blacklist'
 export * from './videos/video-channels'
 export * from './videos/video-comments'
-export * from './videos/video-playlists'
+export * from './videos/video-streaming-playlists'
 export * from './videos/videos'
 export * from './videos/video-change-ownership'
 export * from './feeds/feeds'
index 5186d9c4f70512c3e89c2d4682867ad392233f75..21285688a4c0be1b52ec0619bca201ebb5432aca 100644 (file)
@@ -31,7 +31,7 @@ function getVideoPlaylist (url: string, playlistId: number | string, statusCodeE
   })
 }
 
-function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 200) {
+function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 204) {
   const path = '/api/v1/video-playlists/' + playlistId
 
   return makeDeleteRequest({
@@ -46,7 +46,7 @@ function createVideoPlaylist (options: {
   url: string,
   token: string,
   playlistAttrs: VideoPlaylistCreate,
-  expectedStatus: number
+  expectedStatus?: number
 }) {
   const path = '/api/v1/video-playlists/'
 
@@ -63,7 +63,7 @@ function createVideoPlaylist (options: {
     token: options.token,
     fields,
     attaches,
-    statusCodeExpected: options.expectedStatus
+    statusCodeExpected: options.expectedStatus || 200
   })
 }
 
@@ -71,9 +71,10 @@ function updateVideoPlaylist (options: {
   url: string,
   token: string,
   playlistAttrs: VideoPlaylistUpdate,
-  expectedStatus: number
+  playlistId: number | string,
+  expectedStatus?: number
 }) {
-  const path = '/api/v1/video-playlists/'
+  const path = '/api/v1/video-playlists/' + options.playlistId
 
   const fields = omit(options.playlistAttrs, 'thumbnailfile')
 
@@ -88,7 +89,7 @@ function updateVideoPlaylist (options: {
     token: options.token,
     fields,
     attaches,
-    statusCodeExpected: options.expectedStatus
+    statusCodeExpected: options.expectedStatus || 204
   })
 }
 
@@ -97,7 +98,7 @@ function addVideoInPlaylist (options: {
   token: string,
   playlistId: number | string,
   elementAttrs: VideoPlaylistElementCreate
-  expectedStatus: number
+  expectedStatus?: number
 }) {
   const path = '/api/v1/video-playlists/' + options.playlistId + '/videos'
 
@@ -106,7 +107,7 @@ function addVideoInPlaylist (options: {
     path,
     token: options.token,
     fields: options.elementAttrs,
-    statusCodeExpected: options.expectedStatus
+    statusCodeExpected: options.expectedStatus || 200
   })
 }
 
@@ -116,7 +117,7 @@ function updateVideoPlaylistElement (options: {
   playlistId: number | string,
   videoId: number | string,
   elementAttrs: VideoPlaylistElementUpdate,
-  expectedStatus: number
+  expectedStatus?: number
 }) {
   const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId
 
@@ -125,7 +126,7 @@ function updateVideoPlaylistElement (options: {
     path,
     token: options.token,
     fields: options.elementAttrs,
-    statusCodeExpected: options.expectedStatus
+    statusCodeExpected: options.expectedStatus || 204
   })
 }
 
@@ -142,7 +143,7 @@ function removeVideoFromPlaylist (options: {
     url: options.url,
     path,
     token: options.token,
-    statusCodeExpected: options.expectedStatus
+    statusCodeExpected: options.expectedStatus || 204
   })
 }
 
@@ -152,14 +153,14 @@ function reorderVideosPlaylist (options: {
   playlistId: number | string,
   elementAttrs: {
     startPosition: number,
-    insertAfter: number,
+    insertAfterPosition: number,
     reorderLength?: number
   },
   expectedStatus: number
 }) {
-  const path = '/api/v1/video-playlists/' + options.playlistId + '/videos'
+  const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder'
 
-  return makePutBodyRequest({
+  return makePostBodyRequest({
     url: options.url,
     path,
     token: options.token,
@@ -179,6 +180,7 @@ export {
   deleteVideoPlaylist,
 
   addVideoInPlaylist,
+  updateVideoPlaylistElement,
   removeVideoFromPlaylist,
 
   reorderVideosPlaylist