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, VideoPrivacy } 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 privateVideoId: string
52 let internalVideoId: string
53 let unlistedVideoId: string
55 let playlistIds: (string | number)[] = []
57 before(async function () {
60 servers = await createMultipleServers(2)
62 await setAccessTokensToServers(servers)
64 await doubleFollow(servers[0], servers[1])
66 await setDefaultVideoChannel(servers)
68 await servers[0].channels.update({
69 channelName: servers[0].store.channel.name,
70 attributes: { description: channelDescription }
76 const attributes = { name: videoName, description: videoDescription }
77 await servers[0].videos.upload({ attributes })
79 const { data } = await servers[0].videos.list()
80 expect(data.length).to.equal(1)
83 servers[0].store.video = video
84 videoIds = [ video.id, video.uuid, video.shortUUID ]
88 ({ uuid: privateVideoId } = await servers[0].videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE }));
89 ({ uuid: unlistedVideoId } = await servers[0].videos.quickUpload({ name: 'unlisted', privacy: VideoPrivacy.UNLISTED }));
90 ({ uuid: internalVideoId } = await servers[0].videos.quickUpload({ name: 'internal', privacy: VideoPrivacy.INTERNAL }))
97 displayName: playlistName,
98 description: playlistDescription,
99 privacy: VideoPlaylistPrivacy.PUBLIC,
100 videoChannelId: servers[0].store.channel.id
103 playlist = await servers[0].playlists.create({ attributes })
104 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
106 await servers[0].playlists.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: servers[0].store.video.id } })
112 await servers[0].users.updateMe({ description: 'my account description' })
114 account = await servers[0].accounts.get({ accountName: `${servers[0].store.user.username}@${servers[0].host}` })
117 await waitJobs(servers)
120 describe('oEmbed', function () {
122 it('Should have valid oEmbed discovery tags for videos', async function () {
123 for (const basePath of watchVideoBasePaths) {
124 for (const id of videoIds) {
125 const res = await makeGetRequest({
129 expectedStatus: HttpStatusCode.OK_200
132 const port = servers[0].port
134 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
135 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].store.video.shortUUID}" ` +
136 `title="${servers[0].store.video.name}" />`
138 expect(res.text).to.contain(expectedLink)
143 it('Should have valid oEmbed discovery tags for a playlist', async function () {
144 for (const basePath of watchPlaylistBasePaths) {
145 for (const id of playlistIds) {
146 const res = await makeGetRequest({
150 expectedStatus: HttpStatusCode.OK_200
153 const port = servers[0].port
155 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
156 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.shortUUID}" ` +
157 `title="${playlistName}" />`
159 expect(res.text).to.contain(expectedLink)
165 describe('Open Graph', function () {
167 async function accountPageTest (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="${account.displayName}" />`)
172 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
173 expect(text).to.contain('<meta property="og:type" content="website" />')
174 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].store.user.username}" />`)
177 async function channelPageTest (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="${servers[0].store.channel.displayName}" />`)
182 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
183 expect(text).to.contain('<meta property="og:type" content="website" />')
184 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].store.channel.name}" />`)
187 async function watchVideoPageTest (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="${videoName}" />`)
192 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
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/${servers[0].store.video.shortUUID}" />`)
197 async function watchPlaylistPageTest (path: string) {
198 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
199 const text = res.text
201 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
202 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
203 expect(text).to.contain('<meta property="og:type" content="video" />')
204 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
207 it('Should have valid Open Graph tags on the account page', async function () {
208 await accountPageTest('/accounts/' + servers[0].store.user.username)
209 await accountPageTest('/a/' + servers[0].store.user.username)
210 await accountPageTest('/@' + servers[0].store.user.username)
213 it('Should have valid Open Graph tags on the channel page', async function () {
214 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
215 await channelPageTest('/c/' + servers[0].store.channel.name)
216 await channelPageTest('/@' + servers[0].store.channel.name)
219 it('Should have valid Open Graph tags on the watch page', async function () {
220 for (const path of watchVideoBasePaths) {
221 for (const id of videoIds) {
222 await watchVideoPageTest(path + id)
227 it('Should have valid Open Graph tags on the watch playlist page', async function () {
228 for (const path of watchPlaylistBasePaths) {
229 for (const id of playlistIds) {
230 await watchPlaylistPageTest(path + id)
236 describe('Twitter card', async function () {
238 describe('Not whitelisted', function () {
240 async function accountPageTest (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="${account.name}" />`)
247 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
250 async function channelPageTest (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" />')
255 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
256 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
257 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
260 async function watchVideoPageTest (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_large_image" />')
265 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
266 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
267 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
270 async function watchPlaylistPageTest (path: string) {
271 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
272 const text = res.text
274 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
275 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
276 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
277 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
280 it('Should have valid twitter card on the watch video page', async function () {
281 for (const path of watchVideoBasePaths) {
282 for (const id of videoIds) {
283 await watchVideoPageTest(path + id)
288 it('Should have valid twitter card on the watch playlist page', async function () {
289 for (const path of watchPlaylistBasePaths) {
290 for (const id of playlistIds) {
291 await watchPlaylistPageTest(path + id)
296 it('Should have valid twitter card on the account page', async function () {
297 await accountPageTest('/accounts/' + account.name)
298 await accountPageTest('/a/' + account.name)
299 await accountPageTest('/@' + account.name)
302 it('Should have valid twitter card on the channel page', async function () {
303 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
304 await channelPageTest('/c/' + servers[0].store.channel.name)
305 await channelPageTest('/@' + servers[0].store.channel.name)
309 describe('Whitelisted', function () {
311 before(async function () {
312 const config = await servers[0].config.getCustomConfig()
313 config.services.twitter = {
318 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
321 async function accountPageTest (path: string) {
322 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
323 const text = res.text
325 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
326 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
329 async function channelPageTest (path: string) {
330 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
331 const text = res.text
333 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
334 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
337 async function watchVideoPageTest (path: string) {
338 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
339 const text = res.text
341 expect(text).to.contain('<meta property="twitter:card" content="player" />')
342 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
345 async function watchPlaylistPageTest (path: string) {
346 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
347 const text = res.text
349 expect(text).to.contain('<meta property="twitter:card" content="player" />')
350 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
353 it('Should have valid twitter card on the watch video page', async function () {
354 for (const path of watchVideoBasePaths) {
355 for (const id of videoIds) {
356 await watchVideoPageTest(path + id)
361 it('Should have valid twitter card on the watch playlist page', async function () {
362 for (const path of watchPlaylistBasePaths) {
363 for (const id of playlistIds) {
364 await watchPlaylistPageTest(path + id)
369 it('Should have valid twitter card on the account page', async function () {
370 await accountPageTest('/accounts/' + account.name)
371 await accountPageTest('/a/' + account.name)
372 await accountPageTest('/@' + account.name)
375 it('Should have valid twitter card on the channel page', async function () {
376 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
377 await channelPageTest('/c/' + servers[0].store.channel.name)
378 await channelPageTest('/@' + servers[0].store.channel.name)
383 describe('Index HTML', function () {
385 it('Should have valid index html tags (title, description...)', async function () {
386 const config = await servers[0].config.getConfig()
387 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
389 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
390 checkIndexTags(res.text, 'PeerTube', description, '', config)
393 it('Should update the customized configuration and have the correct index html tags', async function () {
394 await servers[0].config.updateCustomSubConfig({
397 name: 'PeerTube updated',
398 shortDescription: 'my short description',
399 description: 'my super description',
400 terms: 'my super terms',
401 defaultNSFWPolicy: 'blur',
402 defaultClientRoute: '/videos/recently-added',
404 javascript: 'alert("coucou")',
405 css: 'body { background-color: red; }'
411 const config = await servers[0].config.getConfig()
412 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
414 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
417 it('Should have valid index html updated tags (title, description...)', async function () {
418 const config = await servers[0].config.getConfig()
419 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
421 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
424 it('Should use the original video URL for the canonical tag', async function () {
425 for (const basePath of watchVideoBasePaths) {
426 for (const id of videoIds) {
427 const res = await makeHTMLRequest(servers[1].url, basePath + id)
428 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
433 it('Should use the original account URL for the canonical tag', async function () {
434 const accountURLtest = res => {
435 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
438 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
439 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
440 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
443 it('Should use the original channel URL for the canonical tag', async function () {
444 const channelURLtests = res => {
445 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
448 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
449 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
450 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
453 it('Should use the original playlist URL for the canonical tag', async function () {
454 for (const basePath of watchPlaylistBasePaths) {
455 for (const id of playlistIds) {
456 const res = await makeHTMLRequest(servers[1].url, basePath + id)
457 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
462 it('Should add noindex meta tag for remote accounts', async function () {
463 const handle = 'root@' + servers[0].host
464 const paths = [ '/accounts/', '/a/', '/@' ]
466 for (const path of paths) {
468 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
469 expect(text).to.contain('<meta name="robots" content="noindex" />')
473 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
474 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
479 it('Should add noindex meta tag for remote channels', async function () {
480 const handle = 'root_channel@' + servers[0].host
481 const paths = [ '/video-channels/', '/c/', '/@' ]
483 for (const path of paths) {
485 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
486 expect(text).to.contain('<meta name="robots" content="noindex" />')
490 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
491 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
496 it('Should not display internal/private video', async function () {
497 for (const basePath of watchVideoBasePaths) {
498 for (const id of [ privateVideoId, internalVideoId ]) {
499 const res = await makeGetRequest({
503 expectedStatus: HttpStatusCode.NOT_FOUND_404
506 expect(res.text).to.not.contain('internal')
507 expect(res.text).to.not.contain('private')
512 it('Should add noindex meta tag for unlisted video', async function () {
513 for (const basePath of watchVideoBasePaths) {
514 const res = await makeGetRequest({
516 path: basePath + unlistedVideoId,
518 expectedStatus: HttpStatusCode.OK_200
521 expect(res.text).to.contain('unlisted')
522 expect(res.text).to.contain('<meta name="robots" content="noindex" />')
527 describe('Embed HTML', function () {
529 it('Should have the correct embed html tags', async function () {
530 const config = await servers[0].config.getConfig()
531 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
533 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
537 after(async function () {
538 await cleanupTests(servers)