1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
4 import * as chai from 'chai'
5 import * as request from 'supertest'
6 import { Account, HTMLServerConfig, ServerConfig, VideoPlaylistPrivacy } from '@shared/models'
12 flushAndRunMultipleServers,
19 setAccessTokensToServers,
20 setDefaultVideoChannel,
22 updateCustomSubConfig,
27 } from '../../shared/extra-utils'
28 import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
29 import { omit } from 'lodash'
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 playlistUUID: string
54 const channelDescription = 'my super channel description'
56 before(async function () {
59 servers = await flushAndRunMultipleServers(2)
61 await setAccessTokensToServers(servers)
63 await doubleFollow(servers[0], servers[1])
65 await setDefaultVideoChannel(servers)
67 await updateVideoChannel(servers[0].url, servers[0].accessToken, servers[0].videoChannel.name, { description: channelDescription })
71 const videoAttributes = { name: videoName, description: videoDescription }
72 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
74 const resVideosRequest = await getVideosList(servers[0].url)
75 const videos = resVideosRequest.body.data
76 expect(videos.length).to.equal(1)
78 servers[0].video = videos[0]
82 const playlistAttrs = {
83 displayName: playlistName,
84 description: playlistDescription,
85 privacy: VideoPlaylistPrivacy.PUBLIC,
86 videoChannelId: servers[0].videoChannel.id
89 const resVideoPlaylistRequest = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
91 const playlist = resVideoPlaylistRequest.body.videoPlaylist
92 const playlistId = playlist.id
93 playlistUUID = playlist.uuid
95 await addVideoInPlaylist({
97 token: servers[0].accessToken,
99 elementAttrs: { videoId: servers[0].video.id }
104 await updateMyUser({ url: servers[0].url, accessToken: servers[0].accessToken, description: 'my account description' })
106 const resAccountRequest = await getAccount(servers[0].url, `${servers[0].user.username}@${servers[0].host}`)
107 account = resAccountRequest.body
109 await waitJobs(servers)
112 describe('oEmbed', function () {
113 it('Should have valid oEmbed discovery tags for videos', async function () {
114 const path = '/videos/watch/' + servers[0].video.uuid
115 const res = await request(servers[0].url)
117 .set('Accept', 'text/html')
118 .expect(HttpStatusCode.OK_200)
120 const port = servers[0].port
122 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
123 `url=http%3A%2F%2Flocalhost%3A${port}%2Fvideos%2Fwatch%2F${servers[0].video.uuid}" ` +
124 `title="${servers[0].video.name}" />`
126 expect(res.text).to.contain(expectedLink)
129 it('Should have valid oEmbed discovery tags for a playlist', async function () {
130 const res = await request(servers[0].url)
131 .get('/videos/watch/playlist/' + playlistUUID)
132 .set('Accept', 'text/html')
133 .expect(HttpStatusCode.OK_200)
135 const port = servers[0].port
137 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
138 `url=http%3A%2F%2Flocalhost%3A${port}%2Fvideos%2Fwatch%2Fplaylist%2F${playlistUUID}" ` +
139 `title="${playlistName}" />`
141 expect(res.text).to.contain(expectedLink)
145 describe('Open Graph', function () {
147 it('Should have valid Open Graph tags on the account page', async function () {
148 const accountPageTests = (res) => {
149 expect(res.text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
150 expect(res.text).to.contain(`<meta property="og:description" content="${account.description}" />`)
151 expect(res.text).to.contain('<meta property="og:type" content="website" />')
152 expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
155 accountPageTests(await request(servers[0].url)
156 .get('/accounts/' + servers[0].user.username)
157 .set('Accept', 'text/html')
158 .expect(HttpStatusCode.OK_200))
160 accountPageTests(await request(servers[0].url)
161 .get('/a/' + servers[0].user.username)
162 .set('Accept', 'text/html')
163 .expect(HttpStatusCode.OK_200))
165 accountPageTests(await request(servers[0].url)
166 .get('/@' + servers[0].user.username)
167 .set('Accept', 'text/html')
168 .expect(HttpStatusCode.OK_200))
171 it('Should have valid Open Graph tags on the channel page', async function () {
172 const channelPageOGtests = (res) => {
173 expect(res.text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`)
174 expect(res.text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
175 expect(res.text).to.contain('<meta property="og:type" content="website" />')
176 expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
179 channelPageOGtests(await request(servers[0].url)
180 .get('/video-channels/' + servers[0].videoChannel.name)
181 .set('Accept', 'text/html')
182 .expect(HttpStatusCode.OK_200))
184 channelPageOGtests(await request(servers[0].url)
185 .get('/c/' + servers[0].videoChannel.name)
186 .set('Accept', 'text/html')
187 .expect(HttpStatusCode.OK_200))
189 channelPageOGtests(await request(servers[0].url)
190 .get('/@' + servers[0].videoChannel.name)
191 .set('Accept', 'text/html')
192 .expect(HttpStatusCode.OK_200))
195 it('Should have valid Open Graph tags on the watch page with video id', async function () {
196 const res = await request(servers[0].url)
197 .get('/videos/watch/' + servers[0].video.id)
198 .set('Accept', 'text/html')
199 .expect(HttpStatusCode.OK_200)
201 expect(res.text).to.contain(`<meta property="og:title" content="${videoName}" />`)
202 expect(res.text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
203 expect(res.text).to.contain('<meta property="og:type" content="video" />')
204 expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
207 it('Should have valid Open Graph tags on the watch page with video uuid', async function () {
208 const res = await request(servers[0].url)
209 .get('/videos/watch/' + servers[0].video.uuid)
210 .set('Accept', 'text/html')
211 .expect(HttpStatusCode.OK_200)
213 expect(res.text).to.contain(`<meta property="og:title" content="${videoName}" />`)
214 expect(res.text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
215 expect(res.text).to.contain('<meta property="og:type" content="video" />')
216 expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
219 it('Should have valid Open Graph tags on the watch playlist page', async function () {
220 const res = await request(servers[0].url)
221 .get('/videos/watch/playlist/' + playlistUUID)
222 .set('Accept', 'text/html')
223 .expect(HttpStatusCode.OK_200)
225 expect(res.text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
226 expect(res.text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
227 expect(res.text).to.contain('<meta property="og:type" content="video" />')
228 expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/playlist/${playlistUUID}" />`)
232 describe('Twitter card', async function () {
234 it('Should have valid twitter card on the watch video page', async function () {
235 const res = await request(servers[0].url)
236 .get('/videos/watch/' + servers[0].video.uuid)
237 .set('Accept', 'text/html')
238 .expect(HttpStatusCode.OK_200)
240 expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
241 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
242 expect(res.text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
243 expect(res.text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
246 it('Should have valid twitter card on the watch playlist page', async function () {
247 const res = await request(servers[0].url)
248 .get('/videos/watch/playlist/' + playlistUUID)
249 .set('Accept', 'text/html')
250 .expect(HttpStatusCode.OK_200)
252 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
253 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
254 expect(res.text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
255 expect(res.text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
258 it('Should have valid twitter card on the account page', async function () {
259 const accountPageTests = (res) => {
260 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
261 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
262 expect(res.text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
263 expect(res.text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
266 accountPageTests(await request(servers[0].url)
267 .get('/accounts/' + account.name)
268 .set('Accept', 'text/html')
269 .expect(HttpStatusCode.OK_200))
271 accountPageTests(await request(servers[0].url)
272 .get('/a/' + account.name)
273 .set('Accept', 'text/html')
274 .expect(HttpStatusCode.OK_200))
276 accountPageTests(await request(servers[0].url)
277 .get('/@' + account.name)
278 .set('Accept', 'text/html')
279 .expect(HttpStatusCode.OK_200))
282 it('Should have valid twitter card on the channel page', async function () {
283 const channelPageTests = (res) => {
284 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
285 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
286 expect(res.text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`)
287 expect(res.text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
290 channelPageTests(await request(servers[0].url)
291 .get('/video-channels/' + servers[0].videoChannel.name)
292 .set('Accept', 'text/html')
293 .expect(HttpStatusCode.OK_200))
295 channelPageTests(await request(servers[0].url)
296 .get('/c/' + servers[0].videoChannel.name)
297 .set('Accept', 'text/html')
298 .expect(HttpStatusCode.OK_200))
300 channelPageTests(await request(servers[0].url)
301 .get('/@' + servers[0].videoChannel.name)
302 .set('Accept', 'text/html')
303 .expect(HttpStatusCode.OK_200))
306 it('Should have valid twitter card if Twitter is whitelisted', async function () {
307 const res1 = await getCustomConfig(servers[0].url, servers[0].accessToken)
308 const config = res1.body
309 config.services.twitter = {
313 await updateCustomConfig(servers[0].url, servers[0].accessToken, config)
315 const resVideoRequest = await request(servers[0].url)
316 .get('/videos/watch/' + servers[0].video.uuid)
317 .set('Accept', 'text/html')
318 .expect(HttpStatusCode.OK_200)
320 expect(resVideoRequest.text).to.contain('<meta property="twitter:card" content="player" />')
321 expect(resVideoRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
323 const resVideoPlaylistRequest = await request(servers[0].url)
324 .get('/videos/watch/playlist/' + playlistUUID)
325 .set('Accept', 'text/html')
326 .expect(HttpStatusCode.OK_200)
328 expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:card" content="player" />')
329 expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
331 const accountTests = (res) => {
332 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
333 expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
336 accountTests(await request(servers[0].url)
337 .get('/accounts/' + account.name)
338 .set('Accept', 'text/html')
339 .expect(HttpStatusCode.OK_200))
341 accountTests(await request(servers[0].url)
342 .get('/a/' + account.name)
343 .set('Accept', 'text/html')
344 .expect(HttpStatusCode.OK_200))
346 accountTests(await request(servers[0].url)
347 .get('/@' + account.name)
348 .set('Accept', 'text/html')
349 .expect(HttpStatusCode.OK_200))
351 const channelTests = (res) => {
352 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
353 expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
356 channelTests(await request(servers[0].url)
357 .get('/video-channels/' + servers[0].videoChannel.name)
358 .set('Accept', 'text/html')
359 .expect(HttpStatusCode.OK_200))
361 channelTests(await request(servers[0].url)
362 .get('/c/' + servers[0].videoChannel.name)
363 .set('Accept', 'text/html')
364 .expect(HttpStatusCode.OK_200))
366 channelTests(await request(servers[0].url)
367 .get('/@' + servers[0].videoChannel.name)
368 .set('Accept', 'text/html')
369 .expect(HttpStatusCode.OK_200))
373 describe('Index HTML', function () {
375 it('Should have valid index html tags (title, description...)', async function () {
376 const resConfig = await getConfig(servers[0].url)
377 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
379 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
380 checkIndexTags(res.text, 'PeerTube', description, '', resConfig.body)
383 it('Should update the customized configuration and have the correct index html tags', async function () {
384 await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
386 name: 'PeerTube updated',
387 shortDescription: 'my short description',
388 description: 'my super description',
389 terms: 'my super terms',
390 defaultNSFWPolicy: 'blur',
391 defaultClientRoute: '/videos/recently-added',
393 javascript: 'alert("coucou")',
394 css: 'body { background-color: red; }'
399 const resConfig = await getConfig(servers[0].url)
400 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
402 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
405 it('Should have valid index html updated tags (title, description...)', async function () {
406 const resConfig = await getConfig(servers[0].url)
407 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
409 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
412 it('Should use the original video URL for the canonical tag', async function () {
413 const res = await makeHTMLRequest(servers[1].url, '/videos/watch/' + servers[0].video.uuid)
414 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
417 it('Should use the original account URL for the canonical tag', async function () {
418 const accountURLtest = (res) => {
419 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
422 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
423 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
424 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
427 it('Should use the original channel URL for the canonical tag', async function () {
428 const channelURLtests = (res) => {
429 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
432 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
433 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
434 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
437 it('Should use the original playlist URL for the canonical tag', async function () {
438 const res = await makeHTMLRequest(servers[1].url, '/videos/watch/playlist/' + playlistUUID)
439 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`)
443 describe('Embed HTML', function () {
445 it('Should have the correct embed html tags', async function () {
446 const resConfig = await getConfig(servers[0].url)
447 const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath)
449 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body)
453 after(async function () {
454 await cleanupTests(servers)