1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import { omit } from 'lodash'
6 import { Account, HTMLServerConfig, HttpStatusCode, ServerConfig, VideoPlaylistCreateResult, VideoPlaylistPrivacy } from '@shared/models'
14 setAccessTokensToServers,
15 setDefaultVideoChannel,
17 } from '../../shared/server-commands'
19 const expect = chai.expect
21 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
22 expect(html).to.contain('<title>' + title + '</title>')
23 expect(html).to.contain('<meta name="description" content="' + description + '" />')
24 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
26 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
27 const configObjectString = JSON.stringify(htmlConfig)
28 const configEscapedString = JSON.stringify(configObjectString)
30 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = ${configEscapedString}</script>`)
33 describe('Test a client controllers', function () {
34 let servers: PeerTubeServer[] = []
37 const videoName = 'my super name for server 1'
38 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
39 const videoDescriptionPlainText = 'my super description for server 1'
41 const playlistName = 'super playlist name'
42 const playlistDescription = 'super playlist description'
43 let playlist: VideoPlaylistCreateResult
45 const channelDescription = 'my super channel description'
47 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
48 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
50 let videoIds: (string | number)[] = []
51 let playlistIds: (string | number)[] = []
53 before(async function () {
56 servers = await createMultipleServers(2)
58 await setAccessTokensToServers(servers)
60 await doubleFollow(servers[0], servers[1])
62 await setDefaultVideoChannel(servers)
64 await servers[0].channels.update({
65 channelName: servers[0].store.channel.name,
66 attributes: { description: channelDescription }
72 const attributes = { name: videoName, description: videoDescription }
73 await servers[0].videos.upload({ attributes })
75 const { data } = await servers[0].videos.list()
76 expect(data.length).to.equal(1)
79 servers[0].store.video = video
80 videoIds = [ video.id, video.uuid, video.shortUUID ]
87 displayName: playlistName,
88 description: playlistDescription,
89 privacy: VideoPlaylistPrivacy.PUBLIC,
90 videoChannelId: servers[0].store.channel.id
93 playlist = await servers[0].playlists.create({ attributes })
94 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
96 await servers[0].playlists.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: servers[0].store.video.id } })
102 await servers[0].users.updateMe({ description: 'my account description' })
104 account = await servers[0].accounts.get({ accountName: `${servers[0].store.user.username}@${servers[0].host}` })
107 await waitJobs(servers)
110 describe('oEmbed', function () {
112 it('Should have valid oEmbed discovery tags for videos', async function () {
113 for (const basePath of watchVideoBasePaths) {
114 for (const id of videoIds) {
115 const res = await makeGetRequest({
119 expectedStatus: HttpStatusCode.OK_200
122 const port = servers[0].port
124 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
125 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].store.video.shortUUID}" ` +
126 `title="${servers[0].store.video.name}" />`
128 expect(res.text).to.contain(expectedLink)
133 it('Should have valid oEmbed discovery tags for a playlist', async function () {
134 for (const basePath of watchPlaylistBasePaths) {
135 for (const id of playlistIds) {
136 const res = await makeGetRequest({
140 expectedStatus: HttpStatusCode.OK_200
143 const port = servers[0].port
145 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
146 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.shortUUID}" ` +
147 `title="${playlistName}" />`
149 expect(res.text).to.contain(expectedLink)
155 describe('Open Graph', function () {
157 async function accountPageTest (path: string) {
158 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
159 const text = res.text
161 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
162 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
163 expect(text).to.contain('<meta property="og:type" content="website" />')
164 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].store.user.username}" />`)
167 async function channelPageTest (path: string) {
168 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
169 const text = res.text
171 expect(text).to.contain(`<meta property="og:title" content="${servers[0].store.channel.displayName}" />`)
172 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
173 expect(text).to.contain('<meta property="og:type" content="website" />')
174 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].store.channel.name}" />`)
177 async function watchVideoPageTest (path: string) {
178 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
179 const text = res.text
181 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
182 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
183 expect(text).to.contain('<meta property="og:type" content="video" />')
184 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].store.video.shortUUID}" />`)
187 async function watchPlaylistPageTest (path: string) {
188 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
189 const text = res.text
191 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
192 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
193 expect(text).to.contain('<meta property="og:type" content="video" />')
194 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
197 it('Should have valid Open Graph tags on the account page', async function () {
198 await accountPageTest('/accounts/' + servers[0].store.user.username)
199 await accountPageTest('/a/' + servers[0].store.user.username)
200 await accountPageTest('/@' + servers[0].store.user.username)
203 it('Should have valid Open Graph tags on the channel page', async function () {
204 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
205 await channelPageTest('/c/' + servers[0].store.channel.name)
206 await channelPageTest('/@' + servers[0].store.channel.name)
209 it('Should have valid Open Graph tags on the watch page', async function () {
210 for (const path of watchVideoBasePaths) {
211 for (const id of videoIds) {
212 await watchVideoPageTest(path + id)
217 it('Should have valid Open Graph tags on the watch playlist page', async function () {
218 for (const path of watchPlaylistBasePaths) {
219 for (const id of playlistIds) {
220 await watchPlaylistPageTest(path + id)
226 describe('Twitter card', async function () {
228 describe('Not whitelisted', function () {
230 async function accountPageTest (path: string) {
231 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
232 const text = res.text
234 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
235 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
236 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
237 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
240 async function channelPageTest (path: string) {
241 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
242 const text = res.text
244 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
245 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
246 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
247 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
250 async function watchVideoPageTest (path: string) {
251 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
252 const text = res.text
254 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
255 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
256 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
257 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
260 async function watchPlaylistPageTest (path: string) {
261 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
262 const text = res.text
264 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
265 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
266 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
267 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
270 it('Should have valid twitter card on the watch video page', async function () {
271 for (const path of watchVideoBasePaths) {
272 for (const id of videoIds) {
273 await watchVideoPageTest(path + id)
278 it('Should have valid twitter card on the watch playlist page', async function () {
279 for (const path of watchPlaylistBasePaths) {
280 for (const id of playlistIds) {
281 await watchPlaylistPageTest(path + id)
286 it('Should have valid twitter card on the account page', async function () {
287 await accountPageTest('/accounts/' + account.name)
288 await accountPageTest('/a/' + account.name)
289 await accountPageTest('/@' + account.name)
292 it('Should have valid twitter card on the channel page', async function () {
293 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
294 await channelPageTest('/c/' + servers[0].store.channel.name)
295 await channelPageTest('/@' + servers[0].store.channel.name)
299 describe('Whitelisted', function () {
301 before(async function () {
302 const config = await servers[0].config.getCustomConfig()
303 config.services.twitter = {
308 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
311 async function accountPageTest (path: string) {
312 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
313 const text = res.text
315 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
316 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
319 async function channelPageTest (path: string) {
320 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
321 const text = res.text
323 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
324 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
327 async function watchVideoPageTest (path: string) {
328 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
329 const text = res.text
331 expect(text).to.contain('<meta property="twitter:card" content="player" />')
332 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
335 async function watchPlaylistPageTest (path: string) {
336 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
337 const text = res.text
339 expect(text).to.contain('<meta property="twitter:card" content="player" />')
340 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
343 it('Should have valid twitter card on the watch video page', async function () {
344 for (const path of watchVideoBasePaths) {
345 for (const id of videoIds) {
346 await watchVideoPageTest(path + id)
351 it('Should have valid twitter card on the watch playlist page', async function () {
352 for (const path of watchPlaylistBasePaths) {
353 for (const id of playlistIds) {
354 await watchPlaylistPageTest(path + id)
359 it('Should have valid twitter card on the account page', async function () {
360 await accountPageTest('/accounts/' + account.name)
361 await accountPageTest('/a/' + account.name)
362 await accountPageTest('/@' + account.name)
365 it('Should have valid twitter card on the channel page', async function () {
366 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
367 await channelPageTest('/c/' + servers[0].store.channel.name)
368 await channelPageTest('/@' + servers[0].store.channel.name)
373 describe('Index HTML', function () {
375 it('Should have valid index html tags (title, description...)', async function () {
376 const config = await servers[0].config.getConfig()
377 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
379 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
380 checkIndexTags(res.text, 'PeerTube', description, '', config)
383 it('Should update the customized configuration and have the correct index html tags', async function () {
384 await servers[0].config.updateCustomSubConfig({
387 name: 'PeerTube updated',
388 shortDescription: 'my short description',
389 description: 'my super description',
390 terms: 'my super terms',
391 defaultNSFWPolicy: 'blur',
392 defaultClientRoute: '/videos/recently-added',
394 javascript: 'alert("coucou")',
395 css: 'body { background-color: red; }'
401 const config = await servers[0].config.getConfig()
402 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
404 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
407 it('Should have valid index html updated tags (title, description...)', async function () {
408 const config = await servers[0].config.getConfig()
409 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
411 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
414 it('Should use the original video URL for the canonical tag', async function () {
415 for (const basePath of watchVideoBasePaths) {
416 for (const id of videoIds) {
417 const res = await makeHTMLRequest(servers[1].url, basePath + id)
418 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
423 it('Should use the original account URL for the canonical tag', async function () {
424 const accountURLtest = res => {
425 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
428 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
429 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
430 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
433 it('Should use the original channel URL for the canonical tag', async function () {
434 const channelURLtests = res => {
435 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
438 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
439 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
440 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
443 it('Should use the original playlist URL for the canonical tag', async function () {
444 for (const basePath of watchPlaylistBasePaths) {
445 for (const id of playlistIds) {
446 const res = await makeHTMLRequest(servers[1].url, basePath + id)
447 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
452 it('Should add noindex meta tag for remote accounts', async function () {
453 const handle = 'root@' + servers[0].host
454 const paths = [ '/accounts/', '/a/', '/@' ]
456 for (const path of paths) {
458 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
459 expect(text).to.contain('<meta name="robots" content="noindex" />')
463 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
464 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
469 it('Should add noindex meta tag for remote accounts', async function () {
470 const handle = 'root_channel@' + servers[0].host
471 const paths = [ '/video-channels/', '/c/', '/@' ]
473 for (const path of paths) {
475 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
476 expect(text).to.contain('<meta name="robots" content="noindex" />')
480 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
481 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
487 describe('Embed HTML', function () {
489 it('Should have the correct embed html tags', async function () {
490 const config = await servers[0].config.getConfig()
491 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
493 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
497 after(async function () {
498 await cleanupTests(servers)