From f6eebcb336c067e160a62020a5140d8d992ba384 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 22 Aug 2018 11:51:39 +0200 Subject: [PATCH] Add ability to search a video with an URL --- client/src/app/header/header.component.scss | 2 +- .../src/app/shared/user-subscription/index.ts | 2 +- .../video-channel/video-channel.service.ts | 10 +- server/controllers/api/search.ts | 35 +++- server/initializers/constants.ts | 2 + server/lib/activitypub/actor.ts | 4 +- server/lib/activitypub/crawl.ts | 3 +- .../activitypub/process/process-announce.ts | 4 +- .../lib/activitypub/process/process-create.ts | 9 +- server/lib/activitypub/video-comments.ts | 9 +- server/lib/activitypub/videos.ts | 184 ++++++++++-------- .../handlers/activitypub-http-fetcher.ts | 26 ++- server/tests/api/check-params/videos.ts | 6 +- server/tests/api/server/follows.ts | 3 +- server/tests/api/server/handle-down.ts | 3 +- server/tests/api/users/user-subscriptions.ts | 28 ++- server/tests/api/videos/multiple-servers.ts | 18 +- server/tests/api/videos/single-server.ts | 6 +- server/tests/utils/videos/videos.ts | 25 +-- 19 files changed, 244 insertions(+), 135 deletions(-) diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss index d79e6274b..6ce92fc22 100644 --- a/client/src/app/header/header.component.scss +++ b/client/src/app/header/header.component.scss @@ -4,7 +4,7 @@ #search-video { @include peertube-input-text($search-input-width); margin-right: 15px; - padding-right: 25px; // For the search icon + padding-right: 40px; // For the search icon &::placeholder { color: #000; diff --git a/client/src/app/shared/user-subscription/index.ts b/client/src/app/shared/user-subscription/index.ts index 024b36a41..faddae66a 100644 --- a/client/src/app/shared/user-subscription/index.ts +++ b/client/src/app/shared/user-subscription/index.ts @@ -1,2 +1,2 @@ export * from './user-subscription.service' -export * from './subscribe-button.component' \ No newline at end of file +export * from './subscribe-button.component' diff --git a/client/src/app/shared/video-channel/video-channel.service.ts b/client/src/app/shared/video-channel/video-channel.service.ts index 46b121790..c94411146 100644 --- a/client/src/app/shared/video-channel/video-channel.service.ts +++ b/client/src/app/shared/video-channel/video-channel.service.ts @@ -17,11 +17,6 @@ export class VideoChannelService { videoChannelLoaded = new ReplaySubject(1) - constructor ( - private authHttp: HttpClient, - private restExtractor: RestExtractor - ) {} - static extractVideoChannels (result: ResultList) { const videoChannels: VideoChannel[] = [] @@ -32,6 +27,11 @@ export class VideoChannelService { return { data: videoChannels, total: result.total } } + constructor ( + private authHttp: HttpClient, + private restExtractor: RestExtractor + ) { } + getVideoChannel (videoChannelName: string) { return this.authHttp.get(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName) .pipe( diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts index 7a7504b7d..9c2c7d6c1 100644 --- a/server/controllers/api/search.ts +++ b/server/controllers/api/search.ts @@ -13,6 +13,8 @@ import { videosSearchSortValidator } from '../../middlewares' import { VideosSearchQuery } from '../../../shared/models/search' +import { getOrCreateAccountAndVideoAndChannel } from '../../lib/activitypub' +import { logger } from '../../helpers/logger' const searchRouter = express.Router() @@ -33,9 +35,16 @@ export { searchRouter } // --------------------------------------------------------------------------- -async function searchVideos (req: express.Request, res: express.Response) { +function searchVideos (req: express.Request, res: express.Response) { const query: VideosSearchQuery = req.query + if (query.search.startsWith('http://') || query.search.startsWith('https://')) { + return searchVideoUrl(query.search, res) + } + return searchVideosDB(query, res) +} + +async function searchVideosDB (query: VideosSearchQuery, res: express.Response) { const options = Object.assign(query, { includeLocalVideos: true, nsfw: buildNSFWFilter(res, query.nsfw) @@ -44,3 +53,27 @@ async function searchVideos (req: express.Request, res: express.Response) { return res.json(getFormattedObjects(resultList.data, resultList.total)) } + +async function searchVideoUrl (url: string, res: express.Response) { + let video: VideoModel + + try { + const syncParam = { + likes: false, + dislikes: false, + shares: false, + comments: false, + thumbnail: true + } + + const res = await getOrCreateAccountAndVideoAndChannel(url, syncParam) + video = res ? res.video : undefined + } catch (err) { + logger.info('Cannot search remote video %s.', url) + } + + return res.json({ + total: video ? 1 : 0, + data: video ? [ video.toFormattedJSON() ] : [] + }) +} diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 7e865fe3b..99b10a7fc 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -112,6 +112,7 @@ const JOB_TTL: { [ id in JobType ]: number } = { 'email': 60000 * 10 // 10 minutes } const BROADCAST_CONCURRENCY = 10 // How many requests in parallel we do in activitypub-http-broadcast job +const CRAWL_REQUEST_CONCURRENCY = 5 // How many requests in parallel to fetch remote data (likes, shares...) const JOB_REQUEST_TIMEOUT = 3000 // 3 seconds const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days @@ -643,6 +644,7 @@ export { STATIC_DOWNLOAD_PATHS, RATES_LIMIT, VIDEO_EXT_MIMETYPE, + CRAWL_REQUEST_CONCURRENCY, JOB_COMPLETED_LIFETIME, VIDEO_IMPORT_STATES, VIDEO_VIEW_LIFETIME, diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index d84b465b2..9922229d2 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts @@ -177,7 +177,8 @@ async function addFetchOutboxJob (actor: ActorModel) { } const payload = { - uris: [ actor.outboxUrl ] + uri: actor.outboxUrl, + type: 'activity' as 'activity' } return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) @@ -248,6 +249,7 @@ function saveActorAndServerAndModelIfNotExist ( } else if (actorCreated.type === 'Group') { // Video channel actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t) actorCreated.VideoChannel.Actor = actorCreated + actorCreated.VideoChannel.Account = ownerActor.Account } return actorCreated diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts index d4fc786f7..55912341c 100644 --- a/server/lib/activitypub/crawl.ts +++ b/server/lib/activitypub/crawl.ts @@ -1,8 +1,9 @@ import { ACTIVITY_PUB, JOB_REQUEST_TIMEOUT } from '../../initializers' import { doRequest } from '../../helpers/requests' import { logger } from '../../helpers/logger' +import Bluebird = require('bluebird') -async function crawlCollectionPage (uri: string, handler: (items: T[]) => Promise) { +async function crawlCollectionPage (uri: string, handler: (items: T[]) => Promise | Bluebird) { logger.info('Crawling ActivityPub data on %s.', uri) const options = { diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index d8ca59425..b08156aa1 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts @@ -24,10 +24,8 @@ export { async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id - let video: VideoModel - const res = await getOrCreateAccountAndVideoAndChannel(objectUri) - video = res.video + const { video } = await getOrCreateAccountAndVideoAndChannel(objectUri) return sequelizeTypescript.transaction(async t => { // Add share entry diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 791148919..9655d015f 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts @@ -23,7 +23,7 @@ async function processCreateActivity (activity: ActivityCreate) { } else if (activityType === 'Dislike') { return retryTransactionWrapper(processCreateDislike, actor, activity) } else if (activityType === 'Video') { - return processCreateVideo(actor, activity) + return processCreateVideo(activity) } else if (activityType === 'Flag') { return retryTransactionWrapper(processCreateVideoAbuse, actor, activityObject as VideoAbuseObject) } else if (activityType === 'Note') { @@ -42,13 +42,10 @@ export { // --------------------------------------------------------------------------- -async function processCreateVideo ( - actor: ActorModel, - activity: ActivityCreate -) { +async function processCreateVideo (activity: ActivityCreate) { const videoToCreateData = activity.object as VideoTorrentObject - const { video } = await getOrCreateAccountAndVideoAndChannel(videoToCreateData, actor) + const { video } = await getOrCreateAccountAndVideoAndChannel(videoToCreateData) return video } diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index fd03710c2..14c7fde69 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts @@ -2,12 +2,13 @@ import { VideoCommentObject } from '../../../shared/models/activitypub/objects/v import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' import { logger } from '../../helpers/logger' import { doRequest } from '../../helpers/requests' -import { ACTIVITY_PUB } from '../../initializers' +import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers' import { ActorModel } from '../../models/activitypub/actor' import { VideoModel } from '../../models/video/video' import { VideoCommentModel } from '../../models/video/video-comment' import { getOrCreateActorAndServerAndModel } from './actor' import { getOrCreateAccountAndVideoAndChannel } from './videos' +import * as Bluebird from 'bluebird' async function videoCommentActivityObjectToDBAttributes (video: VideoModel, actor: ActorModel, comment: VideoCommentObject) { let originCommentId: number = null @@ -38,9 +39,9 @@ async function videoCommentActivityObjectToDBAttributes (video: VideoModel, acto } async function addVideoComments (commentUrls: string[], instance: VideoModel) { - for (const commentUrl of commentUrls) { - await addVideoComment(instance, commentUrl) - } + return Bluebird.map(commentUrls, commentUrl => { + return addVideoComment(instance, commentUrl) + }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) } async function addVideoComment (videoInstance: VideoModel, commentUrl: string) { diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index d1888556c..fac1d3fc7 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts @@ -11,7 +11,7 @@ import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos import { retryTransactionWrapper } from '../../helpers/database-utils' import { logger } from '../../helpers/logger' import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' -import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, VIDEO_MIMETYPE_EXT } from '../../initializers' +import { ACTIVITY_PUB, CONFIG, CRAWL_REQUEST_CONCURRENCY, REMOTE_SCHEME, sequelizeTypescript, VIDEO_MIMETYPE_EXT } from '../../initializers' import { AccountVideoRateModel } from '../../models/account/account-video-rate' import { ActorModel } from '../../models/activitypub/actor' import { TagModel } from '../../models/video/tag' @@ -26,6 +26,8 @@ import { sendCreateVideo, sendUpdateVideo } from './send' import { shareVideoByServerAndChannel } from './index' import { isArray } from '../../helpers/custom-validators/misc' import { VideoCaptionModel } from '../../models/video/video-caption' +import { JobQueue } from '../job-queue' +import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { // If the video is not private and published, we federate it @@ -178,10 +180,10 @@ function getOrCreateVideoChannel (videoObject: VideoTorrentObject) { return getOrCreateActorAndServerAndModel(channel.id) } -async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: ActorModel) { +async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { logger.debug('Adding remote video %s.', videoObject.id) - return sequelizeTypescript.transaction(async t => { + const videoCreated: VideoModel = await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } @@ -191,10 +193,6 @@ async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) const video = VideoModel.build(videoData) - // Don't block on remote HTTP request (we are in a transaction!) - generateThumbnailFromUrl(video, videoObject.icon) - .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })) - const videoCreated = await video.save(sequelizeOptions) // Process files @@ -222,68 +220,100 @@ async function getOrCreateVideo (videoObject: VideoTorrentObject, channelActor: videoCreated.VideoChannel = channelActor.VideoChannel return videoCreated }) + + const p = generateThumbnailFromUrl(videoCreated, videoObject.icon) + .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })) + + if (waitThumbnail === true) await p + + return videoCreated } -async function getOrCreateAccountAndVideoAndChannel (videoObject: VideoTorrentObject | string, actor?: ActorModel) { +type SyncParam = { + likes: boolean, + dislikes: boolean, + shares: boolean, + comments: boolean, + thumbnail: boolean +} +async function getOrCreateAccountAndVideoAndChannel ( + videoObject: VideoTorrentObject | string, + syncParam: SyncParam = { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true } +) { const videoUrl = typeof videoObject === 'string' ? videoObject : videoObject.id const videoFromDatabase = await VideoModel.loadByUrlAndPopulateAccount(videoUrl) - if (videoFromDatabase) { - return { - video: videoFromDatabase, - actor: videoFromDatabase.VideoChannel.Account.Actor, - channelActor: videoFromDatabase.VideoChannel.Actor - } - } + if (videoFromDatabase) return { video: videoFromDatabase } - videoObject = await fetchRemoteVideo(videoUrl) - if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl) + const fetchedVideo = await fetchRemoteVideo(videoUrl) + if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl) - if (!actor) { - const actorObj = videoObject.attributedTo.find(a => a.type === 'Person') - if (!actorObj) throw new Error('Cannot find associated actor to video ' + videoObject.url) - - actor = await getOrCreateActorAndServerAndModel(actorObj.id) - } - - const channelActor = await getOrCreateVideoChannel(videoObject) - - const video = await retryTransactionWrapper(getOrCreateVideo, videoObject, channelActor) + const channelActor = await getOrCreateVideoChannel(fetchedVideo) + const video = await retryTransactionWrapper(getOrCreateVideo, fetchedVideo, channelActor, syncParam.thumbnail) // Process outside the transaction because we could fetch remote data - logger.info('Adding likes of video %s.', video.uuid) - await crawlCollectionPage(videoObject.likes, (items) => createRates(items, video, 'like')) - logger.info('Adding dislikes of video %s.', video.uuid) - await crawlCollectionPage(videoObject.dislikes, (items) => createRates(items, video, 'dislike')) + logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) - logger.info('Adding shares of video %s.', video.uuid) - await crawlCollectionPage(videoObject.shares, (items) => addVideoShares(items, video)) + const jobPayloads: ActivitypubHttpFetcherPayload[] = [] - logger.info('Adding comments of video %s.', video.uuid) - await crawlCollectionPage(videoObject.comments, (items) => addVideoComments(items, video)) + if (syncParam.likes === true) { + await crawlCollectionPage(fetchedVideo.likes, items => createRates(items, video, 'like')) + .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err })) + } else { + jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) + } - return { actor, channelActor, video } + if (syncParam.dislikes === true) { + await crawlCollectionPage(fetchedVideo.dislikes, items => createRates(items, video, 'dislike')) + .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err })) + } else { + jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) + } + + if (syncParam.shares === true) { + await crawlCollectionPage(fetchedVideo.shares, items => addVideoShares(items, video)) + .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err })) + } else { + jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) + } + + if (syncParam.comments === true) { + await crawlCollectionPage(fetchedVideo.comments, items => addVideoComments(items, video)) + .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err })) + } else { + jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) + } + + await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) + + return { video } } async function createRates (actorUrls: string[], video: VideoModel, rate: VideoRateType) { let rateCounts = 0 - const tasks: Bluebird[] = [] - for (const actorUrl of actorUrls) { - const actor = await getOrCreateActorAndServerAndModel(actorUrl) - const p = AccountVideoRateModel - .create({ - videoId: video.id, - accountId: actor.Account.id, - type: rate - }) - .then(() => rateCounts += 1) + await Bluebird.map(actorUrls, async actorUrl => { + try { + const actor = await getOrCreateActorAndServerAndModel(actorUrl) + const [ , created ] = await AccountVideoRateModel + .findOrCreate({ + where: { + videoId: video.id, + accountId: actor.Account.id + }, + defaults: { + videoId: video.id, + accountId: actor.Account.id, + type: rate + } + }) - tasks.push(p) - } - - await Promise.all(tasks) + if (created) rateCounts += 1 + } catch (err) { + logger.warn('Cannot add rate %s for actor %s.', rate, actorUrl, { err }) + } + }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid) @@ -294,34 +324,35 @@ async function createRates (actorUrls: string[], video: VideoModel, rate: VideoR } async function addVideoShares (shareUrls: string[], instance: VideoModel) { - for (const shareUrl of shareUrls) { - // Fetch url - const { body } = await doRequest({ - uri: shareUrl, - json: true, - activityPub: true - }) - if (!body || !body.actor) { - logger.warn('Cannot add remote share with url: %s, skipping...', shareUrl) - continue - } + await Bluebird.map(shareUrls, async shareUrl => { + try { + // Fetch url + const { body } = await doRequest({ + uri: shareUrl, + json: true, + activityPub: true + }) + if (!body || !body.actor) throw new Error('Body of body actor is invalid') - const actorUrl = body.actor - const actor = await getOrCreateActorAndServerAndModel(actorUrl) + const actorUrl = body.actor + const actor = await getOrCreateActorAndServerAndModel(actorUrl) - const entry = { - actorId: actor.id, - videoId: instance.id, - url: shareUrl - } - - await VideoShareModel.findOrCreate({ - where: { + const entry = { + actorId: actor.id, + videoId: instance.id, url: shareUrl - }, - defaults: entry - }) - } + } + + await VideoShareModel.findOrCreate({ + where: { + url: shareUrl + }, + defaults: entry + }) + } catch (err) { + logger.warn('Cannot add share %s.', shareUrl, { err }) + } + }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) } async function fetchRemoteVideo (videoUrl: string): Promise { @@ -355,5 +386,6 @@ export { videoFileActivityUrlToDBAttributes, getOrCreateVideo, getOrCreateVideoChannel, - addVideoShares + addVideoShares, + createRates } diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts index f21da087e..72d670277 100644 --- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts +++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts @@ -1,22 +1,36 @@ import * as Bull from 'bull' import { logger } from '../../../helpers/logger' import { processActivities } from '../../activitypub/process' -import { ActivitypubHttpBroadcastPayload } from './activitypub-http-broadcast' +import { VideoModel } from '../../../models/video/video' +import { addVideoShares, createRates } from '../../activitypub/videos' +import { addVideoComments } from '../../activitypub/video-comments' import { crawlCollectionPage } from '../../activitypub/crawl' -import { Activity } from '../../../../shared/models/activitypub' + +type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' export type ActivitypubHttpFetcherPayload = { - uris: string[] + uri: string + type: FetchType + videoId?: number } async function processActivityPubHttpFetcher (job: Bull.Job) { logger.info('Processing ActivityPub fetcher in job %d.', job.id) - const payload = job.data as ActivitypubHttpBroadcastPayload + const payload = job.data as ActivitypubHttpFetcherPayload - for (const uri of payload.uris) { - await crawlCollectionPage(uri, (items) => processActivities(items)) + let video: VideoModel + if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) + + const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise } = { + 'activity': items => processActivities(items), + 'video-likes': items => createRates(items, video, 'like'), + 'video-dislikes': items => createRates(items, video, 'dislike'), + 'video-shares': items => addVideoShares(items, video), + 'video-comments': items => addVideoComments(items, video) } + + return crawlCollectionPage(payload.uri, fetcherType[payload.type]) } // --------------------------------------------------------------------------- diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 7fce8ba7c..904d22870 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts @@ -20,7 +20,7 @@ describe('Test videos API validator', function () { let userAccessToken = '' let accountName: string let channelId: number - let channelUUID: string + let channelName: string let videoId // --------------------------------------------------------------- @@ -42,7 +42,7 @@ describe('Test videos API validator', function () { { const res = await getMyUserInformation(server.url, server.accessToken) channelId = res.body.videoChannels[ 0 ].id - channelUUID = res.body.videoChannels[ 0 ].uuid + channelName = res.body.videoChannels[ 0 ].name accountName = res.body.account.name + '@' + res.body.account.host } }) @@ -140,7 +140,7 @@ describe('Test videos API validator', function () { let path: string before(async function () { - path = '/api/v1/video-channels/' + channelUUID + '/videos' + path = '/api/v1/video-channels/' + channelName + '/videos' }) it('Should fail with a bad start pagination', async function () { diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index 243fcd4e7..310c291bf 100644 --- a/server/tests/api/server/follows.ts +++ b/server/tests/api/server/follows.ts @@ -311,7 +311,8 @@ describe('Test follows', function () { likes: 1, dislikes: 1, channel: { - name: 'Main root channel', + displayName: 'Main root channel', + name: 'root_channel', description: '', isLocal }, diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts index df35b36eb..ed15c8090 100644 --- a/server/tests/api/server/handle-down.ts +++ b/server/tests/api/server/handle-down.ts @@ -71,7 +71,8 @@ describe('Test handle downs', function () { privacy: VideoPrivacy.PUBLIC, commentsEnabled: true, channel: { - name: 'Main root channel', + name: 'root_channel', + displayName: 'Main root channel', description: '', isLocal: false }, diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts index 2fbda6828..cb7d94b0b 100644 --- a/server/tests/api/users/user-subscriptions.ts +++ b/server/tests/api/users/user-subscriptions.ts @@ -2,7 +2,7 @@ import * as chai from 'chai' import 'mocha' -import { createUser, doubleFollow, flushAndRunMultipleServers, follow, getVideosList, unfollow, userLogin } from '../../utils' +import { createUser, doubleFollow, flushAndRunMultipleServers, follow, getVideosList, unfollow, updateVideo, userLogin } from '../../utils' import { killallServers, ServerInfo, uploadVideo } from '../../utils/index' import { setAccessTokensToServers } from '../../utils/users/login' import { Video, VideoChannel } from '../../../../shared/models/videos' @@ -20,6 +20,7 @@ const expect = chai.expect describe('Test users subscriptions', function () { let servers: ServerInfo[] = [] const users: { accessToken: string }[] = [] + let video3UUID: string before(async function () { this.timeout(120000) @@ -65,7 +66,8 @@ describe('Test users subscriptions', function () { await waitJobs(servers) - await uploadVideo(servers[2].url, users[2].accessToken, { name: 'video server 3 added after follow' }) + const res = await uploadVideo(servers[2].url, users[2].accessToken, { name: 'video server 3 added after follow' }) + video3UUID = res.body.video.uuid await waitJobs(servers) }) @@ -247,7 +249,21 @@ describe('Test users subscriptions', function () { } }) + it('Should update a video of server 3 and see the updated video on server 1', async function () { + this.timeout(30000) + + await updateVideo(servers[2].url, users[2].accessToken, video3UUID, { name: 'video server 3 added after follow updated' }) + + await waitJobs(servers) + + const res = await listUserSubscriptionVideos(servers[0].url, users[0].accessToken, 'createdAt') + const videos: Video[] = res.body.data + expect(videos[2].name).to.equal('video server 3 added after follow updated') + }) + it('Should remove user of server 3 subscription', async function () { + this.timeout(30000) + await removeUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003') await waitJobs(servers) @@ -267,6 +283,8 @@ describe('Test users subscriptions', function () { }) it('Should remove the root subscription and not display the videos anymore', async function () { + this.timeout(30000) + await removeUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:9001') await waitJobs(servers) @@ -288,7 +306,7 @@ describe('Test users subscriptions', function () { for (const video of res.body.data) { expect(video.name).to.not.contain('1-3') expect(video.name).to.not.contain('2-3') - expect(video.name).to.not.contain('video server 3 added after follow') + expect(video.name).to.not.contain('video server 3 added after follow updated') } }) @@ -309,7 +327,7 @@ describe('Test users subscriptions', function () { expect(videos[0].name).to.equal('video 1-3') expect(videos[1].name).to.equal('video 2-3') - expect(videos[2].name).to.equal('video server 3 added after follow') + expect(videos[2].name).to.equal('video server 3 added after follow updated') } { @@ -319,7 +337,7 @@ describe('Test users subscriptions', function () { for (const video of res.body.data) { expect(video.name).to.not.contain('1-3') expect(video.name).to.not.contain('2-3') - expect(video.name).to.not.contain('video server 3 added after follow') + expect(video.name).to.not.contain('video server 3 added after follow updated') } } }) diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 3c3839338..c551ccc59 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts @@ -128,7 +128,8 @@ describe('Test multiple servers', function () { privacy: VideoPrivacy.PUBLIC, commentsEnabled: true, channel: { - name: 'my channel', + displayName: 'my channel', + name: 'super_channel_name', description: 'super channel', isLocal }, @@ -201,7 +202,8 @@ describe('Test multiple servers', function () { tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], privacy: VideoPrivacy.PUBLIC, channel: { - name: 'Main user1 channel', + displayName: 'Main user1 channel', + name: 'user1_channel', description: 'super channel', isLocal }, @@ -307,7 +309,8 @@ describe('Test multiple servers', function () { tags: [ 'tag1p3' ], privacy: VideoPrivacy.PUBLIC, channel: { - name: 'Main root channel', + displayName: 'Main root channel', + name: 'root_channel', description: '', isLocal }, @@ -339,7 +342,8 @@ describe('Test multiple servers', function () { tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ], privacy: VideoPrivacy.PUBLIC, channel: { - name: 'Main root channel', + displayName: 'Main root channel', + name: 'root_channel', description: '', isLocal }, @@ -647,7 +651,8 @@ describe('Test multiple servers', function () { tags: [ 'tag_up_1', 'tag_up_2' ], privacy: VideoPrivacy.PUBLIC, channel: { - name: 'Main root channel', + displayName: 'Main root channel', + name: 'root_channel', description: '', isLocal }, @@ -967,7 +972,8 @@ describe('Test multiple servers', function () { tags: [ ], privacy: VideoPrivacy.PUBLIC, channel: { - name: 'Main root channel', + displayName: 'Main root channel', + name: 'root_channel', description: '', isLocal }, diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index 12181ad67..a757ad9da 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts @@ -56,7 +56,8 @@ describe('Test a single server', function () { privacy: VideoPrivacy.PUBLIC, commentsEnabled: true, channel: { - name: 'Main root channel', + displayName: 'Main root channel', + name: 'root_channel', description: '', isLocal: true }, @@ -87,7 +88,8 @@ describe('Test a single server', function () { duration: 5, commentsEnabled: false, channel: { - name: 'Main root channel', + name: 'root_channel', + displayName: 'Main root channel', description: '', isLocal: true }, diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts index 592248144..674a92df9 100644 --- a/server/tests/utils/videos/videos.ts +++ b/server/tests/utils/videos/videos.ts @@ -438,18 +438,19 @@ async function completeVideoCheck ( name: string host: string } - isLocal: boolean, - tags: string[], - privacy: number, - likes?: number, - dislikes?: number, - duration: number, + isLocal: boolean + tags: string[] + privacy: number + likes?: number + dislikes?: number + duration: number channel: { - name: string, + displayName: string + name: string description isLocal: boolean } - fixture: string, + fixture: string files: { resolution: number size: number @@ -476,8 +477,8 @@ async function completeVideoCheck ( expect(video.account.uuid).to.be.a('string') expect(video.account.host).to.equal(attributes.account.host) expect(video.account.name).to.equal(attributes.account.name) - expect(video.channel.displayName).to.equal(attributes.channel.name) - expect(video.channel.name).to.have.lengthOf(36) + expect(video.channel.displayName).to.equal(attributes.channel.displayName) + expect(video.channel.name).to.equal(attributes.channel.name) expect(video.likes).to.equal(attributes.likes) expect(video.dislikes).to.equal(attributes.dislikes) expect(video.isLocal).to.equal(attributes.isLocal) @@ -497,8 +498,8 @@ async function completeVideoCheck ( expect(videoDetails.tags).to.deep.equal(attributes.tags) expect(videoDetails.account.name).to.equal(attributes.account.name) expect(videoDetails.account.host).to.equal(attributes.account.host) - expect(videoDetails.channel.displayName).to.equal(attributes.channel.name) - expect(videoDetails.channel.name).to.have.lengthOf(36) + expect(video.channel.displayName).to.equal(attributes.channel.displayName) + expect(video.channel.name).to.equal(attributes.channel.name) expect(videoDetails.channel.host).to.equal(attributes.account.host) expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal) expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true