Add federation to ownership change
authorChocobozzz <me@florianbigard.com>
Tue, 4 Sep 2018 08:22:10 +0000 (10:22 +0200)
committerChocobozzz <me@florianbigard.com>
Tue, 4 Sep 2018 08:49:53 +0000 (10:49 +0200)
13 files changed:
client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts
server/controllers/api/users/index.ts
server/controllers/api/videos/ownership.ts
server/helpers/custom-validators/activitypub/undo.ts
server/lib/activitypub/process/process-undo.ts
server/lib/activitypub/send/send-announce.ts
server/lib/activitypub/send/send-update.ts
server/lib/activitypub/share.ts
server/models/account/user.ts
server/models/video/video-change-ownership.ts
server/models/video/video-channel.ts
server/tests/api/videos/index.ts
server/tests/api/videos/video-change-ownership.ts

index 0aa4c32ee5186034be8e2b120e7a8559e8e2cbd7..7437b939ad12e90b67641414d688b5e356fc804c 100644 (file)
@@ -53,7 +53,7 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
     const query = event.query
     this.userService.autocomplete(query)
       .subscribe(
-        (usernames) => {
+        usernames => {
           this.usernamePropositions = usernames
         },
 
@@ -67,7 +67,7 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
     this.videoOwnershipService
       .changeOwnership(this.video.id, username)
       .subscribe(
-        () => this.notificationsService.success(this.i18n('Success'), this.i18n('Ownership changed.')),
+        () => this.notificationsService.success(this.i18n('Success'), this.i18n('Ownership change request sent.')),
 
         err => this.notificationsService.error(this.i18n('Error'), err.message)
       )
index faba7e20847b9153d78f91fc54c87842a5417ce4..07edf3727117075bd9136382c53d7356d08b8c1e 100644 (file)
@@ -229,7 +229,7 @@ function getUser (req: express.Request, res: express.Response, next: express.Nex
 }
 
 async function autocompleteUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const resultList = await UserModel.autocomplete(req.query.search as string)
+  const resultList = await UserModel.autoComplete(req.query.search as string)
 
   return res.json(resultList)
 }
index fc42f5fff16d748cc9557cc1dd80b67e264682ce..d26ed6cfc39adb8385f5e9e0d5504ec86b8a4a32 100644 (file)
@@ -14,9 +14,11 @@ import {
 import { AccountModel } from '../../../models/account/account'
 import { VideoModel } from '../../../models/video/video'
 import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership'
-import { VideoChangeOwnershipStatus } from '../../../../shared/models/videos'
+import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos'
 import { VideoChannelModel } from '../../../models/video/video-channel'
 import { getFormattedObjects } from '../../../helpers/utils'
+import { changeVideoChannelShare } from '../../../lib/activitypub'
+import { sendUpdateVideo } from '../../../lib/activitypub/send'
 
 const ownershipVideoRouter = express.Router()
 
@@ -59,8 +61,8 @@ async function giveVideoOwnership (req: express.Request, res: express.Response)
   const initiatorAccount = res.locals.oauth.token.User.Account as AccountModel
   const nextOwner = res.locals.nextOwner as AccountModel
 
-  await sequelizeTypescript.transaction(async t => {
-    await VideoChangeOwnershipModel.findOrCreate({
+  await sequelizeTypescript.transaction(t => {
+    return VideoChangeOwnershipModel.findOrCreate({
       where: {
         initiatorAccountId: initiatorAccount.id,
         nextOwnerAccountId: nextOwner.id,
@@ -72,11 +74,14 @@ async function giveVideoOwnership (req: express.Request, res: express.Response)
         nextOwnerAccountId: nextOwner.id,
         videoId: videoInstance.id,
         status: VideoChangeOwnershipStatus.WAITING
-      }
+      },
+      transaction: t
     })
-    logger.info('Ownership change for video %s created.', videoInstance.name)
-    return res.type('json').status(204).end()
+
   })
+
+  logger.info('Ownership change for video %s created.', videoInstance.name)
+  return res.type('json').status(204).end()
 }
 
 async function listVideoOwnership (req: express.Request, res: express.Response) {
@@ -97,11 +102,19 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
     const targetVideo = videoChangeOwnership.Video
     const channel = res.locals.videoChannel as VideoChannelModel
 
+    const oldVideoChannel = await VideoChannelModel.loadByIdAndPopulateAccount(targetVideo.channelId)
+
     targetVideo.set('channelId', channel.id)
+    const targetVideoUpdated = await targetVideo.save({ transaction: t })
+    targetVideoUpdated.VideoChannel = channel
+
+    if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) {
+      await changeVideoChannelShare(targetVideoUpdated, oldVideoChannel, t)
+      await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
+    }
 
-    await targetVideo.save()
     videoChangeOwnership.set('status', VideoChangeOwnershipStatus.ACCEPTED)
-    await videoChangeOwnership.save()
+    await videoChangeOwnership.save({ transaction: t })
 
     return res.sendStatus(204)
   })
@@ -110,8 +123,10 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
 async function refuseOwnership (req: express.Request, res: express.Response) {
   return sequelizeTypescript.transaction(async t => {
     const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
+
     videoChangeOwnership.set('status', VideoChangeOwnershipStatus.REFUSED)
-    await videoChangeOwnership.save()
+    await videoChangeOwnership.save({ transaction: t })
+
     return res.sendStatus(204)
   })
 }
index a2831b0bfbb7926d9ad153f878cf7fe95d5abd7f..f50f224fa433539017561d2f664dd40b52802d68 100644 (file)
@@ -1,13 +1,15 @@
 import { isActorFollowActivityValid } from './actor'
 import { isBaseActivityValid } from './misc'
 import { isDislikeActivityValid, isLikeActivityValid } from './rate'
+import { isAnnounceActivityValid } from './announce'
 
 function isUndoActivityValid (activity: any) {
   return isBaseActivityValid(activity, 'Undo') &&
     (
       isActorFollowActivityValid(activity.object) ||
       isLikeActivityValid(activity.object) ||
-      isDislikeActivityValid(activity.object)
+      isDislikeActivityValid(activity.object) ||
+      isAnnounceActivityValid(activity.object)
     )
 }
 
index eab9e3d61500241ff499f20e4eb6669e6216ad8b..1c1de8827c9da037708111eb8af73981ab68c11c 100644 (file)
@@ -104,17 +104,19 @@ function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) {
 
 function processUndoAnnounce (actorUrl: string, announceActivity: ActivityAnnounce) {
   return sequelizeTypescript.transaction(async t => {
-    const byAccount = await AccountModel.loadByUrl(actorUrl, t)
-    if (!byAccount) throw new Error('Unknown account ' + actorUrl)
+    const byActor = await ActorModel.loadByUrl(actorUrl, t)
+    if (!byActor) throw new Error('Unknown actor ' + actorUrl)
 
     const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
-    if (!share) throw new Error(`'Unknown video share ${announceActivity.id}.`)
+    if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`)
+
+    if (share.actorId !== byActor.id) throw new Error(`${share.url} is not shared by ${byActor.url}.`)
 
     await share.destroy({ transaction: t })
 
     if (share.Video.isOwned()) {
       // Don't resend the activity to the sender
-      const exceptions = [ byAccount.Actor ]
+      const exceptions = [ byActor ]
 
       await forwardVideoRelatedActivity(announceActivity, t, exceptions, share.Video)
     }
index 1ab05ca3ccc2751e1c37af4aa8747e4373ab7db1..352813d73d218dd0b37deb2fcd40345fd774fb6d 100644 (file)
@@ -20,7 +20,10 @@ async function sendVideoAnnounce (byActor: ActorModel, videoShare: VideoShareMod
 
   logger.info('Creating job to send announce %s.', videoShare.url)
 
-  return broadcastToFollowers(data, byActor, [ byActor ], t)
+  const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
+  const followersException = [ byActor ]
+
+  return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException)
 }
 
 function announceActivityData (url: string, byActor: ActorModel, object: string, audience?: ActivityAudience): ActivityAnnounce {
index 17d4f185c9464ad973ab9416053222e60ff1883f..6f1d8089886511c1df7c3b645eb050d56cfb25d4 100644 (file)
@@ -10,13 +10,19 @@ import { getUpdateActivityPubUrl } from '../url'
 import { broadcastToFollowers } from './utils'
 import { audiencify, getAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
+import { videoFeedsValidator } from '../../../middlewares/validators'
+import { VideoCaptionModel } from '../../../models/video/video-caption'
 
-async function sendUpdateVideo (video: VideoModel, t: Transaction) {
+async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) {
   logger.info('Creating job to update video %s.', video.url)
 
-  const byActor = video.VideoChannel.Account.Actor
+  const byActor = overrodeByActor ? overrodeByActor : video.VideoChannel.Account.Actor
 
   const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
+
+  // Needed to build the AP object
+  if (!video.VideoCaptions) video.VideoCaptions = await video.$get('VideoCaptions') as VideoCaptionModel[]
+
   const videoObject = video.toActivityPubObject()
   const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
 
index fe3d73e9b7db6139ce7aebd6a0bd182945b64ac8..3ff60a97cf5aca0abb994965308599b63787a302 100644 (file)
@@ -22,6 +22,8 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction)
 }
 
 async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
+  logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name)
+
   await undoShareByVideoChannel(video, oldVideoChannel, t)
 
   await shareByVideoChannel(video, t)
index 4b13e47a0dc306d76a62acc3a61da8979e2cb2ee..680b1d52d9debb806818162f9de09ebf5972ec38 100644 (file)
@@ -23,13 +23,13 @@ import {
   isUserAutoPlayVideoValid,
   isUserBlockedReasonValid,
   isUserBlockedValid,
-  isUserNSFWPolicyValid,
   isUserEmailVerifiedValid,
+  isUserNSFWPolicyValid,
   isUserPasswordValid,
   isUserRoleValid,
   isUserUsernameValid,
-  isUserVideoQuotaValid,
-  isUserVideoQuotaDailyValid
+  isUserVideoQuotaDailyValid,
+  isUserVideoQuotaValid
 } from '../../helpers/custom-validators/users'
 import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
 import { OAuthTokenModel } from '../oauth/oauth-token'
@@ -39,7 +39,6 @@ import { AccountModel } from './account'
 import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
 import { values } from 'lodash'
 import { NSFW_POLICY_TYPES } from '../../initializers'
-import { VideoFileModel } from '../video/video-file'
 
 enum ScopeNames {
   WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@@ -296,6 +295,20 @@ export class UserModel extends Model<UserModel> {
     }
   }
 
+  static autoComplete (search: string) {
+    const query = {
+      where: {
+        username: {
+          [ Sequelize.Op.like ]: `%${search}%`
+        }
+      },
+      limit: 10
+    }
+
+    return UserModel.findAll(query)
+                    .then(u => u.map(u => u.username))
+  }
+
   hasRight (right: UserRight) {
     return hasUserRight(this.role, right)
   }
@@ -394,15 +407,4 @@ export class UserModel extends Model<UserModel> {
                       return parseInt(total, 10)
                     })
   }
-
-  static autocomplete (search: string) {
-    return UserModel.findAll({
-      where: {
-        username: {
-          [Sequelize.Op.like]: `%${search}%`
-        }
-      }
-    })
-      .then(u => u.map(u => u.username))
-  }
 }
index c9cff50545144eac09d4a7402e12ebb132ab57f8..48c07728f66b9b87af81be3fdf5dbc2d555e04bf 100644 (file)
@@ -39,7 +39,9 @@ enum ScopeNames {
       {
         model: () => VideoModel,
         required: true,
-        include: [{ model: () => VideoFileModel }]
+        include: [
+          { model: () => VideoFileModel }
+        ]
       }
     ]
   }
@@ -94,15 +96,17 @@ export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel>
   Video: VideoModel
 
   static listForApi (nextOwnerId: number, start: number, count: number, sort: string) {
-    return VideoChangeOwnershipModel.scope(ScopeNames.FULL).findAndCountAll({
+    const query = {
       offset: start,
       limit: count,
       order: getSort(sort),
       where: {
         nextOwnerAccountId: nextOwnerId
       }
-    })
-      .then(({ rows, count }) => ({ total: count, data: rows }))
+    }
+
+    return VideoChangeOwnershipModel.scope(ScopeNames.FULL).findAndCountAll(query)
+                                    .then(({ rows, count }) => ({ total: count, data: rows }))
   }
 
   static load (id: number) {
index 475530daf4f423ede8fc5b8b645d773538fa566d..f4586917e83918eaee7e1a6936449da870cc4f94 100644 (file)
@@ -296,6 +296,12 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
       })
   }
 
+  static loadByIdAndPopulateAccount (id: number) {
+    return VideoChannelModel.unscoped()
+      .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
+      .findById(id)
+  }
+
   static loadByIdAndAccount (id: number, accountId: number) {
     const query = {
       where: {
@@ -304,13 +310,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
       }
     }
 
-    return VideoChannelModel
+    return VideoChannelModel.unscoped()
       .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
       .findOne(query)
   }
 
   static loadAndPopulateAccount (id: number) {
-    return VideoChannelModel
+    return VideoChannelModel.unscoped()
       .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
       .findById(id)
   }
@@ -365,7 +371,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
       ]
     }
 
-    return VideoChannelModel
+    return VideoChannelModel.unscoped()
       .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
       .findOne(query)
   }
@@ -390,7 +396,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
       ]
     }
 
-    return VideoChannelModel
+    return VideoChannelModel.unscoped()
       .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
       .findOne(query)
   }
@@ -402,7 +408,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
       ]
     }
 
-    return VideoChannelModel
+    return VideoChannelModel.unscoped()
       .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ])
       .findById(id, options)
   }
index 8286ff35654c9a6a0eb31d74be84812d909864c0..a328a49c1a8398d11585eac120460cc2f5c2279c 100644 (file)
@@ -5,6 +5,7 @@ import './video-abuse'
 import './video-blacklist'
 import './video-blacklist-management'
 import './video-captions'
+import './vidoe-change-ownership'
 import './video-channels'
 import './video-comments'
 import './video-description'
index 275be40be8c310924f3402d46e5104aa3b788fa0..1578a471d8ddb51aa0f98c4ef13da5d659ced9f4 100644 (file)
@@ -5,7 +5,7 @@ import 'mocha'
 import {
   acceptChangeOwnership,
   changeVideoOwnership,
-  createUser,
+  createUser, doubleFollow, flushAndRunMultipleServers,
   flushTests,
   getMyUserInformation,
   getVideoChangeOwnershipList,
@@ -16,15 +16,17 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo,
-  userLogin
+  userLogin,
+  getVideo
 } from '../../utils'
 import { waitJobs } from '../../utils/server/jobs'
 import { User } from '../../../../shared/models/users'
+import { VideoDetails } from '../../../../shared/models/videos'
 
 const expect = chai.expect
 
 describe('Test video change ownership - nominal', function () {
-  let server: ServerInfo = undefined
+  let servers: ServerInfo[] = []
   const firstUser = {
     username: 'first',
     password: 'My great password'
@@ -40,43 +42,44 @@ describe('Test video change ownership - nominal', function () {
   before(async function () {
     this.timeout(50000)
 
-    // Run one server
-    await flushTests()
-    server = await runServer(1)
-    await setAccessTokensToServers([server])
+    servers = await flushAndRunMultipleServers(2)
+    await setAccessTokensToServers(servers)
 
     const videoQuota = 42000000
-    await createUser(server.url, server.accessToken, firstUser.username, firstUser.password, videoQuota)
-    await createUser(server.url, server.accessToken, secondUser.username, secondUser.password, videoQuota)
+    await createUser(servers[0].url, servers[0].accessToken, firstUser.username, firstUser.password, videoQuota)
+    await createUser(servers[0].url, servers[0].accessToken, secondUser.username, secondUser.password, videoQuota)
 
-    firstUserAccessToken = await userLogin(server, firstUser)
-    secondUserAccessToken = await userLogin(server, secondUser)
+    firstUserAccessToken = await userLogin(servers[0], firstUser)
+    secondUserAccessToken = await userLogin(servers[0], secondUser)
 
-    // Upload some videos on the server
-    const video1Attributes = {
+    const videoAttributes = {
       name: 'my super name',
       description: 'my super description'
     }
-    await uploadVideo(server.url, firstUserAccessToken, video1Attributes)
+    await uploadVideo(servers[0].url, firstUserAccessToken, videoAttributes)
 
-    await waitJobs(server)
+    await waitJobs(servers)
 
-    const res = await getVideosList(server.url)
+    const res = await getVideosList(servers[0].url)
     const videos = res.body.data
 
     expect(videos.length).to.equal(1)
 
-    server.video = videos.find(video => video.name === 'my super name')
+    const video = videos.find(video => video.name === 'my super name')
+    expect(video.channel.name).to.equal('first_channel')
+    servers[0].video = video
+
+    await doubleFollow(servers[0], servers[1])
   })
 
   it('Should not have video change ownership', async function () {
-    const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
+    const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken)
 
     expect(resFirstUser.body.total).to.equal(0)
     expect(resFirstUser.body.data).to.be.an('array')
     expect(resFirstUser.body.data.length).to.equal(0)
 
-    const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
+    const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
 
     expect(resSecondUser.body.total).to.equal(0)
     expect(resSecondUser.body.data).to.be.an('array')
@@ -86,17 +89,17 @@ describe('Test video change ownership - nominal', function () {
   it('Should send a request to change ownership of a video', async function () {
     this.timeout(15000)
 
-    await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
+    await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username)
   })
 
   it('Should only return a request to change ownership for the second user', async function () {
-    const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
+    const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken)
 
     expect(resFirstUser.body.total).to.equal(0)
     expect(resFirstUser.body.data).to.be.an('array')
     expect(resFirstUser.body.data.length).to.equal(0)
 
-    const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
+    const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
 
     expect(resSecondUser.body.total).to.equal(1)
     expect(resSecondUser.body.data).to.be.an('array')
@@ -108,13 +111,13 @@ describe('Test video change ownership - nominal', function () {
   it('Should accept the same change ownership request without crashing', async function () {
     this.timeout(10000)
 
-    await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
+    await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username)
   })
 
   it('Should not create multiple change ownership requests while one is waiting', async function () {
     this.timeout(10000)
 
-    const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
+    const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
 
     expect(resSecondUser.body.total).to.equal(1)
     expect(resSecondUser.body.data).to.be.an('array')
@@ -124,29 +127,29 @@ describe('Test video change ownership - nominal', function () {
   it('Should not be possible to refuse the change of ownership from first user', async function () {
     this.timeout(10000)
 
-    await refuseChangeOwnership(server.url, firstUserAccessToken, lastRequestChangeOwnershipId, 403)
+    await refuseChangeOwnership(servers[0].url, firstUserAccessToken, lastRequestChangeOwnershipId, 403)
   })
 
   it('Should be possible to refuse the change of ownership from second user', async function () {
     this.timeout(10000)
 
-    await refuseChangeOwnership(server.url, secondUserAccessToken, lastRequestChangeOwnershipId)
+    await refuseChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId)
   })
 
   it('Should send a new request to change ownership of a video', async function () {
     this.timeout(15000)
 
-    await changeVideoOwnership(server.url, firstUserAccessToken, server.video.id, secondUser.username)
+    await changeVideoOwnership(servers[0].url, firstUserAccessToken, servers[0].video.id, secondUser.username)
   })
 
   it('Should return two requests to change ownership for the second user', async function () {
-    const resFirstUser = await getVideoChangeOwnershipList(server.url, firstUserAccessToken)
+    const resFirstUser = await getVideoChangeOwnershipList(servers[0].url, firstUserAccessToken)
 
     expect(resFirstUser.body.total).to.equal(0)
     expect(resFirstUser.body.data).to.be.an('array')
     expect(resFirstUser.body.data.length).to.equal(0)
 
-    const resSecondUser = await getVideoChangeOwnershipList(server.url, secondUserAccessToken)
+    const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
 
     expect(resSecondUser.body.total).to.equal(2)
     expect(resSecondUser.body.data).to.be.an('array')
@@ -158,23 +161,37 @@ describe('Test video change ownership - nominal', function () {
   it('Should not be possible to accept the change of ownership from first user', async function () {
     this.timeout(10000)
 
-    const secondUserInformationResponse = await getMyUserInformation(server.url, secondUserAccessToken)
+    const secondUserInformationResponse = await getMyUserInformation(servers[0].url, secondUserAccessToken)
     const secondUserInformation: User = secondUserInformationResponse.body
     const channelId = secondUserInformation.videoChannels[0].id
-    await acceptChangeOwnership(server.url, firstUserAccessToken, lastRequestChangeOwnershipId, channelId, 403)
+    await acceptChangeOwnership(servers[0].url, firstUserAccessToken, lastRequestChangeOwnershipId, channelId, 403)
   })
 
   it('Should be possible to accept the change of ownership from second user', async function () {
     this.timeout(10000)
 
-    const secondUserInformationResponse = await getMyUserInformation(server.url, secondUserAccessToken)
+    const secondUserInformationResponse = await getMyUserInformation(servers[0].url, secondUserAccessToken)
     const secondUserInformation: User = secondUserInformationResponse.body
     const channelId = secondUserInformation.videoChannels[0].id
-    await acceptChangeOwnership(server.url, secondUserAccessToken, lastRequestChangeOwnershipId, channelId)
+    await acceptChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId, channelId)
+
+    await waitJobs(servers)
+  })
+
+  it('Should have video channel updated', async function () {
+    for (const server of servers) {
+      const res = await getVideo(server.url, servers[0].video.uuid)
+
+      const video: VideoDetails = res.body
+
+      expect(video.name).to.equal('my super name')
+      expect(video.channel.displayName).to.equal('Main second channel')
+      expect(video.channel.name).to.equal('second_channel')
+    }
   })
 
   after(async function () {
-    killallServers([server])
+    killallServers(servers)
   })
 })