1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { omit } from '@shared/core-utils'
10 VideoPlaylistCreateResult,
13 } from '@shared/models'
16 createMultipleServers,
21 setAccessTokensToServers,
22 setDefaultVideoChannel,
24 } from '../../shared/server-commands'
26 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
27 expect(html).to.contain('<title>' + title + '</title>')
28 expect(html).to.contain('<meta name="description" content="' + description + '" />')
29 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
31 const htmlConfig: HTMLServerConfig = omit(config, [ 'signup' ])
32 const configObjectString = JSON.stringify(htmlConfig)
33 const configEscapedString = JSON.stringify(configObjectString)
35 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = ${configEscapedString}</script>`)
38 describe('Test a client controllers', function () {
39 let servers: PeerTubeServer[] = []
42 const videoName = 'my super name for server 1'
43 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
44 const videoDescriptionPlainText = 'my super description for server 1'
46 const playlistName = 'super playlist name'
47 const playlistDescription = 'super playlist description'
48 let playlist: VideoPlaylistCreateResult
50 const channelDescription = 'my super channel description'
52 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
53 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
55 let videoIds: (string | number)[] = []
56 let privateVideoId: string
57 let internalVideoId: string
58 let unlistedVideoId: string
60 let playlistIds: (string | number)[] = []
62 before(async function () {
65 servers = await createMultipleServers(2)
67 await setAccessTokensToServers(servers)
69 await doubleFollow(servers[0], servers[1])
71 await setDefaultVideoChannel(servers)
73 await servers[0].channels.update({
74 channelName: servers[0].store.channel.name,
75 attributes: { description: channelDescription }
81 const attributes = { name: videoName, description: videoDescription }
82 await servers[0].videos.upload({ attributes })
84 const { data } = await servers[0].videos.list()
85 expect(data.length).to.equal(1)
88 servers[0].store.video = video
89 videoIds = [ video.id, video.uuid, video.shortUUID ]
93 ({ uuid: privateVideoId } = await servers[0].videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE }));
94 ({ uuid: unlistedVideoId } = await servers[0].videos.quickUpload({ name: 'unlisted', privacy: VideoPrivacy.UNLISTED }));
95 ({ uuid: internalVideoId } = await servers[0].videos.quickUpload({ name: 'internal', privacy: VideoPrivacy.INTERNAL }))
102 displayName: playlistName,
103 description: playlistDescription,
104 privacy: VideoPlaylistPrivacy.PUBLIC,
105 videoChannelId: servers[0].store.channel.id
108 playlist = await servers[0].playlists.create({ attributes })
109 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
111 await servers[0].playlists.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: servers[0].store.video.id } })
117 await servers[0].users.updateMe({ description: 'my account description' })
119 account = await servers[0].accounts.get({ accountName: `${servers[0].store.user.username}@${servers[0].host}` })
122 await waitJobs(servers)
125 describe('oEmbed', function () {
127 it('Should have valid oEmbed discovery tags for videos', async function () {
128 for (const basePath of watchVideoBasePaths) {
129 for (const id of videoIds) {
130 const res = await makeGetRequest({
134 expectedStatus: HttpStatusCode.OK_200
137 const port = servers[0].port
139 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
140 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].store.video.shortUUID}" ` +
141 `title="${servers[0].store.video.name}" />`
143 expect(res.text).to.contain(expectedLink)
148 it('Should have valid oEmbed discovery tags for a playlist', async function () {
149 for (const basePath of watchPlaylistBasePaths) {
150 for (const id of playlistIds) {
151 const res = await makeGetRequest({
155 expectedStatus: HttpStatusCode.OK_200
158 const port = servers[0].port
160 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
161 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.shortUUID}" ` +
162 `title="${playlistName}" />`
164 expect(res.text).to.contain(expectedLink)
170 describe('Open Graph', function () {
172 async function accountPageTest (path: string) {
173 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
174 const text = res.text
176 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
177 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
178 expect(text).to.contain('<meta property="og:type" content="website" />')
179 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].store.user.username}" />`)
182 async function channelPageTest (path: string) {
183 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
184 const text = res.text
186 expect(text).to.contain(`<meta property="og:title" content="${servers[0].store.channel.displayName}" />`)
187 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
188 expect(text).to.contain('<meta property="og:type" content="website" />')
189 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].store.channel.name}" />`)
192 async function watchVideoPageTest (path: string) {
193 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
194 const text = res.text
196 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
197 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
198 expect(text).to.contain('<meta property="og:type" content="video" />')
199 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].store.video.shortUUID}" />`)
202 async function watchPlaylistPageTest (path: string) {
203 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
204 const text = res.text
206 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
207 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
208 expect(text).to.contain('<meta property="og:type" content="video" />')
209 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
212 it('Should have valid Open Graph tags on the account page', async function () {
213 await accountPageTest('/accounts/' + servers[0].store.user.username)
214 await accountPageTest('/a/' + servers[0].store.user.username)
215 await accountPageTest('/@' + servers[0].store.user.username)
218 it('Should have valid Open Graph tags on the channel page', async function () {
219 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
220 await channelPageTest('/c/' + servers[0].store.channel.name)
221 await channelPageTest('/@' + servers[0].store.channel.name)
224 it('Should have valid Open Graph tags on the watch page', async function () {
225 for (const path of watchVideoBasePaths) {
226 for (const id of videoIds) {
227 await watchVideoPageTest(path + id)
232 it('Should have valid Open Graph tags on the watch playlist page', async function () {
233 for (const path of watchPlaylistBasePaths) {
234 for (const id of playlistIds) {
235 await watchPlaylistPageTest(path + id)
241 describe('Twitter card', async function () {
243 describe('Not whitelisted', function () {
245 async function accountPageTest (path: string) {
246 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
247 const text = res.text
249 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
250 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
251 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
252 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
255 async function channelPageTest (path: string) {
256 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
257 const text = res.text
259 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
260 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
261 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
262 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
265 async function watchVideoPageTest (path: string) {
266 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
267 const text = res.text
269 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
270 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
271 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
272 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
275 async function watchPlaylistPageTest (path: string) {
276 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
277 const text = res.text
279 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
280 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
281 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
282 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
285 it('Should have valid twitter card on the watch video page', async function () {
286 for (const path of watchVideoBasePaths) {
287 for (const id of videoIds) {
288 await watchVideoPageTest(path + id)
293 it('Should have valid twitter card on the watch playlist page', async function () {
294 for (const path of watchPlaylistBasePaths) {
295 for (const id of playlistIds) {
296 await watchPlaylistPageTest(path + id)
301 it('Should have valid twitter card on the account page', async function () {
302 await accountPageTest('/accounts/' + account.name)
303 await accountPageTest('/a/' + account.name)
304 await accountPageTest('/@' + account.name)
307 it('Should have valid twitter card on the channel page', async function () {
308 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
309 await channelPageTest('/c/' + servers[0].store.channel.name)
310 await channelPageTest('/@' + servers[0].store.channel.name)
314 describe('Whitelisted', function () {
316 before(async function () {
317 const config = await servers[0].config.getCustomConfig()
318 config.services.twitter = {
323 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
326 async function accountPageTest (path: string) {
327 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
328 const text = res.text
330 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
331 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
334 async function channelPageTest (path: string) {
335 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
336 const text = res.text
338 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
339 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
342 async function watchVideoPageTest (path: string) {
343 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
344 const text = res.text
346 expect(text).to.contain('<meta property="twitter:card" content="player" />')
347 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
350 async function watchPlaylistPageTest (path: string) {
351 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
352 const text = res.text
354 expect(text).to.contain('<meta property="twitter:card" content="player" />')
355 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
358 it('Should have valid twitter card on the watch video page', async function () {
359 for (const path of watchVideoBasePaths) {
360 for (const id of videoIds) {
361 await watchVideoPageTest(path + id)
366 it('Should have valid twitter card on the watch playlist page', async function () {
367 for (const path of watchPlaylistBasePaths) {
368 for (const id of playlistIds) {
369 await watchPlaylistPageTest(path + id)
374 it('Should have valid twitter card on the account page', async function () {
375 await accountPageTest('/accounts/' + account.name)
376 await accountPageTest('/a/' + account.name)
377 await accountPageTest('/@' + account.name)
380 it('Should have valid twitter card on the channel page', async function () {
381 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
382 await channelPageTest('/c/' + servers[0].store.channel.name)
383 await channelPageTest('/@' + servers[0].store.channel.name)
388 describe('Index HTML', function () {
390 it('Should have valid index html tags (title, description...)', async function () {
391 const config = await servers[0].config.getConfig()
392 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
394 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
395 checkIndexTags(res.text, 'PeerTube', description, '', config)
398 it('Should update the customized configuration and have the correct index html tags', async function () {
399 await servers[0].config.updateCustomSubConfig({
402 name: 'PeerTube updated',
403 shortDescription: 'my short description',
404 description: 'my super description',
405 terms: 'my super terms',
406 defaultNSFWPolicy: 'blur',
407 defaultClientRoute: '/videos/recently-added',
409 javascript: 'alert("coucou")',
410 css: 'body { background-color: red; }'
416 const config = await servers[0].config.getConfig()
417 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
419 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
422 it('Should have valid index html updated tags (title, description...)', async function () {
423 const config = await servers[0].config.getConfig()
424 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
426 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
429 it('Should use the original video URL for the canonical tag', async function () {
430 for (const basePath of watchVideoBasePaths) {
431 for (const id of videoIds) {
432 const res = await makeHTMLRequest(servers[1].url, basePath + id)
433 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
438 it('Should use the original account URL for the canonical tag', async function () {
439 const accountURLtest = res => {
440 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
443 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
444 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
445 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
448 it('Should use the original channel URL for the canonical tag', async function () {
449 const channelURLtests = res => {
450 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
453 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
454 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
455 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
458 it('Should use the original playlist URL for the canonical tag', async function () {
459 for (const basePath of watchPlaylistBasePaths) {
460 for (const id of playlistIds) {
461 const res = await makeHTMLRequest(servers[1].url, basePath + id)
462 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
467 it('Should add noindex meta tag for remote accounts', async function () {
468 const handle = 'root@' + servers[0].host
469 const paths = [ '/accounts/', '/a/', '/@' ]
471 for (const path of paths) {
473 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
474 expect(text).to.contain('<meta name="robots" content="noindex" />')
478 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
479 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
484 it('Should add noindex meta tag for remote channels', async function () {
485 const handle = 'root_channel@' + servers[0].host
486 const paths = [ '/video-channels/', '/c/', '/@' ]
488 for (const path of paths) {
490 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
491 expect(text).to.contain('<meta name="robots" content="noindex" />')
495 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
496 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
501 it('Should not display internal/private video', async function () {
502 for (const basePath of watchVideoBasePaths) {
503 for (const id of [ privateVideoId, internalVideoId ]) {
504 const res = await makeGetRequest({
508 expectedStatus: HttpStatusCode.NOT_FOUND_404
511 expect(res.text).to.not.contain('internal')
512 expect(res.text).to.not.contain('private')
517 it('Should add noindex meta tag for unlisted video', async function () {
518 for (const basePath of watchVideoBasePaths) {
519 const res = await makeGetRequest({
521 path: basePath + unlistedVideoId,
523 expectedStatus: HttpStatusCode.OK_200
526 expect(res.text).to.contain('unlisted')
527 expect(res.text).to.contain('<meta name="robots" content="noindex" />')
532 describe('Embed HTML', function () {
534 it('Should have the correct embed html tags', async function () {
535 const config = await servers[0].config.getConfig()
536 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
538 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
542 after(async function () {
543 await cleanupTests(servers)