aboutsummaryrefslogtreecommitdiffhomepage
path: root/packages/tests/src/client.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-07-31 14:34:36 +0200
committerChocobozzz <me@florianbigard.com>2023-08-11 15:02:33 +0200
commit3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch)
treee4510b39bdac9c318fdb4b47018d08f15368b8f0 /packages/tests/src/client.ts
parent04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff)
downloadPeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'packages/tests/src/client.ts')
-rw-r--r--packages/tests/src/client.ts556
1 files changed, 556 insertions, 0 deletions
diff --git a/packages/tests/src/client.ts b/packages/tests/src/client.ts
new file mode 100644
index 000000000..a16205494
--- /dev/null
+++ b/packages/tests/src/client.ts
@@ -0,0 +1,556 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { omit } from '@peertube/peertube-core-utils'
5import {
6 Account,
7 HTMLServerConfig,
8 HttpStatusCode,
9 ServerConfig,
10 VideoPlaylistCreateResult,
11 VideoPlaylistPrivacy,
12 VideoPrivacy
13} from '@peertube/peertube-models'
14import {
15 cleanupTests,
16 createMultipleServers,
17 doubleFollow,
18 makeGetRequest,
19 makeHTMLRequest,
20 PeerTubeServer,
21 setAccessTokensToServers,
22 setDefaultVideoChannel,
23 waitJobs
24} from '@peertube/peertube-server-commands'
25
26function 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
38describe('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 let passwordProtectedVideoId: string
60
61 let playlistIds: (string | number)[] = []
62
63 before(async function () {
64 this.timeout(120000)
65
66 servers = await createMultipleServers(2)
67
68 await setAccessTokensToServers(servers)
69
70 await doubleFollow(servers[0], servers[1])
71
72 await setDefaultVideoChannel(servers)
73
74 await servers[0].channels.update({
75 channelName: servers[0].store.channel.name,
76 attributes: { description: channelDescription }
77 })
78
79 // Public video
80
81 {
82 const attributes = { name: videoName, description: videoDescription }
83 await servers[0].videos.upload({ attributes })
84
85 const { data } = await servers[0].videos.list()
86 expect(data.length).to.equal(1)
87
88 const video = data[0]
89 servers[0].store.video = video
90 videoIds = [ video.id, video.uuid, video.shortUUID ]
91 }
92
93 {
94 ({ uuid: privateVideoId } = await servers[0].videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE }));
95 ({ uuid: unlistedVideoId } = await servers[0].videos.quickUpload({ name: 'unlisted', privacy: VideoPrivacy.UNLISTED }));
96 ({ uuid: internalVideoId } = await servers[0].videos.quickUpload({ name: 'internal', privacy: VideoPrivacy.INTERNAL }));
97 ({ uuid: passwordProtectedVideoId } = await servers[0].videos.quickUpload({
98 name: 'password protected',
99 privacy: VideoPrivacy.PASSWORD_PROTECTED,
100 videoPasswords: [ 'password' ]
101 }))
102 }
103
104 // Playlist
105
106 {
107 const attributes = {
108 displayName: playlistName,
109 description: playlistDescription,
110 privacy: VideoPlaylistPrivacy.PUBLIC,
111 videoChannelId: servers[0].store.channel.id
112 }
113
114 playlist = await servers[0].playlists.create({ attributes })
115 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
116
117 await servers[0].playlists.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: servers[0].store.video.id } })
118 }
119
120 // Account
121
122 {
123 await servers[0].users.updateMe({ description: 'my account description' })
124
125 account = await servers[0].accounts.get({ accountName: `${servers[0].store.user.username}@${servers[0].host}` })
126 }
127
128 await waitJobs(servers)
129 })
130
131 describe('oEmbed', function () {
132
133 it('Should have valid oEmbed discovery tags for videos', async function () {
134 for (const basePath of watchVideoBasePaths) {
135 for (const id of videoIds) {
136 const res = await makeGetRequest({
137 url: servers[0].url,
138 path: basePath + id,
139 accept: 'text/html',
140 expectedStatus: HttpStatusCode.OK_200
141 })
142
143 const expectedLink = `<link rel="alternate" type="application/json+oembed" href="${servers[0].url}/services/oembed?` +
144 `url=http%3A%2F%2F${servers[0].hostname}%3A${servers[0].port}%2Fw%2F${servers[0].store.video.shortUUID}" ` +
145 `title="${servers[0].store.video.name}" />`
146
147 expect(res.text).to.contain(expectedLink)
148 }
149 }
150 })
151
152 it('Should have valid oEmbed discovery tags for a playlist', async function () {
153 for (const basePath of watchPlaylistBasePaths) {
154 for (const id of playlistIds) {
155 const res = await makeGetRequest({
156 url: servers[0].url,
157 path: basePath + id,
158 accept: 'text/html',
159 expectedStatus: HttpStatusCode.OK_200
160 })
161
162 const expectedLink = `<link rel="alternate" type="application/json+oembed" href="${servers[0].url}/services/oembed?` +
163 `url=http%3A%2F%2F${servers[0].hostname}%3A${servers[0].port}%2Fw%2Fp%2F${playlist.shortUUID}" ` +
164 `title="${playlistName}" />`
165
166 expect(res.text).to.contain(expectedLink)
167 }
168 }
169 })
170 })
171
172 describe('Open Graph', function () {
173
174 async function accountPageTest (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="${account.displayName}" />`)
179 expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
180 expect(text).to.contain('<meta property="og:type" content="website" />')
181 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/a/${servers[0].store.user.username}" />`)
182 }
183
184 async function channelPageTest (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="${servers[0].store.channel.displayName}" />`)
189 expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
190 expect(text).to.contain('<meta property="og:type" content="website" />')
191 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/c/${servers[0].store.channel.name}" />`)
192 }
193
194 async function watchVideoPageTest (path: string) {
195 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
196 const text = res.text
197
198 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
199 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
200 expect(text).to.contain('<meta property="og:type" content="video" />')
201 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].store.video.shortUUID}" />`)
202 }
203
204 async function watchPlaylistPageTest (path: string) {
205 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
206 const text = res.text
207
208 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
209 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
210 expect(text).to.contain('<meta property="og:type" content="video" />')
211 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
212 }
213
214 it('Should have valid Open Graph tags on the account page', async function () {
215 await accountPageTest('/accounts/' + servers[0].store.user.username)
216 await accountPageTest('/a/' + servers[0].store.user.username)
217 await accountPageTest('/@' + servers[0].store.user.username)
218 })
219
220 it('Should have valid Open Graph tags on the channel page', async function () {
221 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
222 await channelPageTest('/c/' + servers[0].store.channel.name)
223 await channelPageTest('/@' + servers[0].store.channel.name)
224 })
225
226 it('Should have valid Open Graph tags on the watch page', async function () {
227 for (const path of watchVideoBasePaths) {
228 for (const id of videoIds) {
229 await watchVideoPageTest(path + id)
230 }
231 }
232 })
233
234 it('Should have valid Open Graph tags on the watch page with thread id Angular param', async function () {
235 for (const path of watchVideoBasePaths) {
236 for (const id of videoIds) {
237 await watchVideoPageTest(path + id + ';threadId=1')
238 }
239 }
240 })
241
242 it('Should have valid Open Graph tags on the watch playlist page', async function () {
243 for (const path of watchPlaylistBasePaths) {
244 for (const id of playlistIds) {
245 await watchPlaylistPageTest(path + id)
246 }
247 }
248 })
249 })
250
251 describe('Twitter card', async function () {
252
253 describe('Not whitelisted', function () {
254
255 async function accountPageTest (path: string) {
256 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
257 const text = res.text
258
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="${account.name}" />`)
262 expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
263 }
264
265 async function channelPageTest (path: string) {
266 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
267 const text = res.text
268
269 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
270 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
271 expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].store.channel.displayName}" />`)
272 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
273 }
274
275 async function watchVideoPageTest (path: string) {
276 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
277 const text = res.text
278
279 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
280 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
281 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
282 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
283 }
284
285 async function watchPlaylistPageTest (path: string) {
286 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
287 const text = res.text
288
289 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
290 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
291 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
292 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
293 }
294
295 it('Should have valid twitter card on the watch video page', async function () {
296 for (const path of watchVideoBasePaths) {
297 for (const id of videoIds) {
298 await watchVideoPageTest(path + id)
299 }
300 }
301 })
302
303 it('Should have valid twitter card on the watch playlist page', async function () {
304 for (const path of watchPlaylistBasePaths) {
305 for (const id of playlistIds) {
306 await watchPlaylistPageTest(path + id)
307 }
308 }
309 })
310
311 it('Should have valid twitter card on the account page', async function () {
312 await accountPageTest('/accounts/' + account.name)
313 await accountPageTest('/a/' + account.name)
314 await accountPageTest('/@' + account.name)
315 })
316
317 it('Should have valid twitter card on the channel page', async function () {
318 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
319 await channelPageTest('/c/' + servers[0].store.channel.name)
320 await channelPageTest('/@' + servers[0].store.channel.name)
321 })
322 })
323
324 describe('Whitelisted', function () {
325
326 before(async function () {
327 const config = await servers[0].config.getCustomConfig()
328 config.services.twitter = {
329 username: '@Kuja',
330 whitelisted: true
331 }
332
333 await servers[0].config.updateCustomConfig({ newCustomConfig: config })
334 })
335
336 async function accountPageTest (path: string) {
337 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
338 const text = res.text
339
340 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
341 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
342 }
343
344 async function channelPageTest (path: string) {
345 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
346 const text = res.text
347
348 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
349 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
350 }
351
352 async function watchVideoPageTest (path: string) {
353 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
354 const text = res.text
355
356 expect(text).to.contain('<meta property="twitter:card" content="player" />')
357 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
358 }
359
360 async function watchPlaylistPageTest (path: string) {
361 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
362 const text = res.text
363
364 expect(text).to.contain('<meta property="twitter:card" content="player" />')
365 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
366 }
367
368 it('Should have valid twitter card on the watch video page', async function () {
369 for (const path of watchVideoBasePaths) {
370 for (const id of videoIds) {
371 await watchVideoPageTest(path + id)
372 }
373 }
374 })
375
376 it('Should have valid twitter card on the watch playlist page', async function () {
377 for (const path of watchPlaylistBasePaths) {
378 for (const id of playlistIds) {
379 await watchPlaylistPageTest(path + id)
380 }
381 }
382 })
383
384 it('Should have valid twitter card on the account page', async function () {
385 await accountPageTest('/accounts/' + account.name)
386 await accountPageTest('/a/' + account.name)
387 await accountPageTest('/@' + account.name)
388 })
389
390 it('Should have valid twitter card on the channel page', async function () {
391 await channelPageTest('/video-channels/' + servers[0].store.channel.name)
392 await channelPageTest('/c/' + servers[0].store.channel.name)
393 await channelPageTest('/@' + servers[0].store.channel.name)
394 })
395 })
396 })
397
398 describe('Index HTML', function () {
399
400 it('Should have valid index html tags (title, description...)', async function () {
401 const config = await servers[0].config.getConfig()
402 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
403
404 const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
405 checkIndexTags(res.text, 'PeerTube', description, '', config)
406 })
407
408 it('Should update the customized configuration and have the correct index html tags', async function () {
409 await servers[0].config.updateCustomSubConfig({
410 newConfig: {
411 instance: {
412 name: 'PeerTube updated',
413 shortDescription: 'my short description',
414 description: 'my super description',
415 terms: 'my super terms',
416 defaultNSFWPolicy: 'blur',
417 defaultClientRoute: '/videos/recently-added',
418 customizations: {
419 javascript: 'alert("coucou")',
420 css: 'body { background-color: red; }'
421 }
422 }
423 }
424 })
425
426 const config = await servers[0].config.getConfig()
427 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
428
429 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
430 })
431
432 it('Should have valid index html updated tags (title, description...)', async function () {
433 const config = await servers[0].config.getConfig()
434 const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
435
436 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
437 })
438
439 it('Should use the original video URL for the canonical tag', async function () {
440 for (const basePath of watchVideoBasePaths) {
441 for (const id of videoIds) {
442 const res = await makeHTMLRequest(servers[1].url, basePath + id)
443 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].store.video.uuid}" />`)
444 }
445 }
446 })
447
448 it('Should use the original account URL for the canonical tag', async function () {
449 const accountURLtest = res => {
450 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`)
451 }
452
453 accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host))
454 accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host))
455 accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host))
456 })
457
458 it('Should use the original channel URL for the canonical tag', async function () {
459 const channelURLtests = res => {
460 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`)
461 }
462
463 channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host))
464 channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host))
465 channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host))
466 })
467
468 it('Should use the original playlist URL for the canonical tag', async function () {
469 for (const basePath of watchPlaylistBasePaths) {
470 for (const id of playlistIds) {
471 const res = await makeHTMLRequest(servers[1].url, basePath + id)
472 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
473 }
474 }
475 })
476
477 it('Should add noindex meta tag for remote accounts', async function () {
478 const handle = 'root@' + servers[0].host
479 const paths = [ '/accounts/', '/a/', '/@' ]
480
481 for (const path of paths) {
482 {
483 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
484 expect(text).to.contain('<meta name="robots" content="noindex" />')
485 }
486
487 {
488 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
489 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
490 }
491 }
492 })
493
494 it('Should add noindex meta tag for remote channels', async function () {
495 const handle = 'root_channel@' + servers[0].host
496 const paths = [ '/video-channels/', '/c/', '/@' ]
497
498 for (const path of paths) {
499 {
500 const { text } = await makeHTMLRequest(servers[1].url, path + handle)
501 expect(text).to.contain('<meta name="robots" content="noindex" />')
502 }
503
504 {
505 const { text } = await makeHTMLRequest(servers[0].url, path + handle)
506 expect(text).to.not.contain('<meta name="robots" content="noindex" />')
507 }
508 }
509 })
510
511 it('Should not display internal/private/password protected video', async function () {
512 for (const basePath of watchVideoBasePaths) {
513 for (const id of [ privateVideoId, internalVideoId, passwordProtectedVideoId ]) {
514 const res = await makeGetRequest({
515 url: servers[0].url,
516 path: basePath + id,
517 accept: 'text/html',
518 expectedStatus: HttpStatusCode.NOT_FOUND_404
519 })
520
521 expect(res.text).to.not.contain('internal')
522 expect(res.text).to.not.contain('private')
523 expect(res.text).to.not.contain('password protected')
524 }
525 }
526 })
527
528 it('Should add noindex meta tag for unlisted video', async function () {
529 for (const basePath of watchVideoBasePaths) {
530 const res = await makeGetRequest({
531 url: servers[0].url,
532 path: basePath + unlistedVideoId,
533 accept: 'text/html',
534 expectedStatus: HttpStatusCode.OK_200
535 })
536
537 expect(res.text).to.contain('unlisted')
538 expect(res.text).to.contain('<meta name="robots" content="noindex" />')
539 }
540 })
541 })
542
543 describe('Embed HTML', function () {
544
545 it('Should have the correct embed html tags', async function () {
546 const config = await servers[0].config.getConfig()
547 const res = await makeHTMLRequest(servers[0].url, servers[0].store.video.embedPath)
548
549 checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
550 })
551 })
552
553 after(async function () {
554 await cleanupTests(servers)
555 })
556})