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 * as request from 'supertest'
7 import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
8 import { Account, CustomConfig, HTMLServerConfig, ServerConfig, VideoPlaylistPrivacy } from '@shared/models'
14 flushAndRunMultipleServers,
22 setAccessTokensToServers,
23 setDefaultVideoChannel,
25 updateCustomSubConfig,
30 } from '../../shared/extra-utils'
32 const expect = chai.expect
34 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
35 expect(html).to.contain('<title>' + title + '</title>')
36 expect(html).to.contain('<meta name="description" content="' + description + '" />')
37 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
39 const htmlConfig: HTMLServerConfig = omit(config, 'signup')
40 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`)
43 describe('Test a client controllers', function () {
44 let servers: ServerInfo[] = []
47 const videoName = 'my super name for server 1'
48 const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
49 const videoDescriptionPlainText = 'my super description for server 1'
51 const playlistName = 'super playlist name'
52 const playlistDescription = 'super playlist description'
53 let playlistUUID: string
55 const channelDescription = 'my super channel description'
57 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
58 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
60 before(async function () {
63 servers = await flushAndRunMultipleServers(2)
65 await setAccessTokensToServers(servers)
67 await doubleFollow(servers[0], servers[1])
69 await setDefaultVideoChannel(servers)
71 await updateVideoChannel(servers[0].url, servers[0].accessToken, servers[0].videoChannel.name, { description: channelDescription })
75 const videoAttributes = { name: videoName, description: videoDescription }
76 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
78 const resVideosRequest = await getVideosList(servers[0].url)
79 const videos = resVideosRequest.body.data
80 expect(videos.length).to.equal(1)
82 servers[0].video = videos[0]
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 })
95 const playlist = resVideoPlaylistRequest.body.videoPlaylist
96 const playlistId = playlist.id
97 playlistUUID = playlist.uuid
99 await addVideoInPlaylist({
101 token: servers[0].accessToken,
103 elementAttrs: { videoId: servers[0].video.id }
108 await updateMyUser({ url: servers[0].url, accessToken: servers[0].accessToken, description: 'my account description' })
110 const resAccountRequest = await getAccount(servers[0].url, `${servers[0].user.username}@${servers[0].host}`)
111 account = resAccountRequest.body
113 await waitJobs(servers)
116 describe('oEmbed', function () {
118 it('Should have valid oEmbed discovery tags for videos', async function () {
119 for (const basePath of watchVideoBasePaths) {
120 const path = basePath + servers[0].video.uuid
121 const res = await request(servers[0].url)
123 .set('Accept', 'text/html')
124 .expect(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)
136 it('Should have valid oEmbed discovery tags for a playlist', async function () {
137 for (const basePath of watchPlaylistBasePaths) {
138 const res = await request(servers[0].url)
139 .get(basePath + playlistUUID)
140 .set('Accept', 'text/html')
141 .expect(HttpStatusCode.OK_200)
143 const port = servers[0].port
145 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
146 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlistUUID}" ` +
147 `title="${playlistName}" />`
149 expect(res.text).to.contain(expectedLink)
154 describe('Open Graph', function () {
156 async function accountPageTest (path: string) {
157 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
158 const text = res.text
160 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
161 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
162 expect(text).to.contain('<meta property="og:type" content="website" />')
163 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
166 async function channelPageTest (path: string) {
167 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
168 const text = res.text
170 expect(text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`)
171 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
172 expect(text).to.contain('<meta property="og:type" content="website" />')
173 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
176 async function watchVideoPageTest (path: string) {
177 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
178 const text = res.text
180 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
181 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
182 expect(text).to.contain('<meta property="og:type" content="video" />')
183 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`)
186 async function watchPlaylistPageTest (path: string) {
187 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
188 const text = res.text
190 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
191 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
192 expect(text).to.contain('<meta property="og:type" content="video" />')
193 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlistUUID}" />`)
196 it('Should have valid Open Graph tags on the account page', async function () {
197 await accountPageTest('/accounts/' + servers[0].user.username)
198 await accountPageTest('/a/' + servers[0].user.username)
199 await accountPageTest('/@' + servers[0].user.username)
202 it('Should have valid Open Graph tags on the channel page', async function () {
203 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
204 await channelPageTest('/c/' + servers[0].videoChannel.name)
205 await channelPageTest('/@' + servers[0].videoChannel.name)
208 it('Should have valid Open Graph tags on the watch page', async function () {
209 await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
210 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
211 await watchVideoPageTest('/w/' + servers[0].video.uuid)
212 await watchVideoPageTest('/w/' + servers[0].video.id)
215 it('Should have valid Open Graph tags on the watch playlist page', async function () {
216 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
217 await watchPlaylistPageTest('/w/p/' + playlistUUID)
221 describe('Twitter card', async function () {
223 describe('Not whitelisted', function () {
225 async function accountPageTest (path: string) {
226 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
227 const text = res.text
229 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
230 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
231 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
232 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
235 async function channelPageTest (path: string) {
236 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
237 const text = res.text
239 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
240 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
241 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`)
242 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
245 async function watchVideoPageTest (path: string) {
246 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
247 const text = res.text
249 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
250 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
251 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
252 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
255 async function watchPlaylistPageTest (path: string) {
256 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
257 const text = res.text
259 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
260 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
261 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
262 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
265 it('Should have valid twitter card on the watch video page', async function () {
266 await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
267 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
268 await watchVideoPageTest('/w/' + servers[0].video.uuid)
269 await watchVideoPageTest('/w/' + servers[0].video.id)
272 it('Should have valid twitter card on the watch playlist page', async function () {
273 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
274 await watchPlaylistPageTest('/w/p/' + playlistUUID)
277 it('Should have valid twitter card on the account page', async function () {
278 await accountPageTest('/accounts/' + account.name)
279 await accountPageTest('/a/' + account.name)
280 await accountPageTest('/@' + account.name)
283 it('Should have valid twitter card on the channel page', async function () {
284 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
285 await channelPageTest('/c/' + servers[0].videoChannel.name)
286 await channelPageTest('/@' + servers[0].videoChannel.name)
290 describe('Whitelisted', function () {
292 before(async function () {
293 const res = await getCustomConfig(servers[0].url, servers[0].accessToken)
294 const config = res.body as CustomConfig
295 config.services.twitter = {
300 await updateCustomConfig(servers[0].url, servers[0].accessToken, config)
303 async function accountPageTest (path: string) {
304 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
305 const text = res.text
307 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
308 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
311 async function channelPageTest (path: string) {
312 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
313 const text = res.text
315 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
316 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
319 async function watchVideoPageTest (path: string) {
320 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
321 const text = res.text
323 expect(text).to.contain('<meta property="twitter:card" content="player" />')
324 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
327 async function watchPlaylistPageTest (path: string) {
328 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
329 const text = res.text
331 expect(text).to.contain('<meta property="twitter:card" content="player" />')
332 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
335 it('Should have valid twitter card on the watch video page', async function () {
336 await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
337 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
338 await watchVideoPageTest('/w/' + servers[0].video.uuid)
339 await watchVideoPageTest('/w/' + servers[0].video.id)
342 it('Should have valid twitter card on the watch playlist page', async function () {
343 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
344 await watchPlaylistPageTest('/w/p/' + playlistUUID)
347 it('Should have valid twitter card on the account page', async function () {
348 await accountPageTest('/accounts/' + account.name)
349 await accountPageTest('/a/' + account.name)
350 await accountPageTest('/@' + account.name)
353 it('Should have valid twitter card on the channel page', async function () {
354 await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
355 await channelPageTest('/c/' + servers[0].videoChannel.name)
356 await channelPageTest('/@' + servers[0].videoChannel.name)
361 describe('Index HTML', function () {
363 it('Should have valid index html tags (title, description...)', async function () {
364 const resConfig = await getConfig(servers[0].url)
365 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
367 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
368 checkIndexTags(res.text, 'PeerTube', description, '', resConfig.body)
371 it('Should update the customized configuration and have the correct index html tags', async function () {
372 await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
374 name: 'PeerTube updated',
375 shortDescription: 'my short description',
376 description: 'my super description',
377 terms: 'my super terms',
378 defaultNSFWPolicy: 'blur',
379 defaultClientRoute: '/videos/recently-added',
381 javascript: 'alert("coucou")',
382 css: 'body { background-color: red; }'
387 const resConfig = await getConfig(servers[0].url)
388 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
390 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
393 it('Should have valid index html updated tags (title, description...)', async function () {
394 const resConfig = await getConfig(servers[0].url)
395 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
397 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
400 it('Should use the original video URL for the canonical tag', async function () {
401 for (const basePath of watchVideoBasePaths) {
402 const res = await makeHTMLRequest(servers[1].url, basePath + servers[0].video.uuid)
403 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
407 it('Should use the original account URL for the canonical tag', async function () {
408 const accountURLtest = (res) => {
409 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
412 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
413 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
414 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
417 it('Should use the original channel URL for the canonical tag', async function () {
418 const channelURLtests = (res) => {
419 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
422 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
423 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
424 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
427 it('Should use the original playlist URL for the canonical tag', async function () {
428 for (const basePath of watchPlaylistBasePaths) {
429 const res = await makeHTMLRequest(servers[1].url, basePath + playlistUUID)
430 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`)
435 describe('Embed HTML', function () {
437 it('Should have the correct embed html tags', async function () {
438 const resConfig = await getConfig(servers[0].url)
439 const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath)
441 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
445 after(async function () {
446 await cleanupTests(servers)