From d8755eed1e452d2efbfc983af0e9d228d152bf6b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 16 Oct 2017 10:05:49 +0200 Subject: [PATCH] Add oembed endpoint --- .../+video-watch/video-share.component.ts | 2 +- client/src/app/videos/shared/video.model.ts | 5 + client/src/index.html | 4 +- scripts/upgrade-peertube.sh | 2 +- server.ts | 5 +- server/controllers/client.ts | 32 ++-- server/controllers/index.ts | 1 + server/controllers/services.ts | 62 +++++++ server/initializers/constants.ts | 14 +- server/middlewares/validators/index.ts | 1 + server/middlewares/validators/oembed.ts | 63 +++++++ server/models/video/video-interface.ts | 6 + server/models/video/video.ts | 29 +++- server/tests/api/check-params/index.ts | 1 + server/tests/api/check-params/services.ts | 159 ++++++++++++++++++ server/tests/api/index.ts | 1 + server/tests/api/services.ts | 85 ++++++++++ server/tests/client.ts | 17 +- server/tests/utils/index.ts | 1 + server/tests/utils/servers.ts | 2 + server/tests/utils/services.ts | 23 +++ shared/models/videos/video.model.ts | 1 + 22 files changed, 491 insertions(+), 25 deletions(-) create mode 100644 server/controllers/services.ts create mode 100644 server/middlewares/validators/oembed.ts create mode 100644 server/tests/api/check-params/services.ts create mode 100644 server/tests/api/services.ts create mode 100644 server/tests/utils/services.ts diff --git a/client/src/app/videos/+video-watch/video-share.component.ts b/client/src/app/videos/+video-watch/video-share.component.ts index 133f93498..414ed28c6 100644 --- a/client/src/app/videos/+video-watch/video-share.component.ts +++ b/client/src/app/videos/+video-watch/video-share.component.ts @@ -27,7 +27,7 @@ export class VideoShareComponent { getVideoIframeCode () { return '' } diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts index b315e59b1..51c5319ea 100644 --- a/client/src/app/videos/shared/video.model.ts +++ b/client/src/app/videos/shared/video.model.ts @@ -26,6 +26,8 @@ export class Video implements VideoServerModel { thumbnailUrl: string previewPath: string previewUrl: string + embedPath: string + embedUrl: string views: number likes: number dislikes: number @@ -64,6 +66,7 @@ export class Video implements VideoServerModel { tags: string[], thumbnailPath: string, previewPath: string, + embedPath: string, views: number, likes: number, dislikes: number, @@ -91,6 +94,8 @@ export class Video implements VideoServerModel { this.thumbnailUrl = API_URL + hash.thumbnailPath this.previewPath = hash.previewPath this.previewUrl = API_URL + hash.previewPath + this.embedPath = hash.embedPath + this.embedUrl = API_URL + hash.embedPath this.views = hash.views this.likes = hash.likes this.dislikes = hash.dislikes diff --git a/client/src/index.html b/client/src/index.html index 91ed04d17..8e94b903d 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -7,8 +7,8 @@ - - + + diff --git a/scripts/upgrade-peertube.sh b/scripts/upgrade-peertube.sh index 5186a269f..562a2a618 100755 --- a/scripts/upgrade-peertube.sh +++ b/scripts/upgrade-peertube.sh @@ -26,7 +26,7 @@ if ! which yarn > /dev/null; then fi if pgrep peertube > /dev/null; then - echo 'PeerTube is running!' + echo 'PeerTube is running, please shut it off before upgrading' exit 0 fi diff --git a/server.ts b/server.ts index 3f2d27718..72bb11e74 100644 --- a/server.ts +++ b/server.ts @@ -47,7 +47,7 @@ db.init(false).then(() => onDatabaseInitDone()) // ----------- PeerTube modules ----------- import { migrate, installApplication } from './server/initializers' import { JobScheduler, activateSchedulers, VideosPreviewCache } from './server/lib' -import { apiRouter, clientsRouter, staticRouter } from './server/controllers' +import { apiRouter, clientsRouter, staticRouter, servicesRouter } from './server/controllers' // ----------- Command line ----------- @@ -85,6 +85,9 @@ app.use(bodyParser.urlencoded({ extended: false })) const apiRoute = '/api/' + API_VERSION app.use(apiRoute, apiRouter) +// Services (oembed...) +app.use('/services', servicesRouter) + // Client files app.use('/', clientsRouter) diff --git a/server/controllers/client.ts b/server/controllers/client.ts index b23f7e1ae..e3c962058 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts @@ -8,7 +8,7 @@ import { CONFIG, STATIC_PATHS, STATIC_MAX_AGE, - OPENGRAPH_COMMENT + OPENGRAPH_AND_OEMBED_COMMENT } from '../initializers' import { root, readFileBufferPromise } from '../helpers' import { VideoInstance } from '../models' @@ -19,7 +19,7 @@ const distPath = join(root(), 'client', 'dist') const embedPath = join(distPath, 'standalone', 'videos', 'embed.html') const indexPath = join(distPath, 'index.html') -// Special route that add OpenGraph tags +// Special route that add OpenGraph and oEmbed tags // Do not use a template engine for a so little thing clientsRouter.use('/videos/watch/:id', generateWatchHtmlPage) @@ -43,11 +43,11 @@ export { // --------------------------------------------------------------------------- -function addOpenGraphTags (htmlStringPage: string, video: VideoInstance) { +function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance) { const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName() - const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.id + const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid - const metaTags = { + const openGraphMetaTags = { 'og:type': 'video', 'og:title': video.name, 'og:image': previewUrl, @@ -65,14 +65,26 @@ function addOpenGraphTags (htmlStringPage: string, video: VideoInstance) { 'twitter:image': previewUrl } + const oembedLinkTags = [ + { + type: 'application/json+oembed', + href: CONFIG.WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(videoUrl), + title: video.name + } + ] + let tagsString = '' - Object.keys(metaTags).forEach(tagName => { - const tagValue = metaTags[tagName] + Object.keys(openGraphMetaTags).forEach(tagName => { + const tagValue = openGraphMetaTags[tagName] - tagsString += '' + tagsString += `` }) - return htmlStringPage.replace(OPENGRAPH_COMMENT, tagsString) + for (const oembedLinkTag of oembedLinkTags) { + tagsString += `` + } + + return htmlStringPage.replace(OPENGRAPH_AND_OEMBED_COMMENT, tagsString) } function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) { @@ -101,7 +113,7 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex // Let Angular application handle errors if (!video) return res.sendFile(indexPath) - const htmlStringPageWithTags = addOpenGraphTags(html, video) + const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video) res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) }) .catch(err => next(err)) diff --git a/server/controllers/index.ts b/server/controllers/index.ts index 0223a98f1..51cb480a3 100644 --- a/server/controllers/index.ts +++ b/server/controllers/index.ts @@ -1,3 +1,4 @@ export * from './static' export * from './client' +export * from './services' export * from './api' diff --git a/server/controllers/services.ts b/server/controllers/services.ts new file mode 100644 index 000000000..3ce6bd526 --- /dev/null +++ b/server/controllers/services.ts @@ -0,0 +1,62 @@ +import * as express from 'express' + +import { CONFIG, THUMBNAILS_SIZE } from '../initializers' +import { oembedValidator } from '../middlewares' +import { VideoInstance } from '../models' + +const servicesRouter = express.Router() + +servicesRouter.use('/oembed', oembedValidator, generateOEmbed) + +// --------------------------------------------------------------------------- + +export { + servicesRouter +} + +// --------------------------------------------------------------------------- + +function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) { + const video = res.locals.video as VideoInstance + const webserverUrl = CONFIG.WEBSERVER.URL + const maxHeight = parseInt(req.query.maxheight, 10) + const maxWidth = parseInt(req.query.maxwidth, 10) + + const embedUrl = webserverUrl + video.getEmbedPath() + let thumbnailUrl = webserverUrl + video.getThumbnailPath() + let embedWidth = 560 + let embedHeight = 315 + + if (maxHeight < embedHeight) embedHeight = maxHeight + if (maxWidth < embedWidth) embedWidth = maxWidth + + // Our thumbnail is too big for the consumer + if ( + (maxHeight !== undefined && maxHeight < THUMBNAILS_SIZE.height) || + (maxWidth !== undefined && maxWidth < THUMBNAILS_SIZE.width) + ) { + thumbnailUrl = undefined + } + + const html = `` + + const json: any = { + type: 'video', + version: '1.0', + html, + width: embedWidth, + height: embedHeight, + title: video.name, + author_name: video.Author.name, + provider_name: 'PeerTube', + provider_url: webserverUrl + } + + if (thumbnailUrl !== undefined) { + json.thumbnail_url = thumbnailUrl + json.thumbnail_width = THUMBNAILS_SIZE.width + json.thumbnail_height = THUMBNAILS_SIZE.height + } + + return res.json(json) +} diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index b11575b34..6218644cf 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -295,8 +295,14 @@ const STATIC_PATHS = { let STATIC_MAX_AGE = '30d' // Videos thumbnail size -const THUMBNAILS_SIZE = '200x110' -const PREVIEWS_SIZE = '640x480' +const THUMBNAILS_SIZE = { + width: 200, + height: 110 +} +const PREVIEWS_SIZE = { + width: 640, + height: 480 +} // Sub folders of cache directory const CACHE = { @@ -314,7 +320,7 @@ const USER_ROLES: { [ id: string ]: UserRole } = { // --------------------------------------------------------------------------- -const OPENGRAPH_COMMENT = '' +const OPENGRAPH_AND_OEMBED_COMMENT = '' // --------------------------------------------------------------------------- @@ -344,7 +350,7 @@ export { JOBS_FETCHING_INTERVAL, LAST_MIGRATION_VERSION, OAUTH_LIFETIME, - OPENGRAPH_COMMENT, + OPENGRAPH_AND_OEMBED_COMMENT, PAGINATION_COUNT_DEFAULT, PODS_SCORE, PREVIEWS_SIZE, diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index 418fa5f1d..068c41b24 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts @@ -1,3 +1,4 @@ +export * from './oembed' export * from './remote' export * from './pagination' export * from './pods' diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts new file mode 100644 index 000000000..4b8c03faf --- /dev/null +++ b/server/middlewares/validators/oembed.ts @@ -0,0 +1,63 @@ +import { query } from 'express-validator/check' +import * as express from 'express' +import { join } from 'path' + +import { checkErrors } from './utils' +import { CONFIG } from '../../initializers' +import { logger } from '../../helpers' +import { checkVideoExists, isVideoIdOrUUIDValid } from '../../helpers/custom-validators/videos' +import { isTestInstance } from '../../helpers/core-utils' + +const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' +const videoWatchRegex = new RegExp('([^/]+)$') +const isURLOptions = { + require_host: true, + require_tld: true +} + +// We validate 'localhost', so we don't have the top level domain +if (isTestInstance()) { + isURLOptions.require_tld = false +} + +const oembedValidator = [ + query('url').isURL(isURLOptions).withMessage('Should have a valid url'), + query('maxwidth').optional().isInt().withMessage('Should have a valid max width'), + query('maxheight').optional().isInt().withMessage('Should have a valid max height'), + query('format').optional().isIn([ 'xml', 'json' ]).withMessage('Should have a valid format'), + + (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking oembed parameters', { parameters: req.query }) + + checkErrors(req, res, () => { + if (req.query.format !== undefined && req.query.format !== 'json') { + return res.status(501) + .json({ error: 'Requested format is not implemented on server.' }) + .end() + } + + const startIsOk = req.query.url.startsWith(urlShouldStartWith) + const matches = videoWatchRegex.exec(req.query.url) + if (startIsOk === false || matches === null) { + return res.status(400) + .json({ error: 'Invalid url.' }) + .end() + } + + const videoId = matches[1] + if (isVideoIdOrUUIDValid(videoId) === false) { + return res.status(400) + .json({ error: 'Invalid video id.' }) + .end() + } + + checkVideoExists(videoId, res, next) + }) + } +] + +// --------------------------------------------------------------------------- + +export { + oembedValidator +} diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index 6a3db4f3e..1402df26a 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts @@ -32,6 +32,9 @@ export namespace VideoMethods { export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise export type GetOriginalFileHeight = (this: VideoInstance) => Promise + export type GetEmbedPath = (this: VideoInstance) => string + export type GetThumbnailPath = (this: VideoInstance) => string + export type GetPreviewPath = (this: VideoInstance) => string // Return thumbnail name export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise @@ -107,7 +110,9 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In getOriginalFile: VideoMethods.GetOriginalFile generateMagnetUri: VideoMethods.GenerateMagnetUri getPreviewName: VideoMethods.GetPreviewName + getPreviewPath: VideoMethods.GetPreviewPath getThumbnailName: VideoMethods.GetThumbnailName + getThumbnailPath: VideoMethods.GetThumbnailPath getTorrentFileName: VideoMethods.GetTorrentFileName getVideoFilename: VideoMethods.GetVideoFilename getVideoFilePath: VideoMethods.GetVideoFilePath @@ -122,6 +127,7 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile getOriginalFileHeight: VideoMethods.GetOriginalFileHeight + getEmbedPath: VideoMethods.GetEmbedPath setTags: Sequelize.HasManySetAssociationsMixin addVideoFile: Sequelize.HasManyAddAssociationMixin diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 2ba6cf25f..0d0048b4a 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -54,7 +54,9 @@ let getOriginalFile: VideoMethods.GetOriginalFile let generateMagnetUri: VideoMethods.GenerateMagnetUri let getVideoFilename: VideoMethods.GetVideoFilename let getThumbnailName: VideoMethods.GetThumbnailName +let getThumbnailPath: VideoMethods.GetThumbnailPath let getPreviewName: VideoMethods.GetPreviewName +let getPreviewPath: VideoMethods.GetPreviewPath let getTorrentFileName: VideoMethods.GetTorrentFileName let isOwned: VideoMethods.IsOwned let toFormattedJSON: VideoMethods.ToFormattedJSON @@ -67,6 +69,7 @@ let createThumbnail: VideoMethods.CreateThumbnail let getVideoFilePath: VideoMethods.GetVideoFilePath let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight +let getEmbedPath: VideoMethods.GetEmbedPath let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData let list: VideoMethods.List @@ -252,7 +255,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da createTorrentAndSetInfoHash, generateMagnetUri, getPreviewName, + getPreviewPath, getThumbnailName, + getThumbnailPath, getTorrentFileName, getVideoFilename, getVideoFilePath, @@ -267,7 +272,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da toUpdateRemoteJSON, optimizeOriginalVideofile, transcodeOriginalVideofile, - getOriginalFileHeight + getOriginalFileHeight, + getEmbedPath ] addMethodsToModel(Video, classMethods, instanceMethods) @@ -375,11 +381,13 @@ createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) { } createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) { + const imageSize = THUMBNAILS_SIZE.width + 'x' + THUMBNAILS_SIZE.height + return generateImageFromVideoFile( this.getVideoFilePath(videoFile), CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName(), - THUMBNAILS_SIZE + imageSize ) } @@ -438,6 +446,18 @@ generateMagnetUri = function (this: VideoInstance, videoFile: VideoFileInstance) return magnetUtil.encode(magnetHash) } +getEmbedPath = function (this: VideoInstance) { + return '/videos/embed/' + this.uuid +} + +getThumbnailPath = function (this: VideoInstance) { + return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()) +} + +getPreviewPath = function (this: VideoInstance) { + return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) +} + toFormattedJSON = function (this: VideoInstance) { let podHost @@ -480,8 +500,9 @@ toFormattedJSON = function (this: VideoInstance) { likes: this.likes, dislikes: this.dislikes, tags: map(this.Tags, 'name'), - thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), - previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()), + thumbnailPath: this.getThumbnailPath(), + previewPath: this.getPreviewPath(), + embedPath: this.getEmbedPath(), createdAt: this.createdAt, updatedAt: this.updatedAt, files: [] diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 399a05bc3..954b206e9 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts @@ -3,6 +3,7 @@ import './pods' import './remotes' import './users' import './request-schedulers' +import './services' import './videos' import './video-abuses' import './video-blacklist' diff --git a/server/tests/api/check-params/services.ts b/server/tests/api/check-params/services.ts new file mode 100644 index 000000000..780254df5 --- /dev/null +++ b/server/tests/api/check-params/services.ts @@ -0,0 +1,159 @@ +/* tslint:disable:no-unused-expression */ + +import * as request from 'supertest' +import 'mocha' + +import { + flushTests, + runServer, + setAccessTokensToServers, + killallServers +} from '../../utils' +import { getVideosList, uploadVideo } from '../../utils/videos' + +describe('Test services API validators', function () { + let server + + // --------------------------------------------------------------- + + before(async function () { + this.timeout(60000) + + await flushTests() + + server = await runServer(1) + await setAccessTokensToServers([ server ]) + + const videoAttributes = { + name: 'my super name' + } + await uploadVideo(server.url, server.accessToken, videoAttributes) + + const res = await getVideosList(server.url) + server.video = res.body.data[0] + }) + + describe('Test oEmbed API validators', function () { + const path = '/services/oembed' + + it('Should fail with an invalid url', async function () { + const embedUrl = 'hello.com' + + await request(server.url) + .get(path) + .query({ url: embedUrl }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with an invalid host', async function () { + const embedUrl = 'http://hello.com/videos/watch/' + server.video.uuid + + await request(server.url) + .get(path) + .query({ url: embedUrl }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with an invalid video id', async function () { + const embedUrl = 'http://localhost:9001/videos/watch/blabla' + + await request(server.url) + .get(path) + .query({ url: embedUrl }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with an unknown video', async function () { + const embedUrl = 'http://localhost:9001/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c' + + await request(server.url) + .get(path) + .query({ url: embedUrl }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(404) + }) + + it('Should fail with an invalid path', async function () { + const embedUrl = 'http://localhost:9001/videos/watchs/' + server.video.uuid + + await request(server.url) + .get(path) + .query({ url: embedUrl }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with an invalid max height', async function () { + const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid + + await request(server.url) + .get(path) + .query({ + url: embedUrl, + maxheight: 'hello' + }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with an invalid max width', async function () { + const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid + + await request(server.url) + .get(path) + .query({ + url: embedUrl, + maxwidth: 'hello' + }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with an invalid format', async function () { + const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid + + await request(server.url) + .get(path) + .query({ + url: embedUrl, + format: 'blabla' + }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400) + }) + + it('Should fail with a non supported format', async function () { + const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid + + await request(server.url) + .get(path) + .query({ + url: embedUrl, + format: 'xml' + }) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(501) + }) + }) + + after(async function () { + killallServers([ server ]) + + // Keep the logs if the test failed + if (this['ok']) { + await flushTests() + } + }) +}) diff --git a/server/tests/api/index.ts b/server/tests/api/index.ts index 03711e68a..e50e65049 100644 --- a/server/tests/api/index.ts +++ b/server/tests/api/index.ts @@ -8,6 +8,7 @@ import './video-abuse' import './video-blacklist' import './video-blacklist-management' import './multiple-pods' +import './services' import './request-schedulers' import './friends-advanced' import './video-transcoder' diff --git a/server/tests/api/services.ts b/server/tests/api/services.ts new file mode 100644 index 000000000..b396ea582 --- /dev/null +++ b/server/tests/api/services.ts @@ -0,0 +1,85 @@ +/* tslint:disable:no-unused-expression */ + +import 'mocha' +import * as chai from 'chai' +const expect = chai.expect + +import { + ServerInfo, + flushTests, + uploadVideo, + getVideosList, + setAccessTokensToServers, + killallServers, + getOEmbed +} from '../utils' +import { runServer } from '../utils/servers' + +describe('Test services', function () { + let server: ServerInfo = null + + before(async function () { + this.timeout(120000) + + await flushTests() + + server = await runServer(1) + + await setAccessTokensToServers([ server ]) + + const videoAttributes = { + name: 'my super name' + } + await uploadVideo(server.url, server.accessToken, videoAttributes) + + const res = await getVideosList(server.url) + server.video = res.body.data[0] + }) + + it('Should have a valid oEmbed response', async function () { + const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid + + const res = await getOEmbed(server.url, oembedUrl) + const expectedHtml = `' + const expectedThumbnailUrl = 'http://localhost:9001/static/thumbnails/' + server.video.uuid + '.jpg' + + expect(res.body.html).to.equal(expectedHtml) + expect(res.body.title).to.equal(server.video.name) + expect(res.body.author_name).to.equal(server.video.author) + expect(res.body.height).to.equal(315) + expect(res.body.width).to.equal(560) + expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) + expect(res.body.thumbnail_width).to.equal(200) + expect(res.body.thumbnail_height).to.equal(110) + }) + + it('Should have a valid oEmbed response with small max height query', async function () { + const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid + const format = 'json' + const maxHeight = 50 + const maxWidth = 50 + + const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) + const expectedHtml = `' + + expect(res.body.html).to.equal(expectedHtml) + expect(res.body.title).to.equal(server.video.name) + expect(res.body.author_name).to.equal(server.video.author) + expect(res.body.height).to.equal(50) + expect(res.body.width).to.equal(50) + expect(res.body).to.not.have.property('thumbnail_url') + expect(res.body).to.not.have.property('thumbnail_width') + expect(res.body).to.not.have.property('thumbnail_height') + }) + + after(async function () { + killallServers([ server ]) + + // Keep the logs if the test failed + if (this['ok']) { + await flushTests() + } + }) +}) diff --git a/server/tests/client.ts b/server/tests/client.ts index 5e5abba5a..5f947ed2b 100644 --- a/server/tests/client.ts +++ b/server/tests/client.ts @@ -39,7 +39,7 @@ describe('Test a client controllers', function () { server.video = videos[0] }) - it('It should have valid Open Graph tags on the watch page with video id', async function () { + it('Should have valid Open Graph tags on the watch page with video id', async function () { const res = await request(server.url) .get('/videos/watch/' + server.video.id) .expect(200) @@ -48,7 +48,7 @@ describe('Test a client controllers', function () { expect(res.text).to.contain('') }) - it('It should have valid Open Graph tags on the watch page with video uuid', async function () { + it('Should have valid Open Graph tags on the watch page with video uuid', async function () { const res = await request(server.url) .get('/videos/watch/' + server.video.uuid) .expect(200) @@ -57,6 +57,19 @@ describe('Test a client controllers', function () { expect(res.text).to.contain('') }) + it('Should have valid oEmbed discovery tags', async function () { + const path = '/videos/watch/' + server.video.uuid + const res = await request(server.url) + .get(path) + .expect(200) + + const expectedLink = '` + + expect(res.text).to.contain(expectedLink) + }) + after(async function () { process.kill(-server.app.pid) diff --git a/server/tests/utils/index.ts b/server/tests/utils/index.ts index 99c445887..90ee2d515 100644 --- a/server/tests/utils/index.ts +++ b/server/tests/utils/index.ts @@ -7,6 +7,7 @@ export * from './pods' export * from './request-schedulers' export * from './requests' export * from './servers' +export * from './services' export * from './users' export * from './video-abuses' export * from './video-blacklist' diff --git a/server/tests/utils/servers.ts b/server/tests/utils/servers.ts index 88027f74e..3526ffa51 100644 --- a/server/tests/utils/servers.ts +++ b/server/tests/utils/servers.ts @@ -23,6 +23,8 @@ interface ServerInfo { video?: { id: number uuid: string + name: string + author: string } remoteVideo?: { diff --git a/server/tests/utils/services.ts b/server/tests/utils/services.ts new file mode 100644 index 000000000..1a53dd4cf --- /dev/null +++ b/server/tests/utils/services.ts @@ -0,0 +1,23 @@ +import * as request from 'supertest' + +function getOEmbed (url: string, oembedUrl: string, format?: string, maxHeight?: number, maxWidth?: number) { + const path = '/services/oembed' + const query = { + url: oembedUrl, + format, + maxheight: maxHeight, + maxwidth: maxWidth + } + + return request(url) + .get(path) + .query(query) + .set('Accept', 'application/json') + .expect(200) +} + +// --------------------------------------------------------------------------- + +export { + getOEmbed +} diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index 75070bfd6..bbcada845 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts @@ -25,6 +25,7 @@ export interface Video { tags: string[] thumbnailPath: string previewPath: string + embedPath: string views: number likes: number dislikes: number -- 2.25.1