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,
19 setAccessTokensToServers,
20 setDefaultVideoChannel,
25 } from '../../shared/extra-utils'
27 const expect = chai.expect
29 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
30 expect(html).to.contain('<title>' + title + '</title>')
31 expect(html).to.contain('<meta name="description" content="' + description + '" />')
32 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
34 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
35 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`)
38 describe('Test a client controllers', function () {
39 let servers: ServerInfo[] = []
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 playlistIds: (string | number)[] = []
58 before(async function () {
61 servers = await flushAndRunMultipleServers(2)
63 await setAccessTokensToServers(servers)
65 await doubleFollow(servers[0], servers[1])
67 await setDefaultVideoChannel(servers)
69 await updateVideoChannel(servers[0].url, servers[0].accessToken, servers[0].videoChannel.name, { description: channelDescription })
73 const videoAttributes = { name: videoName, description: videoDescription }
74 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
76 const resVideosRequest = await getVideosList(servers[0].url)
77 const videos = resVideosRequest.body.data
78 expect(videos.length).to.equal(1)
80 const video = videos[0]
81 servers[0].video = video
82 videoIds = [ video.id, video.uuid, video.shortUUID ]
86 const playlistAttrs = {
87 displayName: playlistName,
88 description: playlistDescription,
89 privacy: VideoPlaylistPrivacy.PUBLIC,
90 videoChannelId: servers[0].videoChannel.id
93 const resVideoPlaylistRequest = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
94 playlist = resVideoPlaylistRequest.body.videoPlaylist
95 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
97 await addVideoInPlaylist({
99 token: servers[0].accessToken,
100 playlistId: playlist.shortUUID,
101 elementAttrs: { videoId: video.id }
106 await updateMyUser({ url: servers[0].url, accessToken: servers[0].accessToken, description: 'my account description' })
108 const resAccountRequest = await getAccount(servers[0].url, `${servers[0].user.username}@${servers[0].host}`)
109 account = resAccountRequest.body
111 await waitJobs(servers)
114 describe('oEmbed', function () {
116 it('Should have valid oEmbed discovery tags for videos', async function () {
117 for (const basePath of watchVideoBasePaths) {
118 for (const id of videoIds) {
119 const res = await makeGetRequest({
123 statusCodeExpected: HttpStatusCode.OK_200
126 const port = servers[0].port
128 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
129 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` +
130 `title="${servers[0].video.name}" />`
132 expect(res.text).to.contain(expectedLink)
137 it('Should have valid oEmbed discovery tags for a playlist', async function () {
138 for (const basePath of watchPlaylistBasePaths) {
139 for (const id of playlistIds) {
140 const res = await makeGetRequest({
144 statusCodeExpected: HttpStatusCode.OK_200
147 const port = servers[0].port
149 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
150 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.uuid}" ` +
151 `title="${playlistName}" />`
153 expect(res.text).to.contain(expectedLink)
159 describe('Open Graph', function () {
161 async function accountPageTest (path: string) {
162 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
163 const text = res.text
165 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
166 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
167 expect(text).to.contain('<meta property="og:type" content="website" />')
168 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
171 async function channelPageTest (path: string) {
172 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
173 const text = res.text
175 expect(text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`)
176 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
177 expect(text).to.contain('<meta property="og:type" content="website" />')
178 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
181 async function watchVideoPageTest (path: string) {
182 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
183 const text = res.text
185 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
186 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
187 expect(text).to.contain('<meta property="og:type" content="video" />')
188 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`)
191 async function watchPlaylistPageTest (path: string) {
192 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
193 const text = res.text
195 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
196 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
197 expect(text).to.contain('<meta property="og:type" content="video" />')
198 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.uuid}" />`)
201 it('Should have valid Open Graph tags on the account page', async function () {
202 await accountPageTest('/accounts/' + servers[0].user.username)
203 await accountPageTest('/a/' + servers[0].user.username)
204 await accountPageTest('/@' + servers[0].user.username)
207 it('Should have valid Open Graph tags on the channel page', async function () {
208 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
209 await channelPageTest('/c/' + servers[0].videoChannel.name)
210 await channelPageTest('/@' + servers[0].videoChannel.name)
213 it('Should have valid Open Graph tags on the watch page', async function () {
214 for (const path of watchVideoBasePaths) {
215 for (const id of videoIds) {
216 await watchVideoPageTest(path + id)
221 it('Should have valid Open Graph tags on the watch playlist page', async function () {
222 for (const path of watchPlaylistBasePaths) {
223 for (const id of playlistIds) {
224 await watchPlaylistPageTest(path + id)
230 describe('Twitter card', async function () {
232 describe('Not whitelisted', function () {
234 async function accountPageTest (path: string) {
235 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
236 const text = res.text
238 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
239 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
240 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
241 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
244 async function channelPageTest (path: string) {
245 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
246 const text = res.text
248 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
249 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
250 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`)
251 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
254 async function watchVideoPageTest (path: string) {
255 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
256 const text = res.text
258 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
259 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
260 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
261 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
264 async function watchPlaylistPageTest (path: string) {
265 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
266 const text = res.text
268 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
269 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
270 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
271 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
274 it('Should have valid twitter card on the watch video page', async function () {
275 for (const path of watchVideoBasePaths) {
276 for (const id of videoIds) {
277 await watchVideoPageTest(path + id)
282 it('Should have valid twitter card on the watch playlist page', async function () {
283 for (const path of watchPlaylistBasePaths) {
284 for (const id of playlistIds) {
285 await watchPlaylistPageTest(path + id)
290 it('Should have valid twitter card on the account page', async function () {
291 await accountPageTest('/accounts/' + account.name)
292 await accountPageTest('/a/' + account.name)
293 await accountPageTest('/@' + account.name)
296 it('Should have valid twitter card on the channel page', async function () {
297 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
298 await channelPageTest('/c/' + servers[0].videoChannel.name)
299 await channelPageTest('/@' + servers[0].videoChannel.name)
303 describe('Whitelisted', function () {
305 before(async function () {
306 const config = await servers[0].configCommand.getCustomConfig()
307 config.services.twitter = {
312 await servers[0].configCommand.updateCustomConfig({ newCustomConfig: config })
315 async function accountPageTest (path: string) {
316 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
317 const text = res.text
319 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
320 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
323 async function channelPageTest (path: string) {
324 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
325 const text = res.text
327 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
328 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
331 async function watchVideoPageTest (path: string) {
332 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
333 const text = res.text
335 expect(text).to.contain('<meta property="twitter:card" content="player" />')
336 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
339 async function watchPlaylistPageTest (path: string) {
340 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
341 const text = res.text
343 expect(text).to.contain('<meta property="twitter:card" content="player" />')
344 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
347 it('Should have valid twitter card on the watch video page', async function () {
348 for (const path of watchVideoBasePaths) {
349 for (const id of videoIds) {
350 await watchVideoPageTest(path + id)
355 it('Should have valid twitter card on the watch playlist page', async function () {
356 for (const path of watchPlaylistBasePaths) {
357 for (const id of playlistIds) {
358 await watchPlaylistPageTest(path + id)
363 it('Should have valid twitter card on the account page', async function () {
364 await accountPageTest('/accounts/' + account.name)
365 await accountPageTest('/a/' + account.name)
366 await accountPageTest('/@' + account.name)
369 it('Should have valid twitter card on the channel page', async function () {
370 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
371 await channelPageTest('/c/' + servers[0].videoChannel.name)
372 await channelPageTest('/@' + servers[0].videoChannel.name)
377 describe('Index HTML', function () {
379 it('Should have valid index html tags (title, description...)', async function () {
380 const config = await servers[0].configCommand.getConfig()
381 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
383 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
384 checkIndexTags(res.text, 'PeerTube', description, '', config)
387 it('Should update the customized configuration and have the correct index html tags', async function () {
388 await servers[0].configCommand.updateCustomSubConfig({
391 name: 'PeerTube updated',
392 shortDescription: 'my short description',
393 description: 'my super description',
394 terms: 'my super terms',
395 defaultNSFWPolicy: 'blur',
396 defaultClientRoute: '/videos/recently-added',
398 javascript: 'alert("coucou")',
399 css: 'body { background-color: red; }'
405 const config = await servers[0].configCommand.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 have valid index html updated tags (title, description...)', async function () {
412 const config = await servers[0].configCommand.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 use the original video URL for the canonical tag', async function () {
419 for (const basePath of watchVideoBasePaths) {
420 for (const id of videoIds) {
421 const res = await makeHTMLRequest(servers[1].url, basePath + id)
422 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
427 it('Should use the original account URL for the canonical tag', async function () {
428 const accountURLtest = res => {
429 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
432 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
433 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
434 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
437 it('Should use the original channel URL for the canonical tag', async function () {
438 const channelURLtests = res => {
439 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
442 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
443 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
444 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
447 it('Should use the original playlist URL for the canonical tag', async function () {
448 for (const basePath of watchPlaylistBasePaths) {
449 for (const id of playlistIds) {
450 const res = await makeHTMLRequest(servers[1].url, basePath + id)
451 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
457 describe('Embed HTML', function () {
459 it('Should have the correct embed html tags', async function () {
460 const config = await servers[0].configCommand.getConfig()
461 const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath)
463 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
467 after(async function () {
468 await cleanupTests(servers)