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