1
0
Fork 0

Add fcbk open-graph and twitter-card metas for accounts, video-channels, playlists urls (#2996)

* Add open-graph and twitter-card metas to accounts and video-channels

* Add open-graph and twitter-card to video-playlists watch view

* Refactor meta-tags creation server-side

* Add client.ts tests for account, channel and playlist tags

* Correct lint forbidden spaces

* Correct test regression on client.ts

Co-authored-by: kimsible <kimsible@users.noreply.github.com>
This commit is contained in:
Kim 2020-07-31 11:29:15 +02:00 committed by GitHub
parent 7b3909644d
commit 8d987ec63e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 377 additions and 85 deletions

View File

@ -17,6 +17,7 @@ const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html')
// Special route that add OpenGraph and oEmbed tags // Special route that add OpenGraph and oEmbed tags
// Do not use a template engine for a so little thing // Do not use a template engine for a so little thing
clientsRouter.use('/videos/watch/playlist/:id', asyncMiddleware(generateWatchPlaylistHtmlPage))
clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage)) clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage))
clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage)) clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage))
clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage)) clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage))
@ -134,6 +135,12 @@ async function generateWatchHtmlPage (req: express.Request, res: express.Respons
return sendHTML(html, res) return sendHTML(html, res)
} }
async function generateWatchPlaylistHtmlPage (req: express.Request, res: express.Response) {
const html = await ClientHtml.getWatchPlaylistHTMLPage(req.params.id + '', req, res)
return sendHTML(html, res)
}
async function generateAccountHtmlPage (req: express.Request, res: express.Response) { async function generateAccountHtmlPage (req: express.Request, res: express.Response) {
const html = await ClientHtml.getAccountHTMLPage(req.params.nameWithHost, req, res) const html = await ClientHtml.getAccountHTMLPage(req.params.nameWithHost, req, res)

View File

@ -1,11 +1,19 @@
import * as express from 'express' import * as express from 'express'
import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n'
import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER, FILES_CONTENT_HASH } from '../initializers/constants' import {
AVATARS_SIZE,
CUSTOM_HTML_TAG_COMMENTS,
EMBED_SIZE,
PLUGIN_GLOBAL_CSS_PATH,
WEBSERVER,
FILES_CONTENT_HASH
} from '../initializers/constants'
import { join } from 'path' import { join } from 'path'
import { escapeHTML, sha256 } from '../helpers/core-utils' import { escapeHTML, sha256 } from '../helpers/core-utils'
import { VideoModel } from '../models/video/video' import { VideoModel } from '../models/video/video'
import { VideoPlaylistModel } from '../models/video/video-playlist'
import validator from 'validator' import validator from 'validator'
import { VideoPrivacy } from '../../shared/models/videos' import { VideoPrivacy, VideoPlaylistPrivacy } from '../../shared/models/videos'
import { readFile } from 'fs-extra' import { readFile } from 'fs-extra'
import { getActivityStreamDuration } from '../models/video/video-format-utils' import { getActivityStreamDuration } from '../models/video/video-format-utils'
import { AccountModel } from '../models/account/account' import { AccountModel } from '../models/account/account'
@ -13,7 +21,7 @@ import { VideoChannelModel } from '../models/video/video-channel'
import * as Bluebird from 'bluebird' import * as Bluebird from 'bluebird'
import { CONFIG } from '../initializers/config' import { CONFIG } from '../initializers/config'
import { logger } from '../helpers/logger' import { logger } from '../helpers/logger'
import { MAccountActor, MChannelActor, MVideo } from '../types/models' import { MAccountActor, MChannelActor } from '../types/models'
export class ClientHtml { export class ClientHtml {
@ -56,7 +64,69 @@ export class ClientHtml {
let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name))
customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description)) customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description))
customHtml = ClientHtml.addVideoOpenGraphAndOEmbedTags(customHtml, video)
const url = WEBSERVER.URL + video.getWatchStaticPath()
const title = escapeHTML(video.name)
const description = escapeHTML(video.description)
const image = {
url: WEBSERVER.URL + video.getPreviewStaticPath()
}
const embed = {
url: WEBSERVER.URL + video.getEmbedStaticPath(),
createdAt: video.createdAt.toISOString(),
duration: getActivityStreamDuration(video.duration),
views: video.views
}
const ogType = 'video'
const twitterCard = CONFIG.SERVICES.TWITTER.WHITELISTED ? 'player' : 'summary_large_image'
const schemaType = 'VideoObject'
customHtml = ClientHtml.addTags(customHtml, { url, title, description, image, embed, ogType, twitterCard, schemaType })
return customHtml
}
static async getWatchPlaylistHTMLPage (videoPlaylistId: string, req: express.Request, res: express.Response) {
// Let Angular application handle errors
if (!validator.isInt(videoPlaylistId) && !validator.isUUID(videoPlaylistId, 4)) {
res.status(404)
return ClientHtml.getIndexHTML(req, res)
}
const [ html, videoPlaylist ] = await Promise.all([
ClientHtml.getIndexHTML(req, res),
VideoPlaylistModel.loadWithAccountAndChannel(videoPlaylistId, null)
])
// Let Angular application handle errors
if (!videoPlaylist || videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
res.status(404)
return html
}
let customHtml = ClientHtml.addTitleTag(html, escapeHTML(videoPlaylist.name))
customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(videoPlaylist.description))
const url = videoPlaylist.getWatchUrl()
const title = escapeHTML(videoPlaylist.name)
const description = escapeHTML(videoPlaylist.description)
const image = {
url: videoPlaylist.getThumbnailUrl()
}
const list = {
numberOfItems: videoPlaylist.get('videosLength')
}
const ogType = 'video'
const twitterCard = 'summary'
const schemaType = 'ItemList'
customHtml = ClientHtml.addTags(customHtml, { url, title, description, image, list, ogType, twitterCard, schemaType })
return customHtml return customHtml
} }
@ -87,7 +157,22 @@ export class ClientHtml {
let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName())) let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName()))
customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(entity.description)) customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(entity.description))
customHtml = ClientHtml.addAccountOrChannelMetaTags(customHtml, entity)
const url = entity.Actor.url
const title = escapeHTML(entity.getDisplayName())
const description = escapeHTML(entity.description)
const image = {
url: entity.Actor.getAvatarUrl(),
width: AVATARS_SIZE.width,
height: AVATARS_SIZE.height
}
const ogType = 'website'
const twitterCard = 'summary'
const schemaType = 'ProfilePage'
customHtml = ClientHtml.addTags(customHtml, { url, title, description, image, ogType, twitterCard, schemaType })
return customHtml return customHtml
} }
@ -183,60 +268,100 @@ export class ClientHtml {
return htmlStringPage.replace('</head>', linkTag + '</head>') return htmlStringPage.replace('</head>', linkTag + '</head>')
} }
private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) { private static generateOpenGraphMetaTags (tags) {
const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() const metaTags = {
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 'og:type': tags.ogType,
'og:title': tags.title,
'og:image': tags.image.url
}
const videoNameEscaped = escapeHTML(video.name) if (tags.image.width && tags.image.height) {
const videoDescriptionEscaped = escapeHTML(video.description) metaTags['og:image:width'] = tags.image.width
const embedUrl = WEBSERVER.URL + video.getEmbedStaticPath() metaTags['og:image:height'] = tags.image.height
}
const openGraphMetaTags = { metaTags['og:url'] = tags.url
'og:type': 'video', metaTags['og:description'] = tags.description
'og:title': videoNameEscaped,
'og:image': previewUrl,
'og:url': videoUrl,
'og:description': videoDescriptionEscaped,
'og:video:url': embedUrl, if (tags.embed) {
'og:video:secure_url': embedUrl, metaTags['og:video:url'] = tags.embed.url
'og:video:type': 'text/html', metaTags['og:video:secure_url'] = tags.embed.url
'og:video:width': EMBED_SIZE.width, metaTags['og:video:type'] = 'text/html'
'og:video:height': EMBED_SIZE.height, metaTags['og:video:width'] = EMBED_SIZE.width
metaTags['og:video:height'] = EMBED_SIZE.height
}
'name': videoNameEscaped, return metaTags
'description': videoDescriptionEscaped, }
'image': previewUrl,
'twitter:card': CONFIG.SERVICES.TWITTER.WHITELISTED ? 'player' : 'summary_large_image', private static generateStandardMetaTags (tags) {
return {
name: tags.title,
description: tags.description,
image: tags.image.url
}
}
private static generateTwitterCardMetaTags (tags) {
const metaTags = {
'twitter:card': tags.twitterCard,
'twitter:site': CONFIG.SERVICES.TWITTER.USERNAME, 'twitter:site': CONFIG.SERVICES.TWITTER.USERNAME,
'twitter:title': videoNameEscaped, 'twitter:title': tags.title,
'twitter:description': videoDescriptionEscaped, 'twitter:description': tags.description,
'twitter:image': previewUrl, 'twitter:image': tags.image.url
'twitter:player': embedUrl,
'twitter:player:width': EMBED_SIZE.width,
'twitter:player:height': EMBED_SIZE.height
} }
const oembedLinkTags = [ if (tags.image.width && tags.image.height) {
{ metaTags['twitter:image:width'] = tags.image.width
type: 'application/json+oembed', metaTags['twitter:image:height'] = tags.image.height
href: WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(videoUrl),
title: videoNameEscaped
} }
]
const schemaTags = { return metaTags
}
private static generateSchemaTags (tags) {
const schema = {
'@context': 'http://schema.org', '@context': 'http://schema.org',
'@type': 'VideoObject', '@type': tags.schemaType,
'name': videoNameEscaped, 'name': tags.title,
'description': videoDescriptionEscaped, 'description': tags.description,
'thumbnailUrl': previewUrl, 'image': tags.image.url,
'uploadDate': video.createdAt.toISOString(), 'url': tags.url
'duration': getActivityStreamDuration(video.duration), }
'contentUrl': videoUrl,
'embedUrl': embedUrl, if (tags.list) {
'interactionCount': video.views schema['numberOfItems'] = tags.list.numberOfItems
schema['thumbnailUrl'] = tags.image.url
}
if (tags.embed) {
schema['embedUrl'] = tags.embed.url
schema['uploadDate'] = tags.embed.createdAt
schema['duration'] = tags.embed.duration
schema['iterationCount'] = tags.embed.views
schema['thumbnailUrl'] = tags.image.url
schema['contentUrl'] = tags.url
}
return schema
}
private static addTags (htmlStringPage: string, tagsValues: any) {
const openGraphMetaTags = this.generateOpenGraphMetaTags(tagsValues)
const standardMetaTags = this.generateStandardMetaTags(tagsValues)
const twitterCardMetaTags = this.generateTwitterCardMetaTags(tagsValues)
const schemaTags = this.generateSchemaTags(tagsValues)
const { url, title, embed } = tagsValues
const oembedLinkTags = []
if (embed) {
oembedLinkTags.push({
type: 'application/json+oembed',
href: WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(url),
title
})
} }
let tagsString = '' let tagsString = ''
@ -248,28 +373,33 @@ export class ClientHtml {
tagsString += `<meta property="${tagName}" content="${tagValue}" />` tagsString += `<meta property="${tagName}" content="${tagValue}" />`
}) })
// Standard
Object.keys(standardMetaTags).forEach(tagName => {
const tagValue = standardMetaTags[tagName]
tagsString += `<meta property="${tagName}" content="${tagValue}" />`
})
// Twitter card
Object.keys(twitterCardMetaTags).forEach(tagName => {
const tagValue = twitterCardMetaTags[tagName]
tagsString += `<meta property="${tagName}" content="${tagValue}" />`
})
// OEmbed // OEmbed
for (const oembedLinkTag of oembedLinkTags) { for (const oembedLinkTag of oembedLinkTags) {
tagsString += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.title}" />` tagsString += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.title}" />`
} }
// Schema.org // Schema.org
if (schemaTags) {
tagsString += `<script type="application/ld+json">${JSON.stringify(schemaTags)}</script>` tagsString += `<script type="application/ld+json">${JSON.stringify(schemaTags)}</script>`
// SEO, use origin video url so Google does not index remote videos
tagsString += `<link rel="canonical" href="${video.url}" />`
return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString)
} }
private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) { // SEO, use origin URL
// SEO, use origin account or channel URL tagsString += `<link rel="canonical" href="${url}" />`
const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />`
return this.addOpenGraphAndOEmbedTags(htmlStringPage, metaTags) return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.META_TAGS, tagsString)
}
private static addOpenGraphAndOEmbedTags (htmlStringPage: string, metaTags: string) {
return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.META_TAGS, metaTags)
} }
} }

