aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/users/index.ts12
-rw-r--r--server/controllers/api/videos/index.ts2
-rw-r--r--server/controllers/api/videos/ownership.ts117
-rw-r--r--server/helpers/custom-validators/video-ownership.ts42
-rw-r--r--server/initializers/database.ts2
-rw-r--r--server/middlewares/validators/users.ts7
-rw-r--r--server/middlewares/validators/videos.ts82
-rw-r--r--server/models/account/user.ts12
-rw-r--r--server/models/video/video-change-ownership.ts127
-rw-r--r--server/tests/api/videos/video-change-ownership.ts262
-rw-r--r--server/tests/utils/index.ts1
-rw-r--r--server/tests/utils/videos/video-change-ownership.ts54
12 files changed, 718 insertions, 2 deletions
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 01ee73a53..faba7e208 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -18,6 +18,7 @@ import {
18 setDefaultPagination, 18 setDefaultPagination,
19 setDefaultSort, 19 setDefaultSort,
20 token, 20 token,
21 userAutocompleteValidator,
21 usersAddValidator, 22 usersAddValidator,
22 usersGetValidator, 23 usersGetValidator,
23 usersRegisterValidator, 24 usersRegisterValidator,
@@ -51,6 +52,11 @@ const askSendEmailLimiter = new RateLimit({
51const usersRouter = express.Router() 52const usersRouter = express.Router()
52usersRouter.use('/', meRouter) 53usersRouter.use('/', meRouter)
53 54
55usersRouter.get('/autocomplete',
56 userAutocompleteValidator,
57 asyncMiddleware(autocompleteUsers)
58)
59
54usersRouter.get('/', 60usersRouter.get('/',
55 authenticate, 61 authenticate,
56 ensureUserHasRight(UserRight.MANAGE_USERS), 62 ensureUserHasRight(UserRight.MANAGE_USERS),
@@ -222,6 +228,12 @@ function getUser (req: express.Request, res: express.Response, next: express.Nex
222 return res.json((res.locals.user as UserModel).toFormattedJSON()) 228 return res.json((res.locals.user as UserModel).toFormattedJSON())
223} 229}
224 230
231async function autocompleteUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
232 const resultList = await UserModel.autocomplete(req.query.search as string)
233
234 return res.json(resultList)
235}
236
225async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 237async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
226 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort) 238 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort)
227 239
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index be803490b..0c9e6c2d1 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -49,6 +49,7 @@ import { abuseVideoRouter } from './abuse'
49import { blacklistRouter } from './blacklist' 49import { blacklistRouter } from './blacklist'
50import { videoCommentRouter } from './comment' 50import { videoCommentRouter } from './comment'
51import { rateVideoRouter } from './rate' 51import { rateVideoRouter } from './rate'
52import { ownershipVideoRouter } from './ownership'
52import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 53import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
53import { buildNSFWFilter, createReqFiles } from '../../../helpers/express-utils' 54import { buildNSFWFilter, createReqFiles } from '../../../helpers/express-utils'
54import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' 55import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
@@ -84,6 +85,7 @@ videosRouter.use('/', rateVideoRouter)
84videosRouter.use('/', videoCommentRouter) 85videosRouter.use('/', videoCommentRouter)
85videosRouter.use('/', videoCaptionsRouter) 86videosRouter.use('/', videoCaptionsRouter)
86videosRouter.use('/', videoImportsRouter) 87videosRouter.use('/', videoImportsRouter)
88videosRouter.use('/', ownershipVideoRouter)
87 89
88videosRouter.get('/categories', listVideoCategories) 90videosRouter.get('/categories', listVideoCategories)
89videosRouter.get('/licences', listVideoLicences) 91videosRouter.get('/licences', listVideoLicences)
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts
new file mode 100644
index 000000000..fc42f5fff
--- /dev/null
+++ b/server/controllers/api/videos/ownership.ts
@@ -0,0 +1,117 @@
1import * as express from 'express'
2import { logger } from '../../../helpers/logger'
3import { sequelizeTypescript } from '../../../initializers'
4import {
5 asyncMiddleware,
6 asyncRetryTransactionMiddleware,
7 authenticate,
8 paginationValidator,
9 setDefaultPagination,
10 videosAcceptChangeOwnershipValidator,
11 videosChangeOwnershipValidator,
12 videosTerminateChangeOwnershipValidator
13} from '../../../middlewares'
14import { AccountModel } from '../../../models/account/account'
15import { VideoModel } from '../../../models/video/video'
16import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership'
17import { VideoChangeOwnershipStatus } from '../../../../shared/models/videos'
18import { VideoChannelModel } from '../../../models/video/video-channel'
19import { getFormattedObjects } from '../../../helpers/utils'
20
21const ownershipVideoRouter = express.Router()
22
23ownershipVideoRouter.post('/:videoId/give-ownership',
24 authenticate,
25 asyncMiddleware(videosChangeOwnershipValidator),
26 asyncRetryTransactionMiddleware(giveVideoOwnership)
27)
28
29ownershipVideoRouter.get('/ownership',
30 authenticate,
31 paginationValidator,
32 setDefaultPagination,
33 asyncRetryTransactionMiddleware(listVideoOwnership)
34)
35
36ownershipVideoRouter.post('/ownership/:id/accept',
37 authenticate,
38 asyncMiddleware(videosTerminateChangeOwnershipValidator),
39 asyncMiddleware(videosAcceptChangeOwnershipValidator),
40 asyncRetryTransactionMiddleware(acceptOwnership)
41)
42
43ownershipVideoRouter.post('/ownership/:id/refuse',
44 authenticate,
45 asyncMiddleware(videosTerminateChangeOwnershipValidator),
46 asyncRetryTransactionMiddleware(refuseOwnership)
47)
48
49// ---------------------------------------------------------------------------
50
51export {
52 ownershipVideoRouter
53}
54
55// ---------------------------------------------------------------------------
56
57async function giveVideoOwnership (req: express.Request, res: express.Response) {
58 const videoInstance = res.locals.video as VideoModel
59 const initiatorAccount = res.locals.oauth.token.User.Account as AccountModel
60 const nextOwner = res.locals.nextOwner as AccountModel
61
62 await sequelizeTypescript.transaction(async t => {
63 await VideoChangeOwnershipModel.findOrCreate({
64 where: {
65 initiatorAccountId: initiatorAccount.id,
66 nextOwnerAccountId: nextOwner.id,
67 videoId: videoInstance.id,
68 status: VideoChangeOwnershipStatus.WAITING
69 },
70 defaults: {
71 initiatorAccountId: initiatorAccount.id,
72 nextOwnerAccountId: nextOwner.id,
73 videoId: videoInstance.id,
74 status: VideoChangeOwnershipStatus.WAITING
75 }
76 })
77 logger.info('Ownership change for video %s created.', videoInstance.name)
78 return res.type('json').status(204).end()
79 })
80}
81
82async function listVideoOwnership (req: express.Request, res: express.Response) {
83 const currentAccount = res.locals.oauth.token.User.Account as AccountModel
84 const resultList = await VideoChangeOwnershipModel.listForApi(
85 currentAccount.id,
86 req.query.start || 0,
87 req.query.count || 10,
88 req.query.sort || 'createdAt'
89 )
90
91 return res.json(getFormattedObjects(resultList.data, resultList.total))
92}
93
94async function acceptOwnership (req: express.Request, res: express.Response) {
95 return sequelizeTypescript.transaction(async t => {
96 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
97 const targetVideo = videoChangeOwnership.Video
98 const channel = res.locals.videoChannel as VideoChannelModel
99
100 targetVideo.set('channelId', channel.id)
101
102 await targetVideo.save()
103 videoChangeOwnership.set('status', VideoChangeOwnershipStatus.ACCEPTED)
104 await videoChangeOwnership.save()
105
106 return res.sendStatus(204)
107 })
108}
109
110async function refuseOwnership (req: express.Request, res: express.Response) {
111 return sequelizeTypescript.transaction(async t => {
112 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
113 videoChangeOwnership.set('status', VideoChangeOwnershipStatus.REFUSED)
114 await videoChangeOwnership.save()
115 return res.sendStatus(204)
116 })
117}
diff --git a/server/helpers/custom-validators/video-ownership.ts b/server/helpers/custom-validators/video-ownership.ts
new file mode 100644
index 000000000..aaa0c736b
--- /dev/null
+++ b/server/helpers/custom-validators/video-ownership.ts
@@ -0,0 +1,42 @@
1import { Response } from 'express'
2import * as validator from 'validator'
3import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership'
4import { UserModel } from '../../models/account/user'
5
6export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise<boolean> {
7 const videoChangeOwnership = await loadVideoChangeOwnership(id)
8
9 if (!videoChangeOwnership) {
10 res.status(404)
11 .json({ error: 'Video change ownership not found' })
12 .end()
13
14 return false
15 }
16
17 res.locals.videoChangeOwnership = videoChangeOwnership
18 return true
19}
20
21async function loadVideoChangeOwnership (id: string): Promise<VideoChangeOwnershipModel | undefined> {
22 if (validator.isInt(id)) {
23 return VideoChangeOwnershipModel.load(parseInt(id, 10))
24 }
25
26 return undefined
27}
28
29export function checkUserCanTerminateOwnershipChange (
30 user: UserModel,
31 videoChangeOwnership: VideoChangeOwnershipModel,
32 res: Response
33): boolean {
34 if (videoChangeOwnership.NextOwner.userId === user.Account.userId) {
35 return true
36 }
37
38 res.status(403)
39 .json({ error: 'Cannot terminate an ownership change of another user' })
40 .end()
41 return false
42}
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 78bc8101c..b68e1a882 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -26,6 +26,7 @@ import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update'
26import { VideoCaptionModel } from '../models/video/video-caption' 26import { VideoCaptionModel } from '../models/video/video-caption'
27import { VideoImportModel } from '../models/video/video-import' 27import { VideoImportModel } from '../models/video/video-import'
28import { VideoViewModel } from '../models/video/video-views' 28import { VideoViewModel } from '../models/video/video-views'
29import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership'
29 30
30require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string 31require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
31 32
@@ -75,6 +76,7 @@ async function initDatabaseModels (silent: boolean) {
75 AccountVideoRateModel, 76 AccountVideoRateModel,
76 UserModel, 77 UserModel,
77 VideoAbuseModel, 78 VideoAbuseModel,
79 VideoChangeOwnershipModel,
78 VideoChannelModel, 80 VideoChannelModel,
79 VideoShareModel, 81 VideoShareModel,
80 VideoFileModel, 82 VideoFileModel,
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index a595c39ec..d13c50c84 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -290,6 +290,10 @@ const usersVerifyEmailValidator = [
290 } 290 }
291] 291]
292 292
293const userAutocompleteValidator = [
294 param('search').isString().not().isEmpty().withMessage('Should have a search parameter')
295]
296
293// --------------------------------------------------------------------------- 297// ---------------------------------------------------------------------------
294 298
295export { 299export {
@@ -307,7 +311,8 @@ export {
307 usersAskResetPasswordValidator, 311 usersAskResetPasswordValidator,
308 usersResetPasswordValidator, 312 usersResetPasswordValidator,
309 usersAskSendVerifyEmailValidator, 313 usersAskSendVerifyEmailValidator,
310 usersVerifyEmailValidator 314 usersVerifyEmailValidator,
315 userAutocompleteValidator
311} 316}
312 317
313// --------------------------------------------------------------------------- 318// ---------------------------------------------------------------------------
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts
index a2c866152..9befbc9ee 100644
--- a/server/middlewares/validators/videos.ts
+++ b/server/middlewares/validators/videos.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'express-validator' 2import 'express-validator'
3import { body, param, ValidationChain } from 'express-validator/check' 3import { body, param, ValidationChain } from 'express-validator/check'
4import { UserRight, VideoPrivacy } from '../../../shared' 4import { UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../shared'
5import { 5import {
6 isBooleanValid, 6 isBooleanValid,
7 isDateValid, 7 isDateValid,
@@ -37,6 +37,10 @@ import { areValidationErrors } from './utils'
37import { cleanUpReqFiles } from '../../helpers/express-utils' 37import { cleanUpReqFiles } from '../../helpers/express-utils'
38import { VideoModel } from '../../models/video/video' 38import { VideoModel } from '../../models/video/video'
39import { UserModel } from '../../models/account/user' 39import { UserModel } from '../../models/account/user'
40import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../helpers/custom-validators/video-ownership'
41import { VideoChangeOwnershipAccept } from '../../../shared/models/videos/video-change-ownership-accept.model'
42import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership'
43import { AccountModel } from '../../models/account/account'
40 44
41const videosAddValidator = getCommonVideoAttributes().concat([ 45const videosAddValidator = getCommonVideoAttributes().concat([
42 body('videofile') 46 body('videofile')
@@ -217,6 +221,78 @@ const videosShareValidator = [
217 } 221 }
218] 222]
219 223
224const videosChangeOwnershipValidator = [
225 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
226
227 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
228 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
229
230 if (areValidationErrors(req, res)) return
231 if (!await isVideoExist(req.params.videoId, res)) return
232
233 // Check if the user who did the request is able to change the ownership of the video
234 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
235
236 const nextOwner = await AccountModel.loadLocalByName(req.body.username)
237 if (!nextOwner) {
238 res.status(400)
239 .type('json')
240 .end()
241 return
242 }
243 res.locals.nextOwner = nextOwner
244
245 return next()
246 }
247]
248
249const videosTerminateChangeOwnershipValidator = [
250 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
251
252 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
253 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
254
255 if (areValidationErrors(req, res)) return
256 if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return
257
258 // Check if the user who did the request is able to change the ownership of the video
259 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
260
261 return next()
262 },
263 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
264 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
265
266 if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) {
267 return next()
268 } else {
269 res.status(403)
270 .json({ error: 'Ownership already accepted or refused' })
271 .end()
272 return
273 }
274 }
275]
276
277const videosAcceptChangeOwnershipValidator = [
278 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
279 const body = req.body as VideoChangeOwnershipAccept
280 if (!await isVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return
281
282 const user = res.locals.oauth.token.User
283 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
284 const isAble = await user.isAbleToUploadVideo(videoChangeOwnership.Video.getOriginalFile())
285 if (isAble === false) {
286 res.status(403)
287 .json({ error: 'The user video quota is exceeded with this video.' })
288 .end()
289 return
290 }
291
292 return next()
293 }
294]
295
220function getCommonVideoAttributes () { 296function getCommonVideoAttributes () {
221 return [ 297 return [
222 body('thumbnailfile') 298 body('thumbnailfile')
@@ -295,6 +371,10 @@ export {
295 371
296 videoRateValidator, 372 videoRateValidator,
297 373
374 videosChangeOwnershipValidator,
375 videosTerminateChangeOwnershipValidator,
376 videosAcceptChangeOwnershipValidator,
377
298 getCommonVideoAttributes 378 getCommonVideoAttributes
299} 379}
300 380
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 89265774b..4b13e47a0 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -39,6 +39,7 @@ import { AccountModel } from './account'
39import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' 39import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
40import { values } from 'lodash' 40import { values } from 'lodash'
41import { NSFW_POLICY_TYPES } from '../../initializers' 41import { NSFW_POLICY_TYPES } from '../../initializers'
42import { VideoFileModel } from '../video/video-file'
42 43
43enum ScopeNames { 44enum ScopeNames {
44 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' 45 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@@ -393,4 +394,15 @@ export class UserModel extends Model<UserModel> {
393 return parseInt(total, 10) 394 return parseInt(total, 10)
394 }) 395 })
395 } 396 }
397
398 static autocomplete (search: string) {
399 return UserModel.findAll({
400 where: {
401 username: {
402 [Sequelize.Op.like]: `%${search}%`
403 }
404 }
405 })
406 .then(u => u.map(u => u.username))
407 }
396} 408}
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts
new file mode 100644
index 000000000..c9cff5054
--- /dev/null
+++ b/server/models/video/video-change-ownership.ts
@@ -0,0 +1,127 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
2import { AccountModel } from '../account/account'
3import { VideoModel } from './video'
4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
5import { getSort } from '../utils'
6import { VideoFileModel } from './video-file'
7
8enum ScopeNames {
9 FULL = 'FULL'
10}
11
12@Table({
13 tableName: 'videoChangeOwnership',
14 indexes: [
15 {
16 fields: ['videoId']
17 },
18 {
19 fields: ['initiatorAccountId']
20 },
21 {
22 fields: ['nextOwnerAccountId']
23 }
24 ]
25})
26@Scopes({
27 [ScopeNames.FULL]: {
28 include: [
29 {
30 model: () => AccountModel,
31 as: 'Initiator',
32 required: true
33 },
34 {
35 model: () => AccountModel,
36 as: 'NextOwner',
37 required: true
38 },
39 {
40 model: () => VideoModel,
41 required: true,
42 include: [{ model: () => VideoFileModel }]
43 }
44 ]
45 }
46})
47export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> {
48 @CreatedAt
49 createdAt: Date
50
51 @UpdatedAt
52 updatedAt: Date
53
54 @AllowNull(false)
55 @Column
56 status: VideoChangeOwnershipStatus
57
58 @ForeignKey(() => AccountModel)
59 @Column
60 initiatorAccountId: number
61
62 @BelongsTo(() => AccountModel, {
63 foreignKey: {
64 name: 'initiatorAccountId',
65 allowNull: false
66 },
67 onDelete: 'cascade'
68 })
69 Initiator: AccountModel
70
71 @ForeignKey(() => AccountModel)
72 @Column
73 nextOwnerAccountId: number
74
75 @BelongsTo(() => AccountModel, {
76 foreignKey: {
77 name: 'nextOwnerAccountId',
78 allowNull: false
79 },
80 onDelete: 'cascade'
81 })
82 NextOwner: AccountModel
83
84 @ForeignKey(() => VideoModel)
85 @Column
86 videoId: number
87
88 @BelongsTo(() => VideoModel, {
89 foreignKey: {
90 allowNull: false
91 },
92 onDelete: 'cascade'
93 })
94 Video: VideoModel
95
96 static listForApi (nextOwnerId: number, start: number, count: number, sort: string) {
97 return VideoChangeOwnershipModel.scope(ScopeNames.FULL).findAndCountAll({
98 offset: start,
99 limit: count,
100 order: getSort(sort),
101 where: {
102 nextOwnerAccountId: nextOwnerId
103 }
104 })
105 .then(({ rows, count }) => ({ total: count, data: rows }))
106 }
107
108 static load (id: number) {
109 return VideoChangeOwnershipModel.scope(ScopeNames.FULL).findById(id)
110 }
111
112 toFormattedJSON (): VideoChangeOwnership {
113 return {
114 id: this.id,
115 status: this.status,
116 initiatorAccount: this.Initiator.toFormattedJSON(),
117 nextOwnerAccount: this.NextOwner.toFormattedJSON(),
118 video: {
119 id: this.Video.id,
120 uuid: this.Video.uuid,
121 url: this.Video.url,
122 name: this.Video.name
123 },
124 createdAt: this.createdAt
125 }
126 }
127}
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts
new file mode 100644
index 000000000..275be40be
--- /dev/null
+++ b/server/tests/api/videos/video-change-ownership.ts
@@ -0,0 +1,262 @@
1/* tslint:disable:no-unused-expression */
2
3import * as chai from 'chai'
4import 'mocha'
5import {
6 acceptChangeOwnership,
7 changeVideoOwnership,
8 createUser,
9 flushTests,
10 getMyUserInformation,
11 getVideoChangeOwnershipList,
12 getVideosList,
13 killallServers,
14 refuseChangeOwnership,
15 runServer,
16 ServerInfo,
17 setAccessTokensToServers,
18 uploadVideo,
19 userLogin
20} from '../../utils'
21import { waitJobs } from '../../utils/server/jobs'
22import { User } from '../../../../shared/models/users'
23
24const expect = chai.expect
25
26describe('Test video change ownership - nominal', function () {
27 let server: ServerInfo = undefined
28 const firstUser = {
29 username: 'first',
30 password: 'My great password'
31 }
32 const secondUser = {
33 username: 'second',
34 password: 'My other password'
35 }
36 let firstUserAccessToken = ''
37 let secondUserAccessToken = ''
38 let lastRequestChangeOwnershipId = undefined
39
40 before(async function () {
41 this.timeout(50000)
42
43 // Run one server
44 await flushTests()
45 server = await runServer(1)
46 await setAccessTokensToServers([server])
47
48 const videoQuota = 42000000
49 await createUser(server.url, server.accessToken, firstUser.username, firstUser.password, videoQuota)
50 await createUser(server.url, server.accessToken, secondUser.username, secondUser.password, videoQuota)
51
52 firstUserAccessToken = await userLogin(server, firstUser)
53 secondUserAccessToken = await userLogin(server, secondUser)
54
55 // Upload some videos on the server
56 const video1Attributes = {
57 name: 'my super name',
58 description: 'my super description'
59 }
60 await uploadVideo(server.url, firstUserAccessToken, video1Attributes)
61
62 await waitJobs(server)
63
64 const res = await getVideosList(server.url)
65 const videos = res.body.data
66
67 expect(videos.length).to.equal(1)
68
69 server.video = videos.find(video => video.name === 'my super name')
70 })
71
72 it('Should not have video change ownership', async function () {
73 const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
74
75 expect(resFirstUser.body.total).to.equal(0)
76 expect(resFirstUser.body.data).to.be.an('array')
77 expect(resFirstUser.body.data.length).to.equal(0)
78
79 const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
80
81 expect(resSecondUser.body.total).to.equal(0)
82 expect(resSecondUser.body.data).to.be.an('array')
83 expect(resSecondUser.body.data.length).to.equal(0)
84 })
85
86 it('Should send a request to change ownership of a video', async function () {
87 this.timeout(15000)
88
89 await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
90 })
91
92 it('Should only return a request to change ownership for the second user', async function () {
93 const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
94
95 expect(resFirstUser.body.total).to.equal(0)
96 expect(resFirstUser.body.data).to.be.an('array')
97 expect(resFirstUser.body.data.length).to.equal(0)
98
99 const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
100
101 expect(resSecondUser.body.total).to.equal(1)
102 expect(resSecondUser.body.data).to.be.an('array')
103 expect(resSecondUser.body.data.length).to.equal(1)
104
105 lastRequestChangeOwnershipId = resSecondUser.body.data[0].id
106 })
107
108 it('Should accept the same change ownership request without crashing', async function () {
109 this.timeout(10000)
110
111 await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
112 })
113
114 it('Should not create multiple change ownership requests while one is waiting', async function () {
115 this.timeout(10000)
116
117 const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
118
119 expect(resSecondUser.body.total).to.equal(1)
120 expect(resSecondUser.body.data).to.be.an('array')
121 expect(resSecondUser.body.data.length).to.equal(1)
122 })
123
124 it('Should not be possible to refuse the change of ownership from first user', async function () {
125 this.timeout(10000)
126
127 await refuseChangeOwnership(server.url, firstUserAccessToken, lastRequestChangeOwnershipId, 403)
128 })
129
130 it('Should be possible to refuse the change of ownership from second user', async function () {
131 this.timeout(10000)
132
133 await refuseChangeOwnership(server.url, secondUserAccessToken, lastRequestChangeOwnershipId)
134 })
135
136 it('Should send a new request to change ownership of a video', async function () {
137 this.timeout(15000)
138
139 await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
140 })
141
142 it('Should return two requests to change ownership for the second user', async function () {
143 const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
144
145 expect(resFirstUser.body.total).to.equal(0)
146 expect(resFirstUser.body.data).to.be.an('array')
147 expect(resFirstUser.body.data.length).to.equal(0)
148
149 const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
150
151 expect(resSecondUser.body.total).to.equal(2)
152 expect(resSecondUser.body.data).to.be.an('array')
153 expect(resSecondUser.body.data.length).to.equal(2)
154
155 lastRequestChangeOwnershipId = resSecondUser.body.data[0].id
156 })
157
158 it('Should not be possible to accept the change of ownership from first user', async function () {
159 this.timeout(10000)
160
161 const secondUserInformationResponse = await getMyUserInformation(server.url, secondUserAccessToken)
162 const secondUserInformation: User = secondUserInformationResponse.body
163 const channelId = secondUserInformation.videoChannels[0].id
164 await acceptChangeOwnership(server.url, firstUserAccessToken, lastRequestChangeOwnershipId, channelId, 403)
165 })
166
167 it('Should be possible to accept the change of ownership from second user', async function () {
168 this.timeout(10000)
169
170 const secondUserInformationResponse = await getMyUserInformation(server.url, secondUserAccessToken)
171 const secondUserInformation: User = secondUserInformationResponse.body
172 const channelId = secondUserInformation.videoChannels[0].id
173 await acceptChangeOwnership(server.url, secondUserAccessToken, lastRequestChangeOwnershipId, channelId)
174 })
175
176 after(async function () {
177 killallServers([server])
178 })
179})
180
181describe('Test video change ownership - quota too small', function () {
182 let server: ServerInfo = undefined
183 const firstUser = {
184 username: 'first',
185 password: 'My great password'
186 }
187 const secondUser = {
188 username: 'second',
189 password: 'My other password'
190 }
191 let firstUserAccessToken = ''
192 let secondUserAccessToken = ''
193 let lastRequestChangeOwnershipId = undefined
194
195 before(async function () {
196 this.timeout(50000)
197
198 // Run one server
199 await flushTests()
200 server = await runServer(1)
201 await setAccessTokensToServers([server])
202
203 const videoQuota = 42000000
204 const limitedVideoQuota = 10
205 await createUser(server.url, server.accessToken, firstUser.username, firstUser.password, videoQuota)
206 await createUser(server.url, server.accessToken, secondUser.username, secondUser.password, limitedVideoQuota)
207
208 firstUserAccessToken = await userLogin(server, firstUser)
209 secondUserAccessToken = await userLogin(server, secondUser)
210
211 // Upload some videos on the server
212 const video1Attributes = {
213 name: 'my super name',
214 description: 'my super description'
215 }
216 await uploadVideo(server.url, firstUserAccessToken, video1Attributes)
217
218 await waitJobs(server)
219
220 const res = await getVideosList(server.url)
221 const videos = res.body.data
222
223 expect(videos.length).to.equal(1)
224
225 server.video = videos.find(video => video.name === 'my super name')
226 })
227
228 it('Should send a request to change ownership of a video', async function () {
229 this.timeout(15000)
230
231 await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
232 })
233
234 it('Should only return a request to change ownership for the second user', async function () {
235 const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
236
237 expect(resFirstUser.body.total).to.equal(0)
238 expect(resFirstUser.body.data).to.be.an('array')
239 expect(resFirstUser.body.data.length).to.equal(0)
240
241 const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
242
243 expect(resSecondUser.body.total).to.equal(1)
244 expect(resSecondUser.body.data).to.be.an('array')
245 expect(resSecondUser.body.data.length).to.equal(1)
246
247 lastRequestChangeOwnershipId = resSecondUser.body.data[0].id
248 })
249
250 it('Should not be possible to accept the change of ownership from second user because of exceeded quota', async function () {
251 this.timeout(10000)
252
253 const secondUserInformationResponse = await getMyUserInformation(server.url, secondUserAccessToken)
254 const secondUserInformation: User = secondUserInformationResponse.body
255 const channelId = secondUserInformation.videoChannels[0].id
256 await acceptChangeOwnership(server.url, secondUserAccessToken, lastRequestChangeOwnershipId, channelId, 403)
257 })
258
259 after(async function () {
260 killallServers([server])
261 })
262})
diff --git a/server/tests/utils/index.ts b/server/tests/utils/index.ts
index 391db18cf..897389824 100644
--- a/server/tests/utils/index.ts
+++ b/server/tests/utils/index.ts
@@ -13,5 +13,6 @@ export * from './videos/video-abuses'
13export * from './videos/video-blacklist' 13export * from './videos/video-blacklist'
14export * from './videos/video-channels' 14export * from './videos/video-channels'
15export * from './videos/videos' 15export * from './videos/videos'
16export * from './videos/video-change-ownership'
16export * from './feeds/feeds' 17export * from './feeds/feeds'
17export * from './search/videos' 18export * from './search/videos'
diff --git a/server/tests/utils/videos/video-change-ownership.ts b/server/tests/utils/videos/video-change-ownership.ts
new file mode 100644
index 000000000..f288692ea
--- /dev/null
+++ b/server/tests/utils/videos/video-change-ownership.ts
@@ -0,0 +1,54 @@
1import * as request from 'supertest'
2
3function changeVideoOwnership (url: string, token: string, videoId: number | string, username) {
4 const path = '/api/v1/videos/' + videoId + '/give-ownership'
5
6 return request(url)
7 .post(path)
8 .set('Accept', 'application/json')
9 .set('Authorization', 'Bearer ' + token)
10 .send({ username })
11 .expect(204)
12}
13
14function getVideoChangeOwnershipList (url: string, token: string) {
15 const path = '/api/v1/videos/ownership'
16
17 return request(url)
18 .get(path)
19 .query({ sort: '-createdAt' })
20 .set('Accept', 'application/json')
21 .set('Authorization', 'Bearer ' + token)
22 .expect(200)
23 .expect('Content-Type', /json/)
24}
25
26function acceptChangeOwnership (url: string, token: string, ownershipId: string, channelId: number, expectedStatus = 204) {
27 const path = '/api/v1/videos/ownership/' + ownershipId + '/accept'
28
29 return request(url)
30 .post(path)
31 .set('Accept', 'application/json')
32 .set('Authorization', 'Bearer ' + token)
33 .send({ channelId })
34 .expect(expectedStatus)
35}
36
37function refuseChangeOwnership (url: string, token: string, ownershipId: string, expectedStatus = 204) {
38 const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse'
39
40 return request(url)
41 .post(path)
42 .set('Accept', 'application/json')
43 .set('Authorization', 'Bearer ' + token)
44 .expect(expectedStatus)
45}
46
47// ---------------------------------------------------------------------------
48
49export {
50 changeVideoOwnership,
51 getVideoChangeOwnershipList,
52 acceptChangeOwnership,
53 refuseChangeOwnership
54}