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 } from '@shared/models'
14 setAccessTokensToServers,
15 setDefaultVideoChannel,
17 } from '../../shared/extra-utils'
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 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`)
30 describe('Test a client controllers', function () {
31 let servers: PeerTubeServer[] = []
34 const videoName = 'my super name for server 1'
35 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
36 const videoDescriptionPlainText = 'my super description for server 1'
38 const playlistName = 'super playlist name'
39 const playlistDescription = 'super playlist description'
40 let playlist: VideoPlaylistCreateResult
42 const channelDescription = 'my super channel description'
44 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
45 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
47 let videoIds: (string | number)[] = []
48 let playlistIds: (string | number)[] = []
50 before(async function () {
53 servers = await createMultipleServers(2)
55 await setAccessTokensToServers(servers)
57 await doubleFollow(servers[0], servers[1])
59 await setDefaultVideoChannel(servers)
61 await servers[0].channels.update({
62 channelName: servers[0].store.channel.name,
63 attributes: { description: channelDescription }
69 const attributes = { name: videoName, description: videoDescription }
70 await servers[0].videos.upload({ attributes })
72 const { data } = await servers[0].videos.list()
73 expect(data.length).to.equal(1)
76 servers[0].store.video = video
77 videoIds = [ video.id, video.uuid, video.shortUUID ]
84 displayName: playlistName,
85 description: playlistDescription,
86 privacy: VideoPlaylistPrivacy.PUBLIC,
87 videoChannelId: servers[0].store.channel.id
90 playlist = await servers[0].playlists.create({ attributes })
91 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
93 await servers[0].playlists.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: servers[0].store.video.id } })
99 await servers[0].users.updateMe({ description: 'my account description' })
101 account = await servers[0].accounts.get({ accountName: `${servers[0].store.user.username}@${servers[0].host}` })
104 await waitJobs(servers)
107 describe('oEmbed', function () {
109 it('Should have valid oEmbed discovery tags for videos', async function () {
110 for (const basePath of watchVideoBasePaths) {
111 for (const id of videoIds) {
112 const res = await makeGetRequest({
116 expectedStatus: HttpStatusCode.OK_200
119 const port = servers[0].port
121 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
122 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].store.video.shortUUID}" ` +
123 `title="${servers[0].store.video.name}" />`
125 expect(res.text).to.contain(expectedLink)
130 it('Should have valid oEmbed discovery tags for a playlist', async function () {
131 for (const basePath of watchPlaylistBasePaths) {
132 for (const id of playlistIds) {
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%2Fp%2F${playlist.shortUUID}" ` +
144 `title="${playlistName}" />`
146 expect(res.text).to.contain(expectedLink)
152 describe('Open Graph', function () {
154 async function accountPageTest (path: string) {
155 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
156 const text = res.text
158 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
159 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
160 expect(text).to.contain('<meta property="og:type" content="website" />')
161 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].store.user.username}" />`)
164 async function channelPageTest (path: string) {
165 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
166 const text = res.text
168 expect(text).to.contain(`<meta property="og:title" content="${servers[0].store.channel.displayName}" />`)
169 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
170 expect(text).to.contain('<meta property="og:type" content="website" />')
171 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].store.channel.name}" />`)
174 async function watchVideoPageTest (path: string) {
175 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
176 const text = res.text
178 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
179 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
180 expect(text).to.contain('<meta property="og:type" content="video" />')
181 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].store.video.shortUUID}" />`)
184 async function watchPlaylistPageTest (path: string) {
185 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
186 const text = res.text
188 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
189 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
190 expect(text).to.contain('<meta property="og:type" content="video" />')
191 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
194 it('Should have valid Open Graph tags on the account page', async function () {
195 await accountPageTest('/accounts/' + servers[0].store.user.username)
196 await accountPageTest('/a/' + servers[0].store.user.username)
197 await accountPageTest('/@' + servers[0].store.user.username)
200 it('Should have valid Open Graph tags on the channel page', async function () {
201 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
202 await channelPageTest('/c/' + servers[0].store.channel.name)
203 await channelPageTest('/@' + servers[0].store.channel.name)
206 it('Should have valid Open Graph tags on the watch page', async function () {
207 for (const path of watchVideoBasePaths) {
208 for (const id of videoIds) {
209 await watchVideoPageTest(path + id)
214 it('Should have valid Open Graph tags on the watch playlist page', async function () {
215 for (const path of watchPlaylistBasePaths) {
216 for (const id of playlistIds) {
217 await watchPlaylistPageTest(path + id)
223 describe('Twitter card', async function () {
225 describe('Not whitelisted', function () {
227 async function accountPageTest (path: string) {
228 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
229 const text = res.text
231 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
232 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
233 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
234 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
237 async function channelPageTest (path: string) {
238 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
239 const text = res.text
241 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
242 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
243 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
244 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
247 async function watchVideoPageTest (path: string) {
248 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
249 const text = res.text
251 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
252 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
253 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
254 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
257 async function watchPlaylistPageTest (path: string) {
258 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
259 const text = res.text
261 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
262 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
263 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
264 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
267 it('Should have valid twitter card on the watch video page', async function () {
268 for (const path of watchVideoBasePaths) {
269 for (const id of videoIds) {
270 await watchVideoPageTest(path + id)
275 it('Should have valid twitter card on the watch playlist page', async function () {
276 for (const path of watchPlaylistBasePaths) {
277 for (const id of playlistIds) {
278 await watchPlaylistPageTest(path + id)
283 it('Should have valid twitter card on the account page', async function () {
284 await accountPageTest('/accounts/' + account.name)
285 await accountPageTest('/a/' + account.name)
286 await accountPageTest('/@' + account.name)
289 it('Should have valid twitter card on the channel page', async function () {
290 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
291 await channelPageTest('/c/' + servers[0].store.channel.name)
292 await channelPageTest('/@' + servers[0].store.channel.name)
296 describe('Whitelisted', function () {
298 before(async function () {
299 const config = await servers[0].config.getCustomConfig()
300 config.services.twitter = {
305 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
308 async function accountPageTest (path: string) {
309 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
310 const text = res.text
312 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
313 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
316 async function channelPageTest (path: string) {
317 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
318 const text = res.text
320 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
321 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
324 async function watchVideoPageTest (path: string) {
325 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
326 const text = res.text
328 expect(text).to.contain('<meta property="twitter:card" content="player" />')
329 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
332 async function watchPlaylistPageTest (path: string) {
333 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
334 const text = res.text
336 expect(text).to.contain('<meta property="twitter:card" content="player" />')
337 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
340 it('Should have valid twitter card on the watch video page', async function () {
341 for (const path of watchVideoBasePaths) {
342 for (const id of videoIds) {
343 await watchVideoPageTest(path + id)
348 it('Should have valid twitter card on the watch playlist page', async function () {
349 for (const path of watchPlaylistBasePaths) {
350 for (const id of playlistIds) {
351 await watchPlaylistPageTest(path + id)
356 it('Should have valid twitter card on the account page', async function () {
357 await accountPageTest('/accounts/' + account.name)
358 await accountPageTest('/a/' + account.name)
359 await accountPageTest('/@' + account.name)
362 it('Should have valid twitter card on the channel page', async function () {
363 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
364 await channelPageTest('/c/' + servers[0].store.channel.name)
365 await channelPageTest('/@' + servers[0].store.channel.name)
370 describe('Index HTML', function () {
372 it('Should have valid index html tags (title, description...)', async function () {
373 const config = await servers[0].config.getConfig()
374 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
376 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
377 checkIndexTags(res.text, 'PeerTube', description, '', config)
380 it('Should update the customized configuration and have the correct index html tags', async function () {
381 await servers[0].config.updateCustomSubConfig({
384 name: 'PeerTube updated',
385 shortDescription: 'my short description',
386 description: 'my super description',
387 terms: 'my super terms',
388 defaultNSFWPolicy: 'blur',
389 defaultClientRoute: '/videos/recently-added',
391 javascript: 'alert("coucou")',
392 css: 'body { background-color: red; }'
398 const config = await servers[0].config.getConfig()
399 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
401 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
404 it('Should have valid index html updated tags (title, description...)', async function () {
405 const config = await servers[0].config.getConfig()
406 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
408 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
411 it('Should use the original video URL for the canonical tag', async function () {
412 for (const basePath of watchVideoBasePaths) {
413 for (const id of videoIds) {
414 const res = await makeHTMLRequest(servers[1].url, basePath + id)
415 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
420 it('Should use the original account URL for the canonical tag', async function () {
421 const accountURLtest = res => {
422 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
425 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
426 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
427 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
430 it('Should use the original channel URL for the canonical tag', async function () {
431 const channelURLtests = res => {
432 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
435 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
436 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
437 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
440 it('Should use the original playlist URL for the canonical tag', async function () {
441 for (const basePath of watchPlaylistBasePaths) {
442 for (const id of playlistIds) {
443 const res = await makeHTMLRequest(servers[1].url, basePath + id)
444 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
449 it('Should add noindex meta tag for remote accounts', async function () {
450 const handle = 'root@' + servers[0].host
451 const paths = [ '/accounts/', '/a/', '/@' ]
453 for (const path of paths) {
455 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
456 expect(text).to.contain('<meta name="robots" content="noindex" />')
460 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
461 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
466 it('Should add noindex meta tag for remote accounts', async function () {
467 const handle = 'root_channel@' + servers[0].host
468 const paths = [ '/video-channels/', '/c/', '/@' ]
470 for (const path of paths) {
472 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
473 expect(text).to.contain('<meta name="robots" content="noindex" />')
477 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
478 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
484 describe('Embed HTML', function () {
486 it('Should have the correct embed html tags', async function () {
487 const config = await servers[0].config.getConfig()
488 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
490 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
494 after(async function () {
495 await cleanupTests(servers)