1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import { omit } from 'lodash'
11 VideoPlaylistCreateResult,
14 } from '@shared/models'
17 createMultipleServers,
22 setAccessTokensToServers,
23 setDefaultVideoChannel,
25 } from '../../shared/server-commands'
27 const expect = chai.expect
29 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
30 expect(html).to.contain('<title>' + title + '</title>')
31 expect(html).to.contain('<meta name="description" content="' + description + '" />')
32 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
34 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
35 const configObjectString = JSON.stringify(htmlConfig)
36 const configEscapedString = JSON.stringify(configObjectString)
38 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = ${configEscapedString}</script>`)
41 describe('Test a client controllers', function () {
42 let servers: PeerTubeServer[] = []
45 const videoName = 'my super name for server 1'
46 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
47 const videoDescriptionPlainText = 'my super description for server 1'
49 const playlistName = 'super playlist name'
50 const playlistDescription = 'super playlist description'
51 let playlist: VideoPlaylistCreateResult
53 const channelDescription = 'my super channel description'
55 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
56 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
58 let videoIds: (string | number)[] = []
59 let privateVideoId: string
60 let internalVideoId: string
61 let unlistedVideoId: string
63 let playlistIds: (string | number)[] = []
65 before(async function () {
68 servers = await createMultipleServers(2)
70 await setAccessTokensToServers(servers)
72 await doubleFollow(servers[0], servers[1])
74 await setDefaultVideoChannel(servers)
76 await servers[0].channels.update({
77 channelName: servers[0].store.channel.name,
78 attributes: { description: channelDescription }
84 const attributes = { name: videoName, description: videoDescription }
85 await servers[0].videos.upload({ attributes })
87 const { data } = await servers[0].videos.list()
88 expect(data.length).to.equal(1)
91 servers[0].store.video = video
92 videoIds = [ video.id, video.uuid, video.shortUUID ]
96 ({ uuid: privateVideoId } = await servers[0].videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE }));
97 ({ uuid: unlistedVideoId } = await servers[0].videos.quickUpload({ name: 'unlisted', privacy: VideoPrivacy.UNLISTED }));
98 ({ uuid: internalVideoId } = await servers[0].videos.quickUpload({ name: 'internal', privacy: VideoPrivacy.INTERNAL }))
105 displayName: playlistName,
106 description: playlistDescription,
107 privacy: VideoPlaylistPrivacy.PUBLIC,
108 videoChannelId: servers[0].store.channel.id
111 playlist = await servers[0].playlists.create({ attributes })
112 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
114 await servers[0].playlists.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: servers[0].store.video.id } })
120 await servers[0].users.updateMe({ description: 'my account description' })
122 account = await servers[0].accounts.get({ accountName: `${servers[0].store.user.username}@${servers[0].host}` })
125 await waitJobs(servers)
128 describe('oEmbed', function () {
130 it('Should have valid oEmbed discovery tags for videos', async function () {
131 for (const basePath of watchVideoBasePaths) {
132 for (const id of videoIds) {
133 const res = await makeGetRequest({
137 expectedStatus: HttpStatusCode.OK_200
140 const port = servers[0].port
142 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
143 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].store.video.shortUUID}" ` +
144 `title="${servers[0].store.video.name}" />`
146 expect(res.text).to.contain(expectedLink)
151 it('Should have valid oEmbed discovery tags for a playlist', async function () {
152 for (const basePath of watchPlaylistBasePaths) {
153 for (const id of playlistIds) {
154 const res = await makeGetRequest({
158 expectedStatus: HttpStatusCode.OK_200
161 const port = servers[0].port
163 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
164 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.shortUUID}" ` +
165 `title="${playlistName}" />`
167 expect(res.text).to.contain(expectedLink)
173 describe('Open Graph', function () {
175 async function accountPageTest (path: string) {
176 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
177 const text = res.text
179 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
180 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
181 expect(text).to.contain('<meta property="og:type" content="website" />')
182 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].store.user.username}" />`)
185 async function channelPageTest (path: string) {
186 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
187 const text = res.text
189 expect(text).to.contain(`<meta property="og:title" content="${servers[0].store.channel.displayName}" />`)
190 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
191 expect(text).to.contain('<meta property="og:type" content="website" />')
192 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].store.channel.name}" />`)
195 async function watchVideoPageTest (path: string) {
196 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
197 const text = res.text
199 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
200 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
201 expect(text).to.contain('<meta property="og:type" content="video" />')
202 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].store.video.shortUUID}" />`)
205 async function watchPlaylistPageTest (path: string) {
206 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
207 const text = res.text
209 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
210 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
211 expect(text).to.contain('<meta property="og:type" content="video" />')
212 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
215 it('Should have valid Open Graph tags on the account page', async function () {
216 await accountPageTest('/accounts/' + servers[0].store.user.username)
217 await accountPageTest('/a/' + servers[0].store.user.username)
218 await accountPageTest('/@' + servers[0].store.user.username)
221 it('Should have valid Open Graph tags on the channel page', async function () {
222 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
223 await channelPageTest('/c/' + servers[0].store.channel.name)
224 await channelPageTest('/@' + servers[0].store.channel.name)
227 it('Should have valid Open Graph tags on the watch page', async function () {
228 for (const path of watchVideoBasePaths) {
229 for (const id of videoIds) {
230 await watchVideoPageTest(path + id)
235 it('Should have valid Open Graph tags on the watch playlist page', async function () {
236 for (const path of watchPlaylistBasePaths) {
237 for (const id of playlistIds) {
238 await watchPlaylistPageTest(path + id)
244 describe('Twitter card', async function () {
246 describe('Not whitelisted', function () {
248 async function accountPageTest (path: string) {
249 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
250 const text = res.text
252 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
253 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
254 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
255 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
258 async function channelPageTest (path: string) {
259 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
260 const text = res.text
262 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
263 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
264 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
265 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
268 async function watchVideoPageTest (path: string) {
269 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
270 const text = res.text
272 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
273 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
274 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
275 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
278 async function watchPlaylistPageTest (path: string) {
279 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
280 const text = res.text
282 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
283 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
284 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
285 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
288 it('Should have valid twitter card on the watch video page', async function () {
289 for (const path of watchVideoBasePaths) {
290 for (const id of videoIds) {
291 await watchVideoPageTest(path + id)
296 it('Should have valid twitter card on the watch playlist page', async function () {
297 for (const path of watchPlaylistBasePaths) {
298 for (const id of playlistIds) {
299 await watchPlaylistPageTest(path + id)
304 it('Should have valid twitter card on the account page', async function () {
305 await accountPageTest('/accounts/' + account.name)
306 await accountPageTest('/a/' + account.name)
307 await accountPageTest('/@' + account.name)
310 it('Should have valid twitter card on the channel page', async function () {
311 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
312 await channelPageTest('/c/' + servers[0].store.channel.name)
313 await channelPageTest('/@' + servers[0].store.channel.name)
317 describe('Whitelisted', function () {
319 before(async function () {
320 const config = await servers[0].config.getCustomConfig()
321 config.services.twitter = {
326 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
329 async function accountPageTest (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 channelPageTest (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="summary" />')
342 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
345 async function watchVideoPageTest (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 async function watchPlaylistPageTest (path: string) {
354 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
355 const text = res.text
357 expect(text).to.contain('<meta property="twitter:card" content="player" />')
358 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
361 it('Should have valid twitter card on the watch video page', async function () {
362 for (const path of watchVideoBasePaths) {
363 for (const id of videoIds) {
364 await watchVideoPageTest(path + id)
369 it('Should have valid twitter card on the watch playlist page', async function () {
370 for (const path of watchPlaylistBasePaths) {
371 for (const id of playlistIds) {
372 await watchPlaylistPageTest(path + id)
377 it('Should have valid twitter card on the account page', async function () {
378 await accountPageTest('/accounts/' + account.name)
379 await accountPageTest('/a/' + account.name)
380 await accountPageTest('/@' + account.name)
383 it('Should have valid twitter card on the channel page', async function () {
384 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
385 await channelPageTest('/c/' + servers[0].store.channel.name)
386 await channelPageTest('/@' + servers[0].store.channel.name)
391 describe('Index HTML', function () {
393 it('Should have valid index html tags (title, description...)', async function () {
394 const config = await servers[0].config.getConfig()
395 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
397 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
398 checkIndexTags(res.text, 'PeerTube', description, '', config)
401 it('Should update the customized configuration and have the correct index html tags', async function () {
402 await servers[0].config.updateCustomSubConfig({
405 name: 'PeerTube updated',
406 shortDescription: 'my short description',
407 description: 'my super description',
408 terms: 'my super terms',
409 defaultNSFWPolicy: 'blur',
410 defaultClientRoute: '/videos/recently-added',
412 javascript: 'alert("coucou")',
413 css: 'body { background-color: red; }'
419 const config = await servers[0].config.getConfig()
420 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
422 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
425 it('Should have valid index html updated tags (title, description...)', async function () {
426 const config = await servers[0].config.getConfig()
427 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
429 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
432 it('Should use the original video URL for the canonical tag', async function () {
433 for (const basePath of watchVideoBasePaths) {
434 for (const id of videoIds) {
435 const res = await makeHTMLRequest(servers[1].url, basePath + id)
436 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
441 it('Should use the original account URL for the canonical tag', async function () {
442 const accountURLtest = res => {
443 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
446 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
447 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
448 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
451 it('Should use the original channel URL for the canonical tag', async function () {
452 const channelURLtests = res => {
453 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
456 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
457 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
458 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
461 it('Should use the original playlist URL for the canonical tag', async function () {
462 for (const basePath of watchPlaylistBasePaths) {
463 for (const id of playlistIds) {
464 const res = await makeHTMLRequest(servers[1].url, basePath + id)
465 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
470 it('Should add noindex meta tag for remote accounts', async function () {
471 const handle = 'root@' + servers[0].host
472 const paths = [ '/accounts/', '/a/', '/@' ]
474 for (const path of paths) {
476 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
477 expect(text).to.contain('<meta name="robots" content="noindex" />')
481 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
482 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
487 it('Should add noindex meta tag for remote channels', async function () {
488 const handle = 'root_channel@' + servers[0].host
489 const paths = [ '/video-channels/', '/c/', '/@' ]
491 for (const path of paths) {
493 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
494 expect(text).to.contain('<meta name="robots" content="noindex" />')
498 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
499 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
504 it('Should not display internal/private video', async function () {
505 for (const basePath of watchVideoBasePaths) {
506 for (const id of [ privateVideoId, internalVideoId ]) {
507 const res = await makeGetRequest({
511 expectedStatus: HttpStatusCode.NOT_FOUND_404
514 expect(res.text).to.not.contain('internal')
515 expect(res.text).to.not.contain('private')
520 it('Should add noindex meta tag for unlisted video', async function () {
521 for (const basePath of watchVideoBasePaths) {
522 const res = await makeGetRequest({
524 path: basePath + unlistedVideoId,
526 expectedStatus: HttpStatusCode.OK_200
529 expect(res.text).to.contain('unlisted')
530 expect(res.text).to.contain('<meta name="robots" content="noindex" />')
535 describe('Embed HTML', function () {
537 it('Should have the correct embed html tags', async function () {
538 const config = await servers[0].config.getConfig()
539 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
541 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
545 after(async function () {
546 await cleanupTests(servers)