]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/client.ts
Merge branch 'release/5.0.0' into develop
[github/Chocobozzz/PeerTube.git] / server / tests / client.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import { expect } from 'chai'
4 import { omit } from '@shared/core-utils'
5 import {
6 Account,
7 HTMLServerConfig,
8 HttpStatusCode,
9 ServerConfig,
10 VideoPlaylistCreateResult,
11 VideoPlaylistPrivacy,
12 VideoPrivacy
13 } from '@shared/models'
14 import {
15 cleanupTests,
16 createMultipleServers,
17 doubleFollow,
18 makeGetRequest,
19 makeHTMLRequest,
20 PeerTubeServer,
21 setAccessTokensToServers,
22 setDefaultVideoChannel,
23 waitJobs
24 } from '../../shared/server-commands'
25
26 function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) {
27 expect(html).to.contain('<title>' + title + '</title>')
28 expect(html).to.contain('<meta name="description" content="' + description + '" />')
29 expect(html).to.contain('<style class="custom-css-style">' + css + '</style>')
30
31 const htmlConfig: HTMLServerConfig = omit(config, [ 'signup' ])
32 const configObjectString = JSON.stringify(htmlConfig)
33 const configEscapedString = JSON.stringify(configObjectString)
34
35 expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = ${configEscapedString}</script>`)
36 }
37
38 describe('Test a client controllers', function () {
39 let servers: PeerTubeServer[] = []
40 let account: Account
41
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'
45
46 const playlistName = 'super playlist name'
47 const playlistDescription = 'super playlist description'
48 let playlist: VideoPlaylistCreateResult
49
50 const channelDescription = 'my super channel description'
51
52 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
53 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
54
55 let videoIds: (string | number)[] = []
56 let privateVideoId: string
57 let internalVideoId: string
58 let unlistedVideoId: string
59
60 let playlistIds: (string | number)[] = []
61
62 before(async function () {
63 this.timeout(120000)
64
65 servers = await createMultipleServers(2)
66
67 await setAccessTokensToServers(servers)
68
69 await doubleFollow(servers[0], servers[1])
70
71 await setDefaultVideoChannel(servers)
72
73 await servers[0].channels.update({
74 channelName: servers[0].store.channel.name,
75 attributes: { description: channelDescription }
76 })
77
78 // Public video
79
80 {
81 const attributes = { name: videoName, description: videoDescription }
82 await servers[0].videos.upload({ attributes })
83
84 const { data } = await servers[0].videos.list()
85 expect(data.length).to.equal(1)
86
87 const video = data[0]
88 servers[0].store.video = video
89 videoIds = [ video.id, video.uuid, video.shortUUID ]
90 }
91
92 {
93 ({ uuid: privateVideoId } = await servers[0].videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE }));
94 ({ uuid: unlistedVideoId } = await servers[0].videos.quickUpload({ name: 'unlisted', privacy: VideoPrivacy.UNLISTED }));
95 ({ uuid: internalVideoId } = await servers[0].videos.quickUpload({ name: 'internal', privacy: VideoPrivacy.INTERNAL }))
96 }
97
98 // Playlist
99
100 {
101 const attributes = {
102 displayName: playlistName,
103 description: playlistDescription,
104 privacy: VideoPlaylistPrivacy.PUBLIC,
105 videoChannelId: servers[0].store.channel.id
106 }
107
108 playlist = await servers[0].playlists.create({ attributes })
109 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
110
111 await servers[0].playlists.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: servers[0].store.video.id } })
112 }
113
114 // Account
115
116 {
117 await servers[0].users.updateMe({ description: 'my account description' })
118
119 account = await servers[0].accounts.get({ accountName: `${servers[0].store.user.username}@${servers[0].host}` })
120 }
121
122 await waitJobs(servers)
123 })
124
125 describe('oEmbed', function () {
126
127 it('Should have valid oEmbed discovery tags for videos', async function () {
128 for (const basePath of watchVideoBasePaths) {
129 for (const id of videoIds) {
130 const res = await makeGetRequest({
131 url: servers[0].url,
132 path: basePath + id,
133 accept: 'text/html',
134 expectedStatus: HttpStatusCode.OK_200
135 })
136
137 const expectedLink = `<link rel="alternate" type="application/json+oembed" href="${servers[0].url}/services/oembed?` +
138 `url=http%3A%2F%2F${servers[0].hostname}%3A${servers[0].port}%2Fw%2F${servers[0].store.video.shortUUID}" ` +
139 `title="${servers[0].store.video.name}" />`
140
141 expect(res.text).to.contain(expectedLink)
142 }
143 }
144 })
145
146 it('Should have valid oEmbed discovery tags for a playlist', async function () {
147 for (const basePath of watchPlaylistBasePaths) {
148 for (const id of playlistIds) {
149 const res = await makeGetRequest({
150 url: servers[0].url,
151 path: basePath + id,
152 accept: 'text/html',
153 expectedStatus: HttpStatusCode.OK_200
154 })
155
156 const expectedLink = `<link rel="alternate" type="application/json+oembed" href="${servers[0].url}/services/oembed?` +
157 `url=http%3A%2F%2F${servers[0].hostname}%3A${servers[0].port}%2Fw%2Fp%2F${playlist.shortUUID}" ` +
158 `title="${playlistName}" />`
159
160 expect(res.text).to.contain(expectedLink)
161 }
162 }
163 })
164 })
165
166 describe('Open Graph', function () {
167
168 async function accountPageTest (path: string) {
169 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
170 const text = res.text
171
172 expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
173 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
174 expect(text).to.contain('<meta property="og:type" content="website" />')
175 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].store.user.username}" />`)
176 }
177
178 async function channelPageTest (path: string) {
179 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
180 const text = res.text
181
182 expect(text).to.contain(`<meta property="og:title" content="${servers[0].store.channel.displayName}" />`)
183 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
184 expect(text).to.contain('<meta property="og:type" content="website" />')
185 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].store.channel.name}" />`)
186 }
187
188 async function watchVideoPageTest (path: string) {
189 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
190 const text = res.text
191
192 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
193 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
194 expect(text).to.contain('<meta property="og:type" content="video" />')
195 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].store.video.shortUUID}" />`)
196 }
197
198 async function watchPlaylistPageTest (path: string) {
199 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
200 const text = res.text
201
202 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
203 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
204 expect(text).to.contain('<meta property="og:type" content="video" />')
205 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
206 }
207
208 it('Should have valid Open Graph tags on the account page', async function () {
209 await accountPageTest('/accounts/' + servers[0].store.user.username)
210 await accountPageTest('/a/' + servers[0].store.user.username)
211 await accountPageTest('/@' + servers[0].store.user.username)
212 })
213
214 it('Should have valid Open Graph tags on the channel page', async function () {
215 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
216 await channelPageTest('/c/' + servers[0].store.channel.name)
217 await channelPageTest('/@' + servers[0].store.channel.name)
218 })
219
220 it('Should have valid Open Graph tags on the watch page', async function () {
221 for (const path of watchVideoBasePaths) {
222 for (const id of videoIds) {
223 await watchVideoPageTest(path + id)
224 }
225 }
226 })
227
228 it('Should have valid Open Graph tags on the watch playlist page', async function () {
229 for (const path of watchPlaylistBasePaths) {
230 for (const id of playlistIds) {
231 await watchPlaylistPageTest(path + id)
232 }
233 }
234 })
235 })
236
237 describe('Twitter card', async function () {
238
239 describe('Not whitelisted', function () {
240
241 async function accountPageTest (path: string) {
242 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
243 const text = res.text
244
245 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
246 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
247 expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
248 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
249 }
250
251 async function channelPageTest (path: string) {
252 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
253 const text = res.text
254
255 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
256 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
257 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
258 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
259 }
260
261 async function watchVideoPageTest (path: string) {
262 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
263 const text = res.text
264
265 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
266 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
267 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
268 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
269 }
270
271 async function watchPlaylistPageTest (path: string) {
272 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
273 const text = res.text
274
275 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
276 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
277 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
278 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
279 }
280
281 it('Should have valid twitter card on the watch video page', async function () {
282 for (const path of watchVideoBasePaths) {
283 for (const id of videoIds) {
284 await watchVideoPageTest(path + id)
285 }
286 }
287 })
288
289 it('Should have valid twitter card on the watch playlist page', async function () {
290 for (const path of watchPlaylistBasePaths) {
291 for (const id of playlistIds) {
292 await watchPlaylistPageTest(path + id)
293 }
294 }
295 })
296
297 it('Should have valid twitter card on the account page', async function () {
298 await accountPageTest('/accounts/' + account.name)
299 await accountPageTest('/a/' + account.name)
300 await accountPageTest('/@' + account.name)
301 })
302
303 it('Should have valid twitter card on the channel page', async function () {
304 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
305 await channelPageTest('/c/' + servers[0].store.channel.name)
306 await channelPageTest('/@' + servers[0].store.channel.name)
307 })
308 })
309
310 describe('Whitelisted', function () {
311
312 before(async function () {
313 const config = await servers[0].config.getCustomConfig()
314 config.services.twitter = {
315 username: '@Kuja',
316 whitelisted: true
317 }
318
319 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
320 })
321
322 async function accountPageTest (path: string) {
323 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
324 const text = res.text
325
326 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
327 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
328 }
329
330 async function channelPageTest (path: string) {
331 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
332 const text = res.text
333
334 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
335 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
336 }
337
338 async function watchVideoPageTest (path: string) {
339 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
340 const text = res.text
341
342 expect(text).to.contain('<meta property="twitter:card" content="player" />')
343 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
344 }
345
346 async function watchPlaylistPageTest (path: string) {
347 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
348 const text = res.text
349
350 expect(text).to.contain('<meta property="twitter:card" content="player" />')
351 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
352 }
353
354 it('Should have valid twitter card on the watch video page', async function () {
355 for (const path of watchVideoBasePaths) {
356 for (const id of videoIds) {
357 await watchVideoPageTest(path + id)
358 }
359 }
360 })
361
362 it('Should have valid twitter card on the watch playlist page', async function () {
363 for (const path of watchPlaylistBasePaths) {
364 for (const id of playlistIds) {
365 await watchPlaylistPageTest(path + id)
366 }
367 }
368 })
369
370 it('Should have valid twitter card on the account page', async function () {
371 await accountPageTest('/accounts/' + account.name)
372 await accountPageTest('/a/' + account.name)
373 await accountPageTest('/@' + account.name)
374 })
375
376 it('Should have valid twitter card on the channel page', async function () {
377 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
378 await channelPageTest('/c/' + servers[0].store.channel.name)
379 await channelPageTest('/@' + servers[0].store.channel.name)
380 })
381 })
382 })
383
384 describe('Index HTML', function () {
385
386 it('Should have valid index html tags (title, description...)', async function () {
387 const config = await servers[0].config.getConfig()
388 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
389
390 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
391 checkIndexTags(res.text, 'PeerTube', description, '', config)
392 })
393
394 it('Should update the customized configuration and have the correct index html tags', async function () {
395 await servers[0].config.updateCustomSubConfig({
396 newConfig: {
397 instance: {
398 name: 'PeerTube updated',
399 shortDescription: 'my short description',
400 description: 'my super description',
401 terms: 'my super terms',
402 defaultNSFWPolicy: 'blur',
403 defaultClientRoute: '/videos/recently-added',
404 customizations: {
405 javascript: 'alert("coucou")',
406 css: 'body { background-color: red; }'
407 }
408 }
409 }
410 })
411
412 const config = await servers[0].config.getConfig()
413 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
414
415 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
416 })
417
418 it('Should have valid index html updated tags (title, description...)', async function () {
419 const config = await servers[0].config.getConfig()
420 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
421
422 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
423 })
424
425 it('Should use the original video URL for the canonical tag', async function () {
426 for (const basePath of watchVideoBasePaths) {
427 for (const id of videoIds) {
428 const res = await makeHTMLRequest(servers[1].url, basePath + id)
429 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
430 }
431 }
432 })
433
434 it('Should use the original account URL for the canonical tag', async function () {
435 const accountURLtest = res => {
436 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
437 }
438
439 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
440 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
441 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
442 })
443
444 it('Should use the original channel URL for the canonical tag', async function () {
445 const channelURLtests = res => {
446 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
447 }
448
449 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
450 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
451 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
452 })
453
454 it('Should use the original playlist URL for the canonical tag', async function () {
455 for (const basePath of watchPlaylistBasePaths) {
456 for (const id of playlistIds) {
457 const res = await makeHTMLRequest(servers[1].url, basePath + id)
458 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
459 }
460 }
461 })
462
463 it('Should add noindex meta tag for remote accounts', async function () {
464 const handle = 'root@' + servers[0].host
465 const paths = [ '/accounts/', '/a/', '/@' ]
466
467 for (const path of paths) {
468 {
469 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
470 expect(text).to.contain('<meta name="robots" content="noindex" />')
471 }
472
473 {
474 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
475 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
476 }
477 }
478 })
479
480 it('Should add noindex meta tag for remote channels', async function () {
481 const handle = 'root_channel@' + servers[0].host
482 const paths = [ '/video-channels/', '/c/', '/@' ]
483
484 for (const path of paths) {
485 {
486 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
487 expect(text).to.contain('<meta name="robots" content="noindex" />')
488 }
489
490 {
491 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
492 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
493 }
494 }
495 })
496
497 it('Should not display internal/private video', async function () {
498 for (const basePath of watchVideoBasePaths) {
499 for (const id of [ privateVideoId, internalVideoId ]) {
500 const res = await makeGetRequest({
501 url: servers[0].url,
502 path: basePath + id,
503 accept: 'text/html',
504 expectedStatus: HttpStatusCode.NOT_FOUND_404
505 })
506
507 expect(res.text).to.not.contain('internal')
508 expect(res.text).to.not.contain('private')
509 }
510 }
511 })
512
513 it('Should add noindex meta tag for unlisted video', async function () {
514 for (const basePath of watchVideoBasePaths) {
515 const res = await makeGetRequest({
516 url: servers[0].url,
517 path: basePath + unlistedVideoId,
518 accept: 'text/html',
519 expectedStatus: HttpStatusCode.OK_200
520 })
521
522 expect(res.text).to.contain('unlisted')
523 expect(res.text).to.contain('<meta name="robots" content="noindex" />')
524 }
525 })
526 })
527
528 describe('Embed HTML', function () {
529
530 it('Should have the correct embed html tags', async function () {
531 const config = await servers[0].config.getConfig()
532 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
533
534 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
535 })
536 })
537
538 after(async function () {
539 await cleanupTests(servers)
540 })
541 })