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 page with thread id Angular param', async function () {
229 for (const path of watchVideoBasePaths) {
230 for (const id of videoIds) {
231 await watchVideoPageTest(path + id + ';threadId=1')
236 it('Should have valid Open Graph tags on the watch playlist page', async function () {
237 for (const path of watchPlaylistBasePaths) {
238 for (const id of playlistIds) {
239 await watchPlaylistPageTest(path + id)
245 describe('Twitter card', async function () {
247 describe('Not whitelisted', function () {
249 async function accountPageTest (path: string) {
250 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
251 const text = res.text
253 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
254 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
255 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
256 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
259 async function channelPageTest (path: string) {
260 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
261 const text = res.text
263 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
264 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
265 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
266 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
269 async function watchVideoPageTest (path: string) {
270 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
271 const text = res.text
273 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
274 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
275 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
276 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
279 async function watchPlaylistPageTest (path: string) {
280 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
281 const text = res.text
283 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
284 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
285 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
286 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
289 it('Should have valid twitter card on the watch video page', async function () {
290 for (const path of watchVideoBasePaths) {
291 for (const id of videoIds) {
292 await watchVideoPageTest(path + id)
297 it('Should have valid twitter card on the watch playlist page', async function () {
298 for (const path of watchPlaylistBasePaths) {
299 for (const id of playlistIds) {
300 await watchPlaylistPageTest(path + id)
305 it('Should have valid twitter card on the account page', async function () {
306 await accountPageTest('/accounts/' + account.name)
307 await accountPageTest('/a/' + account.name)
308 await accountPageTest('/@' + account.name)
311 it('Should have valid twitter card on the channel page', async function () {
312 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
313 await channelPageTest('/c/' + servers[0].store.channel.name)
314 await channelPageTest('/@' + servers[0].store.channel.name)
318 describe('Whitelisted', function () {
320 before(async function () {
321 const config = await servers[0].config.getCustomConfig()
322 config.services.twitter = {
327 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
330 async function accountPageTest (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 channelPageTest (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="summary" />')
343 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
346 async function watchVideoPageTest (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 async function watchPlaylistPageTest (path: string) {
355 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
356 const text = res.text
358 expect(text).to.contain('<meta property="twitter:card" content="player" />')
359 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
362 it('Should have valid twitter card on the watch video page', async function () {
363 for (const path of watchVideoBasePaths) {
364 for (const id of videoIds) {
365 await watchVideoPageTest(path + id)
370 it('Should have valid twitter card on the watch playlist page', async function () {
371 for (const path of watchPlaylistBasePaths) {
372 for (const id of playlistIds) {
373 await watchPlaylistPageTest(path + id)
378 it('Should have valid twitter card on the account page', async function () {
379 await accountPageTest('/accounts/' + account.name)
380 await accountPageTest('/a/' + account.name)
381 await accountPageTest('/@' + account.name)
384 it('Should have valid twitter card on the channel page', async function () {
385 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
386 await channelPageTest('/c/' + servers[0].store.channel.name)
387 await channelPageTest('/@' + servers[0].store.channel.name)
392 describe('Index HTML', function () {
394 it('Should have valid index html tags (title, description...)', async function () {
395 const config = await servers[0].config.getConfig()
396 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
398 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
399 checkIndexTags(res.text, 'PeerTube', description, '', config)
402 it('Should update the customized configuration and have the correct index html tags', async function () {
403 await servers[0].config.updateCustomSubConfig({
406 name: 'PeerTube updated',
407 shortDescription: 'my short description',
408 description: 'my super description',
409 terms: 'my super terms',
410 defaultNSFWPolicy: 'blur',
411 defaultClientRoute: '/videos/recently-added',
413 javascript: 'alert("coucou")',
414 css: 'body { background-color: red; }'
420 const config = await servers[0].config.getConfig()
421 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
423 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
426 it('Should have valid index html updated tags (title, description...)', async function () {
427 const config = await servers[0].config.getConfig()
428 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
430 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
433 it('Should use the original video URL for the canonical tag', async function () {
434 for (const basePath of watchVideoBasePaths) {
435 for (const id of videoIds) {
436 const res = await makeHTMLRequest(servers[1].url, basePath + id)
437 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
442 it('Should use the original account URL for the canonical tag', async function () {
443 const accountURLtest = res => {
444 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
447 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
448 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
449 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
452 it('Should use the original channel URL for the canonical tag', async function () {
453 const channelURLtests = res => {
454 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
457 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
458 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
459 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
462 it('Should use the original playlist URL for the canonical tag', async function () {
463 for (const basePath of watchPlaylistBasePaths) {
464 for (const id of playlistIds) {
465 const res = await makeHTMLRequest(servers[1].url, basePath + id)
466 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
471 it('Should add noindex meta tag for remote accounts', async function () {
472 const handle = 'root@' + servers[0].host
473 const paths = [ '/accounts/', '/a/', '/@' ]
475 for (const path of paths) {
477 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
478 expect(text).to.contain('<meta name="robots" content="noindex" />')
482 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
483 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
488 it('Should add noindex meta tag for remote channels', async function () {
489 const handle = 'root_channel@' + servers[0].host
490 const paths = [ '/video-channels/', '/c/', '/@' ]
492 for (const path of paths) {
494 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
495 expect(text).to.contain('<meta name="robots" content="noindex" />')
499 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
500 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
505 it('Should not display internal/private video', async function () {
506 for (const basePath of watchVideoBasePaths) {
507 for (const id of [ privateVideoId, internalVideoId ]) {
508 const res = await makeGetRequest({
512 expectedStatus: HttpStatusCode.NOT_FOUND_404
515 expect(res.text).to.not.contain('internal')
516 expect(res.text).to.not.contain('private')
521 it('Should add noindex meta tag for unlisted video', async function () {
522 for (const basePath of watchVideoBasePaths) {
523 const res = await makeGetRequest({
525 path: basePath + unlistedVideoId,
527 expectedStatus: HttpStatusCode.OK_200
530 expect(res.text).to.contain('unlisted')
531 expect(res.text).to.contain('<meta name="robots" content="noindex" />')
536 describe('Embed HTML', function () {
538 it('Should have the correct embed html tags', async function () {
539 const config = await servers[0].config.getConfig()
540 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
542 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
546 after(async function () {
547 await cleanupTests(servers)