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 { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
7 import { Account, CustomConfig, HTMLServerConfig, ServerConfig, VideoPlaylistCreateResult, VideoPlaylistPrivacy } from '@shared/models'
13 flushAndRunMultipleServers,
21 setAccessTokensToServers,
22 setDefaultVideoChannel,
24 updateCustomSubConfig,
29 } from '../../shared/extra-utils'
31 const expect = chai.expect
33 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
34 expect(html).to.contain('<title>' + title + '</title>')
35 expect(html).to.contain('<meta name="description" content="' + description + '" />')
36 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
38 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
39 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`)
42 describe('Test a client controllers', function () {
43 let servers: ServerInfo[] = []
46 const videoName = 'my super name for server 1'
47 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
48 const videoDescriptionPlainText = 'my super description for server 1'
50 const playlistName = 'super playlist name'
51 const playlistDescription = 'super playlist description'
52 let playlist: VideoPlaylistCreateResult
54 const channelDescription = 'my super channel description'
56 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
57 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
59 let videoIds: (string | number)[] = []
60 let playlistIds: (string | number)[] = []
62 before(async function () {
65 servers = await flushAndRunMultipleServers(2)
67 await setAccessTokensToServers(servers)
69 await doubleFollow(servers[0], servers[1])
71 await setDefaultVideoChannel(servers)
73 await updateVideoChannel(servers[0].url, servers[0].accessToken, servers[0].videoChannel.name, { description: channelDescription })
77 const videoAttributes = { name: videoName, description: videoDescription }
78 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
80 const resVideosRequest = await getVideosList(servers[0].url)
81 const videos = resVideosRequest.body.data
82 expect(videos.length).to.equal(1)
84 const video = videos[0]
85 servers[0].video = video
86 videoIds = [ video.id, video.uuid, video.shortUUID ]
90 const playlistAttrs = {
91 displayName: playlistName,
92 description: playlistDescription,
93 privacy: VideoPlaylistPrivacy.PUBLIC,
94 videoChannelId: servers[0].videoChannel.id
97 const resVideoPlaylistRequest = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
98 playlist = resVideoPlaylistRequest.body.videoPlaylist
99 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
101 await addVideoInPlaylist({
103 token: servers[0].accessToken,
104 playlistId: playlist.shortUUID,
105 elementAttrs: { videoId: video.id }
110 await updateMyUser({ url: servers[0].url, accessToken: servers[0].accessToken, description: 'my account description' })
112 const resAccountRequest = await getAccount(servers[0].url, `${servers[0].user.username}@${servers[0].host}`)
113 account = resAccountRequest.body
115 await waitJobs(servers)
118 describe('oEmbed', function () {
120 it('Should have valid oEmbed discovery tags for videos', async function () {
121 for (const basePath of watchVideoBasePaths) {
122 for (const id of videoIds) {
123 const res = await makeGetRequest({
127 statusCodeExpected: HttpStatusCode.OK_200
130 const port = servers[0].port
132 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
133 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` +
134 `title="${servers[0].video.name}" />`
136 expect(res.text).to.contain(expectedLink)
141 it('Should have valid oEmbed discovery tags for a playlist', async function () {
142 for (const basePath of watchPlaylistBasePaths) {
143 for (const id of playlistIds) {
144 const res = await makeGetRequest({
148 statusCodeExpected: HttpStatusCode.OK_200
151 const port = servers[0].port
153 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
154 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.uuid}" ` +
155 `title="${playlistName}" />`
157 expect(res.text).to.contain(expectedLink)
163 describe('Open Graph', function () {
165 async function accountPageTest (path: string) {
166 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
167 const text = res.text
169 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
170 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
171 expect(text).to.contain('<meta property="og:type" content="website" />')
172 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
175 async function channelPageTest (path: string) {
176 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
177 const text = res.text
179 expect(text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`)
180 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
181 expect(text).to.contain('<meta property="og:type" content="website" />')
182 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
185 async function watchVideoPageTest (path: string) {
186 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
187 const text = res.text
189 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
190 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
191 expect(text).to.contain('<meta property="og:type" content="video" />')
192 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`)
195 async function watchPlaylistPageTest (path: string) {
196 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
197 const text = res.text
199 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
200 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
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/p/${playlist.uuid}" />`)
205 it('Should have valid Open Graph tags on the account page', async function () {
206 await accountPageTest('/accounts/' + servers[0].user.username)
207 await accountPageTest('/a/' + servers[0].user.username)
208 await accountPageTest('/@' + servers[0].user.username)
211 it('Should have valid Open Graph tags on the channel page', async function () {
212 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
213 await channelPageTest('/c/' + servers[0].videoChannel.name)
214 await channelPageTest('/@' + servers[0].videoChannel.name)
217 it('Should have valid Open Graph tags on the watch page', async function () {
218 for (const path of watchVideoBasePaths) {
219 for (const id of videoIds) {
220 await watchVideoPageTest(path + id)
225 it('Should have valid Open Graph tags on the watch playlist page', async function () {
226 for (const path of watchPlaylistBasePaths) {
227 for (const id of playlistIds) {
228 await watchPlaylistPageTest(path + id)
234 describe('Twitter card', async function () {
236 describe('Not whitelisted', function () {
238 async function accountPageTest (path: string) {
239 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
240 const text = res.text
242 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
243 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
244 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
245 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
248 async function channelPageTest (path: string) {
249 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: 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="${servers[0].videoChannel.displayName}" />`)
255 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
258 async function watchVideoPageTest (path: string) {
259 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
260 const text = res.text
262 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
263 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
264 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
265 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
268 async function watchPlaylistPageTest (path: string) {
269 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
270 const text = res.text
272 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
273 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
274 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
275 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
278 it('Should have valid twitter card on the watch video page', async function () {
279 for (const path of watchVideoBasePaths) {
280 for (const id of videoIds) {
281 await watchVideoPageTest(path + id)
286 it('Should have valid twitter card on the watch playlist page', async function () {
287 for (const path of watchPlaylistBasePaths) {
288 for (const id of playlistIds) {
289 await watchPlaylistPageTest(path + id)
294 it('Should have valid twitter card on the account page', async function () {
295 await accountPageTest('/accounts/' + account.name)
296 await accountPageTest('/a/' + account.name)
297 await accountPageTest('/@' + account.name)
300 it('Should have valid twitter card on the channel page', async function () {
301 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
302 await channelPageTest('/c/' + servers[0].videoChannel.name)
303 await channelPageTest('/@' + servers[0].videoChannel.name)
307 describe('Whitelisted', function () {
309 before(async function () {
310 const res = await getCustomConfig(servers[0].url, servers[0].accessToken)
311 const config = res.body as CustomConfig
312 config.services.twitter = {
317 await updateCustomConfig(servers[0].url, servers[0].accessToken, config)
320 async function accountPageTest (path: string) {
321 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
322 const text = res.text
324 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
325 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
328 async function channelPageTest (path: string) {
329 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
330 const text = res.text
332 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
333 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
336 async function watchVideoPageTest (path: string) {
337 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
338 const text = res.text
340 expect(text).to.contain('<meta property="twitter:card" content="player" />')
341 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
344 async function watchPlaylistPageTest (path: string) {
345 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
346 const text = res.text
348 expect(text).to.contain('<meta property="twitter:card" content="player" />')
349 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
352 it('Should have valid twitter card on the watch video page', async function () {
353 for (const path of watchVideoBasePaths) {
354 for (const id of videoIds) {
355 await watchVideoPageTest(path + id)
360 it('Should have valid twitter card on the watch playlist page', async function () {
361 for (const path of watchPlaylistBasePaths) {
362 for (const id of playlistIds) {
363 await watchPlaylistPageTest(path + id)
368 it('Should have valid twitter card on the account page', async function () {
369 await accountPageTest('/accounts/' + account.name)
370 await accountPageTest('/a/' + account.name)
371 await accountPageTest('/@' + account.name)
374 it('Should have valid twitter card on the channel page', async function () {
375 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
376 await channelPageTest('/c/' + servers[0].videoChannel.name)
377 await channelPageTest('/@' + servers[0].videoChannel.name)
382 describe('Index HTML', function () {
384 it('Should have valid index html tags (title, description...)', async function () {
385 const resConfig = await getConfig(servers[0].url)
386 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
388 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
389 checkIndexTags(res.text, 'PeerTube', description, '', resConfig.body)
392 it('Should update the customized configuration and have the correct index html tags', async function () {
393 await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
395 name: 'PeerTube updated',
396 shortDescription: 'my short description',
397 description: 'my super description',
398 terms: 'my super terms',
399 defaultNSFWPolicy: 'blur',
400 defaultClientRoute: '/videos/recently-added',
402 javascript: 'alert("coucou")',
403 css: 'body { background-color: red; }'
408 const resConfig = await getConfig(servers[0].url)
409 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
411 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
414 it('Should have valid index html updated tags (title, description...)', async function () {
415 const resConfig = await getConfig(servers[0].url)
416 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
418 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
421 it('Should use the original video URL for the canonical tag', async function () {
422 for (const basePath of watchVideoBasePaths) {
423 for (const id of videoIds) {
424 const res = await makeHTMLRequest(servers[1].url, basePath + id)
425 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
430 it('Should use the original account URL for the canonical tag', async function () {
431 const accountURLtest = (res) => {
432 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
435 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
436 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
437 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
440 it('Should use the original channel URL for the canonical tag', async function () {
441 const channelURLtests = (res) => {
442 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
445 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
446 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
447 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
450 it('Should use the original playlist URL for the canonical tag', async function () {
451 for (const basePath of watchPlaylistBasePaths) {
452 for (const id of playlistIds) {
453 const res = await makeHTMLRequest(servers[1].url, basePath + id)
454 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
460 describe('Embed HTML', function () {
462 it('Should have the correct embed html tags', async function () {
463 const resConfig = await getConfig(servers[0].url)
464 const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath)
466 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
470 after(async function () {
471 await cleanupTests(servers)