View File

@ -490,6 +490,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename)
} }
getWatchUrl () {
return WEBSERVER.URL + '/videos/watch/playlist/' + this.uuid
}
setAsRefreshed () { setAsRefreshed () {
this.changed('updatedAt', true) this.changed('updatedAt', true)

View File

@ -13,8 +13,14 @@ import {
serverLogin, serverLogin,
updateCustomConfig, updateCustomConfig,
updateCustomSubConfig, updateCustomSubConfig,
uploadVideo uploadVideo,
createVideoPlaylist,
addVideoInPlaylist,
getAccount,
addVideoChannel
} from '../../shared/extra-utils' } from '../../shared/extra-utils'
import { VideoPlaylistPrivacy } from '@shared/models'
import { MVideoPlaylist, MAccount, MChannel } from '@server/types/models'
const expect = chai.expect const expect = chai.expect
@ -26,6 +32,11 @@ function checkIndexTags (html: string, title: string, description: string, css:
describe('Test a client controllers', function () { describe('Test a client controllers', function () {
let server: ServerInfo let server: ServerInfo
let videoPlaylist: MVideoPlaylist
let account: MAccount
let videoChannel: MChannel
const name = 'my super name for server 1'
const description = 'my super description for server 1'
before(async function () { before(async function () {
this.timeout(120000) this.timeout(120000)
@ -33,18 +44,56 @@ describe('Test a client controllers', function () {
server = await flushAndRunServer(1) server = await flushAndRunServer(1)
server.accessToken = await serverLogin(server) server.accessToken = await serverLogin(server)
const videoAttributes = { // Video
name: 'my super name for server 1',
description: 'my super description for server 1' const videoAttributes = { name, description }
}
await uploadVideo(server.url, server.accessToken, videoAttributes) await uploadVideo(server.url, server.accessToken, videoAttributes)
const res = await getVideosList(server.url) const resVideosRequest = await getVideosList(server.url)
const videos = res.body.data
const videos = resVideosRequest.body.data
expect(videos.length).to.equal(1) expect(videos.length).to.equal(1)
server.video = videos[0] server.video = videos[0]
// Playlist
const playlistAttrs = {
displayName: name,
description,
privacy: VideoPlaylistPrivacy.PUBLIC
}
const resVideoPlaylistRequest = await createVideoPlaylist({ url: server.url, token: server.accessToken, playlistAttrs })
videoPlaylist = resVideoPlaylistRequest.body.videoPlaylist
await addVideoInPlaylist({
url: server.url,
token: server.accessToken,
playlistId: videoPlaylist.id,
elementAttrs: { videoId: server.video.id }
})
// Account
const resAccountRequest = await getAccount(server.url, `${server.user.username}@${server.host}:${server.port}`)
account = resAccountRequest.body.account
// Channel
const videoChannelAttributesArg = {
name: `${server.user.username}_channel`,
displayName: name,
description
}
const resChannelRequest = await addVideoChannel(server.url, server.accessToken, videoChannelAttributesArg)
videoChannel = resChannelRequest.body.videoChannel
}) })
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 () {
@ -53,8 +102,10 @@ describe('Test a client controllers', function () {
.set('Accept', 'text/html') .set('Accept', 'text/html')
.expect(200) .expect(200)
expect(res.text).to.contain('<meta property="og:title" content="my super name for server 1" />') expect(res.text).to.contain(`<meta property="og:title" content="${name}" />`)
expect(res.text).to.contain('<meta property="og:description" content="my super description for server 1" />') expect(res.text).to.contain(`<meta property="og:description" content="${description}" />`)
expect(res.text).to.contain('<meta property="og:type" content="video" />')
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/videos/watch/${server.video.uuid}" />`)
}) })
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 () {
@ -63,8 +114,46 @@ describe('Test a client controllers', function () {
.set('Accept', 'text/html') .set('Accept', 'text/html')
.expect(200) .expect(200)
expect(res.text).to.contain('<meta property="og:title" content="my super name for server 1" />') expect(res.text).to.contain(`<meta property="og:title" content="${name}" />`)
expect(res.text).to.contain('<meta property="og:description" content="my super description for server 1" />') expect(res.text).to.contain(`<meta property="og:description" content="${description}" />`)
expect(res.text).to.contain('<meta property="og:type" content="video" />')
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/videos/watch/${server.video.uuid}" />`)
})
it('Should have valid Open Graph tags on the watch playlist page', async function () {
const res = await request(server.url)
.get('/videos/watch/playlist/' + videoPlaylist.uuid)
.set('Accept', 'text/html')
.expect(200)
expect(res.text).to.contain(`<meta property="og:title" content="${videoPlaylist.name}" />`)
expect(res.text).to.contain(`<meta property="og:description" content="${videoPlaylist.description}" />`)
expect(res.text).to.contain('<meta property="og:type" content="video" />')
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/videos/watch/playlist/${videoPlaylist.uuid}" />`)
})
it('Should have valid Open Graph tags on the account page', async function () {
const res = await request(server.url)
.get('/accounts/' + server.user.username)
.set('Accept', 'text/html')
.expect(200)
expect(res.text).to.contain(`<meta property="og:title" content="${account.getDisplayName()}" />`)
expect(res.text).to.contain(`<meta property="og:description" content="${account.description}" />`)
expect(res.text).to.contain('<meta property="og:type" content="website" />')
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/accounts/${server.user.username}" />`)
})
it('Should have valid Open Graph tags on the channel page', async function () {
const res = await request(server.url)
.get('/video-channels/' + videoChannel.name)
.set('Accept', 'text/html')
.expect(200)
expect(res.text).to.contain(`<meta property="og:title" content="${videoChannel.getDisplayName()}" />`)
expect(res.text).to.contain(`<meta property="og:description" content="${videoChannel.description}" />`)
expect(res.text).to.contain('<meta property="og:type" content="website" />')
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/video-channels/${videoChannel.name}" />`)
}) })
it('Should have valid oEmbed discovery tags', async function () { it('Should have valid oEmbed discovery tags', async function () {
@ -81,7 +170,7 @@ describe('Test a client controllers', function () {
expect(res.text).to.contain(expectedLink) expect(res.text).to.contain(expectedLink)
}) })
it('Should have valid twitter card', async function () { it('Should have valid twitter card on the whatch video page', async function () {
const res = await request(server.url) const res = await request(server.url)
.get('/videos/watch/' + server.video.uuid) .get('/videos/watch/' + server.video.uuid)
.set('Accept', 'text/html') .set('Accept', 'text/html')
@ -89,6 +178,44 @@ describe('Test a client controllers', function () {
expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />') expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(res.text).to.contain(`<meta property="twitter:title" content="${name}" />`)
expect(res.text).to.contain(`<meta property="twitter:description" content="${description}" />`)
})
it('Should have valid twitter card on the watch playlist page', async function () {
const res = await request(server.url)
.get('/videos/watch/playlist/' + videoPlaylist.uuid)
.set('Accept', 'text/html')
.expect(200)
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(res.text).to.contain(`<meta property="twitter:title" content="${videoPlaylist.name}" />`)
expect(res.text).to.contain(`<meta property="twitter:description" content="${videoPlaylist.description}" />`)
})
it('Should have valid twitter card on the account page', async function () {
const res = await request(server.url)
.get('/accounts/' + account.name)
.set('Accept', 'text/html')
.expect(200)
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(res.text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
expect(res.text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
})
it('Should have valid twitter card on the channel page', async function () {
const res = await request(server.url)
.get('/video-channels/' + videoChannel.name)
.set('Accept', 'text/html')
.expect(200)
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(res.text).to.contain(`<meta property="twitter:title" content="${videoChannel.name}" />`)
expect(res.text).to.contain(`<meta property="twitter:description" content="${videoChannel.description}" />`)
}) })
it('Should have valid twitter card if Twitter is whitelisted', async function () { it('Should have valid twitter card if Twitter is whitelisted', async function () {
@ -100,13 +227,37 @@ describe('Test a client controllers', function () {
} }
await updateCustomConfig(server.url, server.accessToken, config) await updateCustomConfig(server.url, server.accessToken, config)
const res = await request(server.url) const resVideoRequest = await request(server.url)
.get('/videos/watch/' + server.video.uuid) .get('/videos/watch/' + server.video.uuid)
.set('Accept', 'text/html') .set('Accept', 'text/html')
.expect(200) .expect(200)
expect(res.text).to.contain('<meta property="twitter:card" content="player" />') expect(resVideoRequest.text).to.contain('<meta property="twitter:card" content="player" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />') expect(resVideoRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
const resVideoPlaylistRequest = await request(server.url)
.get('/videos/watch/playlist/' + videoPlaylist.uuid)
.set('Accept', 'text/html')
.expect(200)
expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:card" content="player" />')
expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
const resAccountRequest = await request(server.url)
.get('/accounts/' + account.name)
.set('Accept', 'text/html')
.expect(200)
expect(resAccountRequest.text).to.contain('<meta property="twitter:card" content="player" />')
expect(resAccountRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
const resChannelRequest = await request(server.url)
.get('/video-channels/' + videoChannel.name)
.set('Accept', 'text/html')
.expect(200)
expect(resChannelRequest.text).to.contain('<meta property="twitter:card" content="player" />')
expect(resChannelRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
}) })
it('Should have valid index html tags (title, description...)', async function () { it('Should have valid index html tags (title, description...)', async function () {