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, HTMLServerConfig, ServerConfig, VideoPlaylistCreateResult, VideoPlaylistPrivacy } from '@shared/models'
13 flushAndRunMultipleServers,
18 setAccessTokensToServers,
19 setDefaultVideoChannel,
24 } from '../../shared/extra-utils'
26 const expect = chai.expect
28 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
29 expect(html).to.contain('<title>' + title + '</title>')
30 expect(html).to.contain('<meta name="description" content="' + description + '" />')
31 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
33 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
34 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`)
37 describe('Test a client controllers', function () {
38 let servers: ServerInfo[] = []
41 const videoName = 'my super name for server 1'
42 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
43 const videoDescriptionPlainText = 'my super description for server 1'
45 const playlistName = 'super playlist name'
46 const playlistDescription = 'super playlist description'
47 let playlist: VideoPlaylistCreateResult
49 const channelDescription = 'my super channel description'
51 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
52 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
54 let videoIds: (string | number)[] = []
55 let playlistIds: (string | number)[] = []
57 before(async function () {
60 servers = await flushAndRunMultipleServers(2)
62 await setAccessTokensToServers(servers)
64 await doubleFollow(servers[0], servers[1])
66 await setDefaultVideoChannel(servers)
68 await updateVideoChannel(servers[0].url, servers[0].accessToken, servers[0].videoChannel.name, { description: channelDescription })
72 const videoAttributes = { name: videoName, description: videoDescription }
73 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
75 const resVideosRequest = await getVideosList(servers[0].url)
76 const videos = resVideosRequest.body.data
77 expect(videos.length).to.equal(1)
79 const video = videos[0]
80 servers[0].video = video
81 videoIds = [ video.id, video.uuid, video.shortUUID ]
85 const playlistAttrs = {
86 displayName: playlistName,
87 description: playlistDescription,
88 privacy: VideoPlaylistPrivacy.PUBLIC,
89 videoChannelId: servers[0].videoChannel.id
92 const resVideoPlaylistRequest = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
93 playlist = resVideoPlaylistRequest.body.videoPlaylist
94 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
96 await addVideoInPlaylist({
98 token: servers[0].accessToken,
99 playlistId: playlist.shortUUID,
100 elementAttrs: { videoId: video.id }
105 await updateMyUser({ url: servers[0].url, accessToken: servers[0].accessToken, description: 'my account description' })
107 account = await servers[0].accountsCommand.get({ accountName: `${servers[0].user.username}@${servers[0].host}` })
109 await waitJobs(servers)
112 describe('oEmbed', function () {
114 it('Should have valid oEmbed discovery tags for videos', async function () {
115 for (const basePath of watchVideoBasePaths) {
116 for (const id of videoIds) {
117 const res = await makeGetRequest({
121 statusCodeExpected: HttpStatusCode.OK_200
124 const port = servers[0].port
126 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
127 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` +
128 `title="${servers[0].video.name}" />`
130 expect(res.text).to.contain(expectedLink)
135 it('Should have valid oEmbed discovery tags for a playlist', async function () {
136 for (const basePath of watchPlaylistBasePaths) {
137 for (const id of playlistIds) {
138 const res = await makeGetRequest({
142 statusCodeExpected: HttpStatusCode.OK_200
145 const port = servers[0].port
147 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
148 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.uuid}" ` +
149 `title="${playlistName}" />`
151 expect(res.text).to.contain(expectedLink)
157 describe('Open Graph', function () {
159 async function accountPageTest (path: string) {
160 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
161 const text = res.text
163 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
164 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
165 expect(text).to.contain('<meta property="og:type" content="website" />')
166 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
169 async function channelPageTest (path: string) {
170 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
171 const text = res.text
173 expect(text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`)
174 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
175 expect(text).to.contain('<meta property="og:type" content="website" />')
176 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
179 async function watchVideoPageTest (path: string) {
180 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
181 const text = res.text
183 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
184 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
185 expect(text).to.contain('<meta property="og:type" content="video" />')
186 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`)
189 async function watchPlaylistPageTest (path: string) {
190 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
191 const text = res.text
193 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
194 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
195 expect(text).to.contain('<meta property="og:type" content="video" />')
196 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.uuid}" />`)
199 it('Should have valid Open Graph tags on the account page', async function () {
200 await accountPageTest('/accounts/' + servers[0].user.username)
201 await accountPageTest('/a/' + servers[0].user.username)
202 await accountPageTest('/@' + servers[0].user.username)
205 it('Should have valid Open Graph tags on the channel page', async function () {
206 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
207 await channelPageTest('/c/' + servers[0].videoChannel.name)
208 await channelPageTest('/@' + servers[0].videoChannel.name)
211 it('Should have valid Open Graph tags on the watch page', async function () {
212 for (const path of watchVideoBasePaths) {
213 for (const id of videoIds) {
214 await watchVideoPageTest(path + id)
219 it('Should have valid Open Graph tags on the watch playlist page', async function () {
220 for (const path of watchPlaylistBasePaths) {
221 for (const id of playlistIds) {
222 await watchPlaylistPageTest(path + id)
228 describe('Twitter card', async function () {
230 describe('Not whitelisted', function () {
232 async function accountPageTest (path: string) {
233 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
234 const text = res.text
236 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
237 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
238 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
239 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
242 async function channelPageTest (path: string) {
243 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
244 const text = res.text
246 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
247 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
248 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`)
249 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
252 async function watchVideoPageTest (path: string) {
253 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
254 const text = res.text
256 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
257 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
258 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
259 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
262 async function watchPlaylistPageTest (path: string) {
263 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
264 const text = res.text
266 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
267 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
268 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
269 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
272 it('Should have valid twitter card on the watch video page', async function () {
273 for (const path of watchVideoBasePaths) {
274 for (const id of videoIds) {
275 await watchVideoPageTest(path + id)
280 it('Should have valid twitter card on the watch playlist page', async function () {
281 for (const path of watchPlaylistBasePaths) {
282 for (const id of playlistIds) {
283 await watchPlaylistPageTest(path + id)
288 it('Should have valid twitter card on the account page', async function () {
289 await accountPageTest('/accounts/' + account.name)
290 await accountPageTest('/a/' + account.name)
291 await accountPageTest('/@' + account.name)
294 it('Should have valid twitter card on the channel page', async function () {
295 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
296 await channelPageTest('/c/' + servers[0].videoChannel.name)
297 await channelPageTest('/@' + servers[0].videoChannel.name)
301 describe('Whitelisted', function () {
303 before(async function () {
304 const config = await servers[0].configCommand.getCustomConfig()
305 config.services.twitter = {
310 await servers[0].configCommand.updateCustomConfig({ newCustomConfig: config })
313 async function accountPageTest (path: string) {
314 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
315 const text = res.text
317 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
318 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
321 async function channelPageTest (path: string) {
322 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
323 const text = res.text
325 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
326 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
329 async function watchVideoPageTest (path: string) {
330 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
331 const text = res.text
333 expect(text).to.contain('<meta property="twitter:card" content="player" />')
334 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
337 async function watchPlaylistPageTest (path: string) {
338 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
339 const text = res.text
341 expect(text).to.contain('<meta property="twitter:card" content="player" />')
342 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
345 it('Should have valid twitter card on the watch video page', async function () {
346 for (const path of watchVideoBasePaths) {
347 for (const id of videoIds) {
348 await watchVideoPageTest(path + id)
353 it('Should have valid twitter card on the watch playlist page', async function () {
354 for (const path of watchPlaylistBasePaths) {
355 for (const id of playlistIds) {
356 await watchPlaylistPageTest(path + id)
361 it('Should have valid twitter card on the account page', async function () {
362 await accountPageTest('/accounts/' + account.name)
363 await accountPageTest('/a/' + account.name)
364 await accountPageTest('/@' + account.name)
367 it('Should have valid twitter card on the channel page', async function () {
368 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
369 await channelPageTest('/c/' + servers[0].videoChannel.name)
370 await channelPageTest('/@' + servers[0].videoChannel.name)
375 describe('Index HTML', function () {
377 it('Should have valid index html tags (title, description...)', async function () {
378 const config = await servers[0].configCommand.getConfig()
379 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
381 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
382 checkIndexTags(res.text, 'PeerTube', description, '', config)
385 it('Should update the customized configuration and have the correct index html tags', async function () {
386 await servers[0].configCommand.updateCustomSubConfig({
389 name: 'PeerTube updated',
390 shortDescription: 'my short description',
391 description: 'my super description',
392 terms: 'my super terms',
393 defaultNSFWPolicy: 'blur',
394 defaultClientRoute: '/videos/recently-added',
396 javascript: 'alert("coucou")',
397 css: 'body { background-color: red; }'
403 const config = await servers[0].configCommand.getConfig()
404 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
406 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
409 it('Should have valid index html updated tags (title, description...)', async function () {
410 const config = await servers[0].configCommand.getConfig()
411 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
413 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
416 it('Should use the original video URL for the canonical tag', async function () {
417 for (const basePath of watchVideoBasePaths) {
418 for (const id of videoIds) {
419 const res = await makeHTMLRequest(servers[1].url, basePath + id)
420 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
425 it('Should use the original account URL for the canonical tag', async function () {
426 const accountURLtest = res => {
427 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
430 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
431 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
432 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
435 it('Should use the original channel URL for the canonical tag', async function () {
436 const channelURLtests = res => {
437 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
440 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
441 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
442 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
445 it('Should use the original playlist URL for the canonical tag', async function () {
446 for (const basePath of watchPlaylistBasePaths) {
447 for (const id of playlistIds) {
448 const res = await makeHTMLRequest(servers[1].url, basePath + id)
449 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
455 describe('Embed HTML', function () {
457 it('Should have the correct embed html tags', async function () {
458 const config = await servers[0].configCommand.getConfig()
459 const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath)
461 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
465 after(async function () {
466 await cleanupTests(servers)