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 expectedLink = `<link rel="alternate" type="application/json+oembed" href="${servers[0].url}/services/oembed?` +
138 `url=http%3A%2F%2F${servers[0].hostname}%3A${servers[0].port}%2Fw%2F${servers[0].store.video.shortUUID}" ` +
139 `title="${servers[0].store.video.name}" />`
141 expect(res.text).to.contain(expectedLink)
146 it('Should have valid oEmbed discovery tags for a playlist', async function () {
147 for (const basePath of watchPlaylistBasePaths) {
148 for (const id of playlistIds) {
149 const res = await makeGetRequest({
153 expectedStatus: HttpStatusCode.OK_200
156 const expectedLink = `<link rel="alternate" type="application/json+oembed" href="${servers[0].url}/services/oembed?` +
157 `url=http%3A%2F%2F${servers[0].hostname}%3A${servers[0].port}%2Fw%2Fp%2F${playlist.shortUUID}" ` +
158 `title="${playlistName}" />`
160 expect(res.text).to.contain(expectedLink)
166 describe('Open Graph', function () {
168 async function accountPageTest (path: string) {
169 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
170 const text = res.text
172 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
173 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
174 expect(text).to.contain('<meta property="og:type" content="website" />')
175 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].store.user.username}" />`)
178 async function channelPageTest (path: string) {
179 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
180 const text = res.text
182 expect(text).to.contain(`<meta property="og:title" content="${servers[0].store.channel.displayName}" />`)
183 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
184 expect(text).to.contain('<meta property="og:type" content="website" />')
185 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].store.channel.name}" />`)
188 async function watchVideoPageTest (path: string) {
189 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
190 const text = res.text
192 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
193 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
194 expect(text).to.contain('<meta property="og:type" content="video" />')
195 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].store.video.shortUUID}" />`)
198 async function watchPlaylistPageTest (path: string) {
199 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
200 const text = res.text
202 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
203 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
204 expect(text).to.contain('<meta property="og:type" content="video" />')
205 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
208 it('Should have valid Open Graph tags on the account page', async function () {
209 await accountPageTest('/accounts/' + servers[0].store.user.username)
210 await accountPageTest('/a/' + servers[0].store.user.username)
211 await accountPageTest('/@' + servers[0].store.user.username)
214 it('Should have valid Open Graph tags on the channel page', async function () {
215 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
216 await channelPageTest('/c/' + servers[0].store.channel.name)
217 await channelPageTest('/@' + servers[0].store.channel.name)
220 it('Should have valid Open Graph tags on the watch page', async function () {
221 for (const path of watchVideoBasePaths) {
222 for (const id of videoIds) {
223 await watchVideoPageTest(path + id)
228 it('Should have valid Open Graph tags on the watch playlist page', async function () {
229 for (const path of watchPlaylistBasePaths) {
230 for (const id of playlistIds) {
231 await watchPlaylistPageTest(path + id)
237 describe('Twitter card', async function () {
239 describe('Not whitelisted', function () {
241 async function accountPageTest (path: string) {
242 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
243 const text = res.text
245 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
246 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
247 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
248 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
251 async function channelPageTest (path: string) {
252 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
253 const text = res.text
255 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
256 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
257 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
258 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
261 async function watchVideoPageTest (path: string) {
262 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
263 const text = res.text
265 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
266 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
267 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
268 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
271 async function watchPlaylistPageTest (path: string) {
272 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
273 const text = res.text
275 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
276 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
277 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
278 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
281 it('Should have valid twitter card on the watch video page', async function () {
282 for (const path of watchVideoBasePaths) {
283 for (const id of videoIds) {
284 await watchVideoPageTest(path + id)
289 it('Should have valid twitter card on the watch playlist page', async function () {
290 for (const path of watchPlaylistBasePaths) {
291 for (const id of playlistIds) {
292 await watchPlaylistPageTest(path + id)
297 it('Should have valid twitter card on the account page', async function () {
298 await accountPageTest('/accounts/' + account.name)
299 await accountPageTest('/a/' + account.name)
300 await accountPageTest('/@' + account.name)
303 it('Should have valid twitter card on the channel page', async function () {
304 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
305 await channelPageTest('/c/' + servers[0].store.channel.name)
306 await channelPageTest('/@' + servers[0].store.channel.name)
310 describe('Whitelisted', function () {
312 before(async function () {
313 const config = await servers[0].config.getCustomConfig()
314 config.services.twitter = {
319 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
322 async function accountPageTest (path: string) {
323 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
324 const text = res.text
326 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
327 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
330 async function channelPageTest (path: string) {
331 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
332 const text = res.text
334 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
335 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
338 async function watchVideoPageTest (path: string) {
339 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
340 const text = res.text
342 expect(text).to.contain('<meta property="twitter:card" content="player" />')
343 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
346 async function watchPlaylistPageTest (path: string) {
347 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
348 const text = res.text
350 expect(text).to.contain('<meta property="twitter:card" content="player" />')
351 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
354 it('Should have valid twitter card on the watch video page', async function () {
355 for (const path of watchVideoBasePaths) {
356 for (const id of videoIds) {
357 await watchVideoPageTest(path + id)
362 it('Should have valid twitter card on the watch playlist page', async function () {
363 for (const path of watchPlaylistBasePaths) {
364 for (const id of playlistIds) {
365 await watchPlaylistPageTest(path + id)
370 it('Should have valid twitter card on the account page', async function () {
371 await accountPageTest('/accounts/' + account.name)
372 await accountPageTest('/a/' + account.name)
373 await accountPageTest('/@' + account.name)
376 it('Should have valid twitter card on the channel page', async function () {
377 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
378 await channelPageTest('/c/' + servers[0].store.channel.name)
379 await channelPageTest('/@' + servers[0].store.channel.name)
384 describe('Index HTML', function () {
386 it('Should have valid index html tags (title, description...)', async function () {
387 const config = await servers[0].config.getConfig()
388 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
390 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
391 checkIndexTags(res.text, 'PeerTube', description, '', config)
394 it('Should update the customized configuration and have the correct index html tags', async function () {
395 await servers[0].config.updateCustomSubConfig({
398 name: 'PeerTube updated',
399 shortDescription: 'my short description',
400 description: 'my super description',
401 terms: 'my super terms',
402 defaultNSFWPolicy: 'blur',
403 defaultClientRoute: '/videos/recently-added',
405 javascript: 'alert("coucou")',
406 css: 'body { background-color: red; }'
412 const config = await servers[0].config.getConfig()
413 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
415 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
418 it('Should have valid index html updated tags (title, description...)', async function () {
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 use the original video URL for the canonical tag', async function () {
426 for (const basePath of watchVideoBasePaths) {
427 for (const id of videoIds) {
428 const res = await makeHTMLRequest(servers[1].url, basePath + id)
429 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
434 it('Should use the original account URL for the canonical tag', async function () {
435 const accountURLtest = res => {
436 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
439 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
440 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
441 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
444 it('Should use the original channel URL for the canonical tag', async function () {
445 const channelURLtests = res => {
446 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
449 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
450 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
451 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
454 it('Should use the original playlist URL for the canonical tag', async function () {
455 for (const basePath of watchPlaylistBasePaths) {
456 for (const id of playlistIds) {
457 const res = await makeHTMLRequest(servers[1].url, basePath + id)
458 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
463 it('Should add noindex meta tag for remote accounts', async function () {
464 const handle = 'root@' + servers[0].host
465 const paths = [ '/accounts/', '/a/', '/@' ]
467 for (const path of paths) {
469 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
470 expect(text).to.contain('<meta name="robots" content="noindex" />')
474 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
475 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
480 it('Should add noindex meta tag for remote channels', async function () {
481 const handle = 'root_channel@' + servers[0].host
482 const paths = [ '/video-channels/', '/c/', '/@' ]
484 for (const path of paths) {
486 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
487 expect(text).to.contain('<meta name="robots" content="noindex" />')
491 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
492 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
497 it('Should not display internal/private video', async function () {
498 for (const basePath of watchVideoBasePaths) {
499 for (const id of [ privateVideoId, internalVideoId ]) {
500 const res = await makeGetRequest({
504 expectedStatus: HttpStatusCode.NOT_FOUND_404
507 expect(res.text).to.not.contain('internal')
508 expect(res.text).to.not.contain('private')
513 it('Should add noindex meta tag for unlisted video', async function () {
514 for (const basePath of watchVideoBasePaths) {
515 const res = await makeGetRequest({
517 path: basePath + unlistedVideoId,
519 expectedStatus: HttpStatusCode.OK_200
522 expect(res.text).to.contain('unlisted')
523 expect(res.text).to.contain('<meta name="robots" content="noindex" />')
528 describe('Embed HTML', function () {
530 it('Should have the correct embed html tags', async function () {
531 const config = await servers[0].config.getConfig()
532 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
534 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
538 after(async function () {
539 await cleanupTests(servers)