diff options
-rw-r--r-- | server/controllers/api/video-playlist.ts | 16 | ||||
-rw-r--r-- | server/helpers/custom-validators/video-channels.ts | 6 | ||||
-rw-r--r-- | server/lib/activitypub/index.ts | 1 | ||||
-rw-r--r-- | server/lib/activitypub/url.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-playlists.ts | 44 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 2 | ||||
-rw-r--r-- | server/models/video/video-playlist-element.ts | 3 | ||||
-rw-r--r-- | server/models/video/video-playlist.ts | 4 | ||||
-rw-r--r-- | server/tests/api/check-params/video-playlists.ts | 780 | ||||
-rw-r--r-- | server/tests/api/check-params/videos-filter.ts | 33 | ||||
-rw-r--r-- | server/tests/api/redundancy/redundancy.ts | 14 | ||||
-rw-r--r-- | shared/models/videos/playlist/video-playlist-create.model.ts | 4 | ||||
-rw-r--r-- | shared/models/videos/playlist/video-playlist-element-create.model.ts | 2 | ||||
-rw-r--r-- | shared/models/videos/playlist/video-playlist-update.model.ts | 4 | ||||
-rw-r--r-- | shared/utils/index.ts | 3 | ||||
-rw-r--r-- | shared/utils/videos/video-playlists.ts | 30 |
16 files changed, 883 insertions, 65 deletions
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index 709c58beb..e026b4d16 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -4,7 +4,7 @@ import { | |||
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 5 | asyncRetryTransactionMiddleware, |
6 | authenticate, | 6 | authenticate, |
7 | commonVideosFiltersValidator, | 7 | commonVideosFiltersValidator, optionalAuthenticate, |
8 | paginationValidator, | 8 | paginationValidator, |
9 | setDefaultPagination, | 9 | setDefaultPagination, |
10 | setDefaultSort | 10 | setDefaultSort |
@@ -31,12 +31,14 @@ import { processImage } from '../../helpers/image-utils' | |||
31 | import { join } from 'path' | 31 | import { join } from 'path' |
32 | import { UserModel } from '../../models/account/user' | 32 | import { UserModel } from '../../models/account/user' |
33 | import { | 33 | import { |
34 | getVideoPlaylistActivityPubUrl, | ||
35 | getVideoPlaylistElementActivityPubUrl, | ||
36 | sendCreateVideoPlaylist, | 34 | sendCreateVideoPlaylist, |
37 | sendDeleteVideoPlaylist, | 35 | sendDeleteVideoPlaylist, |
38 | sendUpdateVideoPlaylist | 36 | sendUpdateVideoPlaylist |
39 | } from '../../lib/activitypub' | 37 | } from '../../lib/activitypub/send' |
38 | import { | ||
39 | getVideoPlaylistActivityPubUrl, | ||
40 | getVideoPlaylistElementActivityPubUrl | ||
41 | } from '../../lib/activitypub/url' | ||
40 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' | 42 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' |
41 | import { VideoModel } from '../../models/video/video' | 43 | import { VideoModel } from '../../models/video/video' |
42 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | 44 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' |
@@ -85,6 +87,7 @@ videoPlaylistRouter.get('/:playlistId/videos', | |||
85 | asyncMiddleware(videoPlaylistsGetValidator), | 87 | asyncMiddleware(videoPlaylistsGetValidator), |
86 | paginationValidator, | 88 | paginationValidator, |
87 | setDefaultPagination, | 89 | setDefaultPagination, |
90 | optionalAuthenticate, | ||
88 | commonVideosFiltersValidator, | 91 | commonVideosFiltersValidator, |
89 | asyncMiddleware(getVideoPlaylistVideos) | 92 | asyncMiddleware(getVideoPlaylistVideos) |
90 | ) | 93 | ) |
@@ -95,7 +98,7 @@ videoPlaylistRouter.post('/:playlistId/videos', | |||
95 | asyncRetryTransactionMiddleware(addVideoInPlaylist) | 98 | asyncRetryTransactionMiddleware(addVideoInPlaylist) |
96 | ) | 99 | ) |
97 | 100 | ||
98 | videoPlaylistRouter.put('/:playlistId/videos', | 101 | videoPlaylistRouter.post('/:playlistId/videos/reorder', |
99 | authenticate, | 102 | authenticate, |
100 | asyncMiddleware(videoPlaylistsReorderVideosValidator), | 103 | asyncMiddleware(videoPlaylistsReorderVideosValidator), |
101 | asyncRetryTransactionMiddleware(reorderVideosPlaylist) | 104 | asyncRetryTransactionMiddleware(reorderVideosPlaylist) |
@@ -168,6 +171,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { | |||
168 | const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { | 171 | const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { |
169 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) | 172 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) |
170 | 173 | ||
174 | videoPlaylistCreated.OwnerAccount = user.Account | ||
171 | await sendCreateVideoPlaylist(videoPlaylistCreated, t) | 175 | await sendCreateVideoPlaylist(videoPlaylistCreated, t) |
172 | 176 | ||
173 | return videoPlaylistCreated | 177 | return videoPlaylistCreated |
@@ -349,7 +353,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons | |||
349 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | 353 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist |
350 | 354 | ||
351 | const start: number = req.body.startPosition | 355 | const start: number = req.body.startPosition |
352 | const insertAfter: number = req.body.insertAfter | 356 | const insertAfter: number = req.body.insertAfterPosition |
353 | const reorderLength: number = req.body.reorderLength || 1 | 357 | const reorderLength: number = req.body.reorderLength || 1 |
354 | 358 | ||
355 | if (start === insertAfter) { | 359 | if (start === insertAfter) { |
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts index cbf150e53..3792bbdcc 100644 --- a/server/helpers/custom-validators/video-channels.ts +++ b/server/helpers/custom-validators/video-channels.ts | |||
@@ -26,12 +26,12 @@ async function isLocalVideoChannelNameExist (name: string, res: express.Response | |||
26 | return processVideoChannelExist(videoChannel, res) | 26 | return processVideoChannelExist(videoChannel, res) |
27 | } | 27 | } |
28 | 28 | ||
29 | async function isVideoChannelIdExist (id: string, res: express.Response) { | 29 | async function isVideoChannelIdExist (id: number | string, res: express.Response) { |
30 | let videoChannel: VideoChannelModel | 30 | let videoChannel: VideoChannelModel |
31 | if (validator.isInt(id)) { | 31 | if (validator.isInt('' + id)) { |
32 | videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | 32 | videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) |
33 | } else { // UUID | 33 | } else { // UUID |
34 | videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount(id) | 34 | videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount('' + id) |
35 | } | 35 | } |
36 | 36 | ||
37 | return processVideoChannelExist(videoChannel, res) | 37 | return processVideoChannelExist(videoChannel, res) |
diff --git a/server/lib/activitypub/index.ts b/server/lib/activitypub/index.ts index 6906bf9d3..d8c7d83b7 100644 --- a/server/lib/activitypub/index.ts +++ b/server/lib/activitypub/index.ts | |||
@@ -2,6 +2,7 @@ export * from './process' | |||
2 | export * from './send' | 2 | export * from './send' |
3 | export * from './actor' | 3 | export * from './actor' |
4 | export * from './share' | 4 | export * from './share' |
5 | export * from './playlist' | ||
5 | export * from './videos' | 6 | export * from './videos' |
6 | export * from './video-comments' | 7 | export * from './video-comments' |
7 | export * from './video-rates' | 8 | export * from './video-rates' |
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index 00bbbba2d..c80b09436 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts | |||
@@ -5,10 +5,8 @@ import { VideoModel } from '../../models/video/video' | |||
5 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 5 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
6 | import { VideoCommentModel } from '../../models/video/video-comment' | 6 | import { VideoCommentModel } from '../../models/video/video-comment' |
7 | import { VideoFileModel } from '../../models/video/video-file' | 7 | import { VideoFileModel } from '../../models/video/video-file' |
8 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' | ||
9 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | 8 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' |
10 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 9 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
11 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | ||
12 | 10 | ||
13 | function getVideoActivityPubUrl (video: VideoModel) { | 11 | function getVideoActivityPubUrl (video: VideoModel) { |
14 | return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid | 12 | return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid |
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index ef8d0b851..0e97c8dc0 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -6,7 +6,7 @@ import { UserModel } from '../../../models/account/user' | |||
6 | import { areValidationErrors } from '../utils' | 6 | import { areValidationErrors } from '../utils' |
7 | import { isVideoExist, isVideoImage } from '../../../helpers/custom-validators/videos' | 7 | import { isVideoExist, isVideoImage } from '../../../helpers/custom-validators/videos' |
8 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 8 | import { CONSTRAINTS_FIELDS } from '../../../initializers' |
9 | import { isIdOrUUIDValid, toValueOrNull } from '../../../helpers/custom-validators/misc' | 9 | import { isIdOrUUIDValid, isUUIDValid, toValueOrNull } from '../../../helpers/custom-validators/misc' |
10 | import { | 10 | import { |
11 | isVideoPlaylistDescriptionValid, | 11 | isVideoPlaylistDescriptionValid, |
12 | isVideoPlaylistExist, | 12 | isVideoPlaylistExist, |
@@ -43,10 +43,19 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ | |||
43 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) | 43 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) |
44 | 44 | ||
45 | if (!await isVideoPlaylistExist(req.params.playlistId, res)) return cleanUpReqFiles(req) | 45 | if (!await isVideoPlaylistExist(req.params.playlistId, res)) return cleanUpReqFiles(req) |
46 | |||
47 | const videoPlaylist = res.locals.videoPlaylist | ||
48 | |||
46 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { | 49 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { |
47 | return cleanUpReqFiles(req) | 50 | return cleanUpReqFiles(req) |
48 | } | 51 | } |
49 | 52 | ||
53 | if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PRIVATE && req.body.privacy === VideoPlaylistPrivacy.PRIVATE) { | ||
54 | cleanUpReqFiles(req) | ||
55 | return res.status(409) | ||
56 | .json({ error: 'Cannot set "private" a video playlist that was not private.' }) | ||
57 | } | ||
58 | |||
50 | if (req.body.videoChannelId && !await isVideoChannelIdExist(req.body.videoChannelId, res)) return cleanUpReqFiles(req) | 59 | if (req.body.videoChannelId && !await isVideoChannelIdExist(req.body.videoChannelId, res)) return cleanUpReqFiles(req) |
51 | 60 | ||
52 | return next() | 61 | return next() |
@@ -83,6 +92,14 @@ const videoPlaylistsGetValidator = [ | |||
83 | if (!await isVideoPlaylistExist(req.params.playlistId, res)) return | 92 | if (!await isVideoPlaylistExist(req.params.playlistId, res)) return |
84 | 93 | ||
85 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | 94 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist |
95 | |||
96 | // Video is unlisted, check we used the uuid to fetch it | ||
97 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { | ||
98 | if (isUUIDValid(req.params.playlistId)) return next() | ||
99 | |||
100 | return res.status(404).end() | ||
101 | } | ||
102 | |||
86 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | 103 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { |
87 | await authenticatePromiseIfNeeded(req, res) | 104 | await authenticatePromiseIfNeeded(req, res) |
88 | 105 | ||
@@ -121,7 +138,7 @@ const videoPlaylistsAddVideoValidator = [ | |||
121 | if (areValidationErrors(req, res)) return | 138 | if (areValidationErrors(req, res)) return |
122 | 139 | ||
123 | if (!await isVideoPlaylistExist(req.params.playlistId, res)) return | 140 | if (!await isVideoPlaylistExist(req.params.playlistId, res)) return |
124 | if (!await isVideoExist(req.body.videoId, res, 'id')) return | 141 | if (!await isVideoExist(req.body.videoId, res, 'only-video')) return |
125 | 142 | ||
126 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | 143 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist |
127 | const video: VideoModel = res.locals.video | 144 | const video: VideoModel = res.locals.video |
@@ -161,7 +178,7 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ | |||
161 | if (areValidationErrors(req, res)) return | 178 | if (areValidationErrors(req, res)) return |
162 | 179 | ||
163 | if (!await isVideoPlaylistExist(req.params.playlistId, res)) return | 180 | if (!await isVideoPlaylistExist(req.params.playlistId, res)) return |
164 | if (!await isVideoExist(req.params.playlistId, res, 'id')) return | 181 | if (!await isVideoExist(req.params.videoId, res, 'id')) return |
165 | 182 | ||
166 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | 183 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist |
167 | const video: VideoModel = res.locals.video | 184 | const video: VideoModel = res.locals.video |
@@ -233,6 +250,27 @@ const videoPlaylistsReorderVideosValidator = [ | |||
233 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | 250 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist |
234 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return | 251 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return |
235 | 252 | ||
253 | const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id) | ||
254 | const startPosition: number = req.body.startPosition | ||
255 | const insertAfterPosition: number = req.body.insertAfterPosition | ||
256 | const reorderLength: number = req.body.reorderLength | ||
257 | |||
258 | if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) { | ||
259 | res.status(400) | ||
260 | .json({ error: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` }) | ||
261 | .end() | ||
262 | |||
263 | return | ||
264 | } | ||
265 | |||
266 | if (reorderLength && reorderLength + startPosition > nextPosition) { | ||
267 | res.status(400) | ||
268 | .json({ error: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` }) | ||
269 | .end() | ||
270 | |||
271 | return | ||
272 | } | ||
273 | |||
236 | return next() | 274 | return next() |
237 | } | 275 | } |
238 | ] | 276 | ] |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 112abf8cf..c077fb518 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -223,7 +223,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
223 | 223 | ||
224 | @HasMany(() => VideoPlaylistModel, { | 224 | @HasMany(() => VideoPlaylistModel, { |
225 | foreignKey: { | 225 | foreignKey: { |
226 | allowNull: false | 226 | allowNull: true |
227 | }, | 227 | }, |
228 | onDelete: 'cascade', | 228 | onDelete: 'cascade', |
229 | hooks: true | 229 | hooks: true |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index d76149d12..5530e0492 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -188,7 +188,8 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
188 | [Sequelize.Op.lte]: endPosition | 188 | [Sequelize.Op.lte]: endPosition |
189 | } | 189 | } |
190 | }, | 190 | }, |
191 | transaction | 191 | transaction, |
192 | validate: false // We use a literal to update the position | ||
192 | } | 193 | } |
193 | 194 | ||
194 | return VideoPlaylistElementModel.update({ position: Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) }, query) | 195 | return VideoPlaylistElementModel.update({ position: Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) }, query) |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index 93b8c2f58..397887ebf 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -197,7 +197,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
197 | 197 | ||
198 | @BelongsTo(() => VideoChannelModel, { | 198 | @BelongsTo(() => VideoChannelModel, { |
199 | foreignKey: { | 199 | foreignKey: { |
200 | allowNull: false | 200 | allowNull: true |
201 | }, | 201 | }, |
202 | onDelete: 'CASCADE' | 202 | onDelete: 'CASCADE' |
203 | }) | 203 | }) |
@@ -351,7 +351,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
351 | updatedAt: this.updatedAt, | 351 | updatedAt: this.updatedAt, |
352 | 352 | ||
353 | ownerAccount: this.OwnerAccount.toFormattedSummaryJSON(), | 353 | ownerAccount: this.OwnerAccount.toFormattedSummaryJSON(), |
354 | videoChannel: this.VideoChannel.toFormattedSummaryJSON() | 354 | videoChannel: this.VideoChannel ? this.VideoChannel.toFormattedSummaryJSON() : null |
355 | } | 355 | } |
356 | } | 356 | } |
357 | 357 | ||
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts index 7004badac..68fe362e9 100644 --- a/server/tests/api/check-params/video-playlists.ts +++ b/server/tests/api/check-params/video-playlists.ts | |||
@@ -1,35 +1,35 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* tslint:disable:no-unused-expression */ |
2 | 2 | ||
3 | import { omit } from 'lodash' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { join } from 'path' | ||
6 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' | ||
7 | import { | 4 | import { |
8 | createUser, | 5 | createUser, |
6 | createVideoPlaylist, | ||
7 | deleteVideoPlaylist, | ||
9 | flushTests, | 8 | flushTests, |
10 | getMyUserInformation, | 9 | getVideoPlaylist, |
11 | immutableAssign, | 10 | immutableAssign, |
12 | killallServers, | 11 | killallServers, |
13 | makeGetRequest, | 12 | makeGetRequest, |
14 | makePostBodyRequest, | ||
15 | makeUploadRequest, | ||
16 | runServer, | 13 | runServer, |
17 | ServerInfo, | 14 | ServerInfo, |
18 | setAccessTokensToServers, | 15 | setAccessTokensToServers, |
19 | updateCustomSubConfig, | 16 | updateVideoPlaylist, |
20 | userLogin | 17 | userLogin, |
18 | addVideoInPlaylist, uploadVideo, updateVideoPlaylistElement, removeVideoFromPlaylist, reorderVideosPlaylist | ||
21 | } from '../../../../shared/utils' | 19 | } from '../../../../shared/utils' |
22 | import { | 20 | import { |
23 | checkBadCountPagination, | 21 | checkBadCountPagination, |
24 | checkBadSortPagination, | 22 | checkBadSortPagination, |
25 | checkBadStartPagination | 23 | checkBadStartPagination |
26 | } from '../../../../shared/utils/requests/check-api-params' | 24 | } from '../../../../shared/utils/requests/check-api-params' |
27 | import { getMagnetURI, getYoutubeVideoUrl } from '../../../../shared/utils/videos/video-imports' | 25 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
28 | 26 | ||
29 | describe('Test video playlists API validator', function () { | 27 | describe('Test video playlists API validator', function () { |
30 | const path = '/api/v1/videos/video-playlists' | ||
31 | let server: ServerInfo | 28 | let server: ServerInfo |
32 | let userAccessToken = '' | 29 | let userAccessToken = '' |
30 | let playlistUUID: string | ||
31 | let videoId: number | ||
32 | let videoId2: number | ||
33 | 33 | ||
34 | // --------------------------------------------------------------- | 34 | // --------------------------------------------------------------- |
35 | 35 | ||
@@ -46,9 +46,31 @@ describe('Test video playlists API validator', function () { | |||
46 | const password = 'my super password' | 46 | const password = 'my super password' |
47 | await createUser(server.url, server.accessToken, username, password) | 47 | await createUser(server.url, server.accessToken, username, password) |
48 | userAccessToken = await userLogin(server, { username, password }) | 48 | userAccessToken = await userLogin(server, { username, password }) |
49 | |||
50 | { | ||
51 | const res = await uploadVideo(server.url, server.accessToken, { name: 'video 1' }) | ||
52 | videoId = res.body.video.id | ||
53 | } | ||
54 | |||
55 | { | ||
56 | const res = await uploadVideo(server.url, server.accessToken, { name: 'video 2' }) | ||
57 | videoId2 = res.body.video.id | ||
58 | } | ||
59 | |||
60 | { | ||
61 | const res = await createVideoPlaylist({ | ||
62 | url: server.url, | ||
63 | token: server.accessToken, | ||
64 | playlistAttrs: { | ||
65 | displayName: 'super playlist', | ||
66 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
67 | } | ||
68 | }) | ||
69 | playlistUUID = res.body.videoPlaylist.uuid | ||
70 | } | ||
49 | }) | 71 | }) |
50 | 72 | ||
51 | describe('When listing video playlists', function () { | 73 | describe('When listing playlists', function () { |
52 | const globalPath = '/api/v1/video-playlists' | 74 | const globalPath = '/api/v1/video-playlists' |
53 | const accountPath = '/api/v1/accounts/root/video-playlists' | 75 | const accountPath = '/api/v1/accounts/root/video-playlists' |
54 | const videoChannelPath = '/api/v1/video-channels/root_channel/video-playlists' | 76 | const videoChannelPath = '/api/v1/video-channels/root_channel/video-playlists' |
@@ -90,7 +112,7 @@ describe('Test video playlists API validator', function () { | |||
90 | }) | 112 | }) |
91 | }) | 113 | }) |
92 | 114 | ||
93 | describe('When listing videos of a playlist', async function () { | 115 | describe('When listing videos of a playlist', function () { |
94 | const path = '/api/v1/video-playlists' | 116 | const path = '/api/v1/video-playlists' |
95 | 117 | ||
96 | it('Should fail with a bad start pagination', async function () { | 118 | it('Should fail with a bad start pagination', async function () { |
@@ -101,11 +123,743 @@ describe('Test video playlists API validator', function () { | |||
101 | await checkBadCountPagination(server.url, path, server.accessToken) | 123 | await checkBadCountPagination(server.url, path, server.accessToken) |
102 | }) | 124 | }) |
103 | 125 | ||
104 | it('Should fail with an incorrect sort', async function () { | 126 | it('Should fail with a bad filter', async function () { |
105 | await checkBadSortPagination(server.url, path, server.accessToken) | 127 | await checkBadSortPagination(server.url, path, server.accessToken) |
106 | }) | 128 | }) |
107 | }) | 129 | }) |
108 | 130 | ||
131 | describe('When getting a video playlist', function () { | ||
132 | it('Should fail with a bad id or uuid', async function () { | ||
133 | await getVideoPlaylist(server.url, 'toto', 400) | ||
134 | }) | ||
135 | |||
136 | it('Should fail with an unknown playlist', async function () { | ||
137 | await getVideoPlaylist(server.url, 42, 404) | ||
138 | }) | ||
139 | |||
140 | it('Should fail to get an unlisted playlist with the number id', async function () { | ||
141 | const res = await createVideoPlaylist({ | ||
142 | url: server.url, | ||
143 | token: server.accessToken, | ||
144 | playlistAttrs: { | ||
145 | displayName: 'super playlist', | ||
146 | privacy: VideoPlaylistPrivacy.UNLISTED | ||
147 | } | ||
148 | }) | ||
149 | const playlist = res.body.videoPlaylist | ||
150 | |||
151 | await getVideoPlaylist(server.url, playlist.id, 404) | ||
152 | await getVideoPlaylist(server.url, playlist.uuid, 200) | ||
153 | }) | ||
154 | |||
155 | it('Should succeed with the correct params', async function () { | ||
156 | await getVideoPlaylist(server.url, playlistUUID, 200) | ||
157 | }) | ||
158 | }) | ||
159 | |||
160 | describe('When creating/updating a video playlist', function () { | ||
161 | |||
162 | it('Should fail with an unauthenticated user', async function () { | ||
163 | const baseParams = { | ||
164 | url: server.url, | ||
165 | token: null, | ||
166 | playlistAttrs: { | ||
167 | displayName: 'super playlist', | ||
168 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
169 | }, | ||
170 | expectedStatus: 401 | ||
171 | } | ||
172 | |||
173 | await createVideoPlaylist(baseParams) | ||
174 | await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID })) | ||
175 | }) | ||
176 | |||
177 | it('Should fail without displayName', async function () { | ||
178 | const baseParams = { | ||
179 | url: server.url, | ||
180 | token: server.accessToken, | ||
181 | playlistAttrs: { | ||
182 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
183 | } as any, | ||
184 | expectedStatus: 400 | ||
185 | } | ||
186 | |||
187 | await createVideoPlaylist(baseParams) | ||
188 | await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID })) | ||
189 | }) | ||
190 | |||
191 | it('Should fail with an incorrect display name', async function () { | ||
192 | const baseParams = { | ||
193 | url: server.url, | ||
194 | token: server.accessToken, | ||
195 | playlistAttrs: { | ||
196 | displayName: 's'.repeat(300), | ||
197 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
198 | }, | ||
199 | expectedStatus: 400 | ||
200 | } | ||
201 | |||
202 | await createVideoPlaylist(baseParams) | ||
203 | await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID })) | ||
204 | }) | ||
205 | |||
206 | it('Should fail with an incorrect description', async function () { | ||
207 | const baseParams = { | ||
208 | url: server.url, | ||
209 | token: server.accessToken, | ||
210 | playlistAttrs: { | ||
211 | displayName: 'display name', | ||
212 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
213 | description: 't' | ||
214 | }, | ||
215 | expectedStatus: 400 | ||
216 | } | ||
217 | |||
218 | await createVideoPlaylist(baseParams) | ||
219 | await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID })) | ||
220 | }) | ||
221 | |||
222 | it('Should fail with an incorrect privacy', async function () { | ||
223 | const baseParams = { | ||
224 | url: server.url, | ||
225 | token: server.accessToken, | ||
226 | playlistAttrs: { | ||
227 | displayName: 'display name', | ||
228 | privacy: 45 | ||
229 | } as any, | ||
230 | expectedStatus: 400 | ||
231 | } | ||
232 | |||
233 | await createVideoPlaylist(baseParams) | ||
234 | await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID })) | ||
235 | }) | ||
236 | |||
237 | it('Should fail with an unknown video channel id', async function () { | ||
238 | const baseParams = { | ||
239 | url: server.url, | ||
240 | token: server.accessToken, | ||
241 | playlistAttrs: { | ||
242 | displayName: 'display name', | ||
243 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
244 | videoChannelId: 42 | ||
245 | }, | ||
246 | expectedStatus: 404 | ||
247 | } | ||
248 | |||
249 | await createVideoPlaylist(baseParams) | ||
250 | await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID })) | ||
251 | }) | ||
252 | |||
253 | it('Should fail with an incorrect thumbnail file', async function () { | ||
254 | const baseParams = { | ||
255 | url: server.url, | ||
256 | token: server.accessToken, | ||
257 | playlistAttrs: { | ||
258 | displayName: 'display name', | ||
259 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
260 | thumbnailfile: 'avatar.png' | ||
261 | }, | ||
262 | expectedStatus: 400 | ||
263 | } | ||
264 | |||
265 | await createVideoPlaylist(baseParams) | ||
266 | await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID })) | ||
267 | }) | ||
268 | |||
269 | it('Should fail with an unknown playlist to update', async function () { | ||
270 | await updateVideoPlaylist({ | ||
271 | url: server.url, | ||
272 | token: server.accessToken, | ||
273 | playlistId: 42, | ||
274 | playlistAttrs: { | ||
275 | displayName: 'display name', | ||
276 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
277 | }, | ||
278 | expectedStatus: 404 | ||
279 | }) | ||
280 | }) | ||
281 | |||
282 | it('Should fail to update a playlist of another user', async function () { | ||
283 | await updateVideoPlaylist({ | ||
284 | url: server.url, | ||
285 | token: userAccessToken, | ||
286 | playlistId: playlistUUID, | ||
287 | playlistAttrs: { | ||
288 | displayName: 'display name', | ||
289 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
290 | }, | ||
291 | expectedStatus: 403 | ||
292 | }) | ||
293 | }) | ||
294 | |||
295 | it('Should fail to update to private a public/unlisted playlist', async function () { | ||
296 | const res = await createVideoPlaylist({ | ||
297 | url: server.url, | ||
298 | token: server.accessToken, | ||
299 | playlistAttrs: { | ||
300 | displayName: 'super playlist', | ||
301 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
302 | } | ||
303 | }) | ||
304 | const playlist = res.body.videoPlaylist | ||
305 | |||
306 | await updateVideoPlaylist({ | ||
307 | url: server.url, | ||
308 | token: server.accessToken, | ||
309 | playlistId: playlist.id, | ||
310 | playlistAttrs: { | ||
311 | displayName: 'display name', | ||
312 | privacy: VideoPlaylistPrivacy.PRIVATE | ||
313 | }, | ||
314 | expectedStatus: 409 | ||
315 | }) | ||
316 | }) | ||
317 | |||
318 | it('Should succeed with the correct params', async function () { | ||
319 | const baseParams = { | ||
320 | url: server.url, | ||
321 | token: server.accessToken, | ||
322 | playlistAttrs: { | ||
323 | displayName: 'display name', | ||
324 | privacy: VideoPlaylistPrivacy.UNLISTED, | ||
325 | thumbnailfile: 'thumbnail.jpg' | ||
326 | } | ||
327 | } | ||
328 | |||
329 | await createVideoPlaylist(baseParams) | ||
330 | await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID })) | ||
331 | }) | ||
332 | }) | ||
333 | |||
334 | describe('When adding an element in a playlist', function () { | ||
335 | it('Should fail with an unauthenticated user', async function () { | ||
336 | await addVideoInPlaylist({ | ||
337 | url: server.url, | ||
338 | token: null, | ||
339 | elementAttrs: { | ||
340 | videoId: videoId | ||
341 | }, | ||
342 | playlistId: playlistUUID, | ||
343 | expectedStatus: 401 | ||
344 | }) | ||
345 | }) | ||
346 | |||
347 | it('Should fail with the playlist of another user', async function () { | ||
348 | await addVideoInPlaylist({ | ||
349 | url: server.url, | ||
350 | token: userAccessToken, | ||
351 | elementAttrs: { | ||
352 | videoId: videoId | ||
353 | }, | ||
354 | playlistId: playlistUUID, | ||
355 | expectedStatus: 403 | ||
356 | }) | ||
357 | }) | ||
358 | |||
359 | it('Should fail with an unknown or incorrect playlist id', async function () { | ||
360 | await addVideoInPlaylist({ | ||
361 | url: server.url, | ||
362 | token: server.accessToken, | ||
363 | elementAttrs: { | ||
364 | videoId: videoId | ||
365 | }, | ||
366 | playlistId: 'toto', | ||
367 | expectedStatus: 400 | ||
368 | }) | ||
369 | |||
370 | await addVideoInPlaylist({ | ||
371 | url: server.url, | ||
372 | token: server.accessToken, | ||
373 | elementAttrs: { | ||
374 | videoId: videoId | ||
375 | }, | ||
376 | playlistId: 42, | ||
377 | expectedStatus: 404 | ||
378 | }) | ||
379 | }) | ||
380 | |||
381 | it('Should fail with an unknown or incorrect video id', async function () { | ||
382 | await addVideoInPlaylist({ | ||
383 | url: server.url, | ||
384 | token: server.accessToken, | ||
385 | elementAttrs: { | ||
386 | videoId: 'toto' as any | ||
387 | }, | ||
388 | playlistId: playlistUUID, | ||
389 | expectedStatus: 400 | ||
390 | }) | ||
391 | |||
392 | await addVideoInPlaylist({ | ||
393 | url: server.url, | ||
394 | token: server.accessToken, | ||
395 | elementAttrs: { | ||
396 | videoId: 42 | ||
397 | }, | ||
398 | playlistId: playlistUUID, | ||
399 | expectedStatus: 404 | ||
400 | }) | ||
401 | }) | ||
402 | |||
403 | it('Should fail with a bad start/stop timestamp', async function () { | ||
404 | await addVideoInPlaylist({ | ||
405 | url: server.url, | ||
406 | token: server.accessToken, | ||
407 | elementAttrs: { | ||
408 | videoId: videoId, | ||
409 | startTimestamp: -42 | ||
410 | }, | ||
411 | playlistId: playlistUUID, | ||
412 | expectedStatus: 400 | ||
413 | }) | ||
414 | |||
415 | await addVideoInPlaylist({ | ||
416 | url: server.url, | ||
417 | token: server.accessToken, | ||
418 | elementAttrs: { | ||
419 | videoId: videoId, | ||
420 | stopTimestamp: 'toto' as any | ||
421 | }, | ||
422 | playlistId: playlistUUID, | ||
423 | expectedStatus: 400 | ||
424 | }) | ||
425 | }) | ||
426 | |||
427 | it('Succeed with the correct params', async function () { | ||
428 | await addVideoInPlaylist({ | ||
429 | url: server.url, | ||
430 | token: server.accessToken, | ||
431 | elementAttrs: { | ||
432 | videoId: videoId, | ||
433 | stopTimestamp: 3 | ||
434 | }, | ||
435 | playlistId: playlistUUID, | ||
436 | expectedStatus: 200 | ||
437 | }) | ||
438 | }) | ||
439 | |||
440 | it('Should fail if the video was already added in the playlist', async function () { | ||
441 | await addVideoInPlaylist({ | ||
442 | url: server.url, | ||
443 | token: server.accessToken, | ||
444 | elementAttrs: { | ||
445 | videoId: videoId, | ||
446 | stopTimestamp: 3 | ||
447 | }, | ||
448 | playlistId: playlistUUID, | ||
449 | expectedStatus: 409 | ||
450 | }) | ||
451 | }) | ||
452 | }) | ||
453 | |||
454 | describe('When updating an element in a playlist', function () { | ||
455 | it('Should fail with an unauthenticated user', async function () { | ||
456 | await updateVideoPlaylistElement({ | ||
457 | url: server.url, | ||
458 | token: null, | ||
459 | elementAttrs: { }, | ||
460 | videoId: videoId, | ||
461 | playlistId: playlistUUID, | ||
462 | expectedStatus: 401 | ||
463 | }) | ||
464 | }) | ||
465 | |||
466 | it('Should fail with the playlist of another user', async function () { | ||
467 | await updateVideoPlaylistElement({ | ||
468 | url: server.url, | ||
469 | token: userAccessToken, | ||
470 | elementAttrs: { }, | ||
471 | videoId: videoId, | ||
472 | playlistId: playlistUUID, | ||
473 | expectedStatus: 403 | ||
474 | }) | ||
475 | }) | ||
476 | |||
477 | it('Should fail with an unknown or incorrect playlist id', async function () { | ||
478 | await updateVideoPlaylistElement({ | ||
479 | url: server.url, | ||
480 | token: server.accessToken, | ||
481 | elementAttrs: { }, | ||
482 | videoId: videoId, | ||
483 | playlistId: 'toto', | ||
484 | expectedStatus: 400 | ||
485 | }) | ||
486 | |||
487 | await updateVideoPlaylistElement({ | ||
488 | url: server.url, | ||
489 | token: server.accessToken, | ||
490 | elementAttrs: { }, | ||
491 | videoId: videoId, | ||
492 | playlistId: 42, | ||
493 | expectedStatus: 404 | ||
494 | }) | ||
495 | }) | ||
496 | |||
497 | it('Should fail with an unknown or incorrect video id', async function () { | ||
498 | await updateVideoPlaylistElement({ | ||
499 | url: server.url, | ||
500 | token: server.accessToken, | ||
501 | elementAttrs: { }, | ||
502 | videoId: 'toto', | ||
503 | playlistId: playlistUUID, | ||
504 | expectedStatus: 400 | ||
505 | }) | ||
506 | |||
507 | await updateVideoPlaylistElement({ | ||
508 | url: server.url, | ||
509 | token: server.accessToken, | ||
510 | elementAttrs: { }, | ||
511 | videoId: 42, | ||
512 | playlistId: playlistUUID, | ||
513 | expectedStatus: 404 | ||
514 | }) | ||
515 | }) | ||
516 | |||
517 | it('Should fail with a bad start/stop timestamp', async function () { | ||
518 | await updateVideoPlaylistElement({ | ||
519 | url: server.url, | ||
520 | token: server.accessToken, | ||
521 | elementAttrs: { | ||
522 | startTimestamp: 'toto' as any | ||
523 | }, | ||
524 | videoId: videoId, | ||
525 | playlistId: playlistUUID, | ||
526 | expectedStatus: 400 | ||
527 | }) | ||
528 | |||
529 | await updateVideoPlaylistElement({ | ||
530 | url: server.url, | ||
531 | token: server.accessToken, | ||
532 | elementAttrs: { | ||
533 | stopTimestamp: -42 | ||
534 | }, | ||
535 | videoId: videoId, | ||
536 | playlistId: playlistUUID, | ||
537 | expectedStatus: 400 | ||
538 | }) | ||
539 | }) | ||
540 | |||
541 | it('Should fail with an unknown element', async function () { | ||
542 | await updateVideoPlaylistElement({ | ||
543 | url: server.url, | ||
544 | token: server.accessToken, | ||
545 | elementAttrs: { | ||
546 | stopTimestamp: 2 | ||
547 | }, | ||
548 | videoId: videoId2, | ||
549 | playlistId: playlistUUID, | ||
550 | expectedStatus: 404 | ||
551 | }) | ||
552 | }) | ||
553 | |||
554 | it('Succeed with the correct params', async function () { | ||
555 | await updateVideoPlaylistElement({ | ||
556 | url: server.url, | ||
557 | token: server.accessToken, | ||
558 | elementAttrs: { | ||
559 | stopTimestamp: 2 | ||
560 | }, | ||
561 | videoId: videoId, | ||
562 | playlistId: playlistUUID, | ||
563 | expectedStatus: 204 | ||
564 | }) | ||
565 | }) | ||
566 | }) | ||
567 | |||
568 | describe('When reordering elements of a playlist', function () { | ||
569 | let videoId3: number | ||
570 | let videoId4: number | ||
571 | |||
572 | before(async function () { | ||
573 | { | ||
574 | const res = await uploadVideo(server.url, server.accessToken, { name: 'video 3' }) | ||
575 | videoId3 = res.body.video.id | ||
576 | } | ||
577 | |||
578 | { | ||
579 | const res = await uploadVideo(server.url, server.accessToken, { name: 'video 4' }) | ||
580 | videoId4 = res.body.video.id | ||
581 | } | ||
582 | |||
583 | await addVideoInPlaylist({ | ||
584 | url: server.url, | ||
585 | token: server.accessToken, | ||
586 | playlistId: playlistUUID, | ||
587 | elementAttrs: { videoId: videoId3 } | ||
588 | }) | ||
589 | |||
590 | await addVideoInPlaylist({ | ||
591 | url: server.url, | ||
592 | token: server.accessToken, | ||
593 | playlistId: playlistUUID, | ||
594 | elementAttrs: { videoId: videoId4 } | ||
595 | }) | ||
596 | }) | ||
597 | |||
598 | it('Should fail with an unauthenticated user', async function () { | ||
599 | await reorderVideosPlaylist({ | ||
600 | url: server.url, | ||
601 | token: null, | ||
602 | playlistId: playlistUUID, | ||
603 | elementAttrs: { | ||
604 | startPosition: 1, | ||
605 | insertAfterPosition: 2 | ||
606 | }, | ||
607 | expectedStatus: 401 | ||
608 | }) | ||
609 | }) | ||
610 | |||
611 | it('Should fail with the playlist of another user', async function () { | ||
612 | await reorderVideosPlaylist({ | ||
613 | url: server.url, | ||
614 | token: userAccessToken, | ||
615 | playlistId: playlistUUID, | ||
616 | elementAttrs: { | ||
617 | startPosition: 1, | ||
618 | insertAfterPosition: 2 | ||
619 | }, | ||
620 | expectedStatus: 403 | ||
621 | }) | ||
622 | }) | ||
623 | |||
624 | it('Should fail with an invalid playlist', async function () { | ||
625 | await reorderVideosPlaylist({ | ||
626 | url: server.url, | ||
627 | token: server.accessToken, | ||
628 | playlistId: 'toto', | ||
629 | elementAttrs: { | ||
630 | startPosition: 1, | ||
631 | insertAfterPosition: 2 | ||
632 | }, | ||
633 | expectedStatus: 400 | ||
634 | }) | ||
635 | |||
636 | await reorderVideosPlaylist({ | ||
637 | url: server.url, | ||
638 | token: server.accessToken, | ||
639 | playlistId: 42, | ||
640 | elementAttrs: { | ||
641 | startPosition: 1, | ||
642 | insertAfterPosition: 2 | ||
643 | }, | ||
644 | expectedStatus: 404 | ||
645 | }) | ||
646 | }) | ||
647 | |||
648 | it('Should fail with an invalid start position', async function () { | ||
649 | await reorderVideosPlaylist({ | ||
650 | url: server.url, | ||
651 | token: server.accessToken, | ||
652 | playlistId: playlistUUID, | ||
653 | elementAttrs: { | ||
654 | startPosition: -1, | ||
655 | insertAfterPosition: 2 | ||
656 | }, | ||
657 | expectedStatus: 400 | ||
658 | }) | ||
659 | |||
660 | await reorderVideosPlaylist({ | ||
661 | url: server.url, | ||
662 | token: server.accessToken, | ||
663 | playlistId: playlistUUID, | ||
664 | elementAttrs: { | ||
665 | startPosition: 'toto' as any, | ||
666 | insertAfterPosition: 2 | ||
667 | }, | ||
668 | expectedStatus: 400 | ||
669 | }) | ||
670 | |||
671 | await reorderVideosPlaylist({ | ||
672 | url: server.url, | ||
673 | token: server.accessToken, | ||
674 | playlistId: playlistUUID, | ||
675 | elementAttrs: { | ||
676 | startPosition: 42, | ||
677 | insertAfterPosition: 2 | ||
678 | }, | ||
679 | expectedStatus: 400 | ||
680 | }) | ||
681 | }) | ||
682 | |||
683 | it('Should fail with an invalid insert after position', async function () { | ||
684 | await reorderVideosPlaylist({ | ||
685 | url: server.url, | ||
686 | token: server.accessToken, | ||
687 | playlistId: playlistUUID, | ||
688 | elementAttrs: { | ||
689 | startPosition: 1, | ||
690 | insertAfterPosition: 'toto' as any | ||
691 | }, | ||
692 | expectedStatus: 400 | ||
693 | }) | ||
694 | |||
695 | await reorderVideosPlaylist({ | ||
696 | url: server.url, | ||
697 | token: server.accessToken, | ||
698 | playlistId: playlistUUID, | ||
699 | elementAttrs: { | ||
700 | startPosition: 1, | ||
701 | insertAfterPosition: -2 | ||
702 | }, | ||
703 | expectedStatus: 400 | ||
704 | }) | ||
705 | |||
706 | await reorderVideosPlaylist({ | ||
707 | url: server.url, | ||
708 | token: server.accessToken, | ||
709 | playlistId: playlistUUID, | ||
710 | elementAttrs: { | ||
711 | startPosition: 1, | ||
712 | insertAfterPosition: 42 | ||
713 | }, | ||
714 | expectedStatus: 400 | ||
715 | }) | ||
716 | }) | ||
717 | |||
718 | it('Should fail with an invalid reorder length', async function () { | ||
719 | await reorderVideosPlaylist({ | ||
720 | url: server.url, | ||
721 | token: server.accessToken, | ||
722 | playlistId: playlistUUID, | ||
723 | elementAttrs: { | ||
724 | startPosition: 1, | ||
725 | insertAfterPosition: 2, | ||
726 | reorderLength: 'toto' as any | ||
727 | }, | ||
728 | expectedStatus: 400 | ||
729 | }) | ||
730 | |||
731 | await reorderVideosPlaylist({ | ||
732 | url: server.url, | ||
733 | token: server.accessToken, | ||
734 | playlistId: playlistUUID, | ||
735 | elementAttrs: { | ||
736 | startPosition: 1, | ||
737 | insertAfterPosition: 2, | ||
738 | reorderLength: -1 | ||
739 | }, | ||
740 | expectedStatus: 400 | ||
741 | }) | ||
742 | |||
743 | await reorderVideosPlaylist({ | ||
744 | url: server.url, | ||
745 | token: server.accessToken, | ||
746 | playlistId: playlistUUID, | ||
747 | elementAttrs: { | ||
748 | startPosition: 1, | ||
749 | insertAfterPosition: 2, | ||
750 | reorderLength: 4 | ||
751 | }, | ||
752 | expectedStatus: 400 | ||
753 | }) | ||
754 | }) | ||
755 | |||
756 | it('Succeed with the correct params', async function () { | ||
757 | await reorderVideosPlaylist({ | ||
758 | url: server.url, | ||
759 | token: server.accessToken, | ||
760 | playlistId: playlistUUID, | ||
761 | elementAttrs: { | ||
762 | startPosition: 1, | ||
763 | insertAfterPosition: 2, | ||
764 | reorderLength: 3 | ||
765 | }, | ||
766 | expectedStatus: 204 | ||
767 | }) | ||
768 | }) | ||
769 | }) | ||
770 | |||
771 | describe('When deleting an element in a playlist', function () { | ||
772 | it('Should fail with an unauthenticated user', async function () { | ||
773 | await removeVideoFromPlaylist({ | ||
774 | url: server.url, | ||
775 | token: null, | ||
776 | videoId, | ||
777 | playlistId: playlistUUID, | ||
778 | expectedStatus: 401 | ||
779 | }) | ||
780 | }) | ||
781 | |||
782 | it('Should fail with the playlist of another user', async function () { | ||
783 | await removeVideoFromPlaylist({ | ||
784 | url: server.url, | ||
785 | token: userAccessToken, | ||
786 | videoId, | ||
787 | playlistId: playlistUUID, | ||
788 | expectedStatus: 403 | ||
789 | }) | ||
790 | }) | ||
791 | |||
792 | it('Should fail with an unknown or incorrect playlist id', async function () { | ||
793 | await removeVideoFromPlaylist({ | ||
794 | url: server.url, | ||
795 | token: server.accessToken, | ||
796 | videoId, | ||
797 | playlistId: 'toto', | ||
798 | expectedStatus: 400 | ||
799 | }) | ||
800 | |||
801 | await removeVideoFromPlaylist({ | ||
802 | url: server.url, | ||
803 | token: server.accessToken, | ||
804 | videoId, | ||
805 | playlistId: 42, | ||
806 | expectedStatus: 404 | ||
807 | }) | ||
808 | }) | ||
809 | |||
810 | it('Should fail with an unknown or incorrect video id', async function () { | ||
811 | await removeVideoFromPlaylist({ | ||
812 | url: server.url, | ||
813 | token: server.accessToken, | ||
814 | videoId: 'toto', | ||
815 | playlistId: playlistUUID, | ||
816 | expectedStatus: 400 | ||
817 | }) | ||
818 | |||
819 | await removeVideoFromPlaylist({ | ||
820 | url: server.url, | ||
821 | token: server.accessToken, | ||
822 | videoId: 42, | ||
823 | playlistId: playlistUUID, | ||
824 | expectedStatus: 404 | ||
825 | }) | ||
826 | }) | ||
827 | |||
828 | it('Should fail with an unknown element', async function () { | ||
829 | await removeVideoFromPlaylist({ | ||
830 | url: server.url, | ||
831 | token: server.accessToken, | ||
832 | videoId: videoId2, | ||
833 | playlistId: playlistUUID, | ||
834 | expectedStatus: 404 | ||
835 | }) | ||
836 | }) | ||
837 | |||
838 | it('Succeed with the correct params', async function () { | ||
839 | await removeVideoFromPlaylist({ | ||
840 | url: server.url, | ||
841 | token: server.accessToken, | ||
842 | videoId: videoId, | ||
843 | playlistId: playlistUUID, | ||
844 | expectedStatus: 204 | ||
845 | }) | ||
846 | }) | ||
847 | }) | ||
848 | |||
849 | describe('When deleting a playlist', function () { | ||
850 | it('Should fail with an unknown playlist', async function () { | ||
851 | await deleteVideoPlaylist(server.url, server.accessToken, 42, 404) | ||
852 | }) | ||
853 | |||
854 | it('Should fail with a playlist of another user', async function () { | ||
855 | await deleteVideoPlaylist(server.url, userAccessToken, playlistUUID, 403) | ||
856 | }) | ||
857 | |||
858 | it('Should succeed with the correct params', async function () { | ||
859 | await deleteVideoPlaylist(server.url, server.accessToken, playlistUUID) | ||
860 | }) | ||
861 | }) | ||
862 | |||
109 | after(async function () { | 863 | after(async function () { |
110 | killallServers([ server ]) | 864 | killallServers([ server ]) |
111 | 865 | ||
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts index e998c8a3d..cc2f35069 100644 --- a/server/tests/api/check-params/videos-filter.ts +++ b/server/tests/api/check-params/videos-filter.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* tslint:disable:no-unused-expression */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { | 4 | import { |
6 | createUser, | 5 | createUser, |
6 | createVideoPlaylist, | ||
7 | flushTests, | 7 | flushTests, |
8 | killallServers, | 8 | killallServers, |
9 | makeGetRequest, | 9 | makeGetRequest, |
@@ -13,15 +13,15 @@ import { | |||
13 | userLogin | 13 | userLogin |
14 | } from '../../../../shared/utils' | 14 | } from '../../../../shared/utils' |
15 | import { UserRole } from '../../../../shared/models/users' | 15 | import { UserRole } from '../../../../shared/models/users' |
16 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
16 | 17 | ||
17 | const expect = chai.expect | 18 | async function testEndpoints (server: ServerInfo, token: string, filter: string, playlistUUID: string, statusCodeExpected: number) { |
18 | |||
19 | async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) { | ||
20 | const paths = [ | 19 | const paths = [ |
21 | '/api/v1/video-channels/root_channel/videos', | 20 | '/api/v1/video-channels/root_channel/videos', |
22 | '/api/v1/accounts/root/videos', | 21 | '/api/v1/accounts/root/videos', |
23 | '/api/v1/videos', | 22 | '/api/v1/videos', |
24 | '/api/v1/search/videos' | 23 | '/api/v1/search/videos', |
24 | '/api/v1/video-playlists/' + playlistUUID + '/videos' | ||
25 | ] | 25 | ] |
26 | 26 | ||
27 | for (const path of paths) { | 27 | for (const path of paths) { |
@@ -41,6 +41,7 @@ describe('Test videos filters', function () { | |||
41 | let server: ServerInfo | 41 | let server: ServerInfo |
42 | let userAccessToken: string | 42 | let userAccessToken: string |
43 | let moderatorAccessToken: string | 43 | let moderatorAccessToken: string |
44 | let playlistUUID: string | ||
44 | 45 | ||
45 | // --------------------------------------------------------------- | 46 | // --------------------------------------------------------------- |
46 | 47 | ||
@@ -68,28 +69,38 @@ describe('Test videos filters', function () { | |||
68 | UserRole.MODERATOR | 69 | UserRole.MODERATOR |
69 | ) | 70 | ) |
70 | moderatorAccessToken = await userLogin(server, moderator) | 71 | moderatorAccessToken = await userLogin(server, moderator) |
72 | |||
73 | const res = await createVideoPlaylist({ | ||
74 | url: server.url, | ||
75 | token: server.accessToken, | ||
76 | playlistAttrs: { | ||
77 | displayName: 'super playlist', | ||
78 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
79 | } | ||
80 | }) | ||
81 | playlistUUID = res.body.videoPlaylist.uuid | ||
71 | }) | 82 | }) |
72 | 83 | ||
73 | describe('When setting a video filter', function () { | 84 | describe('When setting a video filter', function () { |
74 | 85 | ||
75 | it('Should fail with a bad filter', async function () { | 86 | it('Should fail with a bad filter', async function () { |
76 | await testEndpoints(server, server.accessToken, 'bad-filter', 400) | 87 | await testEndpoints(server, server.accessToken, 'bad-filter', playlistUUID, 400) |
77 | }) | 88 | }) |
78 | 89 | ||
79 | it('Should succeed with a good filter', async function () { | 90 | it('Should succeed with a good filter', async function () { |
80 | await testEndpoints(server, server.accessToken,'local', 200) | 91 | await testEndpoints(server, server.accessToken,'local', playlistUUID, 200) |
81 | }) | 92 | }) |
82 | 93 | ||
83 | it('Should fail to list all-local with a simple user', async function () { | 94 | it('Should fail to list all-local with a simple user', async function () { |
84 | await testEndpoints(server, userAccessToken, 'all-local', 401) | 95 | await testEndpoints(server, userAccessToken, 'all-local', playlistUUID, 401) |
85 | }) | 96 | }) |
86 | 97 | ||
87 | it('Should succeed to list all-local with a moderator', async function () { | 98 | it('Should succeed to list all-local with a moderator', async function () { |
88 | await testEndpoints(server, moderatorAccessToken, 'all-local', 200) | 99 | await testEndpoints(server, moderatorAccessToken, 'all-local', playlistUUID, 200) |
89 | }) | 100 | }) |
90 | 101 | ||
91 | it('Should succeed to list all-local with an admin', async function () { | 102 | it('Should succeed to list all-local with an admin', async function () { |
92 | await testEndpoints(server, server.accessToken, 'all-local', 200) | 103 | await testEndpoints(server, server.accessToken, 'all-local', playlistUUID, 200) |
93 | }) | 104 | }) |
94 | 105 | ||
95 | // Because we cannot authenticate the user on the RSS endpoint | 106 | // Because we cannot authenticate the user on the RSS endpoint |
@@ -104,7 +115,7 @@ describe('Test videos filters', function () { | |||
104 | }) | 115 | }) |
105 | }) | 116 | }) |
106 | 117 | ||
107 | it('Should succed on the feeds endpoint with the local filter', async function () { | 118 | it('Should succeed on the feeds endpoint with the local filter', async function () { |
108 | await makeGetRequest({ | 119 | await makeGetRequest({ |
109 | url: server.url, | 120 | url: server.url, |
110 | path: '/feeds/videos.json', | 121 | path: '/feeds/videos.json', |
diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts index 778611fff..fc5ffbad7 100644 --- a/server/tests/api/redundancy/redundancy.ts +++ b/server/tests/api/redundancy/redundancy.ts | |||
@@ -4,20 +4,26 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoDetails } from '../../../../shared/models/videos' | 5 | import { VideoDetails } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | checkSegmentHash, | ||
8 | checkVideoFilesWereRemoved, | ||
7 | doubleFollow, | 9 | doubleFollow, |
8 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
9 | getFollowingListPaginationAndSort, | 11 | getFollowingListPaginationAndSort, |
10 | getVideo, | 12 | getVideo, |
13 | getVideoWithToken, | ||
11 | immutableAssign, | 14 | immutableAssign, |
12 | killallServers, makeGetRequest, | 15 | killallServers, |
16 | makeGetRequest, | ||
17 | removeVideo, | ||
18 | reRunServer, | ||
13 | root, | 19 | root, |
14 | ServerInfo, | 20 | ServerInfo, |
15 | setAccessTokensToServers, unfollow, | 21 | setAccessTokensToServers, |
22 | unfollow, | ||
16 | uploadVideo, | 23 | uploadVideo, |
17 | viewVideo, | 24 | viewVideo, |
18 | wait, | 25 | wait, |
19 | waitUntilLog, | 26 | waitUntilLog |
20 | checkVideoFilesWereRemoved, removeVideo, getVideoWithToken, reRunServer, checkSegmentHash | ||
21 | } from '../../../../shared/utils' | 27 | } from '../../../../shared/utils' |
22 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 28 | import { waitJobs } from '../../../../shared/utils/server/jobs' |
23 | 29 | ||
diff --git a/shared/models/videos/playlist/video-playlist-create.model.ts b/shared/models/videos/playlist/video-playlist-create.model.ts index 386acbb96..67a33fa35 100644 --- a/shared/models/videos/playlist/video-playlist-create.model.ts +++ b/shared/models/videos/playlist/video-playlist-create.model.ts | |||
@@ -2,10 +2,10 @@ import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' | |||
2 | 2 | ||
3 | export interface VideoPlaylistCreate { | 3 | export interface VideoPlaylistCreate { |
4 | displayName: string | 4 | displayName: string |
5 | description: string | ||
6 | privacy: VideoPlaylistPrivacy | 5 | privacy: VideoPlaylistPrivacy |
7 | 6 | ||
7 | description?: string | ||
8 | videoChannelId?: number | 8 | videoChannelId?: number |
9 | 9 | ||
10 | thumbnailfile?: Blob | 10 | thumbnailfile?: any |
11 | } | 11 | } |
diff --git a/shared/models/videos/playlist/video-playlist-element-create.model.ts b/shared/models/videos/playlist/video-playlist-element-create.model.ts index 9bd56a8ca..c31702892 100644 --- a/shared/models/videos/playlist/video-playlist-element-create.model.ts +++ b/shared/models/videos/playlist/video-playlist-element-create.model.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | export interface VideoPlaylistElementCreate { | 1 | export interface VideoPlaylistElementCreate { |
2 | videoId: number | ||
3 | |||
2 | startTimestamp?: number | 4 | startTimestamp?: number |
3 | stopTimestamp?: number | 5 | stopTimestamp?: number |
4 | } | 6 | } |
diff --git a/shared/models/videos/playlist/video-playlist-update.model.ts b/shared/models/videos/playlist/video-playlist-update.model.ts index c7a15c550..0ff5bcb0f 100644 --- a/shared/models/videos/playlist/video-playlist-update.model.ts +++ b/shared/models/videos/playlist/video-playlist-update.model.ts | |||
@@ -2,9 +2,9 @@ import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' | |||
2 | 2 | ||
3 | export interface VideoPlaylistUpdate { | 3 | export interface VideoPlaylistUpdate { |
4 | displayName: string | 4 | displayName: string |
5 | description: string | ||
6 | privacy: VideoPlaylistPrivacy | 5 | privacy: VideoPlaylistPrivacy |
7 | 6 | ||
7 | description?: string | ||
8 | videoChannelId?: number | 8 | videoChannelId?: number |
9 | thumbnailfile?: Blob | 9 | thumbnailfile?: any |
10 | } | 10 | } |
diff --git a/shared/utils/index.ts b/shared/utils/index.ts index 156901372..c09565d95 100644 --- a/shared/utils/index.ts +++ b/shared/utils/index.ts | |||
@@ -13,12 +13,13 @@ export * from './requests/requests' | |||
13 | export * from './requests/check-api-params' | 13 | export * from './requests/check-api-params' |
14 | export * from './server/servers' | 14 | export * from './server/servers' |
15 | export * from './videos/services' | 15 | export * from './videos/services' |
16 | export * from './videos/video-playlists' | ||
16 | export * from './users/users' | 17 | export * from './users/users' |
17 | export * from './videos/video-abuses' | 18 | export * from './videos/video-abuses' |
18 | export * from './videos/video-blacklist' | 19 | export * from './videos/video-blacklist' |
19 | export * from './videos/video-channels' | 20 | export * from './videos/video-channels' |
20 | export * from './videos/video-comments' | 21 | export * from './videos/video-comments' |
21 | export * from './videos/video-playlists' | 22 | export * from './videos/video-streaming-playlists' |
22 | export * from './videos/videos' | 23 | export * from './videos/videos' |
23 | export * from './videos/video-change-ownership' | 24 | export * from './videos/video-change-ownership' |
24 | export * from './feeds/feeds' | 25 | export * from './feeds/feeds' |
diff --git a/shared/utils/videos/video-playlists.ts b/shared/utils/videos/video-playlists.ts index 5186d9c4f..21285688a 100644 --- a/shared/utils/videos/video-playlists.ts +++ b/shared/utils/videos/video-playlists.ts | |||
@@ -31,7 +31,7 @@ function getVideoPlaylist (url: string, playlistId: number | string, statusCodeE | |||
31 | }) | 31 | }) |
32 | } | 32 | } |
33 | 33 | ||
34 | function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 200) { | 34 | function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 204) { |
35 | const path = '/api/v1/video-playlists/' + playlistId | 35 | const path = '/api/v1/video-playlists/' + playlistId |
36 | 36 | ||
37 | return makeDeleteRequest({ | 37 | return makeDeleteRequest({ |
@@ -46,7 +46,7 @@ function createVideoPlaylist (options: { | |||
46 | url: string, | 46 | url: string, |
47 | token: string, | 47 | token: string, |
48 | playlistAttrs: VideoPlaylistCreate, | 48 | playlistAttrs: VideoPlaylistCreate, |
49 | expectedStatus: number | 49 | expectedStatus?: number |
50 | }) { | 50 | }) { |
51 | const path = '/api/v1/video-playlists/' | 51 | const path = '/api/v1/video-playlists/' |
52 | 52 | ||
@@ -63,7 +63,7 @@ function createVideoPlaylist (options: { | |||
63 | token: options.token, | 63 | token: options.token, |
64 | fields, | 64 | fields, |
65 | attaches, | 65 | attaches, |
66 | statusCodeExpected: options.expectedStatus | 66 | statusCodeExpected: options.expectedStatus || 200 |
67 | }) | 67 | }) |
68 | } | 68 | } |
69 | 69 | ||
@@ -71,9 +71,10 @@ function updateVideoPlaylist (options: { | |||
71 | url: string, | 71 | url: string, |
72 | token: string, | 72 | token: string, |
73 | playlistAttrs: VideoPlaylistUpdate, | 73 | playlistAttrs: VideoPlaylistUpdate, |
74 | expectedStatus: number | 74 | playlistId: number | string, |
75 | expectedStatus?: number | ||
75 | }) { | 76 | }) { |
76 | const path = '/api/v1/video-playlists/' | 77 | const path = '/api/v1/video-playlists/' + options.playlistId |
77 | 78 | ||
78 | const fields = omit(options.playlistAttrs, 'thumbnailfile') | 79 | const fields = omit(options.playlistAttrs, 'thumbnailfile') |
79 | 80 | ||
@@ -88,7 +89,7 @@ function updateVideoPlaylist (options: { | |||
88 | token: options.token, | 89 | token: options.token, |
89 | fields, | 90 | fields, |
90 | attaches, | 91 | attaches, |
91 | statusCodeExpected: options.expectedStatus | 92 | statusCodeExpected: options.expectedStatus || 204 |
92 | }) | 93 | }) |
93 | } | 94 | } |
94 | 95 | ||
@@ -97,7 +98,7 @@ function addVideoInPlaylist (options: { | |||
97 | token: string, | 98 | token: string, |
98 | playlistId: number | string, | 99 | playlistId: number | string, |
99 | elementAttrs: VideoPlaylistElementCreate | 100 | elementAttrs: VideoPlaylistElementCreate |
100 | expectedStatus: number | 101 | expectedStatus?: number |
101 | }) { | 102 | }) { |
102 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' | 103 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' |
103 | 104 | ||
@@ -106,7 +107,7 @@ function addVideoInPlaylist (options: { | |||
106 | path, | 107 | path, |
107 | token: options.token, | 108 | token: options.token, |
108 | fields: options.elementAttrs, | 109 | fields: options.elementAttrs, |
109 | statusCodeExpected: options.expectedStatus | 110 | statusCodeExpected: options.expectedStatus || 200 |
110 | }) | 111 | }) |
111 | } | 112 | } |
112 | 113 | ||
@@ -116,7 +117,7 @@ function updateVideoPlaylistElement (options: { | |||
116 | playlistId: number | string, | 117 | playlistId: number | string, |
117 | videoId: number | string, | 118 | videoId: number | string, |
118 | elementAttrs: VideoPlaylistElementUpdate, | 119 | elementAttrs: VideoPlaylistElementUpdate, |
119 | expectedStatus: number | 120 | expectedStatus?: number |
120 | }) { | 121 | }) { |
121 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId | 122 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId |
122 | 123 | ||
@@ -125,7 +126,7 @@ function updateVideoPlaylistElement (options: { | |||
125 | path, | 126 | path, |
126 | token: options.token, | 127 | token: options.token, |
127 | fields: options.elementAttrs, | 128 | fields: options.elementAttrs, |
128 | statusCodeExpected: options.expectedStatus | 129 | statusCodeExpected: options.expectedStatus || 204 |
129 | }) | 130 | }) |
130 | } | 131 | } |
131 | 132 | ||
@@ -142,7 +143,7 @@ function removeVideoFromPlaylist (options: { | |||
142 | url: options.url, | 143 | url: options.url, |
143 | path, | 144 | path, |
144 | token: options.token, | 145 | token: options.token, |
145 | statusCodeExpected: options.expectedStatus | 146 | statusCodeExpected: options.expectedStatus || 204 |
146 | }) | 147 | }) |
147 | } | 148 | } |
148 | 149 | ||
@@ -152,14 +153,14 @@ function reorderVideosPlaylist (options: { | |||
152 | playlistId: number | string, | 153 | playlistId: number | string, |
153 | elementAttrs: { | 154 | elementAttrs: { |
154 | startPosition: number, | 155 | startPosition: number, |
155 | insertAfter: number, | 156 | insertAfterPosition: number, |
156 | reorderLength?: number | 157 | reorderLength?: number |
157 | }, | 158 | }, |
158 | expectedStatus: number | 159 | expectedStatus: number |
159 | }) { | 160 | }) { |
160 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' | 161 | const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder' |
161 | 162 | ||
162 | return makePutBodyRequest({ | 163 | return makePostBodyRequest({ |
163 | url: options.url, | 164 | url: options.url, |
164 | path, | 165 | path, |
165 | token: options.token, | 166 | token: options.token, |
@@ -179,6 +180,7 @@ export { | |||
179 | deleteVideoPlaylist, | 180 | deleteVideoPlaylist, |
180 | 181 | ||
181 | addVideoInPlaylist, | 182 | addVideoInPlaylist, |
183 | updateVideoPlaylistElement, | ||
182 | removeVideoFromPlaylist, | 184 | removeVideoFromPlaylist, |
183 | 185 | ||
184 | reorderVideosPlaylist | 186 | reorderVideosPlaylist |