aboutsummaryrefslogtreecommitdiffhomepage
path: root/packages/tests/src/api/check-params
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/api/check-params
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/api/check-params')
-rw-r--r--packages/tests/src/api/check-params/abuses.ts438
-rw-r--r--packages/tests/src/api/check-params/accounts.ts43
-rw-r--r--packages/tests/src/api/check-params/blocklist.ts556
-rw-r--r--packages/tests/src/api/check-params/bulk.ts86
-rw-r--r--packages/tests/src/api/check-params/channel-import-videos.ts209
-rw-r--r--packages/tests/src/api/check-params/config.ts428
-rw-r--r--packages/tests/src/api/check-params/contact-form.ts86
-rw-r--r--packages/tests/src/api/check-params/custom-pages.ts79
-rw-r--r--packages/tests/src/api/check-params/debug.ts67
-rw-r--r--packages/tests/src/api/check-params/follows.ts369
-rw-r--r--packages/tests/src/api/check-params/index.ts45
-rw-r--r--packages/tests/src/api/check-params/jobs.ts125
-rw-r--r--packages/tests/src/api/check-params/live.ts590
-rw-r--r--packages/tests/src/api/check-params/logs.ts163
-rw-r--r--packages/tests/src/api/check-params/metrics.ts214
-rw-r--r--packages/tests/src/api/check-params/my-user.ts492
-rw-r--r--packages/tests/src/api/check-params/plugins.ts490
-rw-r--r--packages/tests/src/api/check-params/redundancy.ts240
-rw-r--r--packages/tests/src/api/check-params/registrations.ts446
-rw-r--r--packages/tests/src/api/check-params/runners.ts911
-rw-r--r--packages/tests/src/api/check-params/search.ts278
-rw-r--r--packages/tests/src/api/check-params/services.ts207
-rw-r--r--packages/tests/src/api/check-params/transcoding.ts112
-rw-r--r--packages/tests/src/api/check-params/two-factor.ts294
-rw-r--r--packages/tests/src/api/check-params/upload-quota.ts134
-rw-r--r--packages/tests/src/api/check-params/user-notifications.ts290
-rw-r--r--packages/tests/src/api/check-params/user-subscriptions.ts298
-rw-r--r--packages/tests/src/api/check-params/users-admin.ts457
-rw-r--r--packages/tests/src/api/check-params/users-emails.ts122
-rw-r--r--packages/tests/src/api/check-params/video-blacklist.ts292
-rw-r--r--packages/tests/src/api/check-params/video-captions.ts307
-rw-r--r--packages/tests/src/api/check-params/video-channel-syncs.ts319
-rw-r--r--packages/tests/src/api/check-params/video-channels.ts379
-rw-r--r--packages/tests/src/api/check-params/video-comments.ts484
-rw-r--r--packages/tests/src/api/check-params/video-files.ts195
-rw-r--r--packages/tests/src/api/check-params/video-imports.ts433
-rw-r--r--packages/tests/src/api/check-params/video-passwords.ts604
-rw-r--r--packages/tests/src/api/check-params/video-playlists.ts695
-rw-r--r--packages/tests/src/api/check-params/video-source.ts154
-rw-r--r--packages/tests/src/api/check-params/video-storyboards.ts45
-rw-r--r--packages/tests/src/api/check-params/video-studio.ts392
-rw-r--r--packages/tests/src/api/check-params/video-token.ts70
-rw-r--r--packages/tests/src/api/check-params/videos-common-filters.ts171
-rw-r--r--packages/tests/src/api/check-params/videos-history.ts145
-rw-r--r--packages/tests/src/api/check-params/videos-overviews.ts31
-rw-r--r--packages/tests/src/api/check-params/videos.ts883
-rw-r--r--packages/tests/src/api/check-params/views.ts227
47 files changed, 14095 insertions, 0 deletions
diff --git a/packages/tests/src/api/check-params/abuses.ts b/packages/tests/src/api/check-params/abuses.ts
new file mode 100644
index 000000000..1effc82b1
--- /dev/null
+++ b/packages/tests/src/api/check-params/abuses.ts
@@ -0,0 +1,438 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { AbuseCreate, AbuseState, HttpStatusCode } from '@peertube/peertube-models'
5import {
6 AbusesCommand,
7 cleanupTests,
8 createSingleServer,
9 doubleFollow,
10 makeGetRequest,
11 makePostBodyRequest,
12 PeerTubeServer,
13 setAccessTokensToServers,
14 waitJobs
15} from '@peertube/peertube-server-commands'
16
17describe('Test abuses API validators', function () {
18 const basePath = '/api/v1/abuses/'
19
20 let server: PeerTubeServer
21
22 let userToken = ''
23 let userToken2 = ''
24 let abuseId: number
25 let messageId: number
26
27 let command: AbusesCommand
28
29 // ---------------------------------------------------------------
30
31 before(async function () {
32 this.timeout(30000)
33
34 server = await createSingleServer(1)
35
36 await setAccessTokensToServers([ server ])
37
38 userToken = await server.users.generateUserAndToken('user_1')
39 userToken2 = await server.users.generateUserAndToken('user_2')
40
41 server.store.videoCreated = await server.videos.upload()
42
43 command = server.abuses
44 })
45
46 describe('When listing abuses for admins', function () {
47 const path = basePath
48
49 it('Should fail with a bad start pagination', async function () {
50 await checkBadStartPagination(server.url, path, server.accessToken)
51 })
52
53 it('Should fail with a bad count pagination', async function () {
54 await checkBadCountPagination(server.url, path, server.accessToken)
55 })
56
57 it('Should fail with an incorrect sort', async function () {
58 await checkBadSortPagination(server.url, path, server.accessToken)
59 })
60
61 it('Should fail with a non authenticated user', async function () {
62 await makeGetRequest({
63 url: server.url,
64 path,
65 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
66 })
67 })
68
69 it('Should fail with a non admin user', async function () {
70 await makeGetRequest({
71 url: server.url,
72 path,
73 token: userToken,
74 expectedStatus: HttpStatusCode.FORBIDDEN_403
75 })
76 })
77
78 it('Should fail with a bad id filter', async function () {
79 await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { id: 'toto' } })
80 })
81
82 it('Should fail with a bad filter', async function () {
83 await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { filter: 'toto' } })
84 await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { filter: 'videos' } })
85 })
86
87 it('Should fail with bad predefined reason', async function () {
88 await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { predefinedReason: 'violentOrRepulsives' } })
89 })
90
91 it('Should fail with a bad state filter', async function () {
92 await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { state: 'toto' } })
93 await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { state: 0 } })
94 })
95
96 it('Should fail with a bad videoIs filter', async function () {
97 await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { videoIs: 'toto' } })
98 })
99
100 it('Should succeed with the correct params', async function () {
101 const query = {
102 id: 13,
103 predefinedReason: 'violentOrRepulsive',
104 filter: 'comment',
105 state: 2,
106 videoIs: 'deleted'
107 }
108
109 await makeGetRequest({ url: server.url, path, token: server.accessToken, query, expectedStatus: HttpStatusCode.OK_200 })
110 })
111 })
112
113 describe('When listing abuses for users', function () {
114 const path = '/api/v1/users/me/abuses'
115
116 it('Should fail with a bad start pagination', async function () {
117 await checkBadStartPagination(server.url, path, userToken)
118 })
119
120 it('Should fail with a bad count pagination', async function () {
121 await checkBadCountPagination(server.url, path, userToken)
122 })
123
124 it('Should fail with an incorrect sort', async function () {
125 await checkBadSortPagination(server.url, path, userToken)
126 })
127
128 it('Should fail with a non authenticated user', async function () {
129 await makeGetRequest({
130 url: server.url,
131 path,
132 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
133 })
134 })
135
136 it('Should fail with a bad id filter', async function () {
137 await makeGetRequest({ url: server.url, path, token: userToken, query: { id: 'toto' } })
138 })
139
140 it('Should fail with a bad state filter', async function () {
141 await makeGetRequest({ url: server.url, path, token: userToken, query: { state: 'toto' } })
142 await makeGetRequest({ url: server.url, path, token: userToken, query: { state: 0 } })
143 })
144
145 it('Should succeed with the correct params', async function () {
146 const query = {
147 id: 13,
148 state: 2
149 }
150
151 await makeGetRequest({ url: server.url, path, token: userToken, query, expectedStatus: HttpStatusCode.OK_200 })
152 })
153 })
154
155 describe('When reporting an abuse', function () {
156 const path = basePath
157
158 it('Should fail with nothing', async function () {
159 const fields = {}
160 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
161 })
162
163 it('Should fail with a wrong video', async function () {
164 const fields = { video: { id: 'blabla' }, reason: 'my super reason' }
165 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
166 })
167
168 it('Should fail with an unknown video', async function () {
169 const fields = { video: { id: 42 }, reason: 'my super reason' }
170 await makePostBodyRequest({
171 url: server.url,
172 path,
173 token: userToken,
174 fields,
175 expectedStatus: HttpStatusCode.NOT_FOUND_404
176 })
177 })
178
179 it('Should fail with a wrong comment', async function () {
180 const fields = { comment: { id: 'blabla' }, reason: 'my super reason' }
181 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
182 })
183
184 it('Should fail with an unknown comment', async function () {
185 const fields = { comment: { id: 42 }, reason: 'my super reason' }
186 await makePostBodyRequest({
187 url: server.url,
188 path,
189 token: userToken,
190 fields,
191 expectedStatus: HttpStatusCode.NOT_FOUND_404
192 })
193 })
194
195 it('Should fail with a wrong account', async function () {
196 const fields = { account: { id: 'blabla' }, reason: 'my super reason' }
197 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
198 })
199
200 it('Should fail with an unknown account', async function () {
201 const fields = { account: { id: 42 }, reason: 'my super reason' }
202 await makePostBodyRequest({
203 url: server.url,
204 path,
205 token: userToken,
206 fields,
207 expectedStatus: HttpStatusCode.NOT_FOUND_404
208 })
209 })
210
211 it('Should fail with not account, comment or video', async function () {
212 const fields = { reason: 'my super reason' }
213 await makePostBodyRequest({
214 url: server.url,
215 path,
216 token: userToken,
217 fields,
218 expectedStatus: HttpStatusCode.BAD_REQUEST_400
219 })
220 })
221
222 it('Should fail with a non authenticated user', async function () {
223 const fields = { video: { id: server.store.videoCreated.id }, reason: 'my super reason' }
224
225 await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
226 })
227
228 it('Should fail with a reason too short', async function () {
229 const fields = { video: { id: server.store.videoCreated.id }, reason: 'h' }
230
231 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
232 })
233
234 it('Should fail with a too big reason', async function () {
235 const fields = { video: { id: server.store.videoCreated.id }, reason: 'super'.repeat(605) }
236
237 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
238 })
239
240 it('Should succeed with the correct parameters (basic)', async function () {
241 const fields: AbuseCreate = { video: { id: server.store.videoCreated.shortUUID }, reason: 'my super reason' }
242
243 const res = await makePostBodyRequest({
244 url: server.url,
245 path,
246 token: userToken,
247 fields,
248 expectedStatus: HttpStatusCode.OK_200
249 })
250 abuseId = res.body.abuse.id
251 })
252
253 it('Should fail with a wrong predefined reason', async function () {
254 const fields = { video: server.store.videoCreated, reason: 'my super reason', predefinedReasons: [ 'wrongPredefinedReason' ] }
255
256 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
257 })
258
259 it('Should fail with negative timestamps', async function () {
260 const fields = { video: { id: server.store.videoCreated.id, startAt: -1 }, reason: 'my super reason' }
261
262 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
263 })
264
265 it('Should fail mith misordered startAt/endAt', async function () {
266 const fields = { video: { id: server.store.videoCreated.id, startAt: 5, endAt: 1 }, reason: 'my super reason' }
267
268 await makePostBodyRequest({ url: server.url, path, token: userToken, fields })
269 })
270
271 it('Should succeed with the correct parameters (advanced)', async function () {
272 const fields: AbuseCreate = {
273 video: {
274 id: server.store.videoCreated.id,
275 startAt: 1,
276 endAt: 5
277 },
278 reason: 'my super reason',
279 predefinedReasons: [ 'serverRules' ]
280 }
281
282 await makePostBodyRequest({ url: server.url, path, token: userToken, fields, expectedStatus: HttpStatusCode.OK_200 })
283 })
284 })
285
286 describe('When updating an abuse', function () {
287
288 it('Should fail with a non authenticated user', async function () {
289 await command.update({ token: 'blabla', abuseId, body: {}, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
290 })
291
292 it('Should fail with a non admin user', async function () {
293 await command.update({ token: userToken, abuseId, body: {}, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
294 })
295
296 it('Should fail with a bad abuse id', async function () {
297 await command.update({ abuseId: 45, body: {}, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
298 })
299
300 it('Should fail with a bad state', async function () {
301 const body = { state: 5 as any }
302 await command.update({ abuseId, body, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
303 })
304
305 it('Should fail with a bad moderation comment', async function () {
306 const body = { moderationComment: 'b'.repeat(3001) }
307 await command.update({ abuseId, body, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
308 })
309
310 it('Should succeed with the correct params', async function () {
311 const body = { state: AbuseState.ACCEPTED }
312 await command.update({ abuseId, body })
313 })
314 })
315
316 describe('When creating an abuse message', function () {
317 const message = 'my super message'
318
319 it('Should fail with an invalid abuse id', async function () {
320 await command.addMessage({ token: userToken2, abuseId: 888, message, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
321 })
322
323 it('Should fail with a non authenticated user', async function () {
324 await command.addMessage({ token: 'fake_token', abuseId, message, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
325 })
326
327 it('Should fail with an invalid logged in user', async function () {
328 await command.addMessage({ token: userToken2, abuseId, message, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
329 })
330
331 it('Should fail with an invalid message', async function () {
332 await command.addMessage({ token: userToken, abuseId, message: 'a'.repeat(5000), expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
333 })
334
335 it('Should succeed with the correct params', async function () {
336 const res = await command.addMessage({ token: userToken, abuseId, message })
337 messageId = res.body.abuseMessage.id
338 })
339 })
340
341 describe('When listing abuse messages', function () {
342
343 it('Should fail with an invalid abuse id', async function () {
344 await command.listMessages({ token: userToken, abuseId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
345 })
346
347 it('Should fail with a non authenticated user', async function () {
348 await command.listMessages({ token: 'fake_token', abuseId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
349 })
350
351 it('Should fail with an invalid logged in user', async function () {
352 await command.listMessages({ token: userToken2, abuseId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
353 })
354
355 it('Should succeed with the correct params', async function () {
356 await command.listMessages({ token: userToken, abuseId })
357 })
358 })
359
360 describe('When deleting an abuse message', function () {
361 it('Should fail with an invalid abuse id', async function () {
362 await command.deleteMessage({ token: userToken, abuseId: 888, messageId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
363 })
364
365 it('Should fail with an invalid message id', async function () {
366 await command.deleteMessage({ token: userToken, abuseId, messageId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
367 })
368
369 it('Should fail with a non authenticated user', async function () {
370 await command.deleteMessage({ token: 'fake_token', abuseId, messageId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
371 })
372
373 it('Should fail with an invalid logged in user', async function () {
374 await command.deleteMessage({ token: userToken2, abuseId, messageId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
375 })
376
377 it('Should succeed with the correct params', async function () {
378 await command.deleteMessage({ token: userToken, abuseId, messageId })
379 })
380 })
381
382 describe('When deleting a video abuse', function () {
383
384 it('Should fail with a non authenticated user', async function () {
385 await command.delete({ token: 'blabla', abuseId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
386 })
387
388 it('Should fail with a non admin user', async function () {
389 await command.delete({ token: userToken, abuseId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
390 })
391
392 it('Should fail with a bad abuse id', async function () {
393 await command.delete({ abuseId: 45, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
394 })
395
396 it('Should succeed with the correct params', async function () {
397 await command.delete({ abuseId })
398 })
399 })
400
401 describe('When trying to manage messages of a remote abuse', function () {
402 let remoteAbuseId: number
403 let anotherServer: PeerTubeServer
404
405 before(async function () {
406 this.timeout(50000)
407
408 anotherServer = await createSingleServer(2)
409 await setAccessTokensToServers([ anotherServer ])
410
411 await doubleFollow(anotherServer, server)
412
413 const server2VideoId = await anotherServer.videos.getId({ uuid: server.store.videoCreated.uuid })
414 await anotherServer.abuses.report({ reason: 'remote server', videoId: server2VideoId })
415
416 await waitJobs([ server, anotherServer ])
417
418 const body = await command.getAdminList({ sort: '-createdAt' })
419 remoteAbuseId = body.data[0].id
420 })
421
422 it('Should fail when listing abuse messages of a remote abuse', async function () {
423 await command.listMessages({ abuseId: remoteAbuseId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
424 })
425
426 it('Should fail when creating abuse message of a remote abuse', async function () {
427 await command.addMessage({ abuseId: remoteAbuseId, message: 'message', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
428 })
429
430 after(async function () {
431 await cleanupTests([ anotherServer ])
432 })
433 })
434
435 after(async function () {
436 await cleanupTests([ server ])
437 })
438})
diff --git a/packages/tests/src/api/check-params/accounts.ts b/packages/tests/src/api/check-params/accounts.ts
new file mode 100644
index 000000000..87810bbd3
--- /dev/null
+++ b/packages/tests/src/api/check-params/accounts.ts
@@ -0,0 +1,43 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { HttpStatusCode } from '@peertube/peertube-models'
5import { cleanupTests, createSingleServer, PeerTubeServer } from '@peertube/peertube-server-commands'
6
7describe('Test accounts API validators', function () {
8 const path = '/api/v1/accounts/'
9 let server: PeerTubeServer
10
11 // ---------------------------------------------------------------
12
13 before(async function () {
14 this.timeout(30000)
15
16 server = await createSingleServer(1)
17 })
18
19 describe('When listing accounts', function () {
20 it('Should fail with a bad start pagination', async function () {
21 await checkBadStartPagination(server.url, path, server.accessToken)
22 })
23
24 it('Should fail with a bad count pagination', async function () {
25 await checkBadCountPagination(server.url, path, server.accessToken)
26 })
27
28 it('Should fail with an incorrect sort', async function () {
29 await checkBadSortPagination(server.url, path, server.accessToken)
30 })
31 })
32
33 describe('When getting an account', function () {
34
35 it('Should return 404 with a non existing name', async function () {
36 await server.accounts.get({ accountName: 'arfaze', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
37 })
38 })
39
40 after(async function () {
41 await cleanupTests([ server ])
42 })
43})
diff --git a/packages/tests/src/api/check-params/blocklist.ts b/packages/tests/src/api/check-params/blocklist.ts
new file mode 100644
index 000000000..fcd6d08f8
--- /dev/null
+++ b/packages/tests/src/api/check-params/blocklist.ts
@@ -0,0 +1,556 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { HttpStatusCode } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 makeDeleteRequest,
10 makeGetRequest,
11 makePostBodyRequest,
12 PeerTubeServer,
13 setAccessTokensToServers
14} from '@peertube/peertube-server-commands'
15
16describe('Test blocklist API validators', function () {
17 let servers: PeerTubeServer[]
18 let server: PeerTubeServer
19 let userAccessToken: string
20
21 before(async function () {
22 this.timeout(60000)
23
24 servers = await createMultipleServers(2)
25 await setAccessTokensToServers(servers)
26
27 server = servers[0]
28
29 const user = { username: 'user1', password: 'password' }
30 await server.users.create({ username: user.username, password: user.password })
31
32 userAccessToken = await server.login.getAccessToken(user)
33
34 await doubleFollow(servers[0], servers[1])
35 })
36
37 // ---------------------------------------------------------------
38
39 describe('When managing user blocklist', function () {
40
41 describe('When managing user accounts blocklist', function () {
42 const path = '/api/v1/users/me/blocklist/accounts'
43
44 describe('When listing blocked accounts', function () {
45 it('Should fail with an unauthenticated user', async function () {
46 await makeGetRequest({
47 url: server.url,
48 path,
49 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
50 })
51 })
52
53 it('Should fail with a bad start pagination', async function () {
54 await checkBadStartPagination(server.url, path, server.accessToken)
55 })
56
57 it('Should fail with a bad count pagination', async function () {
58 await checkBadCountPagination(server.url, path, server.accessToken)
59 })
60
61 it('Should fail with an incorrect sort', async function () {
62 await checkBadSortPagination(server.url, path, server.accessToken)
63 })
64 })
65
66 describe('When blocking an account', function () {
67 it('Should fail with an unauthenticated user', async function () {
68 await makePostBodyRequest({
69 url: server.url,
70 path,
71 fields: { accountName: 'user1' },
72 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
73 })
74 })
75
76 it('Should fail with an unknown account', async function () {
77 await makePostBodyRequest({
78 url: server.url,
79 token: server.accessToken,
80 path,
81 fields: { accountName: 'user2' },
82 expectedStatus: HttpStatusCode.NOT_FOUND_404
83 })
84 })
85
86 it('Should fail to block ourselves', async function () {
87 await makePostBodyRequest({
88 url: server.url,
89 token: server.accessToken,
90 path,
91 fields: { accountName: 'root' },
92 expectedStatus: HttpStatusCode.CONFLICT_409
93 })
94 })
95
96 it('Should succeed with the correct params', async function () {
97 await makePostBodyRequest({
98 url: server.url,
99 token: server.accessToken,
100 path,
101 fields: { accountName: 'user1' },
102 expectedStatus: HttpStatusCode.NO_CONTENT_204
103 })
104 })
105 })
106
107 describe('When unblocking an account', function () {
108 it('Should fail with an unauthenticated user', async function () {
109 await makeDeleteRequest({
110 url: server.url,
111 path: path + '/user1',
112 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
113 })
114 })
115
116 it('Should fail with an unknown account block', async function () {
117 await makeDeleteRequest({
118 url: server.url,
119 path: path + '/user2',
120 token: server.accessToken,
121 expectedStatus: HttpStatusCode.NOT_FOUND_404
122 })
123 })
124
125 it('Should succeed with the correct params', async function () {
126 await makeDeleteRequest({
127 url: server.url,
128 path: path + '/user1',
129 token: server.accessToken,
130 expectedStatus: HttpStatusCode.NO_CONTENT_204
131 })
132 })
133 })
134 })
135
136 describe('When managing user servers blocklist', function () {
137 const path = '/api/v1/users/me/blocklist/servers'
138
139 describe('When listing blocked servers', function () {
140 it('Should fail with an unauthenticated user', async function () {
141 await makeGetRequest({
142 url: server.url,
143 path,
144 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
145 })
146 })
147
148 it('Should fail with a bad start pagination', async function () {
149 await checkBadStartPagination(server.url, path, server.accessToken)
150 })
151
152 it('Should fail with a bad count pagination', async function () {
153 await checkBadCountPagination(server.url, path, server.accessToken)
154 })
155
156 it('Should fail with an incorrect sort', async function () {
157 await checkBadSortPagination(server.url, path, server.accessToken)
158 })
159 })
160
161 describe('When blocking a server', function () {
162 it('Should fail with an unauthenticated user', async function () {
163 await makePostBodyRequest({
164 url: server.url,
165 path,
166 fields: { host: '127.0.0.1:9002' },
167 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
168 })
169 })
170
171 it('Should succeed with an unknown server', async function () {
172 await makePostBodyRequest({
173 url: server.url,
174 token: server.accessToken,
175 path,
176 fields: { host: '127.0.0.1:9003' },
177 expectedStatus: HttpStatusCode.NO_CONTENT_204
178 })
179 })
180
181 it('Should fail with our own server', async function () {
182 await makePostBodyRequest({
183 url: server.url,
184 token: server.accessToken,
185 path,
186 fields: { host: server.host },
187 expectedStatus: HttpStatusCode.CONFLICT_409
188 })
189 })
190
191 it('Should succeed with the correct params', async function () {
192 await makePostBodyRequest({
193 url: server.url,
194 token: server.accessToken,
195 path,
196 fields: { host: servers[1].host },
197 expectedStatus: HttpStatusCode.NO_CONTENT_204
198 })
199 })
200 })
201
202 describe('When unblocking a server', function () {
203 it('Should fail with an unauthenticated user', async function () {
204 await makeDeleteRequest({
205 url: server.url,
206 path: path + '/' + servers[1].host,
207 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
208 })
209 })
210
211 it('Should fail with an unknown server block', async function () {
212 await makeDeleteRequest({
213 url: server.url,
214 path: path + '/127.0.0.1:9004',
215 token: server.accessToken,
216 expectedStatus: HttpStatusCode.NOT_FOUND_404
217 })
218 })
219
220 it('Should succeed with the correct params', async function () {
221 await makeDeleteRequest({
222 url: server.url,
223 path: path + '/' + servers[1].host,
224 token: server.accessToken,
225 expectedStatus: HttpStatusCode.NO_CONTENT_204
226 })
227 })
228 })
229 })
230 })
231
232 describe('When managing server blocklist', function () {
233
234 describe('When managing server accounts blocklist', function () {
235 const path = '/api/v1/server/blocklist/accounts'
236
237 describe('When listing blocked accounts', function () {
238 it('Should fail with an unauthenticated user', async function () {
239 await makeGetRequest({
240 url: server.url,
241 path,
242 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
243 })
244 })
245
246 it('Should fail with a user without the appropriate rights', async function () {
247 await makeGetRequest({
248 url: server.url,
249 token: userAccessToken,
250 path,
251 expectedStatus: HttpStatusCode.FORBIDDEN_403
252 })
253 })
254
255 it('Should fail with a bad start pagination', async function () {
256 await checkBadStartPagination(server.url, path, server.accessToken)
257 })
258
259 it('Should fail with a bad count pagination', async function () {
260 await checkBadCountPagination(server.url, path, server.accessToken)
261 })
262
263 it('Should fail with an incorrect sort', async function () {
264 await checkBadSortPagination(server.url, path, server.accessToken)
265 })
266 })
267
268 describe('When blocking an account', function () {
269 it('Should fail with an unauthenticated user', async function () {
270 await makePostBodyRequest({
271 url: server.url,
272 path,
273 fields: { accountName: 'user1' },
274 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
275 })
276 })
277
278 it('Should fail with a user without the appropriate rights', async function () {
279 await makePostBodyRequest({
280 url: server.url,
281 token: userAccessToken,
282 path,
283 fields: { accountName: 'user1' },
284 expectedStatus: HttpStatusCode.FORBIDDEN_403
285 })
286 })
287
288 it('Should fail with an unknown account', async function () {
289 await makePostBodyRequest({
290 url: server.url,
291 token: server.accessToken,
292 path,
293 fields: { accountName: 'user2' },
294 expectedStatus: HttpStatusCode.NOT_FOUND_404
295 })
296 })
297
298 it('Should fail to block ourselves', async function () {
299 await makePostBodyRequest({
300 url: server.url,
301 token: server.accessToken,
302 path,
303 fields: { accountName: 'root' },
304 expectedStatus: HttpStatusCode.CONFLICT_409
305 })
306 })
307
308 it('Should succeed with the correct params', async function () {
309 await makePostBodyRequest({
310 url: server.url,
311 token: server.accessToken,
312 path,
313 fields: { accountName: 'user1' },
314 expectedStatus: HttpStatusCode.NO_CONTENT_204
315 })
316 })
317 })
318
319 describe('When unblocking an account', function () {
320 it('Should fail with an unauthenticated user', async function () {
321 await makeDeleteRequest({
322 url: server.url,
323 path: path + '/user1',
324 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
325 })
326 })
327
328 it('Should fail with a user without the appropriate rights', async function () {
329 await makeDeleteRequest({
330 url: server.url,
331 path: path + '/user1',
332 token: userAccessToken,
333 expectedStatus: HttpStatusCode.FORBIDDEN_403
334 })
335 })
336
337 it('Should fail with an unknown account block', async function () {
338 await makeDeleteRequest({
339 url: server.url,
340 path: path + '/user2',
341 token: server.accessToken,
342 expectedStatus: HttpStatusCode.NOT_FOUND_404
343 })
344 })
345
346 it('Should succeed with the correct params', async function () {
347 await makeDeleteRequest({
348 url: server.url,
349 path: path + '/user1',
350 token: server.accessToken,
351 expectedStatus: HttpStatusCode.NO_CONTENT_204
352 })
353 })
354 })
355 })
356
357 describe('When managing server servers blocklist', function () {
358 const path = '/api/v1/server/blocklist/servers'
359
360 describe('When listing blocked servers', function () {
361 it('Should fail with an unauthenticated user', async function () {
362 await makeGetRequest({
363 url: server.url,
364 path,
365 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
366 })
367 })
368
369 it('Should fail with a user without the appropriate rights', async function () {
370 await makeGetRequest({
371 url: server.url,
372 token: userAccessToken,
373 path,
374 expectedStatus: HttpStatusCode.FORBIDDEN_403
375 })
376 })
377
378 it('Should fail with a bad start pagination', async function () {
379 await checkBadStartPagination(server.url, path, server.accessToken)
380 })
381
382 it('Should fail with a bad count pagination', async function () {
383 await checkBadCountPagination(server.url, path, server.accessToken)
384 })
385
386 it('Should fail with an incorrect sort', async function () {
387 await checkBadSortPagination(server.url, path, server.accessToken)
388 })
389 })
390
391 describe('When blocking a server', function () {
392 it('Should fail with an unauthenticated user', async function () {
393 await makePostBodyRequest({
394 url: server.url,
395 path,
396 fields: { host: servers[1].host },
397 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
398 })
399 })
400
401 it('Should fail with a user without the appropriate rights', async function () {
402 await makePostBodyRequest({
403 url: server.url,
404 token: userAccessToken,
405 path,
406 fields: { host: servers[1].host },
407 expectedStatus: HttpStatusCode.FORBIDDEN_403
408 })
409 })
410
411 it('Should succeed with an unknown server', async function () {
412 await makePostBodyRequest({
413 url: server.url,
414 token: server.accessToken,
415 path,
416 fields: { host: '127.0.0.1:9003' },
417 expectedStatus: HttpStatusCode.NO_CONTENT_204
418 })
419 })
420
421 it('Should fail with our own server', async function () {
422 await makePostBodyRequest({
423 url: server.url,
424 token: server.accessToken,
425 path,
426 fields: { host: server.host },
427 expectedStatus: HttpStatusCode.CONFLICT_409
428 })
429 })
430
431 it('Should succeed with the correct params', async function () {
432 await makePostBodyRequest({
433 url: server.url,
434 token: server.accessToken,
435 path,
436 fields: { host: servers[1].host },
437 expectedStatus: HttpStatusCode.NO_CONTENT_204
438 })
439 })
440 })
441
442 describe('When unblocking a server', function () {
443 it('Should fail with an unauthenticated user', async function () {
444 await makeDeleteRequest({
445 url: server.url,
446 path: path + '/' + servers[1].host,
447 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
448 })
449 })
450
451 it('Should fail with a user without the appropriate rights', async function () {
452 await makeDeleteRequest({
453 url: server.url,
454 path: path + '/' + servers[1].host,
455 token: userAccessToken,
456 expectedStatus: HttpStatusCode.FORBIDDEN_403
457 })
458 })
459
460 it('Should fail with an unknown server block', async function () {
461 await makeDeleteRequest({
462 url: server.url,
463 path: path + '/127.0.0.1:9004',
464 token: server.accessToken,
465 expectedStatus: HttpStatusCode.NOT_FOUND_404
466 })
467 })
468
469 it('Should succeed with the correct params', async function () {
470 await makeDeleteRequest({
471 url: server.url,
472 path: path + '/' + servers[1].host,
473 token: server.accessToken,
474 expectedStatus: HttpStatusCode.NO_CONTENT_204
475 })
476 })
477 })
478 })
479 })
480
481 describe('When getting blocklist status', function () {
482 const path = '/api/v1/blocklist/status'
483
484 it('Should fail with a bad token', async function () {
485 await makeGetRequest({
486 url: server.url,
487 path,
488 token: 'false',
489 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
490 })
491 })
492
493 it('Should fail with a bad accounts field', async function () {
494 await makeGetRequest({
495 url: server.url,
496 path,
497 query: {
498 accounts: 1
499 },
500 expectedStatus: HttpStatusCode.BAD_REQUEST_400
501 })
502
503 await makeGetRequest({
504 url: server.url,
505 path,
506 query: {
507 accounts: [ 1 ]
508 },
509 expectedStatus: HttpStatusCode.BAD_REQUEST_400
510 })
511 })
512
513 it('Should fail with a bad hosts field', async function () {
514 await makeGetRequest({
515 url: server.url,
516 path,
517 query: {
518 hosts: 1
519 },
520 expectedStatus: HttpStatusCode.BAD_REQUEST_400
521 })
522
523 await makeGetRequest({
524 url: server.url,
525 path,
526 query: {
527 hosts: [ 1 ]
528 },
529 expectedStatus: HttpStatusCode.BAD_REQUEST_400
530 })
531 })
532
533 it('Should succeed with the correct parameters', async function () {
534 await makeGetRequest({
535 url: server.url,
536 path,
537 query: {},
538 expectedStatus: HttpStatusCode.OK_200
539 })
540
541 await makeGetRequest({
542 url: server.url,
543 path,
544 query: {
545 hosts: [ 'example.com' ],
546 accounts: [ 'john@example.com' ]
547 },
548 expectedStatus: HttpStatusCode.OK_200
549 })
550 })
551 })
552
553 after(async function () {
554 await cleanupTests(servers)
555 })
556})
diff --git a/packages/tests/src/api/check-params/bulk.ts b/packages/tests/src/api/check-params/bulk.ts
new file mode 100644
index 000000000..def0c38eb
--- /dev/null
+++ b/packages/tests/src/api/check-params/bulk.ts
@@ -0,0 +1,86 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode } from '@peertube/peertube-models'
4import {
5 cleanupTests,
6 createSingleServer,
7 makePostBodyRequest,
8 PeerTubeServer,
9 setAccessTokensToServers
10} from '@peertube/peertube-server-commands'
11
12describe('Test bulk API validators', function () {
13 let server: PeerTubeServer
14 let userAccessToken: string
15
16 // ---------------------------------------------------------------
17
18 before(async function () {
19 this.timeout(120000)
20
21 server = await createSingleServer(1)
22 await setAccessTokensToServers([ server ])
23
24 const user = { username: 'user1', password: 'password' }
25 await server.users.create({ username: user.username, password: user.password })
26
27 userAccessToken = await server.login.getAccessToken(user)
28 })
29
30 describe('When removing comments of', function () {
31 const path = '/api/v1/bulk/remove-comments-of'
32
33 it('Should fail with an unauthenticated user', async function () {
34 await makePostBodyRequest({
35 url: server.url,
36 path,
37 fields: { accountName: 'user1', scope: 'my-videos' },
38 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
39 })
40 })
41
42 it('Should fail with an unknown account', async function () {
43 await makePostBodyRequest({
44 url: server.url,
45 token: server.accessToken,
46 path,
47 fields: { accountName: 'user2', scope: 'my-videos' },
48 expectedStatus: HttpStatusCode.NOT_FOUND_404
49 })
50 })
51
52 it('Should fail with an invalid scope', async function () {
53 await makePostBodyRequest({
54 url: server.url,
55 token: server.accessToken,
56 path,
57 fields: { accountName: 'user1', scope: 'my-videoss' },
58 expectedStatus: HttpStatusCode.BAD_REQUEST_400
59 })
60 })
61
62 it('Should fail to delete comments of the instance without the appropriate rights', async function () {
63 await makePostBodyRequest({
64 url: server.url,
65 token: userAccessToken,
66 path,
67 fields: { accountName: 'user1', scope: 'instance' },
68 expectedStatus: HttpStatusCode.FORBIDDEN_403
69 })
70 })
71
72 it('Should succeed with the correct params', async function () {
73 await makePostBodyRequest({
74 url: server.url,
75 token: server.accessToken,
76 path,
77 fields: { accountName: 'user1', scope: 'instance' },
78 expectedStatus: HttpStatusCode.NO_CONTENT_204
79 })
80 })
81 })
82
83 after(async function () {
84 await cleanupTests([ server ])
85 })
86})
diff --git a/packages/tests/src/api/check-params/channel-import-videos.ts b/packages/tests/src/api/check-params/channel-import-videos.ts
new file mode 100644
index 000000000..0e897dad7
--- /dev/null
+++ b/packages/tests/src/api/check-params/channel-import-videos.ts
@@ -0,0 +1,209 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { FIXTURE_URLS } from '@tests/shared/tests.js'
4import { areHttpImportTestsDisabled } from '@peertube/peertube-node-utils'
5import { HttpStatusCode } from '@peertube/peertube-models'
6import {
7 ChannelsCommand,
8 cleanupTests,
9 createSingleServer,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 setDefaultVideoChannel
13} from '@peertube/peertube-server-commands'
14
15describe('Test videos import in a channel API validator', function () {
16 let server: PeerTubeServer
17 const userInfo = {
18 accessToken: '',
19 channelName: 'fake_channel',
20 channelId: -1,
21 id: -1,
22 videoQuota: -1,
23 videoQuotaDaily: -1,
24 channelSyncId: -1
25 }
26 let command: ChannelsCommand
27
28 // ---------------------------------------------------------------
29
30 before(async function () {
31 this.timeout(120000)
32
33 server = await createSingleServer(1)
34
35 await setAccessTokensToServers([ server ])
36 await setDefaultVideoChannel([ server ])
37
38 await server.config.enableImports()
39 await server.config.enableChannelSync()
40
41 const userCreds = {
42 username: 'fake',
43 password: 'fake_password'
44 }
45
46 {
47 const user = await server.users.create({ username: userCreds.username, password: userCreds.password })
48 userInfo.id = user.id
49 userInfo.accessToken = await server.login.getAccessToken(userCreds)
50
51 const info = await server.users.getMyInfo({ token: userInfo.accessToken })
52 userInfo.channelId = info.videoChannels[0].id
53 }
54
55 {
56 const { videoChannelSync } = await server.channelSyncs.create({
57 token: userInfo.accessToken,
58 attributes: {
59 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
60 videoChannelId: userInfo.channelId
61 }
62 })
63 userInfo.channelSyncId = videoChannelSync.id
64 }
65
66 command = server.channels
67 })
68
69 it('Should fail when HTTP upload is disabled', async function () {
70 await server.config.disableChannelSync()
71 await server.config.disableImports()
72
73 await command.importVideos({
74 channelName: server.store.channel.name,
75 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
76 token: server.accessToken,
77 expectedStatus: HttpStatusCode.FORBIDDEN_403
78 })
79
80 await server.config.enableImports()
81 })
82
83 it('Should fail when externalChannelUrl is not provided', async function () {
84 await command.importVideos({
85 channelName: server.store.channel.name,
86 externalChannelUrl: null,
87 token: server.accessToken,
88 expectedStatus: HttpStatusCode.BAD_REQUEST_400
89 })
90 })
91
92 it('Should fail when externalChannelUrl is malformed', async function () {
93 await command.importVideos({
94 channelName: server.store.channel.name,
95 externalChannelUrl: 'not-a-url',
96 token: server.accessToken,
97 expectedStatus: HttpStatusCode.BAD_REQUEST_400
98 })
99 })
100
101 it('Should fail with a bad sync id', async function () {
102 await command.importVideos({
103 channelName: server.store.channel.name,
104 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
105 videoChannelSyncId: 'toto' as any,
106 token: server.accessToken,
107 expectedStatus: HttpStatusCode.BAD_REQUEST_400
108 })
109 })
110
111 it('Should fail with a unknown sync id', async function () {
112 await command.importVideos({
113 channelName: server.store.channel.name,
114 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
115 videoChannelSyncId: 42,
116 token: server.accessToken,
117 expectedStatus: HttpStatusCode.NOT_FOUND_404
118 })
119 })
120
121 it('Should fail with a sync id of another channel', async function () {
122 await command.importVideos({
123 channelName: server.store.channel.name,
124 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
125 videoChannelSyncId: userInfo.channelSyncId,
126 token: server.accessToken,
127 expectedStatus: HttpStatusCode.FORBIDDEN_403
128 })
129 })
130
131 it('Should fail with no authentication', async function () {
132 await command.importVideos({
133 channelName: server.store.channel.name,
134 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
135 token: null,
136 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
137 })
138 })
139
140 it('Should fail when sync is not owned by the user', async function () {
141 await command.importVideos({
142 channelName: server.store.channel.name,
143 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
144 token: userInfo.accessToken,
145 expectedStatus: HttpStatusCode.FORBIDDEN_403
146 })
147 })
148
149 it('Should fail when the user has no quota', async function () {
150 await server.users.update({
151 userId: userInfo.id,
152 videoQuota: 0
153 })
154
155 await command.importVideos({
156 channelName: 'fake_channel',
157 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
158 token: userInfo.accessToken,
159 expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
160 })
161
162 await server.users.update({
163 userId: userInfo.id,
164 videoQuota: userInfo.videoQuota
165 })
166 })
167
168 it('Should fail when the user has no daily quota', async function () {
169 await server.users.update({
170 userId: userInfo.id,
171 videoQuotaDaily: 0
172 })
173
174 await command.importVideos({
175 channelName: 'fake_channel',
176 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
177 token: userInfo.accessToken,
178 expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
179 })
180
181 await server.users.update({
182 userId: userInfo.id,
183 videoQuotaDaily: userInfo.videoQuotaDaily
184 })
185 })
186
187 it('Should succeed when sync is run by its owner', async function () {
188 if (!areHttpImportTestsDisabled()) return
189
190 await command.importVideos({
191 channelName: 'fake_channel',
192 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
193 token: userInfo.accessToken
194 })
195 })
196
197 it('Should succeed when sync is run with root and for another user\'s channel', async function () {
198 if (!areHttpImportTestsDisabled()) return
199
200 await command.importVideos({
201 channelName: 'fake_channel',
202 externalChannelUrl: FIXTURE_URLS.youtubeChannel
203 })
204 })
205
206 after(async function () {
207 await cleanupTests([ server ])
208 })
209})
diff --git a/packages/tests/src/api/check-params/config.ts b/packages/tests/src/api/check-params/config.ts
new file mode 100644
index 000000000..8179a8815
--- /dev/null
+++ b/packages/tests/src/api/check-params/config.ts
@@ -0,0 +1,428 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2import merge from 'lodash-es/merge.js'
3import { omit } from '@peertube/peertube-core-utils'
4import { CustomConfig, HttpStatusCode } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createSingleServer,
8 makeDeleteRequest,
9 makeGetRequest,
10 makePutBodyRequest,
11 PeerTubeServer,
12 setAccessTokensToServers
13} from '@peertube/peertube-server-commands'
14
15describe('Test config API validators', function () {
16 const path = '/api/v1/config/custom'
17 let server: PeerTubeServer
18 let userAccessToken: string
19 const updateParams: CustomConfig = {
20 instance: {
21 name: 'PeerTube updated',
22 shortDescription: 'my short description',
23 description: 'my super description',
24 terms: 'my super terms',
25 codeOfConduct: 'my super coc',
26
27 creationReason: 'my super reason',
28 moderationInformation: 'my super moderation information',
29 administrator: 'Kuja',
30 maintenanceLifetime: 'forever',
31 businessModel: 'my super business model',
32 hardwareInformation: '2vCore 3GB RAM',
33
34 languages: [ 'en', 'es' ],
35 categories: [ 1, 2 ],
36
37 isNSFW: true,
38 defaultNSFWPolicy: 'blur',
39
40 defaultClientRoute: '/videos/recently-added',
41
42 customizations: {
43 javascript: 'alert("coucou")',
44 css: 'body { background-color: red; }'
45 }
46 },
47 theme: {
48 default: 'default'
49 },
50 services: {
51 twitter: {
52 username: '@MySuperUsername',
53 whitelisted: true
54 }
55 },
56 client: {
57 videos: {
58 miniature: {
59 preferAuthorDisplayName: false
60 }
61 },
62 menu: {
63 login: {
64 redirectOnSingleExternalAuth: false
65 }
66 }
67 },
68 cache: {
69 previews: {
70 size: 2
71 },
72 captions: {
73 size: 3
74 },
75 torrents: {
76 size: 4
77 },
78 storyboards: {
79 size: 5
80 }
81 },
82 signup: {
83 enabled: false,
84 limit: 5,
85 requiresApproval: false,
86 requiresEmailVerification: false,
87 minimumAge: 16
88 },
89 admin: {
90 email: 'superadmin1@example.com'
91 },
92 contactForm: {
93 enabled: false
94 },
95 user: {
96 history: {
97 videos: {
98 enabled: true
99 }
100 },
101 videoQuota: 5242881,
102 videoQuotaDaily: 318742
103 },
104 videoChannels: {
105 maxPerUser: 20
106 },
107 transcoding: {
108 enabled: true,
109 remoteRunners: {
110 enabled: true
111 },
112 allowAdditionalExtensions: true,
113 allowAudioFiles: true,
114 concurrency: 1,
115 threads: 1,
116 profile: 'vod_profile',
117 resolutions: {
118 '0p': false,
119 '144p': false,
120 '240p': false,
121 '360p': true,
122 '480p': true,
123 '720p': false,
124 '1080p': false,
125 '1440p': false,
126 '2160p': false
127 },
128 alwaysTranscodeOriginalResolution: false,
129 webVideos: {
130 enabled: true
131 },
132 hls: {
133 enabled: false
134 }
135 },
136 live: {
137 enabled: true,
138
139 allowReplay: false,
140 latencySetting: {
141 enabled: false
142 },
143 maxDuration: 30,
144 maxInstanceLives: -1,
145 maxUserLives: 50,
146
147 transcoding: {
148 enabled: true,
149 remoteRunners: {
150 enabled: true
151 },
152 threads: 4,
153 profile: 'live_profile',
154 resolutions: {
155 '144p': true,
156 '240p': true,
157 '360p': true,
158 '480p': true,
159 '720p': true,
160 '1080p': true,
161 '1440p': true,
162 '2160p': true
163 },
164 alwaysTranscodeOriginalResolution: false
165 }
166 },
167 videoStudio: {
168 enabled: true,
169 remoteRunners: {
170 enabled: true
171 }
172 },
173 videoFile: {
174 update: {
175 enabled: true
176 }
177 },
178 import: {
179 videos: {
180 concurrency: 1,
181 http: {
182 enabled: false
183 },
184 torrent: {
185 enabled: false
186 }
187 },
188 videoChannelSynchronization: {
189 enabled: false,
190 maxPerUser: 10
191 }
192 },
193 trending: {
194 videos: {
195 algorithms: {
196 enabled: [ 'hot', 'most-viewed', 'most-liked' ],
197 default: 'most-viewed'
198 }
199 }
200 },
201 autoBlacklist: {
202 videos: {
203 ofUsers: {
204 enabled: false
205 }
206 }
207 },
208 followers: {
209 instance: {
210 enabled: false,
211 manualApproval: true
212 }
213 },
214 followings: {
215 instance: {
216 autoFollowBack: {
217 enabled: true
218 },
219 autoFollowIndex: {
220 enabled: true,
221 indexUrl: 'https://index.example.com'
222 }
223 }
224 },
225 broadcastMessage: {
226 enabled: true,
227 dismissable: true,
228 message: 'super message',
229 level: 'warning'
230 },
231 search: {
232 remoteUri: {
233 users: true,
234 anonymous: true
235 },
236 searchIndex: {
237 enabled: true,
238 url: 'https://search.joinpeertube.org',
239 disableLocalSearch: true,
240 isDefaultSearch: true
241 }
242 }
243 }
244
245 // ---------------------------------------------------------------
246
247 before(async function () {
248 this.timeout(30000)
249
250 server = await createSingleServer(1)
251
252 await setAccessTokensToServers([ server ])
253
254 const user = {
255 username: 'user1',
256 password: 'password'
257 }
258 await server.users.create({ username: user.username, password: user.password })
259 userAccessToken = await server.login.getAccessToken(user)
260 })
261
262 describe('When getting the configuration', function () {
263 it('Should fail without token', async function () {
264 await makeGetRequest({
265 url: server.url,
266 path,
267 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
268 })
269 })
270
271 it('Should fail if the user is not an administrator', async function () {
272 await makeGetRequest({
273 url: server.url,
274 path,
275 token: userAccessToken,
276 expectedStatus: HttpStatusCode.FORBIDDEN_403
277 })
278 })
279 })
280
281 describe('When updating the configuration', function () {
282 it('Should fail without token', async function () {
283 await makePutBodyRequest({
284 url: server.url,
285 path,
286 fields: updateParams,
287 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
288 })
289 })
290
291 it('Should fail if the user is not an administrator', async function () {
292 await makePutBodyRequest({
293 url: server.url,
294 path,
295 fields: updateParams,
296 token: userAccessToken,
297 expectedStatus: HttpStatusCode.FORBIDDEN_403
298 })
299 })
300
301 it('Should fail if it misses a key', async function () {
302 const newUpdateParams = { ...updateParams, admin: omit(updateParams.admin, [ 'email' ]) }
303
304 await makePutBodyRequest({
305 url: server.url,
306 path,
307 fields: newUpdateParams,
308 token: server.accessToken,
309 expectedStatus: HttpStatusCode.BAD_REQUEST_400
310 })
311 })
312
313 it('Should fail with a bad default NSFW policy', async function () {
314 const newUpdateParams = {
315 ...updateParams,
316
317 instance: {
318 defaultNSFWPolicy: 'hello'
319 }
320 }
321
322 await makePutBodyRequest({
323 url: server.url,
324 path,
325 fields: newUpdateParams,
326 token: server.accessToken,
327 expectedStatus: HttpStatusCode.BAD_REQUEST_400
328 })
329 })
330
331 it('Should fail if email disabled and signup requires email verification', async function () {
332 // opposite scenario - success when enable enabled - covered via tests/api/users/user-verification.ts
333 const newUpdateParams = {
334 ...updateParams,
335
336 signup: {
337 enabled: true,
338 limit: 5,
339 requiresApproval: true,
340 requiresEmailVerification: true
341 }
342 }
343
344 await makePutBodyRequest({
345 url: server.url,
346 path,
347 fields: newUpdateParams,
348 token: server.accessToken,
349 expectedStatus: HttpStatusCode.BAD_REQUEST_400
350 })
351 })
352
353 it('Should fail with a disabled web videos & hls transcoding', async function () {
354 const newUpdateParams = {
355 ...updateParams,
356
357 transcoding: {
358 hls: {
359 enabled: false
360 },
361 web_videos: {
362 enabled: false
363 }
364 }
365 }
366
367 await makePutBodyRequest({
368 url: server.url,
369 path,
370 fields: newUpdateParams,
371 token: server.accessToken,
372 expectedStatus: HttpStatusCode.BAD_REQUEST_400
373 })
374 })
375
376 it('Should fail with a disabled http upload & enabled sync', async function () {
377 const newUpdateParams: CustomConfig = merge({}, updateParams, {
378 import: {
379 videos: {
380 http: { enabled: false }
381 },
382 videoChannelSynchronization: { enabled: true }
383 }
384 })
385
386 await makePutBodyRequest({
387 url: server.url,
388 path,
389 fields: newUpdateParams,
390 token: server.accessToken,
391 expectedStatus: HttpStatusCode.BAD_REQUEST_400
392 })
393 })
394
395 it('Should succeed with the correct parameters', async function () {
396 await makePutBodyRequest({
397 url: server.url,
398 path,
399 fields: updateParams,
400 token: server.accessToken,
401 expectedStatus: HttpStatusCode.OK_200
402 })
403 })
404 })
405
406 describe('When deleting the configuration', function () {
407 it('Should fail without token', async function () {
408 await makeDeleteRequest({
409 url: server.url,
410 path,
411 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
412 })
413 })
414
415 it('Should fail if the user is not an administrator', async function () {
416 await makeDeleteRequest({
417 url: server.url,
418 path,
419 token: userAccessToken,
420 expectedStatus: HttpStatusCode.FORBIDDEN_403
421 })
422 })
423 })
424
425 after(async function () {
426 await cleanupTests([ server ])
427 })
428})
diff --git a/packages/tests/src/api/check-params/contact-form.ts b/packages/tests/src/api/check-params/contact-form.ts
new file mode 100644
index 000000000..009cb2ad9
--- /dev/null
+++ b/packages/tests/src/api/check-params/contact-form.ts
@@ -0,0 +1,86 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
4import { HttpStatusCode } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 ConfigCommand,
8 ContactFormCommand,
9 createSingleServer,
10 killallServers,
11 PeerTubeServer
12} from '@peertube/peertube-server-commands'
13
14describe('Test contact form API validators', function () {
15 let server: PeerTubeServer
16 const emails: object[] = []
17 const defaultBody = {
18 fromName: 'super name',
19 fromEmail: 'toto@example.com',
20 subject: 'my subject',
21 body: 'Hello, how are you?'
22 }
23 let emailPort: number
24 let command: ContactFormCommand
25
26 // ---------------------------------------------------------------
27
28 before(async function () {
29 this.timeout(60000)
30
31 emailPort = await MockSmtpServer.Instance.collectEmails(emails)
32
33 // Email is disabled
34 server = await createSingleServer(1)
35 command = server.contactForm
36 })
37
38 it('Should not accept a contact form if emails are disabled', async function () {
39 await command.send({ ...defaultBody, expectedStatus: HttpStatusCode.CONFLICT_409 })
40 })
41
42 it('Should not accept a contact form if it is disabled in the configuration', async function () {
43 this.timeout(25000)
44
45 await killallServers([ server ])
46
47 // Contact form is disabled
48 await server.run({ ...ConfigCommand.getEmailOverrideConfig(emailPort), contact_form: { enabled: false } })
49 await command.send({ ...defaultBody, expectedStatus: HttpStatusCode.CONFLICT_409 })
50 })
51
52 it('Should not accept a contact form if from email is invalid', async function () {
53 this.timeout(25000)
54
55 await killallServers([ server ])
56
57 // Email & contact form enabled
58 await server.run(ConfigCommand.getEmailOverrideConfig(emailPort))
59
60 await command.send({ ...defaultBody, fromEmail: 'badEmail', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
61 await command.send({ ...defaultBody, fromEmail: 'badEmail@', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
62 await command.send({ ...defaultBody, fromEmail: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
63 })
64
65 it('Should not accept a contact form if from name is invalid', async function () {
66 await command.send({ ...defaultBody, fromName: 'name'.repeat(100), expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
67 await command.send({ ...defaultBody, fromName: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
68 await command.send({ ...defaultBody, fromName: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
69 })
70
71 it('Should not accept a contact form if body is invalid', async function () {
72 await command.send({ ...defaultBody, body: 'body'.repeat(5000), expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
73 await command.send({ ...defaultBody, body: 'a', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
74 await command.send({ ...defaultBody, body: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
75 })
76
77 it('Should accept a contact form with the correct parameters', async function () {
78 await command.send(defaultBody)
79 })
80
81 after(async function () {
82 MockSmtpServer.Instance.kill()
83
84 await cleanupTests([ server ])
85 })
86})
diff --git a/packages/tests/src/api/check-params/custom-pages.ts b/packages/tests/src/api/check-params/custom-pages.ts
new file mode 100644
index 000000000..180a5e406
--- /dev/null
+++ b/packages/tests/src/api/check-params/custom-pages.ts
@@ -0,0 +1,79 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode } from '@peertube/peertube-models'
4import {
5 cleanupTests,
6 createSingleServer,
7 makeGetRequest,
8 makePutBodyRequest,
9 PeerTubeServer,
10 setAccessTokensToServers
11} from '@peertube/peertube-server-commands'
12
13describe('Test custom pages validators', function () {
14 const path = '/api/v1/custom-pages/homepage/instance'
15
16 let server: PeerTubeServer
17 let userAccessToken: string
18
19 // ---------------------------------------------------------------
20
21 before(async function () {
22 this.timeout(120000)
23
24 server = await createSingleServer(1)
25 await setAccessTokensToServers([ server ])
26
27 const user = { username: 'user1', password: 'password' }
28 await server.users.create({ username: user.username, password: user.password })
29
30 userAccessToken = await server.login.getAccessToken(user)
31 })
32
33 describe('When updating instance homepage', function () {
34
35 it('Should fail with an unauthenticated user', async function () {
36 await makePutBodyRequest({
37 url: server.url,
38 path,
39 fields: { content: 'super content' },
40 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
41 })
42 })
43
44 it('Should fail with a non admin user', async function () {
45 await makePutBodyRequest({
46 url: server.url,
47 path,
48 token: userAccessToken,
49 fields: { content: 'super content' },
50 expectedStatus: HttpStatusCode.FORBIDDEN_403
51 })
52 })
53
54 it('Should succeed with the correct params', async function () {
55 await makePutBodyRequest({
56 url: server.url,
57 path,
58 token: server.accessToken,
59 fields: { content: 'super content' },
60 expectedStatus: HttpStatusCode.NO_CONTENT_204
61 })
62 })
63 })
64
65 describe('When getting instance homapage', function () {
66
67 it('Should succeed with the correct params', async function () {
68 await makeGetRequest({
69 url: server.url,
70 path,
71 expectedStatus: HttpStatusCode.OK_200
72 })
73 })
74 })
75
76 after(async function () {
77 await cleanupTests([ server ])
78 })
79})
diff --git a/packages/tests/src/api/check-params/debug.ts b/packages/tests/src/api/check-params/debug.ts
new file mode 100644
index 000000000..4a7c18a62
--- /dev/null
+++ b/packages/tests/src/api/check-params/debug.ts
@@ -0,0 +1,67 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode } from '@peertube/peertube-models'
4import {
5 cleanupTests,
6 createSingleServer,
7 makeGetRequest,
8 PeerTubeServer,
9 setAccessTokensToServers
10} from '@peertube/peertube-server-commands'
11
12describe('Test debug API validators', function () {
13 const path = '/api/v1/server/debug'
14 let server: PeerTubeServer
15 let userAccessToken = ''
16
17 // ---------------------------------------------------------------
18
19 before(async function () {
20 this.timeout(120000)
21
22 server = await createSingleServer(1)
23
24 await setAccessTokensToServers([ server ])
25
26 const user = {
27 username: 'user1',
28 password: 'my super password'
29 }
30 await server.users.create({ username: user.username, password: user.password })
31 userAccessToken = await server.login.getAccessToken(user)
32 })
33
34 describe('When getting debug endpoint', function () {
35
36 it('Should fail with a non authenticated user', async function () {
37 await makeGetRequest({
38 url: server.url,
39 path,
40 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
41 })
42 })
43
44 it('Should fail with a non admin user', async function () {
45 await makeGetRequest({
46 url: server.url,
47 path,
48 token: userAccessToken,
49 expectedStatus: HttpStatusCode.FORBIDDEN_403
50 })
51 })
52
53 it('Should succeed with the correct params', async function () {
54 await makeGetRequest({
55 url: server.url,
56 path,
57 token: server.accessToken,
58 query: { startDate: new Date().toISOString() },
59 expectedStatus: HttpStatusCode.OK_200
60 })
61 })
62 })
63
64 after(async function () {
65 await cleanupTests([ server ])
66 })
67})
diff --git a/packages/tests/src/api/check-params/follows.ts b/packages/tests/src/api/check-params/follows.ts
new file mode 100644
index 000000000..e92a3acd6
--- /dev/null
+++ b/packages/tests/src/api/check-params/follows.ts
@@ -0,0 +1,369 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { HttpStatusCode } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createSingleServer,
8 makeDeleteRequest,
9 makeGetRequest,
10 makePostBodyRequest,
11 PeerTubeServer,
12 setAccessTokensToServers
13} from '@peertube/peertube-server-commands'
14
15describe('Test server follows API validators', function () {
16 let server: PeerTubeServer
17
18 // ---------------------------------------------------------------
19
20 before(async function () {
21 this.timeout(30000)
22
23 server = await createSingleServer(1)
24
25 await setAccessTokensToServers([ server ])
26 })
27
28 describe('When managing following', function () {
29 let userAccessToken = null
30
31 before(async function () {
32 userAccessToken = await server.users.generateUserAndToken('user1')
33 })
34
35 describe('When adding follows', function () {
36 const path = '/api/v1/server/following'
37
38 it('Should fail with nothing', async function () {
39 await makePostBodyRequest({
40 url: server.url,
41 path,
42 token: server.accessToken,
43 expectedStatus: HttpStatusCode.BAD_REQUEST_400
44 })
45 })
46
47 it('Should fail if hosts is not composed by hosts', async function () {
48 await makePostBodyRequest({
49 url: server.url,
50 path,
51 fields: { hosts: [ '127.0.0.1:9002', '127.0.0.1:coucou' ] },
52 token: server.accessToken,
53 expectedStatus: HttpStatusCode.BAD_REQUEST_400
54 })
55 })
56
57 it('Should fail if hosts is composed with http schemes', async function () {
58 await makePostBodyRequest({
59 url: server.url,
60 path,
61 fields: { hosts: [ '127.0.0.1:9002', 'http://127.0.0.1:9003' ] },
62 token: server.accessToken,
63 expectedStatus: HttpStatusCode.BAD_REQUEST_400
64 })
65 })
66
67 it('Should fail if hosts are not unique', async function () {
68 await makePostBodyRequest({
69 url: server.url,
70 path,
71 fields: { urls: [ '127.0.0.1:9002', '127.0.0.1:9002' ] },
72 token: server.accessToken,
73 expectedStatus: HttpStatusCode.BAD_REQUEST_400
74 })
75 })
76
77 it('Should fail if handles is not composed by handles', async function () {
78 await makePostBodyRequest({
79 url: server.url,
80 path,
81 fields: { handles: [ 'hello@example.com', '127.0.0.1:9001' ] },
82 token: server.accessToken,
83 expectedStatus: HttpStatusCode.BAD_REQUEST_400
84 })
85 })
86
87 it('Should fail if handles are not unique', async function () {
88 await makePostBodyRequest({
89 url: server.url,
90 path,
91 fields: { urls: [ 'hello@example.com', 'hello@example.com' ] },
92 token: server.accessToken,
93 expectedStatus: HttpStatusCode.BAD_REQUEST_400
94 })
95 })
96
97 it('Should fail with an invalid token', async function () {
98 await makePostBodyRequest({
99 url: server.url,
100 path,
101 fields: { hosts: [ '127.0.0.1:9002' ] },
102 token: 'fake_token',
103 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
104 })
105 })
106
107 it('Should fail if the user is not an administrator', async function () {
108 await makePostBodyRequest({
109 url: server.url,
110 path,
111 fields: { hosts: [ '127.0.0.1:9002' ] },
112 token: userAccessToken,
113 expectedStatus: HttpStatusCode.FORBIDDEN_403
114 })
115 })
116 })
117
118 describe('When listing followings', function () {
119 const path = '/api/v1/server/following'
120
121 it('Should fail with a bad start pagination', async function () {
122 await checkBadStartPagination(server.url, path)
123 })
124
125 it('Should fail with a bad count pagination', async function () {
126 await checkBadCountPagination(server.url, path)
127 })
128
129 it('Should fail with an incorrect sort', async function () {
130 await checkBadSortPagination(server.url, path)
131 })
132
133 it('Should fail with an incorrect state', async function () {
134 await makeGetRequest({
135 url: server.url,
136 path,
137 query: {
138 state: 'blabla'
139 }
140 })
141 })
142
143 it('Should fail with an incorrect actor type', async function () {
144 await makeGetRequest({
145 url: server.url,
146 path,
147 query: {
148 actorType: 'blabla'
149 }
150 })
151 })
152
153 it('Should fail succeed with the correct params', async function () {
154 await makeGetRequest({
155 url: server.url,
156 path,
157 expectedStatus: HttpStatusCode.OK_200,
158 query: {
159 state: 'accepted',
160 actorType: 'Application'
161 }
162 })
163 })
164 })
165
166 describe('When listing followers', function () {
167 const path = '/api/v1/server/followers'
168
169 it('Should fail with a bad start pagination', async function () {
170 await checkBadStartPagination(server.url, path)
171 })
172
173 it('Should fail with a bad count pagination', async function () {
174 await checkBadCountPagination(server.url, path)
175 })
176
177 it('Should fail with an incorrect sort', async function () {
178 await checkBadSortPagination(server.url, path)
179 })
180
181 it('Should fail with an incorrect actor type', async function () {
182 await makeGetRequest({
183 url: server.url,
184 path,
185 query: {
186 actorType: 'blabla'
187 }
188 })
189 })
190
191 it('Should fail with an incorrect state', async function () {
192 await makeGetRequest({
193 url: server.url,
194 path,
195 query: {
196 state: 'blabla',
197 actorType: 'Application'
198 }
199 })
200 })
201
202 it('Should fail succeed with the correct params', async function () {
203 await makeGetRequest({
204 url: server.url,
205 path,
206 expectedStatus: HttpStatusCode.OK_200,
207 query: {
208 state: 'accepted'
209 }
210 })
211 })
212 })
213
214 describe('When removing a follower', function () {
215 const path = '/api/v1/server/followers'
216
217 it('Should fail with an invalid token', async function () {
218 await makeDeleteRequest({
219 url: server.url,
220 path: path + '/toto@127.0.0.1:9002',
221 token: 'fake_token',
222 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
223 })
224 })
225
226 it('Should fail if the user is not an administrator', async function () {
227 await makeDeleteRequest({
228 url: server.url,
229 path: path + '/toto@127.0.0.1:9002',
230 token: userAccessToken,
231 expectedStatus: HttpStatusCode.FORBIDDEN_403
232 })
233 })
234
235 it('Should fail with an invalid follower', async function () {
236 await makeDeleteRequest({
237 url: server.url,
238 path: path + '/toto',
239 token: server.accessToken,
240 expectedStatus: HttpStatusCode.BAD_REQUEST_400
241 })
242 })
243
244 it('Should fail with an unknown follower', async function () {
245 await makeDeleteRequest({
246 url: server.url,
247 path: path + '/toto@127.0.0.1:9003',
248 token: server.accessToken,
249 expectedStatus: HttpStatusCode.NOT_FOUND_404
250 })
251 })
252 })
253
254 describe('When accepting a follower', function () {
255 const path = '/api/v1/server/followers'
256
257 it('Should fail with an invalid token', async function () {
258 await makePostBodyRequest({
259 url: server.url,
260 path: path + '/toto@127.0.0.1:9002/accept',
261 token: 'fake_token',
262 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
263 })
264 })
265
266 it('Should fail if the user is not an administrator', async function () {
267 await makePostBodyRequest({
268 url: server.url,
269 path: path + '/toto@127.0.0.1:9002/accept',
270 token: userAccessToken,
271 expectedStatus: HttpStatusCode.FORBIDDEN_403
272 })
273 })
274
275 it('Should fail with an invalid follower', async function () {
276 await makePostBodyRequest({
277 url: server.url,
278 path: path + '/toto/accept',
279 token: server.accessToken,
280 expectedStatus: HttpStatusCode.BAD_REQUEST_400
281 })
282 })
283
284 it('Should fail with an unknown follower', async function () {
285 await makePostBodyRequest({
286 url: server.url,
287 path: path + '/toto@127.0.0.1:9003/accept',
288 token: server.accessToken,
289 expectedStatus: HttpStatusCode.NOT_FOUND_404
290 })
291 })
292 })
293
294 describe('When rejecting a follower', function () {
295 const path = '/api/v1/server/followers'
296
297 it('Should fail with an invalid token', async function () {
298 await makePostBodyRequest({
299 url: server.url,
300 path: path + '/toto@127.0.0.1:9002/reject',
301 token: 'fake_token',
302 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
303 })
304 })
305
306 it('Should fail if the user is not an administrator', async function () {
307 await makePostBodyRequest({
308 url: server.url,
309 path: path + '/toto@127.0.0.1:9002/reject',
310 token: userAccessToken,
311 expectedStatus: HttpStatusCode.FORBIDDEN_403
312 })
313 })
314
315 it('Should fail with an invalid follower', async function () {
316 await makePostBodyRequest({
317 url: server.url,
318 path: path + '/toto/reject',
319 token: server.accessToken,
320 expectedStatus: HttpStatusCode.BAD_REQUEST_400
321 })
322 })
323
324 it('Should fail with an unknown follower', async function () {
325 await makePostBodyRequest({
326 url: server.url,
327 path: path + '/toto@127.0.0.1:9003/reject',
328 token: server.accessToken,
329 expectedStatus: HttpStatusCode.NOT_FOUND_404
330 })
331 })
332 })
333
334 describe('When removing following', function () {
335 const path = '/api/v1/server/following'
336
337 it('Should fail with an invalid token', async function () {
338 await makeDeleteRequest({
339 url: server.url,
340 path: path + '/127.0.0.1:9002',
341 token: 'fake_token',
342 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
343 })
344 })
345
346 it('Should fail if the user is not an administrator', async function () {
347 await makeDeleteRequest({
348 url: server.url,
349 path: path + '/127.0.0.1:9002',
350 token: userAccessToken,
351 expectedStatus: HttpStatusCode.FORBIDDEN_403
352 })
353 })
354
355 it('Should fail if we do not follow this server', async function () {
356 await makeDeleteRequest({
357 url: server.url,
358 path: path + '/example.com',
359 token: server.accessToken,
360 expectedStatus: HttpStatusCode.NOT_FOUND_404
361 })
362 })
363 })
364 })
365
366 after(async function () {
367 await cleanupTests([ server ])
368 })
369})
diff --git a/packages/tests/src/api/check-params/index.ts b/packages/tests/src/api/check-params/index.ts
new file mode 100644
index 000000000..ed5fe6b06
--- /dev/null
+++ b/packages/tests/src/api/check-params/index.ts
@@ -0,0 +1,45 @@
1import './abuses.js'
2import './accounts.js'
3import './blocklist.js'
4import './bulk.js'
5import './channel-import-videos.js'
6import './config.js'
7import './contact-form.js'
8import './custom-pages.js'
9import './debug.js'
10import './follows.js'
11import './jobs.js'
12import './live.js'
13import './logs.js'
14import './metrics.js'
15import './my-user.js'
16import './plugins.js'
17import './redundancy.js'
18import './registrations.js'
19import './runners.js'
20import './search.js'
21import './services.js'
22import './transcoding.js'
23import './two-factor.js'
24import './upload-quota.js'
25import './user-notifications.js'
26import './user-subscriptions.js'
27import './users-admin.js'
28import './users-emails.js'
29import './video-blacklist.js'
30import './video-captions.js'
31import './video-channel-syncs.js'
32import './video-channels.js'
33import './video-comments.js'
34import './video-files.js'
35import './video-imports.js'
36import './video-playlists.js'
37import './video-storyboards.js'
38import './video-source.js'
39import './video-studio.js'
40import './video-token.js'
41import './videos-common-filters.js'
42import './videos-history.js'
43import './videos-overviews.js'
44import './videos.js'
45import './views.js'
diff --git a/packages/tests/src/api/check-params/jobs.ts b/packages/tests/src/api/check-params/jobs.ts
new file mode 100644
index 000000000..331d58c6a
--- /dev/null
+++ b/packages/tests/src/api/check-params/jobs.ts
@@ -0,0 +1,125 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { HttpStatusCode } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createSingleServer,
8 makeGetRequest,
9 makePostBodyRequest,
10 PeerTubeServer,
11 setAccessTokensToServers
12} from '@peertube/peertube-server-commands'
13
14describe('Test jobs API validators', function () {
15 const path = '/api/v1/jobs/failed'
16 let server: PeerTubeServer
17 let userAccessToken = ''
18
19 // ---------------------------------------------------------------
20
21 before(async function () {
22 this.timeout(120000)
23
24 server = await createSingleServer(1)
25
26 await setAccessTokensToServers([ server ])
27
28 const user = {
29 username: 'user1',
30 password: 'my super password'
31 }
32 await server.users.create({ username: user.username, password: user.password })
33 userAccessToken = await server.login.getAccessToken(user)
34 })
35
36 describe('When listing jobs', function () {
37
38 it('Should fail with a bad state', async function () {
39 await makeGetRequest({
40 url: server.url,
41 token: server.accessToken,
42 path: path + 'ade'
43 })
44 })
45
46 it('Should fail with an incorrect job type', async function () {
47 await makeGetRequest({
48 url: server.url,
49 token: server.accessToken,
50 path,
51 query: {
52 jobType: 'toto'
53 }
54 })
55 })
56
57 it('Should fail with a bad start pagination', async function () {
58 await checkBadStartPagination(server.url, path, server.accessToken)
59 })
60
61 it('Should fail with a bad count pagination', async function () {
62 await checkBadCountPagination(server.url, path, server.accessToken)
63 })
64
65 it('Should fail with an incorrect sort', async function () {
66 await checkBadSortPagination(server.url, path, server.accessToken)
67 })
68
69 it('Should fail with a non authenticated user', async function () {
70 await makeGetRequest({
71 url: server.url,
72 path,
73 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
74 })
75 })
76
77 it('Should fail with a non admin user', async function () {
78 await makeGetRequest({
79 url: server.url,
80 path,
81 token: userAccessToken,
82 expectedStatus: HttpStatusCode.FORBIDDEN_403
83 })
84 })
85 })
86
87 describe('When pausing/resuming the job queue', async function () {
88 const commands = [ 'pause', 'resume' ]
89
90 it('Should fail with a non authenticated user', async function () {
91 for (const command of commands) {
92 await makePostBodyRequest({
93 url: server.url,
94 path: '/api/v1/jobs/' + command,
95 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
96 })
97 }
98 })
99
100 it('Should fail with a non admin user', async function () {
101 for (const command of commands) {
102 await makePostBodyRequest({
103 url: server.url,
104 path: '/api/v1/jobs/' + command,
105 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
106 })
107 }
108 })
109
110 it('Should succeed with the correct params', async function () {
111 for (const command of commands) {
112 await makePostBodyRequest({
113 url: server.url,
114 path: '/api/v1/jobs/' + command,
115 token: server.accessToken,
116 expectedStatus: HttpStatusCode.NO_CONTENT_204
117 })
118 }
119 })
120 })
121
122 after(async function () {
123 await cleanupTests([ server ])
124 })
125})
diff --git a/packages/tests/src/api/check-params/live.ts b/packages/tests/src/api/check-params/live.ts
new file mode 100644
index 000000000..5900823ea
--- /dev/null
+++ b/packages/tests/src/api/check-params/live.ts
@@ -0,0 +1,590 @@
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 { HttpStatusCode, LiveVideoLatencyMode, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models'
6import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
7import {
8 cleanupTests,
9 createSingleServer,
10 LiveCommand,
11 makePostBodyRequest,
12 makeUploadRequest,
13 PeerTubeServer,
14 sendRTMPStream,
15 setAccessTokensToServers,
16 stopFfmpeg
17} from '@peertube/peertube-server-commands'
18
19describe('Test video lives API validator', function () {
20 const path = '/api/v1/videos/live'
21 let server: PeerTubeServer
22 let userAccessToken = ''
23 let channelId: number
24 let video: VideoCreateResult
25 let videoIdNotLive: number
26 let command: LiveCommand
27
28 // ---------------------------------------------------------------
29
30 before(async function () {
31 this.timeout(30000)
32
33 server = await createSingleServer(1)
34
35 await setAccessTokensToServers([ server ])
36
37 await server.config.updateCustomSubConfig({
38 newConfig: {
39 live: {
40 enabled: true,
41 latencySetting: {
42 enabled: false
43 },
44 maxInstanceLives: 20,
45 maxUserLives: 20,
46 allowReplay: true
47 }
48 }
49 })
50
51 const username = 'user1'
52 const password = 'my super password'
53 await server.users.create({ username, password })
54 userAccessToken = await server.login.getAccessToken({ username, password })
55
56 {
57 const { videoChannels } = await server.users.getMyInfo()
58 channelId = videoChannels[0].id
59 }
60
61 {
62 videoIdNotLive = (await server.videos.quickUpload({ name: 'not live' })).id
63 }
64
65 command = server.live
66 })
67
68 describe('When creating a live', function () {
69 let baseCorrectParams
70
71 before(function () {
72 baseCorrectParams = {
73 name: 'my super name',
74 category: 5,
75 licence: 1,
76 language: 'pt',
77 nsfw: false,
78 commentsEnabled: true,
79 downloadEnabled: true,
80 waitTranscoding: true,
81 description: 'my super description',
82 support: 'my super support text',
83 tags: [ 'tag1', 'tag2' ],
84 privacy: VideoPrivacy.PUBLIC,
85 channelId,
86 saveReplay: false,
87 replaySettings: undefined,
88 permanentLive: false,
89 latencyMode: LiveVideoLatencyMode.DEFAULT
90 }
91 })
92
93 it('Should fail with nothing', async function () {
94 const fields = {}
95 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
96 })
97
98 it('Should fail with a long name', async function () {
99 const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
100
101 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
102 })
103
104 it('Should fail with a bad category', async function () {
105 const fields = { ...baseCorrectParams, category: 125 }
106
107 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
108 })
109
110 it('Should fail with a bad licence', async function () {
111 const fields = { ...baseCorrectParams, licence: 125 }
112
113 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
114 })
115
116 it('Should fail with a bad language', async function () {
117 const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
118
119 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
120 })
121
122 it('Should fail with a long description', async function () {
123 const fields = { ...baseCorrectParams, description: 'super'.repeat(2500) }
124
125 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
126 })
127
128 it('Should fail with a long support text', async function () {
129 const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
130
131 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
132 })
133
134 it('Should fail without a channel', async function () {
135 const fields = omit(baseCorrectParams, [ 'channelId' ])
136
137 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
138 })
139
140 it('Should fail with a bad channel', async function () {
141 const fields = { ...baseCorrectParams, channelId: 545454 }
142
143 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
144 })
145
146 it('Should fail with a bad privacy for replay settings', async function () {
147 const fields = { ...baseCorrectParams, saveReplay: true, replaySettings: { privacy: 999 } }
148
149 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
150 })
151
152 it('Should fail with another user channel', async function () {
153 const user = {
154 username: 'fake',
155 password: 'fake_password'
156 }
157 await server.users.create({ username: user.username, password: user.password })
158
159 const accessTokenUser = await server.login.getAccessToken(user)
160 const { videoChannels } = await server.users.getMyInfo({ token: accessTokenUser })
161 const customChannelId = videoChannels[0].id
162
163 const fields = { ...baseCorrectParams, channelId: customChannelId }
164
165 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
166 })
167
168 it('Should fail with too many tags', async function () {
169 const fields = { ...baseCorrectParams, tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }
170
171 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
172 })
173
174 it('Should fail with a tag length too low', async function () {
175 const fields = { ...baseCorrectParams, tags: [ 'tag1', 't' ] }
176
177 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
178 })
179
180 it('Should fail with a tag length too big', async function () {
181 const fields = { ...baseCorrectParams, tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }
182
183 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
184 })
185
186 it('Should fail with an incorrect thumbnail file', async function () {
187 const fields = baseCorrectParams
188 const attaches = {
189 thumbnailfile: buildAbsoluteFixturePath('video_short.mp4')
190 }
191
192 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
193 })
194
195 it('Should fail with a big thumbnail file', async function () {
196 const fields = baseCorrectParams
197 const attaches = {
198 thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png')
199 }
200
201 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
202 })
203
204 it('Should fail with an incorrect preview file', async function () {
205 const fields = baseCorrectParams
206 const attaches = {
207 previewfile: buildAbsoluteFixturePath('video_short.mp4')
208 }
209
210 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
211 })
212
213 it('Should fail with a big preview file', async function () {
214 const fields = baseCorrectParams
215 const attaches = {
216 previewfile: buildAbsoluteFixturePath('custom-preview-big.png')
217 }
218
219 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
220 })
221
222 it('Should fail with bad latency setting', async function () {
223 const fields = { ...baseCorrectParams, latencyMode: 42 }
224
225 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
226 })
227
228 it('Should fail to set latency if the server does not allow it', async function () {
229 const fields = { ...baseCorrectParams, latencyMode: LiveVideoLatencyMode.HIGH_LATENCY }
230
231 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
232 })
233
234 it('Should succeed with the correct parameters', async function () {
235 this.timeout(30000)
236
237 const res = await makePostBodyRequest({
238 url: server.url,
239 path,
240 token: server.accessToken,
241 fields: baseCorrectParams,
242 expectedStatus: HttpStatusCode.OK_200
243 })
244
245 video = res.body.video
246 })
247
248 it('Should forbid if live is disabled', async function () {
249 await server.config.updateCustomSubConfig({
250 newConfig: {
251 live: {
252 enabled: false
253 }
254 }
255 })
256
257 await makePostBodyRequest({
258 url: server.url,
259 path,
260 token: server.accessToken,
261 fields: baseCorrectParams,
262 expectedStatus: HttpStatusCode.FORBIDDEN_403
263 })
264 })
265
266 it('Should forbid to save replay if not enabled by the admin', async function () {
267 const fields = { ...baseCorrectParams, saveReplay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } }
268
269 await server.config.updateCustomSubConfig({
270 newConfig: {
271 live: {
272 enabled: true,
273 allowReplay: false
274 }
275 }
276 })
277
278 await makePostBodyRequest({
279 url: server.url,
280 path,
281 token: server.accessToken,
282 fields,
283 expectedStatus: HttpStatusCode.FORBIDDEN_403
284 })
285 })
286
287 it('Should allow to save replay if enabled by the admin', async function () {
288 const fields = { ...baseCorrectParams, saveReplay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } }
289
290 await server.config.updateCustomSubConfig({
291 newConfig: {
292 live: {
293 enabled: true,
294 allowReplay: true
295 }
296 }
297 })
298
299 await makePostBodyRequest({
300 url: server.url,
301 path,
302 token: server.accessToken,
303 fields,
304 expectedStatus: HttpStatusCode.OK_200
305 })
306 })
307
308 it('Should not allow live if max instance lives is reached', async function () {
309 await server.config.updateCustomSubConfig({
310 newConfig: {
311 live: {
312 enabled: true,
313 maxInstanceLives: 1
314 }
315 }
316 })
317
318 await makePostBodyRequest({
319 url: server.url,
320 path,
321 token: server.accessToken,
322 fields: baseCorrectParams,
323 expectedStatus: HttpStatusCode.FORBIDDEN_403
324 })
325 })
326
327 it('Should not allow live if max user lives is reached', async function () {
328 await server.config.updateCustomSubConfig({
329 newConfig: {
330 live: {
331 enabled: true,
332 maxInstanceLives: 20,
333 maxUserLives: 1
334 }
335 }
336 })
337
338 await makePostBodyRequest({
339 url: server.url,
340 path,
341 token: server.accessToken,
342 fields: baseCorrectParams,
343 expectedStatus: HttpStatusCode.FORBIDDEN_403
344 })
345 })
346 })
347
348 describe('When getting live information', function () {
349
350 it('Should fail with a bad access token', async function () {
351 await command.get({ token: 'toto', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
352 })
353
354 it('Should not display private information without access token', async function () {
355 const live = await command.get({ token: '', videoId: video.id })
356
357 expect(live.rtmpUrl).to.not.exist
358 expect(live.streamKey).to.not.exist
359 expect(live.latencyMode).to.exist
360 })
361
362 it('Should not display private information with token of another user', async function () {
363 const live = await command.get({ token: userAccessToken, videoId: video.id })
364
365 expect(live.rtmpUrl).to.not.exist
366 expect(live.streamKey).to.not.exist
367 expect(live.latencyMode).to.exist
368 })
369
370 it('Should display private information with appropriate token', async function () {
371 const live = await command.get({ videoId: video.id })
372
373 expect(live.rtmpUrl).to.exist
374 expect(live.streamKey).to.exist
375 expect(live.latencyMode).to.exist
376 })
377
378 it('Should fail with a bad video id', async function () {
379 await command.get({ videoId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
380 })
381
382 it('Should fail with an unknown video id', async function () {
383 await command.get({ videoId: 454555, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
384 })
385
386 it('Should fail with a non live video', async function () {
387 await command.get({ videoId: videoIdNotLive, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
388 })
389
390 it('Should succeed with the correct params', async function () {
391 await command.get({ videoId: video.id })
392 await command.get({ videoId: video.uuid })
393 await command.get({ videoId: video.shortUUID })
394 })
395 })
396
397 describe('When getting live sessions', function () {
398
399 it('Should fail with a bad access token', async function () {
400 await command.listSessions({ token: 'toto', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
401 })
402
403 it('Should fail without token', async function () {
404 await command.listSessions({ token: null, videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
405 })
406
407 it('Should fail with the token of another user', async function () {
408 await command.listSessions({ token: userAccessToken, videoId: video.id, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
409 })
410
411 it('Should fail with a bad video id', async function () {
412 await command.listSessions({ videoId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
413 })
414
415 it('Should fail with an unknown video id', async function () {
416 await command.listSessions({ videoId: 454555, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
417 })
418
419 it('Should fail with a non live video', async function () {
420 await command.listSessions({ videoId: videoIdNotLive, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
421 })
422
423 it('Should succeed with the correct params', async function () {
424 await command.listSessions({ videoId: video.id })
425 })
426 })
427
428 describe('When getting live session of a replay', function () {
429
430 it('Should fail with a bad video id', async function () {
431 await command.getReplaySession({ videoId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
432 })
433
434 it('Should fail with an unknown video id', async function () {
435 await command.getReplaySession({ videoId: 454555, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
436 })
437
438 it('Should fail with a non replay video', async function () {
439 await command.getReplaySession({ videoId: videoIdNotLive, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
440 })
441 })
442
443 describe('When updating live information', async function () {
444
445 it('Should fail without access token', async function () {
446 await command.update({ token: '', videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
447 })
448
449 it('Should fail with a bad access token', async function () {
450 await command.update({ token: 'toto', videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
451 })
452
453 it('Should fail with access token of another user', async function () {
454 await command.update({ token: userAccessToken, videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
455 })
456
457 it('Should fail with a bad video id', async function () {
458 await command.update({ videoId: 'toto', fields: {}, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
459 })
460
461 it('Should fail with an unknown video id', async function () {
462 await command.update({ videoId: 454555, fields: {}, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
463 })
464
465 it('Should fail with a non live video', async function () {
466 await command.update({ videoId: videoIdNotLive, fields: {}, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
467 })
468
469 it('Should fail with bad latency setting', async function () {
470 const fields = { latencyMode: 42 as any }
471
472 await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
473 })
474
475 it('Should fail with a bad privacy for replay settings', async function () {
476 const fields = { saveReplay: true, replaySettings: { privacy: 999 as any } }
477
478 await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
479 })
480
481 it('Should fail with save replay enabled but without replay settings', async function () {
482 await server.config.updateCustomSubConfig({
483 newConfig: {
484 live: {
485 enabled: true,
486 allowReplay: true
487 }
488 }
489 })
490
491 const fields = { saveReplay: true }
492
493 await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
494 })
495
496 it('Should fail with save replay disabled and replay settings', async function () {
497 const fields = { saveReplay: false, replaySettings: { privacy: VideoPrivacy.INTERNAL } }
498
499 await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
500 })
501
502 it('Should fail with only replay settings when save replay is disabled', async function () {
503 const fields = { replaySettings: { privacy: VideoPrivacy.INTERNAL } }
504
505 await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
506 })
507
508 it('Should fail to set latency if the server does not allow it', async function () {
509 const fields = { latencyMode: LiveVideoLatencyMode.HIGH_LATENCY }
510
511 await command.update({ videoId: video.id, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
512 })
513
514 it('Should succeed with the correct params', async function () {
515 await command.update({ videoId: video.id, fields: { saveReplay: false } })
516 await command.update({ videoId: video.uuid, fields: { saveReplay: false } })
517 await command.update({ videoId: video.shortUUID, fields: { saveReplay: false } })
518
519 await command.update({ videoId: video.id, fields: { saveReplay: true, replaySettings: { privacy: VideoPrivacy.PUBLIC } } })
520
521 })
522
523 it('Should fail to update replay status if replay is not allowed on the instance', async function () {
524 await server.config.updateCustomSubConfig({
525 newConfig: {
526 live: {
527 enabled: true,
528 allowReplay: false
529 }
530 }
531 })
532
533 await command.update({ videoId: video.id, fields: { saveReplay: true }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
534 })
535
536 it('Should fail to update a live if it has already started', async function () {
537 this.timeout(40000)
538
539 const live = await command.get({ videoId: video.id })
540
541 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
542
543 await command.waitUntilPublished({ videoId: video.id })
544 await command.update({ videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
545
546 await stopFfmpeg(ffmpegCommand)
547 })
548
549 it('Should fail to change live privacy if it has already started', async function () {
550 this.timeout(40000)
551
552 const live = await command.get({ videoId: video.id })
553
554 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
555
556 await command.waitUntilPublished({ videoId: video.id })
557
558 await server.videos.update({
559 id: video.id,
560 attributes: { privacy: VideoPrivacy.PUBLIC } // Same privacy, it's fine
561 })
562
563 await server.videos.update({
564 id: video.id,
565 attributes: { privacy: VideoPrivacy.UNLISTED },
566 expectedStatus: HttpStatusCode.BAD_REQUEST_400
567 })
568
569 await stopFfmpeg(ffmpegCommand)
570 })
571
572 it('Should fail to stream twice in the save live', async function () {
573 this.timeout(40000)
574
575 const live = await command.get({ videoId: video.id })
576
577 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
578
579 await command.waitUntilPublished({ videoId: video.id })
580
581 await command.runAndTestStreamError({ videoId: video.id, shouldHaveError: true })
582
583 await stopFfmpeg(ffmpegCommand)
584 })
585 })
586
587 after(async function () {
588 await cleanupTests([ server ])
589 })
590})
diff --git a/packages/tests/src/api/check-params/logs.ts b/packages/tests/src/api/check-params/logs.ts
new file mode 100644
index 000000000..629530e30
--- /dev/null
+++ b/packages/tests/src/api/check-params/logs.ts
@@ -0,0 +1,163 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { HttpStatusCode } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createSingleServer,
8 makeGetRequest,
9 PeerTubeServer,
10 setAccessTokensToServers
11} from '@peertube/peertube-server-commands'
12
13describe('Test logs API validators', function () {
14 const path = '/api/v1/server/logs'
15 let server: PeerTubeServer
16 let userAccessToken = ''
17
18 // ---------------------------------------------------------------
19
20 before(async function () {
21 this.timeout(120000)
22
23 server = await createSingleServer(1)
24
25 await setAccessTokensToServers([ server ])
26
27 const user = {
28 username: 'user1',
29 password: 'my super password'
30 }
31 await server.users.create({ username: user.username, password: user.password })
32 userAccessToken = await server.login.getAccessToken(user)
33 })
34
35 describe('When getting logs', function () {
36
37 it('Should fail with a non authenticated user', async function () {
38 await makeGetRequest({
39 url: server.url,
40 path,
41 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
42 })
43 })
44
45 it('Should fail with a non admin user', async function () {
46 await makeGetRequest({
47 url: server.url,
48 path,
49 token: userAccessToken,
50 expectedStatus: HttpStatusCode.FORBIDDEN_403
51 })
52 })
53
54 it('Should fail with a missing startDate query', async function () {
55 await makeGetRequest({
56 url: server.url,
57 path,
58 token: server.accessToken,
59 expectedStatus: HttpStatusCode.BAD_REQUEST_400
60 })
61 })
62
63 it('Should fail with a bad startDate query', async function () {
64 await makeGetRequest({
65 url: server.url,
66 path,
67 token: server.accessToken,
68 query: { startDate: 'toto' },
69 expectedStatus: HttpStatusCode.BAD_REQUEST_400
70 })
71 })
72
73 it('Should fail with a bad endDate query', async function () {
74 await makeGetRequest({
75 url: server.url,
76 path,
77 token: server.accessToken,
78 query: { startDate: new Date().toISOString(), endDate: 'toto' },
79 expectedStatus: HttpStatusCode.BAD_REQUEST_400
80 })
81 })
82
83 it('Should fail with a bad level parameter', async function () {
84 await makeGetRequest({
85 url: server.url,
86 path,
87 token: server.accessToken,
88 query: { startDate: new Date().toISOString(), level: 'toto' },
89 expectedStatus: HttpStatusCode.BAD_REQUEST_400
90 })
91 })
92
93 it('Should succeed with the correct params', async function () {
94 await makeGetRequest({
95 url: server.url,
96 path,
97 token: server.accessToken,
98 query: { startDate: new Date().toISOString() },
99 expectedStatus: HttpStatusCode.OK_200
100 })
101 })
102 })
103
104 describe('When creating client logs', function () {
105 const base = {
106 level: 'warn' as 'warn',
107 message: 'my super message',
108 url: 'https://example.com/toto'
109 }
110 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
111
112 it('Should fail with an invalid level', async function () {
113 await server.logs.createLogClient({ payload: { ...base, level: '' as any }, expectedStatus })
114 await server.logs.createLogClient({ payload: { ...base, level: undefined }, expectedStatus })
115 await server.logs.createLogClient({ payload: { ...base, level: 'toto' as any }, expectedStatus })
116 })
117
118 it('Should fail with an invalid message', async function () {
119 await server.logs.createLogClient({ payload: { ...base, message: undefined }, expectedStatus })
120 await server.logs.createLogClient({ payload: { ...base, message: '' }, expectedStatus })
121 await server.logs.createLogClient({ payload: { ...base, message: 'm'.repeat(2500) }, expectedStatus })
122 })
123
124 it('Should fail with an invalid url', async function () {
125 await server.logs.createLogClient({ payload: { ...base, url: undefined }, expectedStatus })
126 await server.logs.createLogClient({ payload: { ...base, url: 'toto' }, expectedStatus })
127 })
128
129 it('Should fail with an invalid stackTrace', async function () {
130 await server.logs.createLogClient({ payload: { ...base, stackTrace: 's'.repeat(20000) }, expectedStatus })
131 })
132
133 it('Should fail with an invalid userAgent', async function () {
134 await server.logs.createLogClient({ payload: { ...base, userAgent: 's'.repeat(500) }, expectedStatus })
135 })
136
137 it('Should fail with an invalid meta', async function () {
138 await server.logs.createLogClient({ payload: { ...base, meta: 's'.repeat(10000) }, expectedStatus })
139 })
140
141 it('Should succeed with the correct params', async function () {
142 await server.logs.createLogClient({ payload: { ...base, stackTrace: 'stackTrace', meta: '{toto}', userAgent: 'userAgent' } })
143 })
144
145 it('Should rate limit log creation', async function () {
146 let fail = false
147
148 for (let i = 0; i < 10; i++) {
149 try {
150 await server.logs.createLogClient({ token: null, payload: base })
151 } catch {
152 fail = true
153 }
154 }
155
156 expect(fail).to.be.true
157 })
158 })
159
160 after(async function () {
161 await cleanupTests([ server ])
162 })
163})
diff --git a/packages/tests/src/api/check-params/metrics.ts b/packages/tests/src/api/check-params/metrics.ts
new file mode 100644
index 000000000..cda854554
--- /dev/null
+++ b/packages/tests/src/api/check-params/metrics.ts
@@ -0,0 +1,214 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { omit } from '@peertube/peertube-core-utils'
4import { HttpStatusCode, PlaybackMetricCreate, VideoResolution } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createSingleServer,
8 makePostBodyRequest,
9 PeerTubeServer,
10 setAccessTokensToServers
11} from '@peertube/peertube-server-commands'
12
13describe('Test metrics API validators', function () {
14 let server: PeerTubeServer
15 let videoUUID: string
16
17 // ---------------------------------------------------------------
18
19 before(async function () {
20 this.timeout(120000)
21
22 server = await createSingleServer(1, {
23 open_telemetry: {
24 metrics: {
25 enabled: true
26 }
27 }
28 })
29
30 await setAccessTokensToServers([ server ])
31
32 const { uuid } = await server.videos.quickUpload({ name: 'video' })
33 videoUUID = uuid
34 })
35
36 describe('When adding playback metrics', function () {
37 const path = '/api/v1/metrics/playback'
38 let baseParams: PlaybackMetricCreate
39
40 before(function () {
41 baseParams = {
42 playerMode: 'p2p-media-loader',
43 resolution: VideoResolution.H_1080P,
44 fps: 30,
45 resolutionChanges: 1,
46 errors: 2,
47 p2pEnabled: true,
48 downloadedBytesP2P: 0,
49 downloadedBytesHTTP: 0,
50 uploadedBytesP2P: 0,
51 videoId: videoUUID
52 }
53 })
54
55 it('Should fail with an invalid resolution', async function () {
56 await makePostBodyRequest({
57 url: server.url,
58 path,
59 fields: { ...baseParams, resolution: 'toto' }
60 })
61 })
62
63 it('Should fail with an invalid fps', async function () {
64 await makePostBodyRequest({
65 url: server.url,
66 path,
67 fields: { ...baseParams, fps: 'toto' }
68 })
69 })
70
71 it('Should fail with a missing/invalid player mode', async function () {
72 await makePostBodyRequest({
73 url: server.url,
74 path,
75 fields: omit(baseParams, [ 'playerMode' ])
76 })
77
78 await makePostBodyRequest({
79 url: server.url,
80 path,
81 fields: { ...baseParams, playerMode: 'toto' }
82 })
83 })
84
85 it('Should fail with an missing/invalid resolution changes', async function () {
86 await makePostBodyRequest({
87 url: server.url,
88 path,
89 fields: omit(baseParams, [ 'resolutionChanges' ])
90 })
91
92 await makePostBodyRequest({
93 url: server.url,
94 path,
95 fields: { ...baseParams, resolutionChanges: 'toto' }
96 })
97 })
98
99 it('Should fail with an missing/invalid errors', async function () {
100 await makePostBodyRequest({
101 url: server.url,
102 path,
103 fields: omit(baseParams, [ 'errors' ])
104 })
105
106 await makePostBodyRequest({
107 url: server.url,
108 path,
109 fields: { ...baseParams, errors: 'toto' }
110 })
111 })
112
113 it('Should fail with an missing/invalid downloadedBytesP2P', async function () {
114 await makePostBodyRequest({
115 url: server.url,
116 path,
117 fields: omit(baseParams, [ 'downloadedBytesP2P' ])
118 })
119
120 await makePostBodyRequest({
121 url: server.url,
122 path,
123 fields: { ...baseParams, downloadedBytesP2P: 'toto' }
124 })
125 })
126
127 it('Should fail with an missing/invalid downloadedBytesHTTP', async function () {
128 await makePostBodyRequest({
129 url: server.url,
130 path,
131 fields: omit(baseParams, [ 'downloadedBytesHTTP' ])
132 })
133
134 await makePostBodyRequest({
135 url: server.url,
136 path,
137 fields: { ...baseParams, downloadedBytesHTTP: 'toto' }
138 })
139 })
140
141 it('Should fail with an missing/invalid uploadedBytesP2P', async function () {
142 await makePostBodyRequest({
143 url: server.url,
144 path,
145 fields: omit(baseParams, [ 'uploadedBytesP2P' ])
146 })
147
148 await makePostBodyRequest({
149 url: server.url,
150 path,
151 fields: { ...baseParams, uploadedBytesP2P: 'toto' }
152 })
153 })
154
155 it('Should fail with a missing/invalid p2pEnabled', async function () {
156 await makePostBodyRequest({
157 url: server.url,
158 path,
159 fields: omit(baseParams, [ 'p2pEnabled' ])
160 })
161
162 await makePostBodyRequest({
163 url: server.url,
164 path,
165 fields: { ...baseParams, p2pEnabled: 'toto' }
166 })
167 })
168
169 it('Should fail with an invalid totalPeers', async function () {
170 await makePostBodyRequest({
171 url: server.url,
172 path,
173 fields: { ...baseParams, p2pPeers: 'toto' }
174 })
175 })
176
177 it('Should fail with a bad video id', async function () {
178 await makePostBodyRequest({
179 url: server.url,
180 path,
181 fields: { ...baseParams, videoId: 'toto' }
182 })
183 })
184
185 it('Should fail with an unknown video', async function () {
186 await makePostBodyRequest({
187 url: server.url,
188 path,
189 fields: { ...baseParams, videoId: 42 },
190 expectedStatus: HttpStatusCode.NOT_FOUND_404
191 })
192 })
193
194 it('Should succeed with the correct params', async function () {
195 await makePostBodyRequest({
196 url: server.url,
197 path,
198 fields: baseParams,
199 expectedStatus: HttpStatusCode.NO_CONTENT_204
200 })
201
202 await makePostBodyRequest({
203 url: server.url,
204 path,
205 fields: { ...baseParams, p2pEnabled: false, totalPeers: 32 },
206 expectedStatus: HttpStatusCode.NO_CONTENT_204
207 })
208 })
209 })
210
211 after(async function () {
212 await cleanupTests([ server ])
213 })
214})
diff --git a/packages/tests/src/api/check-params/my-user.ts b/packages/tests/src/api/check-params/my-user.ts
new file mode 100644
index 000000000..2ef2e242a
--- /dev/null
+++ b/packages/tests/src/api/check-params/my-user.ts
@@ -0,0 +1,492 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
5import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
6import { HttpStatusCode, UserRole, VideoCreateResult } from '@peertube/peertube-models'
7import {
8 cleanupTests,
9 createSingleServer,
10 makeGetRequest,
11 makePutBodyRequest,
12 makeUploadRequest,
13 PeerTubeServer,
14 setAccessTokensToServers,
15 UsersCommand
16} from '@peertube/peertube-server-commands'
17
18describe('Test my user API validators', function () {
19 const path = '/api/v1/users/'
20 let userId: number
21 let rootId: number
22 let moderatorId: number
23 let video: VideoCreateResult
24 let server: PeerTubeServer
25 let userToken = ''
26 let moderatorToken = ''
27
28 // ---------------------------------------------------------------
29
30 before(async function () {
31 this.timeout(30000)
32
33 {
34 server = await createSingleServer(1)
35 await setAccessTokensToServers([ server ])
36 }
37
38 {
39 const result = await server.users.generate('user1')
40 userToken = result.token
41 userId = result.userId
42 }
43
44 {
45 const result = await server.users.generate('moderator1', UserRole.MODERATOR)
46 moderatorToken = result.token
47 }
48
49 {
50 const result = await server.users.generate('moderator2', UserRole.MODERATOR)
51 moderatorId = result.userId
52 }
53
54 {
55 video = await server.videos.upload()
56 }
57 })
58
59 describe('When updating my account', function () {
60
61 it('Should fail with an invalid email attribute', async function () {
62 const fields = {
63 email: 'blabla'
64 }
65
66 await makePutBodyRequest({ url: server.url, path: path + 'me', token: server.accessToken, fields })
67 })
68
69 it('Should fail with a too small password', async function () {
70 const fields = {
71 currentPassword: 'password',
72 password: 'bla'
73 }
74
75 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
76 })
77
78 it('Should fail with a too long password', async function () {
79 const fields = {
80 currentPassword: 'password',
81 password: 'super'.repeat(61)
82 }
83
84 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
85 })
86
87 it('Should fail without the current password', async function () {
88 const fields = {
89 currentPassword: 'password',
90 password: 'super'.repeat(61)
91 }
92
93 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
94 })
95
96 it('Should fail with an invalid current password', async function () {
97 const fields = {
98 currentPassword: 'my super password fail',
99 password: 'super'.repeat(61)
100 }
101
102 await makePutBodyRequest({
103 url: server.url,
104 path: path + 'me',
105 token: userToken,
106 fields,
107 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
108 })
109 })
110
111 it('Should fail with an invalid NSFW policy attribute', async function () {
112 const fields = {
113 nsfwPolicy: 'hello'
114 }
115
116 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
117 })
118
119 it('Should fail with an invalid autoPlayVideo attribute', async function () {
120 const fields = {
121 autoPlayVideo: -1
122 }
123
124 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
125 })
126
127 it('Should fail with an invalid autoPlayNextVideo attribute', async function () {
128 const fields = {
129 autoPlayNextVideo: -1
130 }
131
132 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
133 })
134
135 it('Should fail with an invalid videosHistoryEnabled attribute', async function () {
136 const fields = {
137 videosHistoryEnabled: -1
138 }
139
140 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
141 })
142
143 it('Should fail with an non authenticated user', async function () {
144 const fields = {
145 currentPassword: 'password',
146 password: 'my super password'
147 }
148
149 await makePutBodyRequest({
150 url: server.url,
151 path: path + 'me',
152 token: 'super token',
153 fields,
154 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
155 })
156 })
157
158 it('Should fail with a too long description', async function () {
159 const fields = {
160 description: 'super'.repeat(201)
161 }
162
163 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
164 })
165
166 it('Should fail with an invalid videoLanguages attribute', async function () {
167 {
168 const fields = {
169 videoLanguages: 'toto'
170 }
171
172 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
173 }
174
175 {
176 const languages = []
177 for (let i = 0; i < 1000; i++) {
178 languages.push('fr')
179 }
180
181 const fields = {
182 videoLanguages: languages
183 }
184
185 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
186 }
187 })
188
189 it('Should fail with an invalid theme', async function () {
190 const fields = { theme: 'invalid' }
191 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
192 })
193
194 it('Should fail with an unknown theme', async function () {
195 const fields = { theme: 'peertube-theme-unknown' }
196 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
197 })
198
199 it('Should fail with invalid no modal attributes', async function () {
200 const keys = [
201 'noInstanceConfigWarningModal',
202 'noAccountSetupWarningModal',
203 'noWelcomeModal'
204 ]
205
206 for (const key of keys) {
207 const fields = {
208 [key]: -1
209 }
210
211 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields })
212 }
213 })
214
215 it('Should succeed to change password with the correct params', async function () {
216 const fields = {
217 currentPassword: 'password',
218 password: 'my super password',
219 nsfwPolicy: 'blur',
220 autoPlayVideo: false,
221 email: 'super_email@example.com',
222 theme: 'default',
223 noInstanceConfigWarningModal: true,
224 noWelcomeModal: true,
225 noAccountSetupWarningModal: true
226 }
227
228 await makePutBodyRequest({
229 url: server.url,
230 path: path + 'me',
231 token: userToken,
232 fields,
233 expectedStatus: HttpStatusCode.NO_CONTENT_204
234 })
235 })
236
237 it('Should succeed without password change with the correct params', async function () {
238 const fields = {
239 nsfwPolicy: 'blur',
240 autoPlayVideo: false
241 }
242
243 await makePutBodyRequest({
244 url: server.url,
245 path: path + 'me',
246 token: userToken,
247 fields,
248 expectedStatus: HttpStatusCode.NO_CONTENT_204
249 })
250 })
251 })
252
253 describe('When updating my avatar', function () {
254 it('Should fail without an incorrect input file', async function () {
255 const fields = {}
256 const attaches = {
257 avatarfile: buildAbsoluteFixturePath('video_short.mp4')
258 }
259 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
260 })
261
262 it('Should fail with a big file', async function () {
263 const fields = {}
264 const attaches = {
265 avatarfile: buildAbsoluteFixturePath('avatar-big.png')
266 }
267 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
268 })
269
270 it('Should fail with an unauthenticated user', async function () {
271 const fields = {}
272 const attaches = {
273 avatarfile: buildAbsoluteFixturePath('avatar.png')
274 }
275 await makeUploadRequest({
276 url: server.url,
277 path: path + '/me/avatar/pick',
278 fields,
279 attaches,
280 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
281 })
282 })
283
284 it('Should succeed with the correct params', async function () {
285 const fields = {}
286 const attaches = {
287 avatarfile: buildAbsoluteFixturePath('avatar.png')
288 }
289 await makeUploadRequest({
290 url: server.url,
291 path: path + '/me/avatar/pick',
292 token: server.accessToken,
293 fields,
294 attaches,
295 expectedStatus: HttpStatusCode.OK_200
296 })
297 })
298 })
299
300 describe('When managing my scoped tokens', function () {
301
302 it('Should fail to get my scoped tokens with an non authenticated user', async function () {
303 await server.users.getMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
304 })
305
306 it('Should fail to get my scoped tokens with a bad token', async function () {
307 await server.users.getMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
308
309 })
310
311 it('Should succeed to get my scoped tokens', async function () {
312 await server.users.getMyScopedTokens()
313 })
314
315 it('Should fail to renew my scoped tokens with an non authenticated user', async function () {
316 await server.users.renewMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
317 })
318
319 it('Should fail to renew my scoped tokens with a bad token', async function () {
320 await server.users.renewMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
321 })
322
323 it('Should succeed to renew my scoped tokens', async function () {
324 await server.users.renewMyScopedTokens()
325 })
326 })
327
328 describe('When getting my information', function () {
329 it('Should fail with a non authenticated user', async function () {
330 await server.users.getMyInfo({ token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
331 })
332
333 it('Should success with the correct parameters', async function () {
334 await server.users.getMyInfo({ token: userToken })
335 })
336 })
337
338 describe('When getting my video rating', function () {
339 let command: UsersCommand
340
341 before(function () {
342 command = server.users
343 })
344
345 it('Should fail with a non authenticated user', async function () {
346 await command.getMyRating({ token: 'fake_token', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
347 })
348
349 it('Should fail with an incorrect video uuid', async function () {
350 await command.getMyRating({ videoId: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
351 })
352
353 it('Should fail with an unknown video', async function () {
354 await command.getMyRating({ videoId: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
355 })
356
357 it('Should succeed with the correct parameters', async function () {
358 await command.getMyRating({ videoId: video.id })
359 await command.getMyRating({ videoId: video.uuid })
360 await command.getMyRating({ videoId: video.shortUUID })
361 })
362 })
363
364 describe('When retrieving my global ratings', function () {
365 const path = '/api/v1/accounts/user1/ratings'
366
367 it('Should fail with a bad start pagination', async function () {
368 await checkBadStartPagination(server.url, path, userToken)
369 })
370
371 it('Should fail with a bad count pagination', async function () {
372 await checkBadCountPagination(server.url, path, userToken)
373 })
374
375 it('Should fail with an incorrect sort', async function () {
376 await checkBadSortPagination(server.url, path, userToken)
377 })
378
379 it('Should fail with a unauthenticated user', async function () {
380 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
381 })
382
383 it('Should fail with a another user', async function () {
384 await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
385 })
386
387 it('Should fail with a bad type', async function () {
388 await makeGetRequest({
389 url: server.url,
390 path,
391 token: userToken,
392 query: { rating: 'toto ' },
393 expectedStatus: HttpStatusCode.BAD_REQUEST_400
394 })
395 })
396
397 it('Should succeed with the correct params', async function () {
398 await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 })
399 })
400 })
401
402 describe('When getting my global followers', function () {
403 const path = '/api/v1/accounts/user1/followers'
404
405 it('Should fail with a bad start pagination', async function () {
406 await checkBadStartPagination(server.url, path, userToken)
407 })
408
409 it('Should fail with a bad count pagination', async function () {
410 await checkBadCountPagination(server.url, path, userToken)
411 })
412
413 it('Should fail with an incorrect sort', async function () {
414 await checkBadSortPagination(server.url, path, userToken)
415 })
416
417 it('Should fail with a unauthenticated user', async function () {
418 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
419 })
420
421 it('Should fail with a another user', async function () {
422 await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
423 })
424
425 it('Should succeed with the correct params', async function () {
426 await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 })
427 })
428 })
429
430 describe('When blocking/unblocking/removing user', function () {
431
432 it('Should fail with an incorrect id', async function () {
433 const options = { userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
434
435 await server.users.remove(options)
436 await server.users.banUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
437 await server.users.unbanUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
438 })
439
440 it('Should fail with the root user', async function () {
441 const options = { userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
442
443 await server.users.remove(options)
444 await server.users.banUser(options)
445 await server.users.unbanUser(options)
446 })
447
448 it('Should return 404 with a non existing id', async function () {
449 const options = { userId: 4545454, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
450
451 await server.users.remove(options)
452 await server.users.banUser(options)
453 await server.users.unbanUser(options)
454 })
455
456 it('Should fail with a non admin user', async function () {
457 const options = { userId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }
458
459 await server.users.remove(options)
460 await server.users.banUser(options)
461 await server.users.unbanUser(options)
462 })
463
464 it('Should fail on a moderator with a moderator', async function () {
465 const options = { userId: moderatorId, token: moderatorToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }
466
467 await server.users.remove(options)
468 await server.users.banUser(options)
469 await server.users.unbanUser(options)
470 })
471
472 it('Should succeed on a user with a moderator', async function () {
473 const options = { userId, token: moderatorToken }
474
475 await server.users.banUser(options)
476 await server.users.unbanUser(options)
477 })
478 })
479
480 describe('When deleting our account', function () {
481
482 it('Should fail with with the root account', async function () {
483 await server.users.deleteMe({ expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
484 })
485 })
486
487 after(async function () {
488 MockSmtpServer.Instance.kill()
489
490 await cleanupTests([ server ])
491 })
492})
diff --git a/packages/tests/src/api/check-params/plugins.ts b/packages/tests/src/api/check-params/plugins.ts
new file mode 100644
index 000000000..ab2a426fe
--- /dev/null
+++ b/packages/tests/src/api/check-params/plugins.ts
@@ -0,0 +1,490 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { HttpStatusCode, PeerTubePlugin, PluginType } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createSingleServer,
8 makeGetRequest,
9 makePostBodyRequest,
10 makePutBodyRequest,
11 PeerTubeServer,
12 setAccessTokensToServers
13} from '@peertube/peertube-server-commands'
14
15describe('Test server plugins API validators', function () {
16 let server: PeerTubeServer
17 let userAccessToken = null
18
19 const npmPlugin = 'peertube-plugin-hello-world'
20 const pluginName = 'hello-world'
21 let npmVersion: string
22
23 const themePlugin = 'peertube-theme-background-red'
24 const themeName = 'background-red'
25 let themeVersion: string
26
27 // ---------------------------------------------------------------
28
29 before(async function () {
30 this.timeout(60000)
31
32 server = await createSingleServer(1)
33
34 await setAccessTokensToServers([ server ])
35
36 const user = {
37 username: 'user1',
38 password: 'password'
39 }
40
41 await server.users.create({ username: user.username, password: user.password })
42 userAccessToken = await server.login.getAccessToken(user)
43
44 {
45 const res = await server.plugins.install({ npmName: npmPlugin })
46 const plugin = res.body as PeerTubePlugin
47 npmVersion = plugin.version
48 }
49
50 {
51 const res = await server.plugins.install({ npmName: themePlugin })
52 const plugin = res.body as PeerTubePlugin
53 themeVersion = plugin.version
54 }
55 })
56
57 describe('With static plugin routes', function () {
58 it('Should fail with an unknown plugin name/plugin version', async function () {
59 const paths = [
60 '/plugins/' + pluginName + '/0.0.1/auth/fake-auth',
61 '/plugins/' + pluginName + '/0.0.1/static/images/chocobo.png',
62 '/plugins/' + pluginName + '/0.0.1/client-scripts/client/common-client-plugin.js',
63 '/themes/' + themeName + '/0.0.1/static/images/chocobo.png',
64 '/themes/' + themeName + '/0.0.1/client-scripts/client/video-watch-client-plugin.js',
65 '/themes/' + themeName + '/0.0.1/css/assets/style1.css'
66 ]
67
68 for (const p of paths) {
69 await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
70 }
71 })
72
73 it('Should fail when requesting a plugin in the theme path', async function () {
74 await makeGetRequest({
75 url: server.url,
76 path: '/themes/' + pluginName + '/' + npmVersion + '/static/images/chocobo.png',
77 expectedStatus: HttpStatusCode.NOT_FOUND_404
78 })
79 })
80
81 it('Should fail with invalid versions', async function () {
82 const paths = [
83 '/plugins/' + pluginName + '/0.0.1.1/auth/fake-auth',
84 '/plugins/' + pluginName + '/0.0.1.1/static/images/chocobo.png',
85 '/plugins/' + pluginName + '/0.1/client-scripts/client/common-client-plugin.js',
86 '/themes/' + themeName + '/1/static/images/chocobo.png',
87 '/themes/' + themeName + '/0.0.1000a/client-scripts/client/video-watch-client-plugin.js',
88 '/themes/' + themeName + '/0.a.1/css/assets/style1.css'
89 ]
90
91 for (const p of paths) {
92 await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
93 }
94 })
95
96 it('Should fail with invalid paths', async function () {
97 const paths = [
98 '/plugins/' + pluginName + '/' + npmVersion + '/static/images/../chocobo.png',
99 '/plugins/' + pluginName + '/' + npmVersion + '/client-scripts/../client/common-client-plugin.js',
100 '/themes/' + themeName + '/' + themeVersion + '/static/../images/chocobo.png',
101 '/themes/' + themeName + '/' + themeVersion + '/client-scripts/client/video-watch-client-plugin.js/..',
102 '/themes/' + themeName + '/' + themeVersion + '/css/../assets/style1.css'
103 ]
104
105 for (const p of paths) {
106 await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
107 }
108 })
109
110 it('Should fail with an unknown auth name', async function () {
111 const path = '/plugins/' + pluginName + '/' + npmVersion + '/auth/bad-auth'
112
113 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
114 })
115
116 it('Should fail with an unknown static file', async function () {
117 const paths = [
118 '/plugins/' + pluginName + '/' + npmVersion + '/static/fake/chocobo.png',
119 '/plugins/' + pluginName + '/' + npmVersion + '/client-scripts/client/fake.js',
120 '/themes/' + themeName + '/' + themeVersion + '/static/fake/chocobo.png',
121 '/themes/' + themeName + '/' + themeVersion + '/client-scripts/client/fake.js'
122 ]
123
124 for (const p of paths) {
125 await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
126 }
127 })
128
129 it('Should fail with an unknown CSS file', async function () {
130 await makeGetRequest({
131 url: server.url,
132 path: '/themes/' + themeName + '/' + themeVersion + '/css/assets/fake.css',
133 expectedStatus: HttpStatusCode.NOT_FOUND_404
134 })
135 })
136
137 it('Should succeed with the correct parameters', async function () {
138 const paths = [
139 '/plugins/' + pluginName + '/' + npmVersion + '/static/images/chocobo.png',
140 '/plugins/' + pluginName + '/' + npmVersion + '/client-scripts/client/common-client-plugin.js',
141 '/themes/' + themeName + '/' + themeVersion + '/static/images/chocobo.png',
142 '/themes/' + themeName + '/' + themeVersion + '/client-scripts/client/video-watch-client-plugin.js',
143 '/themes/' + themeName + '/' + themeVersion + '/css/assets/style1.css'
144 ]
145
146 for (const p of paths) {
147 await makeGetRequest({ url: server.url, path: p, expectedStatus: HttpStatusCode.OK_200 })
148 }
149
150 const authPath = '/plugins/' + pluginName + '/' + npmVersion + '/auth/fake-auth'
151 await makeGetRequest({ url: server.url, path: authPath, expectedStatus: HttpStatusCode.FOUND_302 })
152 })
153 })
154
155 describe('When listing available plugins/themes', function () {
156 const path = '/api/v1/plugins/available'
157 const baseQuery = {
158 search: 'super search',
159 pluginType: PluginType.PLUGIN,
160 currentPeerTubeEngine: '1.2.3'
161 }
162
163 it('Should fail with an invalid token', async function () {
164 await makeGetRequest({
165 url: server.url,
166 path,
167 token: 'fake_token',
168 query: baseQuery,
169 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
170 })
171 })
172
173 it('Should fail if the user is not an administrator', async function () {
174 await makeGetRequest({
175 url: server.url,
176 path,
177 token: userAccessToken,
178 query: baseQuery,
179 expectedStatus: HttpStatusCode.FORBIDDEN_403
180 })
181 })
182
183 it('Should fail with a bad start pagination', async function () {
184 await checkBadStartPagination(server.url, path, server.accessToken)
185 })
186
187 it('Should fail with a bad count pagination', async function () {
188 await checkBadCountPagination(server.url, path, server.accessToken)
189 })
190
191 it('Should fail with an incorrect sort', async function () {
192 await checkBadSortPagination(server.url, path, server.accessToken)
193 })
194
195 it('Should fail with an invalid plugin type', async function () {
196 const query = { ...baseQuery, pluginType: 5 }
197
198 await makeGetRequest({
199 url: server.url,
200 path,
201 token: server.accessToken,
202 query
203 })
204 })
205
206 it('Should fail with an invalid current peertube engine', async function () {
207 const query = { ...baseQuery, currentPeerTubeEngine: '1.0' }
208
209 await makeGetRequest({
210 url: server.url,
211 path,
212 token: server.accessToken,
213 query
214 })
215 })
216
217 it('Should success with the correct parameters', async function () {
218 await makeGetRequest({
219 url: server.url,
220 path,
221 token: server.accessToken,
222 query: baseQuery,
223 expectedStatus: HttpStatusCode.OK_200
224 })
225 })
226 })
227
228 describe('When listing local plugins/themes', function () {
229 const path = '/api/v1/plugins'
230 const baseQuery = {
231 pluginType: PluginType.THEME
232 }
233
234 it('Should fail with an invalid token', async function () {
235 await makeGetRequest({
236 url: server.url,
237 path,
238 token: 'fake_token',
239 query: baseQuery,
240 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
241 })
242 })
243
244 it('Should fail if the user is not an administrator', async function () {
245 await makeGetRequest({
246 url: server.url,
247 path,
248 token: userAccessToken,
249 query: baseQuery,
250 expectedStatus: HttpStatusCode.FORBIDDEN_403
251 })
252 })
253
254 it('Should fail with a bad start pagination', async function () {
255 await checkBadStartPagination(server.url, path, server.accessToken)
256 })
257
258 it('Should fail with a bad count pagination', async function () {
259 await checkBadCountPagination(server.url, path, server.accessToken)
260 })
261
262 it('Should fail with an incorrect sort', async function () {
263 await checkBadSortPagination(server.url, path, server.accessToken)
264 })
265
266 it('Should fail with an invalid plugin type', async function () {
267 const query = { ...baseQuery, pluginType: 5 }
268
269 await makeGetRequest({
270 url: server.url,
271 path,
272 token: server.accessToken,
273 query
274 })
275 })
276
277 it('Should success with the correct parameters', async function () {
278 await makeGetRequest({
279 url: server.url,
280 path,
281 token: server.accessToken,
282 query: baseQuery,
283 expectedStatus: HttpStatusCode.OK_200
284 })
285 })
286 })
287
288 describe('When getting a plugin or the registered settings or public settings', function () {
289 const path = '/api/v1/plugins/'
290
291 it('Should fail with an invalid token', async function () {
292 for (const suffix of [ npmPlugin, `${npmPlugin}/registered-settings` ]) {
293 await makeGetRequest({
294 url: server.url,
295 path: path + suffix,
296 token: 'fake_token',
297 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
298 })
299 }
300 })
301
302 it('Should fail if the user is not an administrator', async function () {
303 for (const suffix of [ npmPlugin, `${npmPlugin}/registered-settings` ]) {
304 await makeGetRequest({
305 url: server.url,
306 path: path + suffix,
307 token: userAccessToken,
308 expectedStatus: HttpStatusCode.FORBIDDEN_403
309 })
310 }
311 })
312
313 it('Should fail with an invalid npm name', async function () {
314 for (const suffix of [ 'toto', 'toto/registered-settings', 'toto/public-settings' ]) {
315 await makeGetRequest({
316 url: server.url,
317 path: path + suffix,
318 token: server.accessToken,
319 expectedStatus: HttpStatusCode.BAD_REQUEST_400
320 })
321 }
322
323 for (const suffix of [ 'peertube-plugin-TOTO', 'peertube-plugin-TOTO/registered-settings', 'peertube-plugin-TOTO/public-settings' ]) {
324 await makeGetRequest({
325 url: server.url,
326 path: path + suffix,
327 token: server.accessToken,
328 expectedStatus: HttpStatusCode.BAD_REQUEST_400
329 })
330 }
331 })
332
333 it('Should fail with an unknown plugin', async function () {
334 for (const suffix of [ 'peertube-plugin-toto', 'peertube-plugin-toto/registered-settings', 'peertube-plugin-toto/public-settings' ]) {
335 await makeGetRequest({
336 url: server.url,
337 path: path + suffix,
338 token: server.accessToken,
339 expectedStatus: HttpStatusCode.NOT_FOUND_404
340 })
341 }
342 })
343
344 it('Should succeed with the correct parameters', async function () {
345 for (const suffix of [ npmPlugin, `${npmPlugin}/registered-settings`, `${npmPlugin}/public-settings` ]) {
346 await makeGetRequest({
347 url: server.url,
348 path: path + suffix,
349 token: server.accessToken,
350 expectedStatus: HttpStatusCode.OK_200
351 })
352 }
353 })
354 })
355
356 describe('When updating plugin settings', function () {
357 const path = '/api/v1/plugins/'
358 const settings = { setting1: 'value1' }
359
360 it('Should fail with an invalid token', async function () {
361 await makePutBodyRequest({
362 url: server.url,
363 path: path + npmPlugin + '/settings',
364 fields: { settings },
365 token: 'fake_token',
366 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
367 })
368 })
369
370 it('Should fail if the user is not an administrator', async function () {
371 await makePutBodyRequest({
372 url: server.url,
373 path: path + npmPlugin + '/settings',
374 fields: { settings },
375 token: userAccessToken,
376 expectedStatus: HttpStatusCode.FORBIDDEN_403
377 })
378 })
379
380 it('Should fail with an invalid npm name', async function () {
381 await makePutBodyRequest({
382 url: server.url,
383 path: path + 'toto/settings',
384 fields: { settings },
385 token: server.accessToken,
386 expectedStatus: HttpStatusCode.BAD_REQUEST_400
387 })
388
389 await makePutBodyRequest({
390 url: server.url,
391 path: path + 'peertube-plugin-TOTO/settings',
392 fields: { settings },
393 token: server.accessToken,
394 expectedStatus: HttpStatusCode.BAD_REQUEST_400
395 })
396 })
397
398 it('Should fail with an unknown plugin', async function () {
399 await makePutBodyRequest({
400 url: server.url,
401 path: path + 'peertube-plugin-toto/settings',
402 fields: { settings },
403 token: server.accessToken,
404 expectedStatus: HttpStatusCode.NOT_FOUND_404
405 })
406 })
407
408 it('Should succeed with the correct parameters', async function () {
409 await makePutBodyRequest({
410 url: server.url,
411 path: path + npmPlugin + '/settings',
412 fields: { settings },
413 token: server.accessToken,
414 expectedStatus: HttpStatusCode.NO_CONTENT_204
415 })
416 })
417 })
418
419 describe('When installing/updating/uninstalling a plugin', function () {
420 const path = '/api/v1/plugins/'
421
422 it('Should fail with an invalid token', async function () {
423 for (const suffix of [ 'install', 'update', 'uninstall' ]) {
424 await makePostBodyRequest({
425 url: server.url,
426 path: path + suffix,
427 fields: { npmName: npmPlugin },
428 token: 'fake_token',
429 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
430 })
431 }
432 })
433
434 it('Should fail if the user is not an administrator', async function () {
435 for (const suffix of [ 'install', 'update', 'uninstall' ]) {
436 await makePostBodyRequest({
437 url: server.url,
438 path: path + suffix,
439 fields: { npmName: npmPlugin },
440 token: userAccessToken,
441 expectedStatus: HttpStatusCode.FORBIDDEN_403
442 })
443 }
444 })
445
446 it('Should fail with an invalid npm name', async function () {
447 for (const suffix of [ 'install', 'update', 'uninstall' ]) {
448 await makePostBodyRequest({
449 url: server.url,
450 path: path + suffix,
451 fields: { npmName: 'toto' },
452 token: server.accessToken,
453 expectedStatus: HttpStatusCode.BAD_REQUEST_400
454 })
455 }
456
457 for (const suffix of [ 'install', 'update', 'uninstall' ]) {
458 await makePostBodyRequest({
459 url: server.url,
460 path: path + suffix,
461 fields: { npmName: 'peertube-plugin-TOTO' },
462 token: server.accessToken,
463 expectedStatus: HttpStatusCode.BAD_REQUEST_400
464 })
465 }
466 })
467
468 it('Should succeed with the correct parameters', async function () {
469 const it = [
470 { suffix: 'install', status: HttpStatusCode.OK_200 },
471 { suffix: 'update', status: HttpStatusCode.OK_200 },
472 { suffix: 'uninstall', status: HttpStatusCode.NO_CONTENT_204 }
473 ]
474
475 for (const obj of it) {
476 await makePostBodyRequest({
477 url: server.url,
478 path: path + obj.suffix,
479 fields: { npmName: npmPlugin },
480 token: server.accessToken,
481 expectedStatus: obj.status
482 })
483 }
484 })
485 })
486
487 after(async function () {
488 await cleanupTests([ server ])
489 })
490})
diff --git a/packages/tests/src/api/check-params/redundancy.ts b/packages/tests/src/api/check-params/redundancy.ts
new file mode 100644
index 000000000..16a5d0a3d
--- /dev/null
+++ b/packages/tests/src/api/check-params/redundancy.ts
@@ -0,0 +1,240 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { HttpStatusCode, VideoCreateResult } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 makeDeleteRequest,
10 makeGetRequest,
11 makePostBodyRequest,
12 makePutBodyRequest,
13 PeerTubeServer,
14 setAccessTokensToServers,
15 waitJobs
16} from '@peertube/peertube-server-commands'
17
18describe('Test server redundancy API validators', function () {
19 let servers: PeerTubeServer[]
20 let userAccessToken = null
21 let videoIdLocal: number
22 let videoRemote: VideoCreateResult
23
24 // ---------------------------------------------------------------
25
26 before(async function () {
27 this.timeout(160000)
28
29 servers = await createMultipleServers(2)
30
31 await setAccessTokensToServers(servers)
32 await doubleFollow(servers[0], servers[1])
33
34 const user = {
35 username: 'user1',
36 password: 'password'
37 }
38
39 await servers[0].users.create({ username: user.username, password: user.password })
40 userAccessToken = await servers[0].login.getAccessToken(user)
41
42 videoIdLocal = (await servers[0].videos.quickUpload({ name: 'video' })).id
43
44 const remoteUUID = (await servers[1].videos.quickUpload({ name: 'video' })).uuid
45
46 await waitJobs(servers)
47
48 videoRemote = await servers[0].videos.get({ id: remoteUUID })
49 })
50
51 describe('When listing redundancies', function () {
52 const path = '/api/v1/server/redundancy/videos'
53
54 let url: string
55 let token: string
56
57 before(function () {
58 url = servers[0].url
59 token = servers[0].accessToken
60 })
61
62 it('Should fail with an invalid token', async function () {
63 await makeGetRequest({ url, path, token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
64 })
65
66 it('Should fail if the user is not an administrator', async function () {
67 await makeGetRequest({ url, path, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
68 })
69
70 it('Should fail with a bad start pagination', async function () {
71 await checkBadStartPagination(url, path, servers[0].accessToken)
72 })
73
74 it('Should fail with a bad count pagination', async function () {
75 await checkBadCountPagination(url, path, servers[0].accessToken)
76 })
77
78 it('Should fail with an incorrect sort', async function () {
79 await checkBadSortPagination(url, path, servers[0].accessToken)
80 })
81
82 it('Should fail with a bad target', async function () {
83 await makeGetRequest({ url, path, token, query: { target: 'bad target' } })
84 })
85
86 it('Should fail without target', async function () {
87 await makeGetRequest({ url, path, token })
88 })
89
90 it('Should succeed with the correct params', async function () {
91 await makeGetRequest({ url, path, token, query: { target: 'my-videos' }, expectedStatus: HttpStatusCode.OK_200 })
92 })
93 })
94
95 describe('When manually adding a redundancy', function () {
96 const path = '/api/v1/server/redundancy/videos'
97
98 let url: string
99 let token: string
100
101 before(function () {
102 url = servers[0].url
103 token = servers[0].accessToken
104 })
105
106 it('Should fail with an invalid token', async function () {
107 await makePostBodyRequest({ url, path, token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
108 })
109
110 it('Should fail if the user is not an administrator', async function () {
111 await makePostBodyRequest({ url, path, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
112 })
113
114 it('Should fail without a video id', async function () {
115 await makePostBodyRequest({ url, path, token })
116 })
117
118 it('Should fail with an incorrect video id', async function () {
119 await makePostBodyRequest({ url, path, token, fields: { videoId: 'peertube' } })
120 })
121
122 it('Should fail with a not found video id', async function () {
123 await makePostBodyRequest({ url, path, token, fields: { videoId: 6565 }, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
124 })
125
126 it('Should fail with a local a video id', async function () {
127 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdLocal } })
128 })
129
130 it('Should succeed with the correct params', async function () {
131 await makePostBodyRequest({
132 url,
133 path,
134 token,
135 fields: { videoId: videoRemote.shortUUID },
136 expectedStatus: HttpStatusCode.NO_CONTENT_204
137 })
138 })
139
140 it('Should fail if the video is already duplicated', async function () {
141 this.timeout(30000)
142
143 await waitJobs(servers)
144
145 await makePostBodyRequest({
146 url,
147 path,
148 token,
149 fields: { videoId: videoRemote.uuid },
150 expectedStatus: HttpStatusCode.CONFLICT_409
151 })
152 })
153 })
154
155 describe('When manually removing a redundancy', function () {
156 const path = '/api/v1/server/redundancy/videos/'
157
158 let url: string
159 let token: string
160
161 before(function () {
162 url = servers[0].url
163 token = servers[0].accessToken
164 })
165
166 it('Should fail with an invalid token', async function () {
167 await makeDeleteRequest({ url, path: path + '1', token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
168 })
169
170 it('Should fail if the user is not an administrator', async function () {
171 await makeDeleteRequest({ url, path: path + '1', token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
172 })
173
174 it('Should fail with an incorrect video id', async function () {
175 await makeDeleteRequest({ url, path: path + 'toto', token })
176 })
177
178 it('Should fail with a not found video redundancy', async function () {
179 await makeDeleteRequest({ url, path: path + '454545', token, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
180 })
181 })
182
183 describe('When updating server redundancy', function () {
184 const path = '/api/v1/server/redundancy'
185
186 it('Should fail with an invalid token', async function () {
187 await makePutBodyRequest({
188 url: servers[0].url,
189 path: path + '/' + servers[1].host,
190 fields: { redundancyAllowed: true },
191 token: 'fake_token',
192 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
193 })
194 })
195
196 it('Should fail if the user is not an administrator', async function () {
197 await makePutBodyRequest({
198 url: servers[0].url,
199 path: path + '/' + servers[1].host,
200 fields: { redundancyAllowed: true },
201 token: userAccessToken,
202 expectedStatus: HttpStatusCode.FORBIDDEN_403
203 })
204 })
205
206 it('Should fail if we do not follow this server', async function () {
207 await makePutBodyRequest({
208 url: servers[0].url,
209 path: path + '/example.com',
210 fields: { redundancyAllowed: true },
211 token: servers[0].accessToken,
212 expectedStatus: HttpStatusCode.NOT_FOUND_404
213 })
214 })
215
216 it('Should fail without de redundancyAllowed param', async function () {
217 await makePutBodyRequest({
218 url: servers[0].url,
219 path: path + '/' + servers[1].host,
220 fields: { blabla: true },
221 token: servers[0].accessToken,
222 expectedStatus: HttpStatusCode.BAD_REQUEST_400
223 })
224 })
225
226 it('Should succeed with the correct parameters', async function () {
227 await makePutBodyRequest({
228 url: servers[0].url,
229 path: path + '/' + servers[1].host,
230 fields: { redundancyAllowed: true },
231 token: servers[0].accessToken,
232 expectedStatus: HttpStatusCode.NO_CONTENT_204
233 })
234 })
235 })
236
237 after(async function () {
238 await cleanupTests(servers)
239 })
240})
diff --git a/packages/tests/src/api/check-params/registrations.ts b/packages/tests/src/api/check-params/registrations.ts
new file mode 100644
index 000000000..e4e46da2a
--- /dev/null
+++ b/packages/tests/src/api/check-params/registrations.ts
@@ -0,0 +1,446 @@
1import { omit } from '@peertube/peertube-core-utils'
2import { HttpStatusCode, HttpStatusCodeType, UserRole } from '@peertube/peertube-models'
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import {
5 cleanupTests,
6 createSingleServer,
7 makePostBodyRequest,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 setDefaultAccountAvatar,
11 setDefaultChannelAvatar
12} from '@peertube/peertube-server-commands'
13
14describe('Test registrations API validators', function () {
15 let server: PeerTubeServer
16 let userToken: string
17 let moderatorToken: string
18
19 // ---------------------------------------------------------------
20
21 before(async function () {
22 this.timeout(30000)
23
24 server = await createSingleServer(1)
25
26 await setAccessTokensToServers([ server ])
27 await setDefaultAccountAvatar([ server ])
28 await setDefaultChannelAvatar([ server ])
29
30 await server.config.enableSignup(false);
31
32 ({ token: moderatorToken } = await server.users.generate('moderator', UserRole.MODERATOR));
33 ({ token: userToken } = await server.users.generate('user', UserRole.USER))
34 })
35
36 describe('Register', function () {
37 const registrationPath = '/api/v1/users/register'
38 const registrationRequestPath = '/api/v1/users/registrations/request'
39
40 const baseCorrectParams = {
41 username: 'user3',
42 displayName: 'super user',
43 email: 'test3@example.com',
44 password: 'my super password',
45 registrationReason: 'my super registration reason'
46 }
47
48 describe('When registering a new user or requesting user registration', function () {
49
50 async function check (fields: any, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
51 await server.config.enableSignup(false)
52 await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus })
53
54 await server.config.enableSignup(true)
55 await makePostBodyRequest({ url: server.url, path: registrationRequestPath, fields, expectedStatus })
56 }
57
58 it('Should fail with a too small username', async function () {
59 const fields = { ...baseCorrectParams, username: '' }
60
61 await check(fields)
62 })
63
64 it('Should fail with a too long username', async function () {
65 const fields = { ...baseCorrectParams, username: 'super'.repeat(50) }
66
67 await check(fields)
68 })
69
70 it('Should fail with an incorrect username', async function () {
71 const fields = { ...baseCorrectParams, username: 'my username' }
72
73 await check(fields)
74 })
75
76 it('Should fail with a missing email', async function () {
77 const fields = omit(baseCorrectParams, [ 'email' ])
78
79 await check(fields)
80 })
81
82 it('Should fail with an invalid email', async function () {
83 const fields = { ...baseCorrectParams, email: 'test_example.com' }
84
85 await check(fields)
86 })
87
88 it('Should fail with a too small password', async function () {
89 const fields = { ...baseCorrectParams, password: 'bla' }
90
91 await check(fields)
92 })
93
94 it('Should fail with a too long password', async function () {
95 const fields = { ...baseCorrectParams, password: 'super'.repeat(61) }
96
97 await check(fields)
98 })
99
100 it('Should fail if we register a user with the same username', async function () {
101 const fields = { ...baseCorrectParams, username: 'root' }
102
103 await check(fields, HttpStatusCode.CONFLICT_409)
104 })
105
106 it('Should fail with a "peertube" username', async function () {
107 const fields = { ...baseCorrectParams, username: 'peertube' }
108
109 await check(fields, HttpStatusCode.CONFLICT_409)
110 })
111
112 it('Should fail if we register a user with the same email', async function () {
113 const fields = { ...baseCorrectParams, email: 'admin' + server.internalServerNumber + '@example.com' }
114
115 await check(fields, HttpStatusCode.CONFLICT_409)
116 })
117
118 it('Should fail with a bad display name', async function () {
119 const fields = { ...baseCorrectParams, displayName: 'a'.repeat(150) }
120
121 await check(fields)
122 })
123
124 it('Should fail with a bad channel name', async function () {
125 const fields = { ...baseCorrectParams, channel: { name: '[]azf', displayName: 'toto' } }
126
127 await check(fields)
128 })
129
130 it('Should fail with a bad channel display name', async function () {
131 const fields = { ...baseCorrectParams, channel: { name: 'toto', displayName: '' } }
132
133 await check(fields)
134 })
135
136 it('Should fail with a channel name that is the same as username', async function () {
137 const source = { username: 'super_user', channel: { name: 'super_user', displayName: 'display name' } }
138 const fields = { ...baseCorrectParams, ...source }
139
140 await check(fields)
141 })
142
143 it('Should fail with an existing channel', async function () {
144 const attributes = { name: 'existing_channel', displayName: 'hello', description: 'super description' }
145 await server.channels.create({ attributes })
146
147 const fields = { ...baseCorrectParams, channel: { name: 'existing_channel', displayName: 'toto' } }
148
149 await check(fields, HttpStatusCode.CONFLICT_409)
150 })
151
152 it('Should fail on a server with registration disabled', async function () {
153 this.timeout(60000)
154
155 await server.config.updateExistingSubConfig({
156 newConfig: {
157 signup: {
158 enabled: false
159 }
160 }
161 })
162
163 await server.registrations.register({ username: 'user4', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
164 await server.registrations.requestRegistration({
165 username: 'user4',
166 registrationReason: 'reason',
167 expectedStatus: HttpStatusCode.FORBIDDEN_403
168 })
169 })
170
171 it('Should fail if the user limit is reached', async function () {
172 this.timeout(60000)
173
174 const { total } = await server.users.list()
175
176 await server.config.enableSignup(false, total)
177 await server.registrations.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
178
179 await server.config.enableSignup(true, total)
180 await server.registrations.requestRegistration({
181 username: 'user42',
182 registrationReason: 'reason',
183 expectedStatus: HttpStatusCode.FORBIDDEN_403
184 })
185 })
186
187 it('Should succeed if the user limit is not reached', async function () {
188 this.timeout(60000)
189
190 const { total } = await server.users.list()
191
192 await server.config.enableSignup(false, total + 1)
193 await server.registrations.register({ username: 'user43', expectedStatus: HttpStatusCode.NO_CONTENT_204 })
194
195 await server.config.enableSignup(true, total + 2)
196 await server.registrations.requestRegistration({
197 username: 'user44',
198 registrationReason: 'reason',
199 expectedStatus: HttpStatusCode.OK_200
200 })
201 })
202 })
203
204 describe('On direct registration', function () {
205
206 it('Should succeed with the correct params', async function () {
207 await server.config.enableSignup(false)
208
209 const fields = {
210 username: 'user_direct_1',
211 displayName: 'super user direct 1',
212 email: 'user_direct_1@example.com',
213 password: 'my super password',
214 channel: { name: 'super_user_direct_1_channel', displayName: 'super user direct 1 channel' }
215 }
216
217 await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
218 })
219
220 it('Should fail if the instance requires approval', async function () {
221 this.timeout(60000)
222
223 await server.config.enableSignup(true)
224 await server.registrations.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
225 })
226 })
227
228 describe('On registration request', function () {
229
230 before(async function () {
231 this.timeout(60000)
232
233 await server.config.enableSignup(true)
234 })
235
236 it('Should fail with an invalid registration reason', async function () {
237 for (const registrationReason of [ '', 't', 't'.repeat(5000) ]) {
238 await server.registrations.requestRegistration({
239 username: 'user_request_1',
240 registrationReason,
241 expectedStatus: HttpStatusCode.BAD_REQUEST_400
242 })
243 }
244 })
245
246 it('Should succeed with the correct params', async function () {
247 await server.registrations.requestRegistration({
248 username: 'user_request_2',
249 registrationReason: 'tt',
250 channel: {
251 displayName: 'my user request 2 channel',
252 name: 'user_request_2_channel'
253 }
254 })
255 })
256
257 it('Should fail if the username is already awaiting registration approval', async function () {
258 await server.registrations.requestRegistration({
259 username: 'user_request_2',
260 registrationReason: 'tt',
261 channel: {
262 displayName: 'my user request 42 channel',
263 name: 'user_request_42_channel'
264 },
265 expectedStatus: HttpStatusCode.CONFLICT_409
266 })
267 })
268
269 it('Should fail if the email is already awaiting registration approval', async function () {
270 await server.registrations.requestRegistration({
271 username: 'user42',
272 email: 'user_request_2@example.com',
273 registrationReason: 'tt',
274 channel: {
275 displayName: 'my user request 42 channel',
276 name: 'user_request_42_channel'
277 },
278 expectedStatus: HttpStatusCode.CONFLICT_409
279 })
280 })
281
282 it('Should fail if the channel is already awaiting registration approval', async function () {
283 await server.registrations.requestRegistration({
284 username: 'user42',
285 registrationReason: 'tt',
286 channel: {
287 displayName: 'my user request 2 channel',
288 name: 'user_request_2_channel'
289 },
290 expectedStatus: HttpStatusCode.CONFLICT_409
291 })
292 })
293
294 it('Should fail if the instance does not require approval', async function () {
295 this.timeout(60000)
296
297 await server.config.enableSignup(false)
298
299 await server.registrations.requestRegistration({
300 username: 'user42',
301 registrationReason: 'toto',
302 expectedStatus: HttpStatusCode.BAD_REQUEST_400
303 })
304 })
305 })
306 })
307
308 describe('Registrations accept/reject', function () {
309 let id1: number
310 let id2: number
311
312 before(async function () {
313 this.timeout(60000)
314
315 await server.config.enableSignup(true);
316
317 ({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_2', registrationReason: 'toto' }));
318 ({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_3', registrationReason: 'toto' }))
319 })
320
321 it('Should fail to accept/reject registration without token', async function () {
322 const options = { id: id1, moderationResponse: 'tt', token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }
323 await server.registrations.accept(options)
324 await server.registrations.reject(options)
325 })
326
327 it('Should fail to accept/reject registration with a non moderator user', async function () {
328 const options = { id: id1, moderationResponse: 'tt', token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }
329 await server.registrations.accept(options)
330 await server.registrations.reject(options)
331 })
332
333 it('Should fail to accept/reject registration with a bad registration id', async function () {
334 {
335 const options = { id: 't' as any, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
336 await server.registrations.accept(options)
337 await server.registrations.reject(options)
338 }
339
340 {
341 const options = { id: 42, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
342 await server.registrations.accept(options)
343 await server.registrations.reject(options)
344 }
345 })
346
347 it('Should fail to accept/reject registration with a bad moderation resposne', async function () {
348 for (const moderationResponse of [ '', 't', 't'.repeat(5000) ]) {
349 const options = { id: id1, moderationResponse, token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
350 await server.registrations.accept(options)
351 await server.registrations.reject(options)
352 }
353 })
354
355 it('Should succeed to accept a registration', async function () {
356 await server.registrations.accept({ id: id1, moderationResponse: 'tt', token: moderatorToken })
357 })
358
359 it('Should succeed to reject a registration', async function () {
360 await server.registrations.reject({ id: id2, moderationResponse: 'tt', token: moderatorToken })
361 })
362
363 it('Should fail to accept/reject a registration that was already accepted/rejected', async function () {
364 for (const id of [ id1, id2 ]) {
365 const options = { id, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.CONFLICT_409 }
366 await server.registrations.accept(options)
367 await server.registrations.reject(options)
368 }
369 })
370 })
371
372 describe('Registrations deletion', function () {
373 let id1: number
374 let id2: number
375 let id3: number
376
377 before(async function () {
378 ({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_4', registrationReason: 'toto' }));
379 ({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_5', registrationReason: 'toto' }));
380 ({ id: id3 } = await server.registrations.requestRegistration({ username: 'request_6', registrationReason: 'toto' }))
381
382 await server.registrations.accept({ id: id2, moderationResponse: 'tt' })
383 await server.registrations.reject({ id: id3, moderationResponse: 'tt' })
384 })
385
386 it('Should fail to delete registration without token', async function () {
387 await server.registrations.delete({ id: id1, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
388 })
389
390 it('Should fail to delete registration with a non moderator user', async function () {
391 await server.registrations.delete({ id: id1, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
392 })
393
394 it('Should fail to delete registration with a bad registration id', async function () {
395 await server.registrations.delete({ id: 't' as any, token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
396 await server.registrations.delete({ id: 42, token: moderatorToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
397 })
398
399 it('Should succeed with the correct params', async function () {
400 await server.registrations.delete({ id: id1, token: moderatorToken })
401 await server.registrations.delete({ id: id2, token: moderatorToken })
402 await server.registrations.delete({ id: id3, token: moderatorToken })
403 })
404 })
405
406 describe('Listing registrations', function () {
407 const path = '/api/v1/users/registrations'
408
409 it('Should fail with a bad start pagination', async function () {
410 await checkBadStartPagination(server.url, path, server.accessToken)
411 })
412
413 it('Should fail with a bad count pagination', async function () {
414 await checkBadCountPagination(server.url, path, server.accessToken)
415 })
416
417 it('Should fail with an incorrect sort', async function () {
418 await checkBadSortPagination(server.url, path, server.accessToken)
419 })
420
421 it('Should fail with a non authenticated user', async function () {
422 await server.registrations.list({
423 token: null,
424 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
425 })
426 })
427
428 it('Should fail with a non admin user', async function () {
429 await server.registrations.list({
430 token: userToken,
431 expectedStatus: HttpStatusCode.FORBIDDEN_403
432 })
433 })
434
435 it('Should succeed with the correct params', async function () {
436 await server.registrations.list({
437 token: moderatorToken,
438 search: 'toto'
439 })
440 })
441 })
442
443 after(async function () {
444 await cleanupTests([ server ])
445 })
446})
diff --git a/packages/tests/src/api/check-params/runners.ts b/packages/tests/src/api/check-params/runners.ts
new file mode 100644
index 000000000..dd2d2f0a1
--- /dev/null
+++ b/packages/tests/src/api/check-params/runners.ts
@@ -0,0 +1,911 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2import { basename } from 'path'
3import {
4 HttpStatusCode,
5 HttpStatusCodeType,
6 isVideoStudioTaskIntro,
7 RunnerJob,
8 RunnerJobState,
9 RunnerJobStudioTranscodingPayload,
10 RunnerJobSuccessPayload,
11 RunnerJobUpdatePayload,
12 VideoPrivacy,
13 VideoStudioTaskIntro
14} from '@peertube/peertube-models'
15import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
16import {
17 cleanupTests,
18 createSingleServer,
19 makePostBodyRequest,
20 PeerTubeServer,
21 sendRTMPStream,
22 setAccessTokensToServers,
23 setDefaultVideoChannel,
24 stopFfmpeg,
25 VideoStudioCommand,
26 waitJobs
27} from '@peertube/peertube-server-commands'
28
29const badUUID = '910ec12a-d9e6-458b-a274-0abb655f9464'
30
31describe('Test managing runners', function () {
32 let server: PeerTubeServer
33
34 let userToken: string
35
36 let registrationTokenId: number
37 let registrationToken: string
38
39 let runnerToken: string
40 let runnerToken2: string
41
42 let completedJobToken: string
43 let completedJobUUID: string
44
45 let cancelledJobToken: string
46 let cancelledJobUUID: string
47
48 before(async function () {
49 this.timeout(120000)
50
51 const config = {
52 rates_limit: {
53 api: {
54 max: 5000
55 }
56 }
57 }
58
59 server = await createSingleServer(1, config)
60 await setAccessTokensToServers([ server ])
61 await setDefaultVideoChannel([ server ])
62
63 userToken = await server.users.generateUserAndToken('user1')
64
65 const { data } = await server.runnerRegistrationTokens.list()
66 registrationToken = data[0].registrationToken
67 registrationTokenId = data[0].id
68
69 await server.config.enableTranscoding({ hls: true, webVideo: true })
70 await server.config.enableStudio()
71 await server.config.enableRemoteTranscoding()
72 await server.config.enableRemoteStudio()
73
74 runnerToken = await server.runners.autoRegisterRunner()
75 runnerToken2 = await server.runners.autoRegisterRunner()
76
77 {
78 await server.videos.quickUpload({ name: 'video 1' })
79 await server.videos.quickUpload({ name: 'video 2' })
80
81 await waitJobs([ server ])
82
83 {
84 const job = await server.runnerJobs.autoProcessWebVideoJob(runnerToken)
85 completedJobToken = job.jobToken
86 completedJobUUID = job.uuid
87 }
88
89 {
90 const { job } = await server.runnerJobs.autoAccept({ runnerToken })
91 cancelledJobToken = job.jobToken
92 cancelledJobUUID = job.uuid
93 await server.runnerJobs.cancelByAdmin({ jobUUID: cancelledJobUUID })
94 }
95 }
96 })
97
98 describe('Managing runner registration tokens', function () {
99
100 describe('Common', function () {
101
102 it('Should fail to generate, list or delete runner registration token without oauth token', async function () {
103 const expectedStatus = HttpStatusCode.UNAUTHORIZED_401
104
105 await server.runnerRegistrationTokens.generate({ token: null, expectedStatus })
106 await server.runnerRegistrationTokens.list({ token: null, expectedStatus })
107 await server.runnerRegistrationTokens.delete({ token: null, id: registrationTokenId, expectedStatus })
108 })
109
110 it('Should fail to generate, list or delete runner registration token without admin rights', async function () {
111 const expectedStatus = HttpStatusCode.FORBIDDEN_403
112
113 await server.runnerRegistrationTokens.generate({ token: userToken, expectedStatus })
114 await server.runnerRegistrationTokens.list({ token: userToken, expectedStatus })
115 await server.runnerRegistrationTokens.delete({ token: userToken, id: registrationTokenId, expectedStatus })
116 })
117 })
118
119 describe('Delete', function () {
120
121 it('Should fail to delete with a bad id', async function () {
122 await server.runnerRegistrationTokens.delete({ id: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
123 })
124 })
125
126 describe('List', function () {
127 const path = '/api/v1/runners/registration-tokens'
128
129 it('Should fail to list with a bad start pagination', async function () {
130 await checkBadStartPagination(server.url, path, server.accessToken)
131 })
132
133 it('Should fail to list with a bad count pagination', async function () {
134 await checkBadCountPagination(server.url, path, server.accessToken)
135 })
136
137 it('Should fail to list with an incorrect sort', async function () {
138 await checkBadSortPagination(server.url, path, server.accessToken)
139 })
140
141 it('Should succeed to list with the correct params', async function () {
142 await server.runnerRegistrationTokens.list({ start: 0, count: 5, sort: '-createdAt' })
143 })
144 })
145 })
146
147 describe('Managing runners', function () {
148 let toDeleteId: number
149
150 describe('Register', function () {
151 const name = 'runner name'
152
153 it('Should fail with a bad registration token', async function () {
154 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
155
156 await server.runners.register({ name, registrationToken: 'a'.repeat(4000), expectedStatus })
157 await server.runners.register({ name, registrationToken: null, expectedStatus })
158 })
159
160 it('Should fail with an unknown registration token', async function () {
161 await server.runners.register({ name, registrationToken: 'aaa', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
162 })
163
164 it('Should fail with a bad name', async function () {
165 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
166
167 await server.runners.register({ name: '', registrationToken, expectedStatus })
168 await server.runners.register({ name: 'a'.repeat(200), registrationToken, expectedStatus })
169 })
170
171 it('Should fail with an invalid description', async function () {
172 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
173
174 await server.runners.register({ name, description: '', registrationToken, expectedStatus })
175 await server.runners.register({ name, description: 'a'.repeat(5000), registrationToken, expectedStatus })
176 })
177
178 it('Should succeed with the correct params', async function () {
179 const { id } = await server.runners.register({ name, description: 'super description', registrationToken })
180
181 toDeleteId = id
182 })
183
184 it('Should fail with the same runner name', async function () {
185 await server.runners.register({
186 name,
187 description: 'super description',
188 registrationToken,
189 expectedStatus: HttpStatusCode.BAD_REQUEST_400
190 })
191 })
192 })
193
194 describe('Delete', function () {
195
196 it('Should fail without oauth token', async function () {
197 await server.runners.delete({ token: null, id: toDeleteId, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
198 })
199
200 it('Should fail without admin rights', async function () {
201 await server.runners.delete({ token: userToken, id: toDeleteId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
202 })
203
204 it('Should fail with a bad id', async function () {
205 await server.runners.delete({ id: 'hi' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
206 })
207
208 it('Should fail with an unknown id', async function () {
209 await server.runners.delete({ id: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
210 })
211
212 it('Should succeed with the correct params', async function () {
213 await server.runners.delete({ id: toDeleteId })
214 })
215 })
216
217 describe('List', function () {
218 const path = '/api/v1/runners'
219
220 it('Should fail without oauth token', async function () {
221 await server.runners.list({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
222 })
223
224 it('Should fail without admin rights', async function () {
225 await server.runners.list({ token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
226 })
227
228 it('Should fail to list with a bad start pagination', async function () {
229 await checkBadStartPagination(server.url, path, server.accessToken)
230 })
231
232 it('Should fail to list with a bad count pagination', async function () {
233 await checkBadCountPagination(server.url, path, server.accessToken)
234 })
235
236 it('Should fail to list with an incorrect sort', async function () {
237 await checkBadSortPagination(server.url, path, server.accessToken)
238 })
239
240 it('Should fail with an invalid state', async function () {
241 await server.runners.list({ start: 0, count: 5, sort: '-createdAt' })
242 })
243
244 it('Should succeed to list with the correct params', async function () {
245 await server.runners.list({ start: 0, count: 5, sort: '-createdAt' })
246 })
247 })
248
249 })
250
251 describe('Runner jobs by admin', function () {
252
253 describe('Cancel', function () {
254 let jobUUID: string
255
256 before(async function () {
257 this.timeout(60000)
258
259 await server.videos.quickUpload({ name: 'video' })
260 await waitJobs([ server ])
261
262 const { availableJobs } = await server.runnerJobs.request({ runnerToken })
263 jobUUID = availableJobs[0].uuid
264 })
265
266 it('Should fail without oauth token', async function () {
267 await server.runnerJobs.cancelByAdmin({ token: null, jobUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
268 })
269
270 it('Should fail without admin rights', async function () {
271 await server.runnerJobs.cancelByAdmin({ token: userToken, jobUUID, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
272 })
273
274 it('Should fail with a bad job uuid', async function () {
275 await server.runnerJobs.cancelByAdmin({ jobUUID: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
276 })
277
278 it('Should fail with an unknown job uuid', async function () {
279 const jobUUID = badUUID
280 await server.runnerJobs.cancelByAdmin({ jobUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
281 })
282
283 it('Should fail with an already cancelled job', async function () {
284 await server.runnerJobs.cancelByAdmin({ jobUUID: cancelledJobUUID, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
285 })
286
287 it('Should succeed with the correct params', async function () {
288 await server.runnerJobs.cancelByAdmin({ jobUUID })
289 })
290 })
291
292 describe('List', function () {
293 const path = '/api/v1/runners/jobs'
294
295 it('Should fail without oauth token', async function () {
296 await server.runnerJobs.list({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
297 })
298
299 it('Should fail without admin rights', async function () {
300 await server.runnerJobs.list({ token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
301 })
302
303 it('Should fail to list with a bad start pagination', async function () {
304 await checkBadStartPagination(server.url, path, server.accessToken)
305 })
306
307 it('Should fail to list with a bad count pagination', async function () {
308 await checkBadCountPagination(server.url, path, server.accessToken)
309 })
310
311 it('Should fail to list with an incorrect sort', async function () {
312 await checkBadSortPagination(server.url, path, server.accessToken)
313 })
314
315 it('Should fail with an invalid state', async function () {
316 await server.runnerJobs.list({ start: 0, count: 5, sort: '-createdAt', stateOneOf: 42 as any })
317 await server.runnerJobs.list({ start: 0, count: 5, sort: '-createdAt', stateOneOf: [ 42 ] as any })
318 })
319
320 it('Should succeed with the correct params', async function () {
321 await server.runnerJobs.list({ start: 0, count: 5, sort: '-createdAt', stateOneOf: [ RunnerJobState.COMPLETED ] })
322 })
323 })
324
325 describe('Delete', function () {
326 let jobUUID: string
327
328 before(async function () {
329 this.timeout(60000)
330
331 await server.videos.quickUpload({ name: 'video' })
332 await waitJobs([ server ])
333
334 const { availableJobs } = await server.runnerJobs.request({ runnerToken })
335 jobUUID = availableJobs[0].uuid
336 })
337
338 it('Should fail without oauth token', async function () {
339 await server.runnerJobs.deleteByAdmin({ token: null, jobUUID, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
340 })
341
342 it('Should fail without admin rights', async function () {
343 await server.runnerJobs.deleteByAdmin({ token: userToken, jobUUID, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
344 })
345
346 it('Should fail with a bad job uuid', async function () {
347 await server.runnerJobs.deleteByAdmin({ jobUUID: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
348 })
349
350 it('Should fail with an unknown job uuid', async function () {
351 const jobUUID = badUUID
352 await server.runnerJobs.deleteByAdmin({ jobUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
353 })
354
355 it('Should succeed with the correct params', async function () {
356 await server.runnerJobs.deleteByAdmin({ jobUUID })
357 })
358 })
359
360 })
361
362 describe('Runner jobs by runners', function () {
363 let jobUUID: string
364 let jobToken: string
365 let videoUUID: string
366
367 let jobUUID2: string
368 let jobToken2: string
369
370 let videoUUID2: string
371
372 let pendingUUID: string
373
374 let videoStudioUUID: string
375 let studioFile: string
376
377 let liveAcceptedJob: RunnerJob & { jobToken: string }
378 let studioAcceptedJob: RunnerJob & { jobToken: string }
379
380 async function fetchVideoInputFiles (options: {
381 jobUUID: string
382 videoUUID: string
383 runnerToken: string
384 jobToken: string
385 expectedStatus: HttpStatusCodeType
386 }) {
387 const { jobUUID, expectedStatus, videoUUID, runnerToken, jobToken } = options
388
389 const basePath = '/api/v1/runners/jobs/' + jobUUID + '/files/videos/' + videoUUID
390 const paths = [ `${basePath}/max-quality`, `${basePath}/previews/max-quality` ]
391
392 for (const path of paths) {
393 await makePostBodyRequest({ url: server.url, path, fields: { runnerToken, jobToken }, expectedStatus })
394 }
395 }
396
397 async function fetchStudioFiles (options: {
398 jobUUID: string
399 videoUUID: string
400 runnerToken: string
401 jobToken: string
402 studioFile?: string
403 expectedStatus: HttpStatusCodeType
404 }) {
405 const { jobUUID, expectedStatus, videoUUID, runnerToken, jobToken, studioFile } = options
406
407 const path = `/api/v1/runners/jobs/${jobUUID}/files/videos/${videoUUID}/studio/task-files/${studioFile}`
408
409 await makePostBodyRequest({ url: server.url, path, fields: { runnerToken, jobToken }, expectedStatus })
410 }
411
412 before(async function () {
413 this.timeout(120000)
414
415 {
416 await server.runnerJobs.cancelAllJobs({ state: RunnerJobState.PENDING })
417 }
418
419 {
420 const { uuid } = await server.videos.quickUpload({ name: 'video' })
421 videoUUID = uuid
422
423 await waitJobs([ server ])
424
425 const { job } = await server.runnerJobs.autoAccept({ runnerToken })
426 jobUUID = job.uuid
427 jobToken = job.jobToken
428 }
429
430 {
431 const { uuid } = await server.videos.quickUpload({ name: 'video' })
432 videoUUID2 = uuid
433
434 await waitJobs([ server ])
435
436 const { job } = await server.runnerJobs.autoAccept({ runnerToken: runnerToken2 })
437 jobUUID2 = job.uuid
438 jobToken2 = job.jobToken
439 }
440
441 {
442 await server.videos.quickUpload({ name: 'video' })
443 await waitJobs([ server ])
444
445 const { availableJobs } = await server.runnerJobs.request({ runnerToken })
446 pendingUUID = availableJobs[0].uuid
447 }
448
449 {
450 await server.config.disableTranscoding()
451
452 const { uuid } = await server.videos.quickUpload({ name: 'video studio' })
453 videoStudioUUID = uuid
454
455 await server.config.enableTranscoding({ hls: true, webVideo: true })
456 await server.config.enableStudio()
457
458 await server.videoStudio.createEditionTasks({
459 videoId: videoStudioUUID,
460 tasks: VideoStudioCommand.getComplexTask()
461 })
462
463 const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'video-studio-transcoding' })
464 studioAcceptedJob = job
465
466 const tasks = (job.payload as RunnerJobStudioTranscodingPayload).tasks
467 const fileUrl = (tasks.find(t => isVideoStudioTaskIntro(t)) as VideoStudioTaskIntro).options.file as string
468 studioFile = basename(fileUrl)
469 }
470
471 {
472 await server.config.enableLive({
473 allowReplay: false,
474 resolutions: 'max',
475 transcoding: true
476 })
477
478 const { live } = await server.live.quickCreate({ permanentLive: true, saveReplay: false, privacy: VideoPrivacy.PUBLIC })
479
480 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
481 await waitJobs([ server ])
482
483 await server.runnerJobs.requestLiveJob(runnerToken)
484
485 const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'live-rtmp-hls-transcoding' })
486 liveAcceptedJob = job
487
488 await stopFfmpeg(ffmpegCommand)
489 }
490 })
491
492 describe('Common runner tokens validations', function () {
493
494 async function testEndpoints (options: {
495 jobUUID: string
496 runnerToken: string
497 jobToken: string
498 expectedStatus: HttpStatusCodeType
499 }) {
500 await server.runnerJobs.abort({ ...options, reason: 'reason' })
501 await server.runnerJobs.update({ ...options })
502 await server.runnerJobs.error({ ...options, message: 'message' })
503 await server.runnerJobs.success({ ...options, payload: { videoFile: 'video_short.mp4' } })
504 }
505
506 it('Should fail with an invalid job uuid', async function () {
507 const options = { jobUUID: 'a', runnerToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
508
509 await testEndpoints({ ...options, jobToken })
510 await fetchVideoInputFiles({ ...options, videoUUID, jobToken })
511 await fetchStudioFiles({ ...options, videoUUID, jobToken: studioAcceptedJob.jobToken, studioFile })
512 })
513
514 it('Should fail with an unknown job uuid', async function () {
515 const options = { jobUUID: badUUID, runnerToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
516
517 await testEndpoints({ ...options, jobToken })
518 await fetchVideoInputFiles({ ...options, videoUUID, jobToken })
519 await fetchStudioFiles({ ...options, jobToken: studioAcceptedJob.jobToken, videoUUID, studioFile })
520 })
521
522 it('Should fail with an invalid runner token', async function () {
523 const options = { runnerToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
524
525 await testEndpoints({ ...options, jobUUID, jobToken })
526 await fetchVideoInputFiles({ ...options, jobUUID, videoUUID, jobToken })
527 await fetchStudioFiles({
528 ...options,
529 jobToken: studioAcceptedJob.jobToken,
530 jobUUID: studioAcceptedJob.uuid,
531 videoUUID: videoStudioUUID,
532 studioFile
533 })
534 })
535
536 it('Should fail with an unknown runner token', async function () {
537 const options = { runnerToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
538
539 await testEndpoints({ ...options, jobUUID, jobToken })
540 await fetchVideoInputFiles({ ...options, jobUUID, videoUUID, jobToken })
541 await fetchStudioFiles({
542 ...options,
543 jobToken: studioAcceptedJob.jobToken,
544 jobUUID: studioAcceptedJob.uuid,
545 videoUUID: videoStudioUUID,
546 studioFile
547 })
548 })
549
550 it('Should fail with an invalid job token job uuid', async function () {
551 const options = { runnerToken, jobToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }
552
553 await testEndpoints({ ...options, jobUUID })
554 await fetchVideoInputFiles({ ...options, jobUUID, videoUUID })
555 await fetchStudioFiles({ ...options, jobUUID: studioAcceptedJob.uuid, videoUUID: videoStudioUUID, studioFile })
556 })
557
558 it('Should fail with an unknown job token job uuid', async function () {
559 const options = { runnerToken, jobToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
560
561 await testEndpoints({ ...options, jobUUID })
562 await fetchVideoInputFiles({ ...options, jobUUID, videoUUID })
563 await fetchStudioFiles({ ...options, jobUUID: studioAcceptedJob.uuid, videoUUID: videoStudioUUID, studioFile })
564 })
565
566 it('Should fail with a runner token not associated to this job', async function () {
567 const options = { runnerToken: runnerToken2, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
568
569 await testEndpoints({ ...options, jobUUID, jobToken })
570 await fetchVideoInputFiles({ ...options, jobUUID, videoUUID, jobToken })
571 await fetchStudioFiles({
572 ...options,
573 jobToken: studioAcceptedJob.jobToken,
574 jobUUID: studioAcceptedJob.uuid,
575 videoUUID: videoStudioUUID,
576 studioFile
577 })
578 })
579
580 it('Should fail with a job uuid not associated to the job token', async function () {
581 {
582 const options = { jobUUID: jobUUID2, runnerToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
583
584 await testEndpoints({ ...options, jobToken })
585 await fetchVideoInputFiles({ ...options, jobToken, videoUUID })
586 await fetchStudioFiles({ ...options, jobToken: studioAcceptedJob.jobToken, videoUUID: videoStudioUUID, studioFile })
587 }
588
589 {
590 const options = { runnerToken, jobToken: jobToken2, expectedStatus: HttpStatusCode.NOT_FOUND_404 }
591
592 await testEndpoints({ ...options, jobUUID })
593 await fetchVideoInputFiles({ ...options, jobUUID, videoUUID })
594 await fetchStudioFiles({ ...options, jobUUID: studioAcceptedJob.uuid, videoUUID: videoStudioUUID, studioFile })
595 }
596 })
597 })
598
599 describe('Unregister', function () {
600
601 it('Should fail without a runner token', async function () {
602 await server.runners.unregister({ runnerToken: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
603 })
604
605 it('Should fail with a bad a runner token', async function () {
606 await server.runners.unregister({ runnerToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
607 })
608
609 it('Should fail with an unknown runner token', async function () {
610 await server.runners.unregister({ runnerToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
611 })
612 })
613
614 describe('Request', function () {
615
616 it('Should fail without a runner token', async function () {
617 await server.runnerJobs.request({ runnerToken: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
618 })
619
620 it('Should fail with a bad a runner token', async function () {
621 await server.runnerJobs.request({ runnerToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
622 })
623
624 it('Should fail with an unknown runner token', async function () {
625 await server.runnerJobs.request({ runnerToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
626 })
627 })
628
629 describe('Accept', function () {
630
631 it('Should fail with a bad a job uuid', async function () {
632 await server.runnerJobs.accept({ jobUUID: '', runnerToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
633 })
634
635 it('Should fail with an unknown job uuid', async function () {
636 await server.runnerJobs.accept({ jobUUID: badUUID, runnerToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
637 })
638
639 it('Should fail with a job not in pending state', async function () {
640 await server.runnerJobs.accept({ jobUUID: completedJobUUID, runnerToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
641 await server.runnerJobs.accept({ jobUUID: cancelledJobUUID, runnerToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
642 })
643
644 it('Should fail without a runner token', async function () {
645 await server.runnerJobs.accept({ jobUUID: pendingUUID, runnerToken: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
646 })
647
648 it('Should fail with a bad a runner token', async function () {
649 await server.runnerJobs.accept({ jobUUID: pendingUUID, runnerToken: '', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
650 })
651
652 it('Should fail with an unknown runner token', async function () {
653 await server.runnerJobs.accept({ jobUUID: pendingUUID, runnerToken: badUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
654 })
655 })
656
657 describe('Abort', function () {
658
659 it('Should fail without a reason', async function () {
660 await server.runnerJobs.abort({ jobUUID, jobToken, runnerToken, reason: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
661 })
662
663 it('Should fail with a bad reason', async function () {
664 const reason = 'reason'.repeat(5000)
665 await server.runnerJobs.abort({ jobUUID, jobToken, runnerToken, reason, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
666 })
667
668 it('Should fail with a job not in processing state', async function () {
669 await server.runnerJobs.abort({
670 jobUUID: completedJobUUID,
671 jobToken: completedJobToken,
672 runnerToken,
673 reason: 'reason',
674 expectedStatus: HttpStatusCode.BAD_REQUEST_400
675 })
676 })
677 })
678
679 describe('Update', function () {
680
681 describe('Common', function () {
682
683 it('Should fail with an invalid progress', async function () {
684 await server.runnerJobs.update({ jobUUID, jobToken, runnerToken, progress: 101, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
685 })
686
687 it('Should fail with a job not in processing state', async function () {
688 await server.runnerJobs.update({
689 jobUUID: cancelledJobUUID,
690 jobToken: cancelledJobToken,
691 runnerToken,
692 expectedStatus: HttpStatusCode.NOT_FOUND_404
693 })
694 })
695 })
696
697 describe('Live RTMP to HLS', function () {
698 const base: RunnerJobUpdatePayload = {
699 masterPlaylistFile: 'live/master.m3u8',
700 resolutionPlaylistFilename: '0.m3u8',
701 resolutionPlaylistFile: 'live/1.m3u8',
702 type: 'add-chunk',
703 videoChunkFile: 'live/1-000069.ts',
704 videoChunkFilename: '1-000068.ts'
705 }
706
707 function testUpdate (payload: RunnerJobUpdatePayload) {
708 return server.runnerJobs.update({
709 jobUUID: liveAcceptedJob.uuid,
710 jobToken: liveAcceptedJob.jobToken,
711 payload,
712 runnerToken,
713 expectedStatus: HttpStatusCode.BAD_REQUEST_400
714 })
715 }
716
717 it('Should fail with an invalid resolutionPlaylistFilename', async function () {
718 await testUpdate({ ...base, resolutionPlaylistFilename: undefined })
719 await testUpdate({ ...base, resolutionPlaylistFilename: 'coucou/hello' })
720 await testUpdate({ ...base, resolutionPlaylistFilename: 'hello' })
721 })
722
723 it('Should fail with an invalid videoChunkFilename', async function () {
724 await testUpdate({ ...base, resolutionPlaylistFilename: undefined })
725 await testUpdate({ ...base, resolutionPlaylistFilename: 'coucou/hello' })
726 await testUpdate({ ...base, resolutionPlaylistFilename: 'hello' })
727 })
728
729 it('Should fail with an invalid type', async function () {
730 await testUpdate({ ...base, type: undefined })
731 await testUpdate({ ...base, type: 'toto' as any })
732 })
733 })
734 })
735
736 describe('Error', function () {
737
738 it('Should fail with a missing error message', async function () {
739 await server.runnerJobs.error({ jobUUID, jobToken, runnerToken, message: null, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
740 })
741
742 it('Should fail with an invalid error messgae', async function () {
743 const message = 'a'.repeat(6000)
744 await server.runnerJobs.error({ jobUUID, jobToken, runnerToken, message, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
745 })
746
747 it('Should fail with a job not in processing state', async function () {
748 await server.runnerJobs.error({
749 jobUUID: completedJobUUID,
750 jobToken: completedJobToken,
751 message: 'my message',
752 runnerToken,
753 expectedStatus: HttpStatusCode.BAD_REQUEST_400
754 })
755 })
756 })
757
758 describe('Success', function () {
759 let vodJobUUID: string
760 let vodJobToken: string
761
762 describe('Common', function () {
763
764 it('Should fail with a job not in processing state', async function () {
765 await server.runnerJobs.success({
766 jobUUID: completedJobUUID,
767 jobToken: completedJobToken,
768 payload: { videoFile: 'video_short.mp4' },
769 runnerToken,
770 expectedStatus: HttpStatusCode.BAD_REQUEST_400
771 })
772 })
773 })
774
775 describe('VOD', function () {
776
777 it('Should fail with an invalid vod web video payload', async function () {
778 const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'vod-web-video-transcoding' })
779
780 await server.runnerJobs.success({
781 jobUUID: job.uuid,
782 jobToken: job.jobToken,
783 payload: { hello: 'video_short.mp4' } as any,
784 runnerToken,
785 expectedStatus: HttpStatusCode.BAD_REQUEST_400
786 })
787
788 vodJobUUID = job.uuid
789 vodJobToken = job.jobToken
790 })
791
792 it('Should fail with an invalid vod hls payload', async function () {
793 // To create HLS jobs
794 const payload: RunnerJobSuccessPayload = { videoFile: 'video_short.mp4' }
795 await server.runnerJobs.success({ runnerToken, jobUUID: vodJobUUID, jobToken: vodJobToken, payload })
796
797 await waitJobs([ server ])
798
799 const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'vod-hls-transcoding' })
800
801 await server.runnerJobs.success({
802 jobUUID: job.uuid,
803 jobToken: job.jobToken,
804 payload: { videoFile: 'video_short.mp4' } as any,
805 runnerToken,
806 expectedStatus: HttpStatusCode.BAD_REQUEST_400
807 })
808 })
809
810 it('Should fail with an invalid vod audio merge payload', async function () {
811 const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' }
812 await server.videos.upload({ attributes, mode: 'legacy' })
813
814 await waitJobs([ server ])
815
816 const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'vod-audio-merge-transcoding' })
817
818 await server.runnerJobs.success({
819 jobUUID: job.uuid,
820 jobToken: job.jobToken,
821 payload: { hello: 'video_short.mp4' } as any,
822 runnerToken,
823 expectedStatus: HttpStatusCode.BAD_REQUEST_400
824 })
825 })
826 })
827
828 describe('Video studio', function () {
829
830 it('Should fail with an invalid video studio transcoding payload', async function () {
831 await server.runnerJobs.success({
832 jobUUID: studioAcceptedJob.uuid,
833 jobToken: studioAcceptedJob.jobToken,
834 payload: { hello: 'video_short.mp4' } as any,
835 runnerToken,
836 expectedStatus: HttpStatusCode.BAD_REQUEST_400
837 })
838 })
839 })
840 })
841
842 describe('Job files', function () {
843
844 describe('Check video param for common job file routes', function () {
845
846 async function fetchFiles (options: {
847 videoUUID?: string
848 expectedStatus: HttpStatusCodeType
849 }) {
850 await fetchVideoInputFiles({ videoUUID, ...options, jobToken, jobUUID, runnerToken })
851
852 await fetchStudioFiles({
853 videoUUID: videoStudioUUID,
854
855 ...options,
856
857 jobToken: studioAcceptedJob.jobToken,
858 jobUUID: studioAcceptedJob.uuid,
859 runnerToken,
860 studioFile
861 })
862 }
863
864 it('Should fail with an invalid video id', async function () {
865 await fetchFiles({
866 videoUUID: 'a',
867 expectedStatus: HttpStatusCode.BAD_REQUEST_400
868 })
869 })
870
871 it('Should fail with an unknown video id', async function () {
872 const videoUUID = '910ec12a-d9e6-458b-a274-0abb655f9464'
873
874 await fetchFiles({
875 videoUUID,
876 expectedStatus: HttpStatusCode.NOT_FOUND_404
877 })
878 })
879
880 it('Should fail with a video id not associated to this job', async function () {
881 await fetchFiles({
882 videoUUID: videoUUID2,
883 expectedStatus: HttpStatusCode.FORBIDDEN_403
884 })
885 })
886
887 it('Should succeed with the correct params', async function () {
888 await fetchFiles({ expectedStatus: HttpStatusCode.OK_200 })
889 })
890 })
891
892 describe('Video studio tasks file routes', function () {
893
894 it('Should fail with an invalid studio filename', async function () {
895 await fetchStudioFiles({
896 videoUUID: videoStudioUUID,
897 jobUUID: studioAcceptedJob.uuid,
898 runnerToken,
899 jobToken: studioAcceptedJob.jobToken,
900 studioFile: 'toto',
901 expectedStatus: HttpStatusCode.BAD_REQUEST_400
902 })
903 })
904 })
905 })
906 })
907
908 after(async function () {
909 await cleanupTests([ server ])
910 })
911})
diff --git a/packages/tests/src/api/check-params/search.ts b/packages/tests/src/api/check-params/search.ts
new file mode 100644
index 000000000..b886cbc82
--- /dev/null
+++ b/packages/tests/src/api/check-params/search.ts
@@ -0,0 +1,278 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode } from '@peertube/peertube-models'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
5import {
6 cleanupTests,
7 createSingleServer,
8 makeGetRequest,
9 PeerTubeServer,
10 setAccessTokensToServers
11} from '@peertube/peertube-server-commands'
12
13function updateSearchIndex (server: PeerTubeServer, enabled: boolean, disableLocalSearch = false) {
14 return server.config.updateCustomSubConfig({
15 newConfig: {
16 search: {
17 searchIndex: {
18 enabled,
19 disableLocalSearch
20 }
21 }
22 }
23 })
24}
25
26describe('Test videos API validator', function () {
27 let server: PeerTubeServer
28
29 // ---------------------------------------------------------------
30
31 before(async function () {
32 this.timeout(30000)
33
34 server = await createSingleServer(1)
35 await setAccessTokensToServers([ server ])
36 })
37
38 describe('When searching videos', function () {
39 const path = '/api/v1/search/videos/'
40
41 const query = {
42 search: 'coucou'
43 }
44
45 it('Should fail with a bad start pagination', async function () {
46 await checkBadStartPagination(server.url, path, null, query)
47 })
48
49 it('Should fail with a bad count pagination', async function () {
50 await checkBadCountPagination(server.url, path, null, query)
51 })
52
53 it('Should fail with an incorrect sort', async function () {
54 await checkBadSortPagination(server.url, path, null, query)
55 })
56
57 it('Should succeed with the correct parameters', async function () {
58 await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
59 })
60
61 it('Should fail with an invalid category', async function () {
62 const customQuery1 = { ...query, categoryOneOf: [ 'aa', 'b' ] }
63 await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
64
65 const customQuery2 = { ...query, categoryOneOf: 'a' }
66 await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
67 })
68
69 it('Should succeed with a valid category', async function () {
70 const customQuery1 = { ...query, categoryOneOf: [ 1, 7 ] }
71 await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.OK_200 })
72
73 const customQuery2 = { ...query, categoryOneOf: 1 }
74 await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.OK_200 })
75 })
76
77 it('Should fail with an invalid licence', async function () {
78 const customQuery1 = { ...query, licenceOneOf: [ 'aa', 'b' ] }
79 await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
80
81 const customQuery2 = { ...query, licenceOneOf: 'a' }
82 await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
83 })
84
85 it('Should succeed with a valid licence', async function () {
86 const customQuery1 = { ...query, licenceOneOf: [ 1, 2 ] }
87 await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.OK_200 })
88
89 const customQuery2 = { ...query, licenceOneOf: 1 }
90 await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.OK_200 })
91 })
92
93 it('Should succeed with a valid language', async function () {
94 const customQuery1 = { ...query, languageOneOf: [ 'fr', 'en' ] }
95 await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.OK_200 })
96
97 const customQuery2 = { ...query, languageOneOf: 'fr' }
98 await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.OK_200 })
99 })
100
101 it('Should succeed with valid tags', async function () {
102 const customQuery1 = { ...query, tagsOneOf: [ 'tag1', 'tag2' ] }
103 await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.OK_200 })
104
105 const customQuery2 = { ...query, tagsOneOf: 'tag1' }
106 await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.OK_200 })
107
108 const customQuery3 = { ...query, tagsAllOf: [ 'tag1', 'tag2' ] }
109 await makeGetRequest({ url: server.url, path, query: customQuery3, expectedStatus: HttpStatusCode.OK_200 })
110
111 const customQuery4 = { ...query, tagsAllOf: 'tag1' }
112 await makeGetRequest({ url: server.url, path, query: customQuery4, expectedStatus: HttpStatusCode.OK_200 })
113 })
114
115 it('Should fail with invalid durations', async function () {
116 const customQuery1 = { ...query, durationMin: 'hello' }
117 await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
118
119 const customQuery2 = { ...query, durationMax: 'hello' }
120 await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
121 })
122
123 it('Should fail with invalid dates', async function () {
124 const customQuery1 = { ...query, startDate: 'hello' }
125 await makeGetRequest({ url: server.url, path, query: customQuery1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
126
127 const customQuery2 = { ...query, endDate: 'hello' }
128 await makeGetRequest({ url: server.url, path, query: customQuery2, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
129
130 const customQuery3 = { ...query, originallyPublishedStartDate: 'hello' }
131 await makeGetRequest({ url: server.url, path, query: customQuery3, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
132
133 const customQuery4 = { ...query, originallyPublishedEndDate: 'hello' }
134 await makeGetRequest({ url: server.url, path, query: customQuery4, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
135 })
136
137 it('Should fail with an invalid host', async function () {
138 const customQuery = { ...query, host: '6565' }
139 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
140 })
141
142 it('Should succeed with a host', async function () {
143 const customQuery = { ...query, host: 'example.com' }
144 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
145 })
146
147 it('Should fail with invalid uuids', async function () {
148 const customQuery = { ...query, uuids: [ '6565', 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
149 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
150 })
151
152 it('Should succeed with valid uuids', async function () {
153 const customQuery = { ...query, uuids: [ 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
154 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
155 })
156 })
157
158 describe('When searching video playlists', function () {
159 const path = '/api/v1/search/video-playlists/'
160
161 const query = {
162 search: 'coucou',
163 host: 'example.com'
164 }
165
166 it('Should fail with a bad start pagination', async function () {
167 await checkBadStartPagination(server.url, path, null, query)
168 })
169
170 it('Should fail with a bad count pagination', async function () {
171 await checkBadCountPagination(server.url, path, null, query)
172 })
173
174 it('Should fail with an incorrect sort', async function () {
175 await checkBadSortPagination(server.url, path, null, query)
176 })
177
178 it('Should fail with an invalid host', async function () {
179 await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
180 })
181
182 it('Should fail with invalid uuids', async function () {
183 const customQuery = { ...query, uuids: [ '6565', 'dfd70b83-639f-4980-94af-304a56ab4b35' ] }
184 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
185 })
186
187 it('Should succeed with the correct parameters', async function () {
188 await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
189 })
190 })
191
192 describe('When searching video channels', function () {
193 const path = '/api/v1/search/video-channels/'
194
195 const query = {
196 search: 'coucou',
197 host: 'example.com'
198 }
199
200 it('Should fail with a bad start pagination', async function () {
201 await checkBadStartPagination(server.url, path, null, query)
202 })
203
204 it('Should fail with a bad count pagination', async function () {
205 await checkBadCountPagination(server.url, path, null, query)
206 })
207
208 it('Should fail with an incorrect sort', async function () {
209 await checkBadSortPagination(server.url, path, null, query)
210 })
211
212 it('Should fail with an invalid host', async function () {
213 await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
214 })
215
216 it('Should fail with invalid handles', async function () {
217 await makeGetRequest({ url: server.url, path, query: { ...query, handles: [ '' ] }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
218 })
219
220 it('Should succeed with the correct parameters', async function () {
221 await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 })
222 })
223 })
224
225 describe('Search target', function () {
226
227 it('Should fail/succeed depending on the search target', async function () {
228 const query = { search: 'coucou' }
229 const paths = [
230 '/api/v1/search/video-playlists/',
231 '/api/v1/search/video-channels/',
232 '/api/v1/search/videos/'
233 ]
234
235 for (const path of paths) {
236 {
237 const customQuery = { ...query, searchTarget: 'hello' }
238 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
239 }
240
241 {
242 const customQuery = { ...query, searchTarget: undefined }
243 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
244 }
245
246 {
247 const customQuery = { ...query, searchTarget: 'local' }
248 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
249 }
250
251 {
252 const customQuery = { ...query, searchTarget: 'search-index' }
253 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
254 }
255
256 await updateSearchIndex(server, true, true)
257
258 {
259 const customQuery = { ...query, searchTarget: 'search-index' }
260 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
261 }
262
263 await updateSearchIndex(server, true, false)
264
265 {
266 const customQuery = { ...query, searchTarget: 'local' }
267 await makeGetRequest({ url: server.url, path, query: customQuery, expectedStatus: HttpStatusCode.OK_200 })
268 }
269
270 await updateSearchIndex(server, false, false)
271 }
272 })
273 })
274
275 after(async function () {
276 await cleanupTests([ server ])
277 })
278})
diff --git a/packages/tests/src/api/check-params/services.ts b/packages/tests/src/api/check-params/services.ts
new file mode 100644
index 000000000..0b0466d84
--- /dev/null
+++ b/packages/tests/src/api/check-params/services.ts
@@ -0,0 +1,207 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import {
4 HttpStatusCode,
5 HttpStatusCodeType,
6 VideoCreateResult,
7 VideoPlaylistCreateResult,
8 VideoPlaylistPrivacy,
9 VideoPrivacy
10} from '@peertube/peertube-models'
11import {
12 cleanupTests,
13 createSingleServer,
14 makeGetRequest,
15 PeerTubeServer,
16 setAccessTokensToServers,
17 setDefaultVideoChannel
18} from '@peertube/peertube-server-commands'
19
20describe('Test services API validators', function () {
21 let server: PeerTubeServer
22 let playlistUUID: string
23
24 let privateVideo: VideoCreateResult
25 let unlistedVideo: VideoCreateResult
26
27 let privatePlaylist: VideoPlaylistCreateResult
28 let unlistedPlaylist: VideoPlaylistCreateResult
29
30 // ---------------------------------------------------------------
31
32 before(async function () {
33 this.timeout(60000)
34
35 server = await createSingleServer(1)
36 await setAccessTokensToServers([ server ])
37 await setDefaultVideoChannel([ server ])
38
39 server.store.videoCreated = await server.videos.upload({ attributes: { name: 'my super name' } })
40
41 privateVideo = await server.videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE })
42 unlistedVideo = await server.videos.quickUpload({ name: 'unlisted', privacy: VideoPrivacy.UNLISTED })
43
44 {
45 const created = await server.playlists.create({
46 attributes: {
47 displayName: 'super playlist',
48 privacy: VideoPlaylistPrivacy.PUBLIC,
49 videoChannelId: server.store.channel.id
50 }
51 })
52
53 playlistUUID = created.uuid
54
55 privatePlaylist = await server.playlists.create({
56 attributes: {
57 displayName: 'private',
58 privacy: VideoPlaylistPrivacy.PRIVATE,
59 videoChannelId: server.store.channel.id
60 }
61 })
62
63 unlistedPlaylist = await server.playlists.create({
64 attributes: {
65 displayName: 'unlisted',
66 privacy: VideoPlaylistPrivacy.UNLISTED,
67 videoChannelId: server.store.channel.id
68 }
69 })
70 }
71 })
72
73 describe('Test oEmbed API validators', function () {
74
75 it('Should fail with an invalid url', async function () {
76 const embedUrl = 'hello.com'
77 await checkParamEmbed(server, embedUrl)
78 })
79
80 it('Should fail with an invalid host', async function () {
81 const embedUrl = 'http://hello.com/videos/watch/' + server.store.videoCreated.uuid
82 await checkParamEmbed(server, embedUrl)
83 })
84
85 it('Should fail with an invalid element id', async function () {
86 const embedUrl = `${server.url}/videos/watch/blabla`
87 await checkParamEmbed(server, embedUrl)
88 })
89
90 it('Should fail with an unknown element', async function () {
91 const embedUrl = `${server.url}/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c`
92 await checkParamEmbed(server, embedUrl, HttpStatusCode.NOT_FOUND_404)
93 })
94
95 it('Should fail with an invalid path', async function () {
96 const embedUrl = `${server.url}/videos/watchs/${server.store.videoCreated.uuid}`
97
98 await checkParamEmbed(server, embedUrl)
99 })
100
101 it('Should fail with an invalid max height', async function () {
102 const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
103
104 await checkParamEmbed(server, embedUrl, HttpStatusCode.BAD_REQUEST_400, { maxheight: 'hello' })
105 })
106
107 it('Should fail with an invalid max width', async function () {
108 const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
109
110 await checkParamEmbed(server, embedUrl, HttpStatusCode.BAD_REQUEST_400, { maxwidth: 'hello' })
111 })
112
113 it('Should fail with an invalid format', async function () {
114 const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
115
116 await checkParamEmbed(server, embedUrl, HttpStatusCode.BAD_REQUEST_400, { format: 'blabla' })
117 })
118
119 it('Should fail with a non supported format', async function () {
120 const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
121
122 await checkParamEmbed(server, embedUrl, HttpStatusCode.NOT_IMPLEMENTED_501, { format: 'xml' })
123 })
124
125 it('Should fail with a private video', async function () {
126 const embedUrl = `${server.url}/videos/watch/${privateVideo.uuid}`
127
128 await checkParamEmbed(server, embedUrl, HttpStatusCode.FORBIDDEN_403)
129 })
130
131 it('Should fail with an unlisted video with the int id', async function () {
132 const embedUrl = `${server.url}/videos/watch/${unlistedVideo.id}`
133
134 await checkParamEmbed(server, embedUrl, HttpStatusCode.FORBIDDEN_403)
135 })
136
137 it('Should succeed with an unlisted video using the uuid id', async function () {
138 for (const uuid of [ unlistedVideo.uuid, unlistedVideo.shortUUID ]) {
139 const embedUrl = `${server.url}/videos/watch/${uuid}`
140
141 await checkParamEmbed(server, embedUrl, HttpStatusCode.OK_200)
142 }
143 })
144
145 it('Should fail with a private playlist', async function () {
146 const embedUrl = `${server.url}/videos/watch/playlist/${privatePlaylist.uuid}`
147
148 await checkParamEmbed(server, embedUrl, HttpStatusCode.FORBIDDEN_403)
149 })
150
151 it('Should fail with an unlisted playlist using the int id', async function () {
152 const embedUrl = `${server.url}/videos/watch/playlist/${unlistedPlaylist.id}`
153
154 await checkParamEmbed(server, embedUrl, HttpStatusCode.FORBIDDEN_403)
155 })
156
157 it('Should succeed with an unlisted playlist using the uuid id', async function () {
158 for (const uuid of [ unlistedPlaylist.uuid, unlistedPlaylist.shortUUID ]) {
159 const embedUrl = `${server.url}/videos/watch/playlist/${uuid}`
160
161 await checkParamEmbed(server, embedUrl, HttpStatusCode.OK_200)
162 }
163 })
164
165 it('Should succeed with the correct params with a video', async function () {
166 const embedUrl = `${server.url}/videos/watch/${server.store.videoCreated.uuid}`
167 const query = {
168 format: 'json',
169 maxheight: 400,
170 maxwidth: 400
171 }
172
173 await checkParamEmbed(server, embedUrl, HttpStatusCode.OK_200, query)
174 })
175
176 it('Should succeed with the correct params with a playlist', async function () {
177 const embedUrl = `${server.url}/videos/watch/playlist/${playlistUUID}`
178 const query = {
179 format: 'json',
180 maxheight: 400,
181 maxwidth: 400
182 }
183
184 await checkParamEmbed(server, embedUrl, HttpStatusCode.OK_200, query)
185 })
186 })
187
188 after(async function () {
189 await cleanupTests([ server ])
190 })
191})
192
193function checkParamEmbed (
194 server: PeerTubeServer,
195 embedUrl: string,
196 expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400,
197 query = {}
198) {
199 const path = '/services/oembed'
200
201 return makeGetRequest({
202 url: server.url,
203 path,
204 query: Object.assign(query, { url: embedUrl }),
205 expectedStatus
206 })
207}
diff --git a/packages/tests/src/api/check-params/transcoding.ts b/packages/tests/src/api/check-params/transcoding.ts
new file mode 100644
index 000000000..50935c59e
--- /dev/null
+++ b/packages/tests/src/api/check-params/transcoding.ts
@@ -0,0 +1,112 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode, UserRole } from '@peertube/peertube-models'
4import {
5 cleanupTests,
6 createMultipleServers,
7 doubleFollow,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 waitJobs
11} from '@peertube/peertube-server-commands'
12
13describe('Test transcoding API validators', function () {
14 let servers: PeerTubeServer[]
15
16 let userToken: string
17 let moderatorToken: string
18
19 let remoteId: string
20 let validId: string
21
22 // ---------------------------------------------------------------
23
24 before(async function () {
25 this.timeout(120000)
26
27 servers = await createMultipleServers(2)
28 await setAccessTokensToServers(servers)
29
30 await doubleFollow(servers[0], servers[1])
31
32 userToken = await servers[0].users.generateUserAndToken('user', UserRole.USER)
33 moderatorToken = await servers[0].users.generateUserAndToken('moderator', UserRole.MODERATOR)
34
35 {
36 const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' })
37 remoteId = uuid
38 }
39
40 {
41 const { uuid } = await servers[0].videos.quickUpload({ name: 'both 1' })
42 validId = uuid
43 }
44
45 await waitJobs(servers)
46
47 await servers[0].config.enableTranscoding()
48 })
49
50 it('Should not run transcoding of a unknown video', async function () {
51 await servers[0].videos.runTranscoding({ videoId: 404, transcodingType: 'hls', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
52 await servers[0].videos.runTranscoding({ videoId: 404, transcodingType: 'web-video', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
53 })
54
55 it('Should not run transcoding of a remote video', async function () {
56 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
57
58 await servers[0].videos.runTranscoding({ videoId: remoteId, transcodingType: 'hls', expectedStatus })
59 await servers[0].videos.runTranscoding({ videoId: remoteId, transcodingType: 'web-video', expectedStatus })
60 })
61
62 it('Should not run transcoding by a non admin user', async function () {
63 const expectedStatus = HttpStatusCode.FORBIDDEN_403
64
65 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'hls', token: userToken, expectedStatus })
66 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', token: moderatorToken, expectedStatus })
67 })
68
69 it('Should not run transcoding without transcoding type', async function () {
70 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
71 })
72
73 it('Should not run transcoding with an incorrect transcoding type', async function () {
74 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
75
76 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'toto' as any, expectedStatus })
77 })
78
79 it('Should not run transcoding if the instance disabled it', async function () {
80 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
81
82 await servers[0].config.disableTranscoding()
83
84 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'hls', expectedStatus })
85 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', expectedStatus })
86 })
87
88 it('Should run transcoding', async function () {
89 this.timeout(120_000)
90
91 await servers[0].config.enableTranscoding()
92
93 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'hls' })
94 await waitJobs(servers)
95
96 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', forceTranscoding: true })
97 await waitJobs(servers)
98 })
99
100 it('Should not run transcoding on a video that is already being transcoded if forceTranscoding is not set', async function () {
101 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video' })
102
103 const expectedStatus = HttpStatusCode.CONFLICT_409
104 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', expectedStatus })
105
106 await servers[0].videos.runTranscoding({ videoId: validId, transcodingType: 'web-video', forceTranscoding: true })
107 })
108
109 after(async function () {
110 await cleanupTests(servers)
111 })
112})
diff --git a/packages/tests/src/api/check-params/two-factor.ts b/packages/tests/src/api/check-params/two-factor.ts
new file mode 100644
index 000000000..0b1766eca
--- /dev/null
+++ b/packages/tests/src/api/check-params/two-factor.ts
@@ -0,0 +1,294 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode } from '@peertube/peertube-models'
4import {
5 cleanupTests,
6 createSingleServer,
7 PeerTubeServer,
8 setAccessTokensToServers,
9 TwoFactorCommand
10} from '@peertube/peertube-server-commands'
11
12describe('Test two factor API validators', function () {
13 let server: PeerTubeServer
14
15 let rootId: number
16 let rootPassword: string
17 let rootRequestToken: string
18 let rootOTPToken: string
19
20 let userId: number
21 let userToken = ''
22 let userPassword: string
23 let userRequestToken: string
24 let userOTPToken: string
25
26 // ---------------------------------------------------------------
27
28 before(async function () {
29 this.timeout(30000)
30
31 {
32 server = await createSingleServer(1)
33 await setAccessTokensToServers([ server ])
34 }
35
36 {
37 const result = await server.users.generate('user1')
38 userToken = result.token
39 userId = result.userId
40 userPassword = result.password
41 }
42
43 {
44 const { id } = await server.users.getMyInfo()
45 rootId = id
46 rootPassword = server.store.user.password
47 }
48 })
49
50 describe('When requesting two factor', function () {
51
52 it('Should fail with an unknown user id', async function () {
53 await server.twoFactor.request({ userId: 42, currentPassword: rootPassword, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
54 })
55
56 it('Should fail with an invalid user id', async function () {
57 await server.twoFactor.request({
58 userId: 'invalid' as any,
59 currentPassword: rootPassword,
60 expectedStatus: HttpStatusCode.BAD_REQUEST_400
61 })
62 })
63
64 it('Should fail to request another user two factor without the appropriate rights', async function () {
65 await server.twoFactor.request({
66 userId: rootId,
67 token: userToken,
68 currentPassword: userPassword,
69 expectedStatus: HttpStatusCode.FORBIDDEN_403
70 })
71 })
72
73 it('Should succeed to request another user two factor with the appropriate rights', async function () {
74 await server.twoFactor.request({ userId, currentPassword: rootPassword })
75 })
76
77 it('Should fail to request two factor without a password', async function () {
78 await server.twoFactor.request({
79 userId,
80 token: userToken,
81 currentPassword: undefined,
82 expectedStatus: HttpStatusCode.BAD_REQUEST_400
83 })
84 })
85
86 it('Should fail to request two factor with an incorrect password', async function () {
87 await server.twoFactor.request({
88 userId,
89 token: userToken,
90 currentPassword: rootPassword,
91 expectedStatus: HttpStatusCode.FORBIDDEN_403
92 })
93 })
94
95 it('Should succeed to request two factor without a password when targeting a remote user with an admin account', async function () {
96 await server.twoFactor.request({ userId })
97 })
98
99 it('Should fail to request two factor without a password when targeting myself with an admin account', async function () {
100 await server.twoFactor.request({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
101 await server.twoFactor.request({ userId: rootId, currentPassword: 'bad', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
102 })
103
104 it('Should succeed to request my two factor auth', async function () {
105 {
106 const { otpRequest } = await server.twoFactor.request({ userId, token: userToken, currentPassword: userPassword })
107 userRequestToken = otpRequest.requestToken
108 userOTPToken = TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate()
109 }
110
111 {
112 const { otpRequest } = await server.twoFactor.request({ userId: rootId, currentPassword: rootPassword })
113 rootRequestToken = otpRequest.requestToken
114 rootOTPToken = TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate()
115 }
116 })
117 })
118
119 describe('When confirming two factor request', function () {
120
121 it('Should fail with an unknown user id', async function () {
122 await server.twoFactor.confirmRequest({
123 userId: 42,
124 requestToken: rootRequestToken,
125 otpToken: rootOTPToken,
126 expectedStatus: HttpStatusCode.NOT_FOUND_404
127 })
128 })
129
130 it('Should fail with an invalid user id', async function () {
131 await server.twoFactor.confirmRequest({
132 userId: 'invalid' as any,
133 requestToken: rootRequestToken,
134 otpToken: rootOTPToken,
135 expectedStatus: HttpStatusCode.BAD_REQUEST_400
136 })
137 })
138
139 it('Should fail to confirm another user two factor request without the appropriate rights', async function () {
140 await server.twoFactor.confirmRequest({
141 userId: rootId,
142 token: userToken,
143 requestToken: rootRequestToken,
144 otpToken: rootOTPToken,
145 expectedStatus: HttpStatusCode.FORBIDDEN_403
146 })
147 })
148
149 it('Should fail without request token', async function () {
150 await server.twoFactor.confirmRequest({
151 userId,
152 requestToken: undefined,
153 otpToken: userOTPToken,
154 expectedStatus: HttpStatusCode.BAD_REQUEST_400
155 })
156 })
157
158 it('Should fail with an invalid request token', async function () {
159 await server.twoFactor.confirmRequest({
160 userId,
161 requestToken: 'toto',
162 otpToken: userOTPToken,
163 expectedStatus: HttpStatusCode.FORBIDDEN_403
164 })
165 })
166
167 it('Should fail with request token of another user', async function () {
168 await server.twoFactor.confirmRequest({
169 userId,
170 requestToken: rootRequestToken,
171 otpToken: userOTPToken,
172 expectedStatus: HttpStatusCode.FORBIDDEN_403
173 })
174 })
175
176 it('Should fail without an otp token', async function () {
177 await server.twoFactor.confirmRequest({
178 userId,
179 requestToken: userRequestToken,
180 otpToken: undefined,
181 expectedStatus: HttpStatusCode.BAD_REQUEST_400
182 })
183 })
184
185 it('Should fail with a bad otp token', async function () {
186 await server.twoFactor.confirmRequest({
187 userId,
188 requestToken: userRequestToken,
189 otpToken: '123456',
190 expectedStatus: HttpStatusCode.FORBIDDEN_403
191 })
192 })
193
194 it('Should succeed to confirm another user two factor request with the appropriate rights', async function () {
195 await server.twoFactor.confirmRequest({
196 userId,
197 requestToken: userRequestToken,
198 otpToken: userOTPToken
199 })
200
201 // Reinit
202 await server.twoFactor.disable({ userId, currentPassword: rootPassword })
203 })
204
205 it('Should succeed to confirm my two factor request', async function () {
206 await server.twoFactor.confirmRequest({
207 userId,
208 token: userToken,
209 requestToken: userRequestToken,
210 otpToken: userOTPToken
211 })
212 })
213
214 it('Should fail to confirm again two factor request', async function () {
215 await server.twoFactor.confirmRequest({
216 userId,
217 token: userToken,
218 requestToken: userRequestToken,
219 otpToken: userOTPToken,
220 expectedStatus: HttpStatusCode.BAD_REQUEST_400
221 })
222 })
223 })
224
225 describe('When disabling two factor', function () {
226
227 it('Should fail with an unknown user id', async function () {
228 await server.twoFactor.disable({
229 userId: 42,
230 currentPassword: rootPassword,
231 expectedStatus: HttpStatusCode.NOT_FOUND_404
232 })
233 })
234
235 it('Should fail with an invalid user id', async function () {
236 await server.twoFactor.disable({
237 userId: 'invalid' as any,
238 currentPassword: rootPassword,
239 expectedStatus: HttpStatusCode.BAD_REQUEST_400
240 })
241 })
242
243 it('Should fail to disable another user two factor without the appropriate rights', async function () {
244 await server.twoFactor.disable({
245 userId: rootId,
246 token: userToken,
247 currentPassword: userPassword,
248 expectedStatus: HttpStatusCode.FORBIDDEN_403
249 })
250 })
251
252 it('Should fail to disable two factor with an incorrect password', async function () {
253 await server.twoFactor.disable({
254 userId,
255 token: userToken,
256 currentPassword: rootPassword,
257 expectedStatus: HttpStatusCode.FORBIDDEN_403
258 })
259 })
260
261 it('Should succeed to disable two factor without a password when targeting a remote user with an admin account', async function () {
262 await server.twoFactor.disable({ userId })
263 await server.twoFactor.requestAndConfirm({ userId })
264 })
265
266 it('Should fail to disable two factor without a password when targeting myself with an admin account', async function () {
267 await server.twoFactor.disable({ userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
268 await server.twoFactor.disable({ userId: rootId, currentPassword: 'bad', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
269 })
270
271 it('Should succeed to disable another user two factor with the appropriate rights', async function () {
272 await server.twoFactor.disable({ userId, currentPassword: rootPassword })
273
274 await server.twoFactor.requestAndConfirm({ userId })
275 })
276
277 it('Should succeed to update my two factor auth', async function () {
278 await server.twoFactor.disable({ userId, token: userToken, currentPassword: userPassword })
279 })
280
281 it('Should fail to disable again two factor', async function () {
282 await server.twoFactor.disable({
283 userId,
284 token: userToken,
285 currentPassword: userPassword,
286 expectedStatus: HttpStatusCode.BAD_REQUEST_400
287 })
288 })
289 })
290
291 after(async function () {
292 await cleanupTests([ server ])
293 })
294})
diff --git a/packages/tests/src/api/check-params/upload-quota.ts b/packages/tests/src/api/check-params/upload-quota.ts
new file mode 100644
index 000000000..a77792822
--- /dev/null
+++ b/packages/tests/src/api/check-params/upload-quota.ts
@@ -0,0 +1,134 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { FIXTURE_URLS } from '@tests/shared/tests.js'
5import { randomInt } from '@peertube/peertube-core-utils'
6import { HttpStatusCode, VideoImportState, VideoPrivacy } from '@peertube/peertube-models'
7import {
8 cleanupTests,
9 createSingleServer,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 setDefaultVideoChannel,
13 VideosCommand,
14 waitJobs
15} from '@peertube/peertube-server-commands'
16
17describe('Test upload quota', function () {
18 let server: PeerTubeServer
19 let rootId: number
20 let command: VideosCommand
21
22 // ---------------------------------------------------------------
23
24 before(async function () {
25 this.timeout(30000)
26
27 server = await createSingleServer(1)
28 await setAccessTokensToServers([ server ])
29 await setDefaultVideoChannel([ server ])
30
31 const user = await server.users.getMyInfo()
32 rootId = user.id
33
34 await server.users.update({ userId: rootId, videoQuota: 42 })
35
36 command = server.videos
37 })
38
39 describe('When having a video quota', function () {
40
41 it('Should fail with a registered user having too many videos with legacy upload', async function () {
42 this.timeout(120000)
43
44 const user = { username: 'registered' + randomInt(1, 1500), password: 'password' }
45 await server.registrations.register(user)
46 const userToken = await server.login.getAccessToken(user)
47
48 const attributes = { fixture: 'video_short2.webm' }
49 for (let i = 0; i < 5; i++) {
50 await command.upload({ token: userToken, attributes })
51 }
52
53 await command.upload({ token: userToken, attributes, expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'legacy' })
54 })
55
56 it('Should fail with a registered user having too many videos with resumable upload', async function () {
57 this.timeout(120000)
58
59 const user = { username: 'registered' + randomInt(1, 1500), password: 'password' }
60 await server.registrations.register(user)
61 const userToken = await server.login.getAccessToken(user)
62
63 const attributes = { fixture: 'video_short2.webm' }
64 for (let i = 0; i < 5; i++) {
65 await command.upload({ token: userToken, attributes })
66 }
67
68 await command.upload({ token: userToken, attributes, expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'resumable' })
69 })
70
71 it('Should fail to import with HTTP/Torrent/magnet', async function () {
72 this.timeout(120_000)
73
74 const baseAttributes = {
75 channelId: server.store.channel.id,
76 privacy: VideoPrivacy.PUBLIC
77 }
78 await server.imports.importVideo({ attributes: { ...baseAttributes, targetUrl: FIXTURE_URLS.goodVideo } })
79 await server.imports.importVideo({ attributes: { ...baseAttributes, magnetUri: FIXTURE_URLS.magnet } })
80 await server.imports.importVideo({ attributes: { ...baseAttributes, torrentfile: 'video-720p.torrent' as any } })
81
82 await waitJobs([ server ])
83
84 const { total, data: videoImports } = await server.imports.getMyVideoImports()
85 expect(total).to.equal(3)
86
87 expect(videoImports).to.have.lengthOf(3)
88
89 for (const videoImport of videoImports) {
90 expect(videoImport.state.id).to.equal(VideoImportState.FAILED)
91 expect(videoImport.error).not.to.be.undefined
92 expect(videoImport.error).to.contain('user video quota is exceeded')
93 }
94 })
95 })
96
97 describe('When having a daily video quota', function () {
98
99 it('Should fail with a user having too many videos daily', async function () {
100 await server.users.update({ userId: rootId, videoQuotaDaily: 42 })
101
102 await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'legacy' })
103 await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'resumable' })
104 })
105 })
106
107 describe('When having an absolute and daily video quota', function () {
108 it('Should fail if exceeding total quota', async function () {
109 await server.users.update({
110 userId: rootId,
111 videoQuota: 42,
112 videoQuotaDaily: 1024 * 1024 * 1024
113 })
114
115 await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'legacy' })
116 await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'resumable' })
117 })
118
119 it('Should fail if exceeding daily quota', async function () {
120 await server.users.update({
121 userId: rootId,
122 videoQuota: 1024 * 1024 * 1024,
123 videoQuotaDaily: 42
124 })
125
126 await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'legacy' })
127 await command.upload({ expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413, mode: 'resumable' })
128 })
129 })
130
131 after(async function () {
132 await cleanupTests([ server ])
133 })
134})
diff --git a/packages/tests/src/api/check-params/user-notifications.ts b/packages/tests/src/api/check-params/user-notifications.ts
new file mode 100644
index 000000000..cf20324a1
--- /dev/null
+++ b/packages/tests/src/api/check-params/user-notifications.ts
@@ -0,0 +1,290 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { io } from 'socket.io-client'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
5import { wait } from '@peertube/peertube-core-utils'
6import { HttpStatusCode, UserNotificationSetting, UserNotificationSettingValue } from '@peertube/peertube-models'
7import {
8 cleanupTests,
9 createSingleServer,
10 makeGetRequest,
11 makePostBodyRequest,
12 makePutBodyRequest,
13 PeerTubeServer,
14 setAccessTokensToServers
15} from '@peertube/peertube-server-commands'
16
17describe('Test user notifications API validators', function () {
18 let server: PeerTubeServer
19
20 // ---------------------------------------------------------------
21
22 before(async function () {
23 this.timeout(30000)
24
25 server = await createSingleServer(1)
26
27 await setAccessTokensToServers([ server ])
28 })
29
30 describe('When listing my notifications', function () {
31 const path = '/api/v1/users/me/notifications'
32
33 it('Should fail with a bad start pagination', async function () {
34 await checkBadStartPagination(server.url, path, server.accessToken)
35 })
36
37 it('Should fail with a bad count pagination', async function () {
38 await checkBadCountPagination(server.url, path, server.accessToken)
39 })
40
41 it('Should fail with an incorrect sort', async function () {
42 await checkBadSortPagination(server.url, path, server.accessToken)
43 })
44
45 it('Should fail with an incorrect unread parameter', async function () {
46 await makeGetRequest({
47 url: server.url,
48 path,
49 query: {
50 unread: 'toto'
51 },
52 token: server.accessToken,
53 expectedStatus: HttpStatusCode.OK_200
54 })
55 })
56
57 it('Should fail with a non authenticated user', async function () {
58 await makeGetRequest({
59 url: server.url,
60 path,
61 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
62 })
63 })
64
65 it('Should succeed with the correct parameters', async function () {
66 await makeGetRequest({
67 url: server.url,
68 path,
69 token: server.accessToken,
70 expectedStatus: HttpStatusCode.OK_200
71 })
72 })
73 })
74
75 describe('When marking as read my notifications', function () {
76 const path = '/api/v1/users/me/notifications/read'
77
78 it('Should fail with wrong ids parameters', async function () {
79 await makePostBodyRequest({
80 url: server.url,
81 path,
82 fields: {
83 ids: [ 'hello' ]
84 },
85 token: server.accessToken,
86 expectedStatus: HttpStatusCode.BAD_REQUEST_400
87 })
88
89 await makePostBodyRequest({
90 url: server.url,
91 path,
92 fields: {
93 ids: [ ]
94 },
95 token: server.accessToken,
96 expectedStatus: HttpStatusCode.BAD_REQUEST_400
97 })
98
99 await makePostBodyRequest({
100 url: server.url,
101 path,
102 fields: {
103 ids: 5
104 },
105 token: server.accessToken,
106 expectedStatus: HttpStatusCode.BAD_REQUEST_400
107 })
108 })
109
110 it('Should fail with a non authenticated user', async function () {
111 await makePostBodyRequest({
112 url: server.url,
113 path,
114 fields: {
115 ids: [ 5 ]
116 },
117 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
118 })
119 })
120
121 it('Should succeed with the correct parameters', async function () {
122 await makePostBodyRequest({
123 url: server.url,
124 path,
125 fields: {
126 ids: [ 5 ]
127 },
128 token: server.accessToken,
129 expectedStatus: HttpStatusCode.NO_CONTENT_204
130 })
131 })
132 })
133
134 describe('When marking as read my notifications', function () {
135 const path = '/api/v1/users/me/notifications/read-all'
136
137 it('Should fail with a non authenticated user', async function () {
138 await makePostBodyRequest({
139 url: server.url,
140 path,
141 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
142 })
143 })
144
145 it('Should succeed with the correct parameters', async function () {
146 await makePostBodyRequest({
147 url: server.url,
148 path,
149 token: server.accessToken,
150 expectedStatus: HttpStatusCode.NO_CONTENT_204
151 })
152 })
153 })
154
155 describe('When updating my notification settings', function () {
156 const path = '/api/v1/users/me/notification-settings'
157 const correctFields: UserNotificationSetting = {
158 newVideoFromSubscription: UserNotificationSettingValue.WEB,
159 newCommentOnMyVideo: UserNotificationSettingValue.WEB,
160 abuseAsModerator: UserNotificationSettingValue.WEB,
161 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB,
162 blacklistOnMyVideo: UserNotificationSettingValue.WEB,
163 myVideoImportFinished: UserNotificationSettingValue.WEB,
164 myVideoPublished: UserNotificationSettingValue.WEB,
165 commentMention: UserNotificationSettingValue.WEB,
166 newFollow: UserNotificationSettingValue.WEB,
167 newUserRegistration: UserNotificationSettingValue.WEB,
168 newInstanceFollower: UserNotificationSettingValue.WEB,
169 autoInstanceFollowing: UserNotificationSettingValue.WEB,
170 abuseNewMessage: UserNotificationSettingValue.WEB,
171 abuseStateChange: UserNotificationSettingValue.WEB,
172 newPeerTubeVersion: UserNotificationSettingValue.WEB,
173 myVideoStudioEditionFinished: UserNotificationSettingValue.WEB,
174 newPluginVersion: UserNotificationSettingValue.WEB
175 }
176
177 it('Should fail with missing fields', async function () {
178 await makePutBodyRequest({
179 url: server.url,
180 path,
181 token: server.accessToken,
182 fields: { newVideoFromSubscription: UserNotificationSettingValue.WEB },
183 expectedStatus: HttpStatusCode.BAD_REQUEST_400
184 })
185 })
186
187 it('Should fail with incorrect field values', async function () {
188 {
189 const fields = { ...correctFields, newCommentOnMyVideo: 15 }
190
191 await makePutBodyRequest({
192 url: server.url,
193 path,
194 token: server.accessToken,
195 fields,
196 expectedStatus: HttpStatusCode.BAD_REQUEST_400
197 })
198 }
199
200 {
201 const fields = { ...correctFields, newCommentOnMyVideo: 'toto' }
202
203 await makePutBodyRequest({
204 url: server.url,
205 path,
206 fields,
207 token: server.accessToken,
208 expectedStatus: HttpStatusCode.BAD_REQUEST_400
209 })
210 }
211 })
212
213 it('Should fail with a non authenticated user', async function () {
214 await makePutBodyRequest({
215 url: server.url,
216 path,
217 fields: correctFields,
218 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
219 })
220 })
221
222 it('Should succeed with the correct parameters', async function () {
223 await makePutBodyRequest({
224 url: server.url,
225 path,
226 token: server.accessToken,
227 fields: correctFields,
228 expectedStatus: HttpStatusCode.NO_CONTENT_204
229 })
230 })
231 })
232
233 describe('When connecting to my notification socket', function () {
234
235 it('Should fail with no token', function (next) {
236 const socket = io(`${server.url}/user-notifications`, { reconnection: false })
237
238 socket.once('connect_error', function () {
239 socket.disconnect()
240 next()
241 })
242
243 socket.on('connect', () => {
244 socket.disconnect()
245 next(new Error('Connected with a missing token.'))
246 })
247 })
248
249 it('Should fail with an invalid token', function (next) {
250 const socket = io(`${server.url}/user-notifications`, {
251 query: { accessToken: 'bad_access_token' },
252 reconnection: false
253 })
254
255 socket.once('connect_error', function () {
256 socket.disconnect()
257 next()
258 })
259
260 socket.on('connect', () => {
261 socket.disconnect()
262 next(new Error('Connected with an invalid token.'))
263 })
264 })
265
266 it('Should success with the correct token', function (next) {
267 const socket = io(`${server.url}/user-notifications`, {
268 query: { accessToken: server.accessToken },
269 reconnection: false
270 })
271
272 function errorListener (err) {
273 next(new Error('Error in connection: ' + err))
274 }
275
276 socket.on('connect_error', errorListener)
277
278 socket.once('connect', async () => {
279 socket.disconnect()
280
281 await wait(500)
282 next()
283 })
284 })
285 })
286
287 after(async function () {
288 await cleanupTests([ server ])
289 })
290})
diff --git a/packages/tests/src/api/check-params/user-subscriptions.ts b/packages/tests/src/api/check-params/user-subscriptions.ts
new file mode 100644
index 000000000..e97f513a0
--- /dev/null
+++ b/packages/tests/src/api/check-params/user-subscriptions.ts
@@ -0,0 +1,298 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import {
4 cleanupTests,
5 createSingleServer,
6 makeDeleteRequest,
7 makeGetRequest,
8 makePostBodyRequest,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 waitJobs
12} from '@peertube/peertube-server-commands'
13import { HttpStatusCode } from '@peertube/peertube-models'
14import { checkBadStartPagination, checkBadCountPagination, checkBadSortPagination } from '@tests/shared/checks.js'
15
16describe('Test user subscriptions API validators', function () {
17 const path = '/api/v1/users/me/subscriptions'
18 let server: PeerTubeServer
19 let userAccessToken = ''
20
21 // ---------------------------------------------------------------
22
23 before(async function () {
24 this.timeout(30000)
25
26 server = await createSingleServer(1)
27
28 await setAccessTokensToServers([ server ])
29
30 const user = {
31 username: 'user1',
32 password: 'my super password'
33 }
34 await server.users.create({ username: user.username, password: user.password })
35 userAccessToken = await server.login.getAccessToken(user)
36 })
37
38 describe('When listing my subscriptions', function () {
39 it('Should fail with a bad start pagination', async function () {
40 await checkBadStartPagination(server.url, path, server.accessToken)
41 })
42
43 it('Should fail with a bad count pagination', async function () {
44 await checkBadCountPagination(server.url, path, server.accessToken)
45 })
46
47 it('Should fail with an incorrect sort', async function () {
48 await checkBadSortPagination(server.url, path, server.accessToken)
49 })
50
51 it('Should fail with a non authenticated user', async function () {
52 await makeGetRequest({
53 url: server.url,
54 path,
55 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
56 })
57 })
58
59 it('Should succeed with the correct parameters', async function () {
60 await makeGetRequest({
61 url: server.url,
62 path,
63 token: userAccessToken,
64 expectedStatus: HttpStatusCode.OK_200
65 })
66 })
67 })
68
69 describe('When listing my subscriptions videos', function () {
70 const path = '/api/v1/users/me/subscriptions/videos'
71
72 it('Should fail with a bad start pagination', async function () {
73 await checkBadStartPagination(server.url, path, server.accessToken)
74 })
75
76 it('Should fail with a bad count pagination', async function () {
77 await checkBadCountPagination(server.url, path, server.accessToken)
78 })
79
80 it('Should fail with an incorrect sort', async function () {
81 await checkBadSortPagination(server.url, path, server.accessToken)
82 })
83
84 it('Should fail with a non authenticated user', async function () {
85 await makeGetRequest({
86 url: server.url,
87 path,
88 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
89 })
90 })
91
92 it('Should succeed with the correct parameters', async function () {
93 await makeGetRequest({
94 url: server.url,
95 path,
96 token: userAccessToken,
97 expectedStatus: HttpStatusCode.OK_200
98 })
99 })
100 })
101
102 describe('When adding a subscription', function () {
103 it('Should fail with a non authenticated user', async function () {
104 await makePostBodyRequest({
105 url: server.url,
106 path,
107 fields: { uri: 'user1_channel@' + server.host },
108 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
109 })
110 })
111
112 it('Should fail with bad URIs', async function () {
113 await makePostBodyRequest({
114 url: server.url,
115 path,
116 token: server.accessToken,
117 fields: { uri: 'root' },
118 expectedStatus: HttpStatusCode.BAD_REQUEST_400
119 })
120
121 await makePostBodyRequest({
122 url: server.url,
123 path,
124 token: server.accessToken,
125 fields: { uri: 'root@' },
126 expectedStatus: HttpStatusCode.BAD_REQUEST_400
127 })
128
129 await makePostBodyRequest({
130 url: server.url,
131 path,
132 token: server.accessToken,
133 fields: { uri: 'root@hello@' },
134 expectedStatus: HttpStatusCode.BAD_REQUEST_400
135 })
136 })
137
138 it('Should succeed with the correct parameters', async function () {
139 this.timeout(20000)
140
141 await makePostBodyRequest({
142 url: server.url,
143 path,
144 token: server.accessToken,
145 fields: { uri: 'user1_channel@' + server.host },
146 expectedStatus: HttpStatusCode.NO_CONTENT_204
147 })
148
149 await waitJobs([ server ])
150 })
151 })
152
153 describe('When getting a subscription', function () {
154 it('Should fail with a non authenticated user', async function () {
155 await makeGetRequest({
156 url: server.url,
157 path: path + '/user1_channel@' + server.host,
158 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
159 })
160 })
161
162 it('Should fail with bad URIs', async function () {
163 await makeGetRequest({
164 url: server.url,
165 path: path + '/root',
166 token: server.accessToken,
167 expectedStatus: HttpStatusCode.BAD_REQUEST_400
168 })
169
170 await makeGetRequest({
171 url: server.url,
172 path: path + '/root@',
173 token: server.accessToken,
174 expectedStatus: HttpStatusCode.BAD_REQUEST_400
175 })
176
177 await makeGetRequest({
178 url: server.url,
179 path: path + '/root@hello@',
180 token: server.accessToken,
181 expectedStatus: HttpStatusCode.BAD_REQUEST_400
182 })
183 })
184
185 it('Should fail with an unknown subscription', async function () {
186 await makeGetRequest({
187 url: server.url,
188 path: path + '/root1@' + server.host,
189 token: server.accessToken,
190 expectedStatus: HttpStatusCode.NOT_FOUND_404
191 })
192 })
193
194 it('Should succeed with the correct parameters', async function () {
195 await makeGetRequest({
196 url: server.url,
197 path: path + '/user1_channel@' + server.host,
198 token: server.accessToken,
199 expectedStatus: HttpStatusCode.OK_200
200 })
201 })
202 })
203
204 describe('When checking if subscriptions exist', function () {
205 const existPath = path + '/exist'
206
207 it('Should fail with a non authenticated user', async function () {
208 await makeGetRequest({
209 url: server.url,
210 path: existPath,
211 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
212 })
213 })
214
215 it('Should fail with bad URIs', async function () {
216 await makeGetRequest({
217 url: server.url,
218 path: existPath,
219 query: { uris: 'toto' },
220 token: server.accessToken,
221 expectedStatus: HttpStatusCode.BAD_REQUEST_400
222 })
223
224 await makeGetRequest({
225 url: server.url,
226 path: existPath,
227 query: { 'uris[]': 1 },
228 token: server.accessToken,
229 expectedStatus: HttpStatusCode.BAD_REQUEST_400
230 })
231 })
232
233 it('Should succeed with the correct parameters', async function () {
234 await makeGetRequest({
235 url: server.url,
236 path: existPath,
237 query: { 'uris[]': 'coucou@' + server.host },
238 token: server.accessToken,
239 expectedStatus: HttpStatusCode.OK_200
240 })
241 })
242 })
243
244 describe('When removing a subscription', function () {
245 it('Should fail with a non authenticated user', async function () {
246 await makeDeleteRequest({
247 url: server.url,
248 path: path + '/user1_channel@' + server.host,
249 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
250 })
251 })
252
253 it('Should fail with bad URIs', async function () {
254 await makeDeleteRequest({
255 url: server.url,
256 path: path + '/root',
257 token: server.accessToken,
258 expectedStatus: HttpStatusCode.BAD_REQUEST_400
259 })
260
261 await makeDeleteRequest({
262 url: server.url,
263 path: path + '/root@',
264 token: server.accessToken,
265 expectedStatus: HttpStatusCode.BAD_REQUEST_400
266 })
267
268 await makeDeleteRequest({
269 url: server.url,
270 path: path + '/root@hello@',
271 token: server.accessToken,
272 expectedStatus: HttpStatusCode.BAD_REQUEST_400
273 })
274 })
275
276 it('Should fail with an unknown subscription', async function () {
277 await makeDeleteRequest({
278 url: server.url,
279 path: path + '/root1@' + server.host,
280 token: server.accessToken,
281 expectedStatus: HttpStatusCode.NOT_FOUND_404
282 })
283 })
284
285 it('Should succeed with the correct parameters', async function () {
286 await makeDeleteRequest({
287 url: server.url,
288 path: path + '/user1_channel@' + server.host,
289 token: server.accessToken,
290 expectedStatus: HttpStatusCode.NO_CONTENT_204
291 })
292 })
293 })
294
295 after(async function () {
296 await cleanupTests([ server ])
297 })
298})
diff --git a/packages/tests/src/api/check-params/users-admin.ts b/packages/tests/src/api/check-params/users-admin.ts
new file mode 100644
index 000000000..1ad222ddc
--- /dev/null
+++ b/packages/tests/src/api/check-params/users-admin.ts
@@ -0,0 +1,457 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { MockSmtpServer } from '@tests/shared/mock-servers/index.js'
5import { omit } from '@peertube/peertube-core-utils'
6import { HttpStatusCode, UserAdminFlag, UserRole } from '@peertube/peertube-models'
7import {
8 cleanupTests,
9 ConfigCommand,
10 createSingleServer,
11 killallServers,
12 makeGetRequest,
13 makePostBodyRequest,
14 makePutBodyRequest,
15 PeerTubeServer,
16 setAccessTokensToServers
17} from '@peertube/peertube-server-commands'
18
19describe('Test users admin API validators', function () {
20 const path = '/api/v1/users/'
21 let userId: number
22 let rootId: number
23 let moderatorId: number
24 let server: PeerTubeServer
25 let userToken = ''
26 let moderatorToken = ''
27 let emailPort: number
28
29 // ---------------------------------------------------------------
30
31 before(async function () {
32 this.timeout(30000)
33
34 const emails: object[] = []
35 emailPort = await MockSmtpServer.Instance.collectEmails(emails)
36
37 {
38 server = await createSingleServer(1)
39
40 await setAccessTokensToServers([ server ])
41 }
42
43 {
44 const result = await server.users.generate('user1')
45 userToken = result.token
46 userId = result.userId
47 }
48
49 {
50 const result = await server.users.generate('moderator1', UserRole.MODERATOR)
51 moderatorToken = result.token
52 }
53
54 {
55 const result = await server.users.generate('moderator2', UserRole.MODERATOR)
56 moderatorId = result.userId
57 }
58 })
59
60 describe('When listing users', function () {
61 it('Should fail with a bad start pagination', async function () {
62 await checkBadStartPagination(server.url, path, server.accessToken)
63 })
64
65 it('Should fail with a bad count pagination', async function () {
66 await checkBadCountPagination(server.url, path, server.accessToken)
67 })
68
69 it('Should fail with an incorrect sort', async function () {
70 await checkBadSortPagination(server.url, path, server.accessToken)
71 })
72
73 it('Should fail with a non authenticated user', async function () {
74 await makeGetRequest({
75 url: server.url,
76 path,
77 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
78 })
79 })
80
81 it('Should fail with a non admin user', async function () {
82 await makeGetRequest({
83 url: server.url,
84 path,
85 token: userToken,
86 expectedStatus: HttpStatusCode.FORBIDDEN_403
87 })
88 })
89 })
90
91 describe('When adding a new user', function () {
92 const baseCorrectParams = {
93 username: 'user2',
94 email: 'test@example.com',
95 password: 'my super password',
96 videoQuota: -1,
97 videoQuotaDaily: -1,
98 role: UserRole.USER,
99 adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST
100 }
101
102 it('Should fail with a too small username', async function () {
103 const fields = { ...baseCorrectParams, username: '' }
104
105 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
106 })
107
108 it('Should fail with a too long username', async function () {
109 const fields = { ...baseCorrectParams, username: 'super'.repeat(50) }
110
111 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
112 })
113
114 it('Should fail with a not lowercase username', async function () {
115 const fields = { ...baseCorrectParams, username: 'Toto' }
116
117 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
118 })
119
120 it('Should fail with an incorrect username', async function () {
121 const fields = { ...baseCorrectParams, username: 'my username' }
122
123 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
124 })
125
126 it('Should fail with a missing email', async function () {
127 const fields = omit(baseCorrectParams, [ 'email' ])
128
129 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
130 })
131
132 it('Should fail with an invalid email', async function () {
133 const fields = { ...baseCorrectParams, email: 'test_example.com' }
134
135 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
136 })
137
138 it('Should fail with a too small password', async function () {
139 const fields = { ...baseCorrectParams, password: 'bla' }
140
141 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
142 })
143
144 it('Should fail with a too long password', async function () {
145 const fields = { ...baseCorrectParams, password: 'super'.repeat(61) }
146
147 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
148 })
149
150 it('Should fail with empty password and no smtp configured', async function () {
151 const fields = { ...baseCorrectParams, password: '' }
152
153 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
154 })
155
156 it('Should succeed with no password on a server with smtp enabled', async function () {
157 this.timeout(20000)
158
159 await killallServers([ server ])
160
161 await server.run(ConfigCommand.getEmailOverrideConfig(emailPort))
162
163 const fields = {
164 ...baseCorrectParams,
165
166 password: '',
167 username: 'create_password',
168 email: 'create_password@example.com'
169 }
170
171 await makePostBodyRequest({
172 url: server.url,
173 path,
174 token: server.accessToken,
175 fields,
176 expectedStatus: HttpStatusCode.OK_200
177 })
178 })
179
180 it('Should fail with invalid admin flags', async function () {
181 const fields = { ...baseCorrectParams, adminFlags: 'toto' }
182
183 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
184 })
185
186 it('Should fail with an non authenticated user', async function () {
187 await makePostBodyRequest({
188 url: server.url,
189 path,
190 token: 'super token',
191 fields: baseCorrectParams,
192 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
193 })
194 })
195
196 it('Should fail if we add a user with the same username', async function () {
197 const fields = { ...baseCorrectParams, username: 'user1' }
198
199 await makePostBodyRequest({
200 url: server.url,
201 path,
202 token: server.accessToken,
203 fields,
204 expectedStatus: HttpStatusCode.CONFLICT_409
205 })
206 })
207
208 it('Should fail if we add a user with the same email', async function () {
209 const fields = { ...baseCorrectParams, email: 'user1@example.com' }
210
211 await makePostBodyRequest({
212 url: server.url,
213 path,
214 token: server.accessToken,
215 fields,
216 expectedStatus: HttpStatusCode.CONFLICT_409
217 })
218 })
219
220 it('Should fail with an invalid videoQuota', async function () {
221 const fields = { ...baseCorrectParams, videoQuota: -5 }
222
223 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
224 })
225
226 it('Should fail with an invalid videoQuotaDaily', async function () {
227 const fields = { ...baseCorrectParams, videoQuotaDaily: -7 }
228
229 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
230 })
231
232 it('Should fail without a user role', async function () {
233 const fields = omit(baseCorrectParams, [ 'role' ])
234
235 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
236 })
237
238 it('Should fail with an invalid user role', async function () {
239 const fields = { ...baseCorrectParams, role: 88989 }
240
241 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
242 })
243
244 it('Should fail with a "peertube" username', async function () {
245 const fields = { ...baseCorrectParams, username: 'peertube' }
246
247 await makePostBodyRequest({
248 url: server.url,
249 path,
250 token: server.accessToken,
251 fields,
252 expectedStatus: HttpStatusCode.CONFLICT_409
253 })
254 })
255
256 it('Should fail to create a moderator or an admin with a moderator', async function () {
257 for (const role of [ UserRole.MODERATOR, UserRole.ADMINISTRATOR ]) {
258 const fields = { ...baseCorrectParams, role }
259
260 await makePostBodyRequest({
261 url: server.url,
262 path,
263 token: moderatorToken,
264 fields,
265 expectedStatus: HttpStatusCode.FORBIDDEN_403
266 })
267 }
268 })
269
270 it('Should succeed to create a user with a moderator', async function () {
271 const fields = { ...baseCorrectParams, username: 'a4656', email: 'a4656@example.com', role: UserRole.USER }
272
273 await makePostBodyRequest({
274 url: server.url,
275 path,
276 token: moderatorToken,
277 fields,
278 expectedStatus: HttpStatusCode.OK_200
279 })
280 })
281
282 it('Should succeed with the correct params', async function () {
283 await makePostBodyRequest({
284 url: server.url,
285 path,
286 token: server.accessToken,
287 fields: baseCorrectParams,
288 expectedStatus: HttpStatusCode.OK_200
289 })
290 })
291
292 it('Should fail with a non admin user', async function () {
293 const user = { username: 'user1' }
294 userToken = await server.login.getAccessToken(user)
295
296 const fields = {
297 username: 'user3',
298 email: 'test@example.com',
299 password: 'my super password',
300 videoQuota: 42000000
301 }
302 await makePostBodyRequest({ url: server.url, path, token: userToken, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
303 })
304 })
305
306 describe('When getting a user', function () {
307
308 it('Should fail with an non authenticated user', async function () {
309 await makeGetRequest({
310 url: server.url,
311 path: path + userId,
312 token: 'super token',
313 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
314 })
315 })
316
317 it('Should fail with a non admin user', async function () {
318 await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
319 })
320
321 it('Should succeed with the correct params', async function () {
322 await makeGetRequest({ url: server.url, path: path + userId, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
323 })
324 })
325
326 describe('When updating a user', function () {
327
328 it('Should fail with an invalid email attribute', async function () {
329 const fields = {
330 email: 'blabla'
331 }
332
333 await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
334 })
335
336 it('Should fail with an invalid emailVerified attribute', async function () {
337 const fields = {
338 emailVerified: 'yes'
339 }
340
341 await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
342 })
343
344 it('Should fail with an invalid videoQuota attribute', async function () {
345 const fields = {
346 videoQuota: -90
347 }
348
349 await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
350 })
351
352 it('Should fail with an invalid user role attribute', async function () {
353 const fields = {
354 role: 54878
355 }
356
357 await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
358 })
359
360 it('Should fail with a too small password', async function () {
361 const fields = {
362 currentPassword: 'password',
363 password: 'bla'
364 }
365
366 await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
367 })
368
369 it('Should fail with a too long password', async function () {
370 const fields = {
371 currentPassword: 'password',
372 password: 'super'.repeat(61)
373 }
374
375 await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields })
376 })
377
378 it('Should fail with an non authenticated user', async function () {
379 const fields = {
380 videoQuota: 42
381 }
382
383 await makePutBodyRequest({
384 url: server.url,
385 path: path + userId,
386 token: 'super token',
387 fields,
388 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
389 })
390 })
391
392 it('Should fail when updating root role', async function () {
393 const fields = {
394 role: UserRole.MODERATOR
395 }
396
397 await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields })
398 })
399
400 it('Should fail with invalid admin flags', async function () {
401 const fields = { adminFlags: 'toto' }
402
403 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
404 })
405
406 it('Should fail to update an admin with a moderator', async function () {
407 const fields = {
408 videoQuota: 42
409 }
410
411 await makePutBodyRequest({
412 url: server.url,
413 path: path + moderatorId,
414 token: moderatorToken,
415 fields,
416 expectedStatus: HttpStatusCode.FORBIDDEN_403
417 })
418 })
419
420 it('Should succeed to update a user with a moderator', async function () {
421 const fields = {
422 videoQuota: 42
423 }
424
425 await makePutBodyRequest({
426 url: server.url,
427 path: path + userId,
428 token: moderatorToken,
429 fields,
430 expectedStatus: HttpStatusCode.NO_CONTENT_204
431 })
432 })
433
434 it('Should succeed with the correct params', async function () {
435 const fields = {
436 email: 'email@example.com',
437 emailVerified: true,
438 videoQuota: 42,
439 role: UserRole.USER
440 }
441
442 await makePutBodyRequest({
443 url: server.url,
444 path: path + userId,
445 token: server.accessToken,
446 fields,
447 expectedStatus: HttpStatusCode.NO_CONTENT_204
448 })
449 })
450 })
451
452 after(async function () {
453 MockSmtpServer.Instance.kill()
454
455 await cleanupTests([ server ])
456 })
457})
diff --git a/packages/tests/src/api/check-params/users-emails.ts b/packages/tests/src/api/check-params/users-emails.ts
new file mode 100644
index 000000000..e382190ec
--- /dev/null
+++ b/packages/tests/src/api/check-params/users-emails.ts
@@ -0,0 +1,122 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2import { HttpStatusCode, UserRole } from '@peertube/peertube-models'
3import {
4 cleanupTests,
5 createSingleServer,
6 makePostBodyRequest,
7 PeerTubeServer,
8 setAccessTokensToServers
9} from '@peertube/peertube-server-commands'
10
11describe('Test users API validators', function () {
12 let server: PeerTubeServer
13
14 // ---------------------------------------------------------------
15
16 before(async function () {
17 this.timeout(30000)
18
19 server = await createSingleServer(1, {
20 rates_limit: {
21 ask_send_email: {
22 max: 10
23 }
24 }
25 })
26
27 await setAccessTokensToServers([ server ])
28 await server.config.enableSignup(true)
29
30 await server.users.generate('moderator2', UserRole.MODERATOR)
31
32 await server.registrations.requestRegistration({
33 username: 'request1',
34 registrationReason: 'tt'
35 })
36 })
37
38 describe('When asking a password reset', function () {
39 const path = '/api/v1/users/ask-reset-password'
40
41 it('Should fail with a missing email', async function () {
42 const fields = {}
43
44 await makePostBodyRequest({ url: server.url, path, fields })
45 })
46
47 it('Should fail with an invalid email', async function () {
48 const fields = { email: 'hello' }
49
50 await makePostBodyRequest({ url: server.url, path, fields })
51 })
52
53 it('Should success with the correct params', async function () {
54 const fields = { email: 'admin@example.com' }
55
56 await makePostBodyRequest({
57 url: server.url,
58 path,
59 fields,
60 expectedStatus: HttpStatusCode.NO_CONTENT_204
61 })
62 })
63 })
64
65 describe('When asking for an account verification email', function () {
66 const path = '/api/v1/users/ask-send-verify-email'
67
68 it('Should fail with a missing email', async function () {
69 const fields = {}
70
71 await makePostBodyRequest({ url: server.url, path, fields })
72 })
73
74 it('Should fail with an invalid email', async function () {
75 const fields = { email: 'hello' }
76
77 await makePostBodyRequest({ url: server.url, path, fields })
78 })
79
80 it('Should succeed with the correct params', async function () {
81 const fields = { email: 'admin@example.com' }
82
83 await makePostBodyRequest({
84 url: server.url,
85 path,
86 fields,
87 expectedStatus: HttpStatusCode.NO_CONTENT_204
88 })
89 })
90 })
91
92 describe('When asking for a registration verification email', function () {
93 const path = '/api/v1/users/registrations/ask-send-verify-email'
94
95 it('Should fail with a missing email', async function () {
96 const fields = {}
97
98 await makePostBodyRequest({ url: server.url, path, fields })
99 })
100
101 it('Should fail with an invalid email', async function () {
102 const fields = { email: 'hello' }
103
104 await makePostBodyRequest({ url: server.url, path, fields })
105 })
106
107 it('Should succeed with the correct params', async function () {
108 const fields = { email: 'request1@example.com' }
109
110 await makePostBodyRequest({
111 url: server.url,
112 path,
113 fields,
114 expectedStatus: HttpStatusCode.NO_CONTENT_204
115 })
116 })
117 })
118
119 after(async function () {
120 await cleanupTests([ server ])
121 })
122})
diff --git a/packages/tests/src/api/check-params/video-blacklist.ts b/packages/tests/src/api/check-params/video-blacklist.ts
new file mode 100644
index 000000000..6ec070b9b
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-blacklist.ts
@@ -0,0 +1,292 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
5import { HttpStatusCode, VideoBlacklistType } from '@peertube/peertube-models'
6import {
7 BlacklistCommand,
8 cleanupTests,
9 createMultipleServers,
10 doubleFollow,
11 makePostBodyRequest,
12 makePutBodyRequest,
13 PeerTubeServer,
14 setAccessTokensToServers,
15 waitJobs
16} from '@peertube/peertube-server-commands'
17
18describe('Test video blacklist API validators', function () {
19 let servers: PeerTubeServer[]
20 let notBlacklistedVideoId: string
21 let remoteVideoUUID: string
22 let userAccessToken1 = ''
23 let userAccessToken2 = ''
24 let command: BlacklistCommand
25
26 // ---------------------------------------------------------------
27
28 before(async function () {
29 this.timeout(120000)
30
31 servers = await createMultipleServers(2)
32
33 await setAccessTokensToServers(servers)
34 await doubleFollow(servers[0], servers[1])
35
36 {
37 const username = 'user1'
38 const password = 'my super password'
39 await servers[0].users.create({ username, password })
40 userAccessToken1 = await servers[0].login.getAccessToken({ username, password })
41 }
42
43 {
44 const username = 'user2'
45 const password = 'my super password'
46 await servers[0].users.create({ username, password })
47 userAccessToken2 = await servers[0].login.getAccessToken({ username, password })
48 }
49
50 {
51 servers[0].store.videoCreated = await servers[0].videos.upload({ token: userAccessToken1 })
52 }
53
54 {
55 const { uuid } = await servers[0].videos.upload()
56 notBlacklistedVideoId = uuid
57 }
58
59 {
60 const { uuid } = await servers[1].videos.upload()
61 remoteVideoUUID = uuid
62 }
63
64 await waitJobs(servers)
65
66 command = servers[0].blacklist
67 })
68
69 describe('When adding a video in blacklist', function () {
70 const basePath = '/api/v1/videos/'
71
72 it('Should fail with nothing', async function () {
73 const path = basePath + servers[0].store.videoCreated + '/blacklist'
74 const fields = {}
75 await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
76 })
77
78 it('Should fail with a wrong video', async function () {
79 const wrongPath = '/api/v1/videos/blabla/blacklist'
80 const fields = {}
81 await makePostBodyRequest({ url: servers[0].url, path: wrongPath, token: servers[0].accessToken, fields })
82 })
83
84 it('Should fail with a non authenticated user', async function () {
85 const path = basePath + servers[0].store.videoCreated + '/blacklist'
86 const fields = {}
87 await makePostBodyRequest({ url: servers[0].url, path, token: 'hello', fields, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
88 })
89
90 it('Should fail with a non admin user', async function () {
91 const path = basePath + servers[0].store.videoCreated + '/blacklist'
92 const fields = {}
93 await makePostBodyRequest({
94 url: servers[0].url,
95 path,
96 token: userAccessToken2,
97 fields,
98 expectedStatus: HttpStatusCode.FORBIDDEN_403
99 })
100 })
101
102 it('Should fail with an invalid reason', async function () {
103 const path = basePath + servers[0].store.videoCreated.uuid + '/blacklist'
104 const fields = { reason: 'a'.repeat(305) }
105
106 await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
107 })
108
109 it('Should fail to unfederate a remote video', async function () {
110 const path = basePath + remoteVideoUUID + '/blacklist'
111 const fields = { unfederate: true }
112
113 await makePostBodyRequest({
114 url: servers[0].url,
115 path,
116 token: servers[0].accessToken,
117 fields,
118 expectedStatus: HttpStatusCode.CONFLICT_409
119 })
120 })
121
122 it('Should succeed with the correct params', async function () {
123 const path = basePath + servers[0].store.videoCreated.uuid + '/blacklist'
124 const fields = {}
125
126 await makePostBodyRequest({
127 url: servers[0].url,
128 path,
129 token: servers[0].accessToken,
130 fields,
131 expectedStatus: HttpStatusCode.NO_CONTENT_204
132 })
133 })
134 })
135
136 describe('When updating a video in blacklist', function () {
137 const basePath = '/api/v1/videos/'
138
139 it('Should fail with a wrong video', async function () {
140 const wrongPath = '/api/v1/videos/blabla/blacklist'
141 const fields = {}
142 await makePutBodyRequest({ url: servers[0].url, path: wrongPath, token: servers[0].accessToken, fields })
143 })
144
145 it('Should fail with a video not blacklisted', async function () {
146 const path = '/api/v1/videos/' + notBlacklistedVideoId + '/blacklist'
147 const fields = {}
148 await makePutBodyRequest({
149 url: servers[0].url,
150 path,
151 token: servers[0].accessToken,
152 fields,
153 expectedStatus: HttpStatusCode.NOT_FOUND_404
154 })
155 })
156
157 it('Should fail with a non authenticated user', async function () {
158 const path = basePath + servers[0].store.videoCreated + '/blacklist'
159 const fields = {}
160 await makePutBodyRequest({ url: servers[0].url, path, token: 'hello', fields, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
161 })
162
163 it('Should fail with a non admin user', async function () {
164 const path = basePath + servers[0].store.videoCreated + '/blacklist'
165 const fields = {}
166 await makePutBodyRequest({
167 url: servers[0].url,
168 path,
169 token: userAccessToken2,
170 fields,
171 expectedStatus: HttpStatusCode.FORBIDDEN_403
172 })
173 })
174
175 it('Should fail with an invalid reason', async function () {
176 const path = basePath + servers[0].store.videoCreated.uuid + '/blacklist'
177 const fields = { reason: 'a'.repeat(305) }
178
179 await makePutBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
180 })
181
182 it('Should succeed with the correct params', async function () {
183 const path = basePath + servers[0].store.videoCreated.shortUUID + '/blacklist'
184 const fields = { reason: 'hello' }
185
186 await makePutBodyRequest({
187 url: servers[0].url,
188 path,
189 token: servers[0].accessToken,
190 fields,
191 expectedStatus: HttpStatusCode.NO_CONTENT_204
192 })
193 })
194 })
195
196 describe('When getting blacklisted video', function () {
197
198 it('Should fail with a non authenticated user', async function () {
199 await servers[0].videos.get({ id: servers[0].store.videoCreated.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
200 })
201
202 it('Should fail with another user', async function () {
203 await servers[0].videos.getWithToken({
204 token: userAccessToken2,
205 id: servers[0].store.videoCreated.uuid,
206 expectedStatus: HttpStatusCode.FORBIDDEN_403
207 })
208 })
209
210 it('Should succeed with the owner authenticated user', async function () {
211 const video = await servers[0].videos.getWithToken({ token: userAccessToken1, id: servers[0].store.videoCreated.uuid })
212 expect(video.blacklisted).to.be.true
213 })
214
215 it('Should succeed with an admin', async function () {
216 const video = servers[0].store.videoCreated
217
218 for (const id of [ video.id, video.uuid, video.shortUUID ]) {
219 const video = await servers[0].videos.getWithToken({ id, expectedStatus: HttpStatusCode.OK_200 })
220 expect(video.blacklisted).to.be.true
221 }
222 })
223 })
224
225 describe('When removing a video in blacklist', function () {
226
227 it('Should fail with a non authenticated user', async function () {
228 await command.remove({
229 token: 'fake token',
230 videoId: servers[0].store.videoCreated.uuid,
231 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
232 })
233 })
234
235 it('Should fail with a non admin user', async function () {
236 await command.remove({
237 token: userAccessToken2,
238 videoId: servers[0].store.videoCreated.uuid,
239 expectedStatus: HttpStatusCode.FORBIDDEN_403
240 })
241 })
242
243 it('Should fail with an incorrect id', async function () {
244 await command.remove({ videoId: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
245 })
246
247 it('Should fail with a not blacklisted video', async function () {
248 // The video was not added to the blacklist so it should fail
249 await command.remove({ videoId: notBlacklistedVideoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
250 })
251
252 it('Should succeed with the correct params', async function () {
253 await command.remove({ videoId: servers[0].store.videoCreated.uuid, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
254 })
255 })
256
257 describe('When listing videos in blacklist', function () {
258 const basePath = '/api/v1/videos/blacklist/'
259
260 it('Should fail with a non authenticated user', async function () {
261 await servers[0].blacklist.list({ token: 'fake token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
262 })
263
264 it('Should fail with a non admin user', async function () {
265 await servers[0].blacklist.list({ token: userAccessToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
266 })
267
268 it('Should fail with a bad start pagination', async function () {
269 await checkBadStartPagination(servers[0].url, basePath, servers[0].accessToken)
270 })
271
272 it('Should fail with a bad count pagination', async function () {
273 await checkBadCountPagination(servers[0].url, basePath, servers[0].accessToken)
274 })
275
276 it('Should fail with an incorrect sort', async function () {
277 await checkBadSortPagination(servers[0].url, basePath, servers[0].accessToken)
278 })
279
280 it('Should fail with an invalid type', async function () {
281 await servers[0].blacklist.list({ type: 0 as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
282 })
283
284 it('Should succeed with the correct parameters', async function () {
285 await servers[0].blacklist.list({ type: VideoBlacklistType.MANUAL })
286 })
287 })
288
289 after(async function () {
290 await cleanupTests(servers)
291 })
292})
diff --git a/packages/tests/src/api/check-params/video-captions.ts b/packages/tests/src/api/check-params/video-captions.ts
new file mode 100644
index 000000000..4150b095f
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-captions.ts
@@ -0,0 +1,307 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
4import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createSingleServer,
8 makeDeleteRequest,
9 makeGetRequest,
10 makeUploadRequest,
11 PeerTubeServer,
12 setAccessTokensToServers
13} from '@peertube/peertube-server-commands'
14
15describe('Test video captions API validator', function () {
16 const path = '/api/v1/videos/'
17
18 let server: PeerTubeServer
19 let userAccessToken: string
20 let video: VideoCreateResult
21 let privateVideo: VideoCreateResult
22
23 // ---------------------------------------------------------------
24
25 before(async function () {
26 this.timeout(120000)
27
28 server = await createSingleServer(1)
29
30 await setAccessTokensToServers([ server ])
31
32 video = await server.videos.upload()
33 privateVideo = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PRIVATE } })
34
35 {
36 const user = {
37 username: 'user1',
38 password: 'my super password'
39 }
40 await server.users.create({ username: user.username, password: user.password })
41 userAccessToken = await server.login.getAccessToken(user)
42 }
43 })
44
45 describe('When adding video caption', function () {
46 const fields = { }
47 const attaches = {
48 captionfile: buildAbsoluteFixturePath('subtitle-good1.vtt')
49 }
50
51 it('Should fail without a valid uuid', async function () {
52 await makeUploadRequest({
53 method: 'PUT',
54 url: server.url,
55 path: path + '4da6fde3-88f7-4d16-b119-108df563d0b06/captions/fr',
56 token: server.accessToken,
57 fields,
58 attaches
59 })
60 })
61
62 it('Should fail with an unknown id', async function () {
63 await makeUploadRequest({
64 method: 'PUT',
65 url: server.url,
66 path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/captions/fr',
67 token: server.accessToken,
68 fields,
69 attaches,
70 expectedStatus: 404
71 })
72 })
73
74 it('Should fail with a missing language in path', async function () {
75 const captionPath = path + video.uuid + '/captions'
76 await makeUploadRequest({
77 method: 'PUT',
78 url: server.url,
79 path: captionPath,
80 token: server.accessToken,
81 fields,
82 attaches
83 })
84 })
85
86 it('Should fail with an unknown language', async function () {
87 const captionPath = path + video.uuid + '/captions/15'
88 await makeUploadRequest({
89 method: 'PUT',
90 url: server.url,
91 path: captionPath,
92 token: server.accessToken,
93 fields,
94 attaches
95 })
96 })
97
98 it('Should fail without access token', async function () {
99 const captionPath = path + video.uuid + '/captions/fr'
100 await makeUploadRequest({
101 method: 'PUT',
102 url: server.url,
103 path: captionPath,
104 fields,
105 attaches,
106 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
107 })
108 })
109
110 it('Should fail with a bad access token', async function () {
111 const captionPath = path + video.uuid + '/captions/fr'
112 await makeUploadRequest({
113 method: 'PUT',
114 url: server.url,
115 path: captionPath,
116 token: 'blabla',
117 fields,
118 attaches,
119 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
120 })
121 })
122
123 // We accept any file now
124 // it('Should fail with an invalid captionfile extension', async function () {
125 // const attaches = {
126 // 'captionfile': buildAbsoluteFixturePath('subtitle-bad.txt')
127 // }
128 //
129 // const captionPath = path + video.uuid + '/captions/fr'
130 // await makeUploadRequest({
131 // method: 'PUT',
132 // url: server.url,
133 // path: captionPath,
134 // token: server.accessToken,
135 // fields,
136 // attaches,
137 // expectedStatus: HttpStatusCode.BAD_REQUEST_400
138 // })
139 // })
140
141 // We don't check the extension yet
142 // it('Should fail with an invalid captionfile extension and octet-stream mime type', async function () {
143 // await createVideoCaption({
144 // url: server.url,
145 // accessToken: server.accessToken,
146 // language: 'zh',
147 // videoId: video.uuid,
148 // fixture: 'subtitle-bad.txt',
149 // mimeType: 'application/octet-stream',
150 // expectedStatus: HttpStatusCode.BAD_REQUEST_400
151 // })
152 // })
153
154 it('Should succeed with a valid captionfile extension and octet-stream mime type', async function () {
155 await server.captions.add({
156 language: 'zh',
157 videoId: video.uuid,
158 fixture: 'subtitle-good.srt',
159 mimeType: 'application/octet-stream'
160 })
161 })
162
163 // We don't check the file validity yet
164 // it('Should fail with an invalid captionfile srt', async function () {
165 // const attaches = {
166 // 'captionfile': buildAbsoluteFixturePath('subtitle-bad.srt')
167 // }
168 //
169 // const captionPath = path + video.uuid + '/captions/fr'
170 // await makeUploadRequest({
171 // method: 'PUT',
172 // url: server.url,
173 // path: captionPath,
174 // token: server.accessToken,
175 // fields,
176 // attaches,
177 // expectedStatus: HttpStatusCode.INTERNAL_SERVER_ERROR_500
178 // })
179 // })
180
181 it('Should success with the correct parameters', async function () {
182 const captionPath = path + video.uuid + '/captions/fr'
183 await makeUploadRequest({
184 method: 'PUT',
185 url: server.url,
186 path: captionPath,
187 token: server.accessToken,
188 fields,
189 attaches,
190 expectedStatus: HttpStatusCode.NO_CONTENT_204
191 })
192 })
193 })
194
195 describe('When listing video captions', function () {
196 it('Should fail without a valid uuid', async function () {
197 await makeGetRequest({ url: server.url, path: path + '4da6fde3-88f7-4d16-b119-108df563d0b06/captions' })
198 })
199
200 it('Should fail with an unknown id', async function () {
201 await makeGetRequest({
202 url: server.url,
203 path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/captions',
204 expectedStatus: HttpStatusCode.NOT_FOUND_404
205 })
206 })
207
208 it('Should fail with a private video without token', async function () {
209 await makeGetRequest({
210 url: server.url,
211 path: path + privateVideo.shortUUID + '/captions',
212 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
213 })
214 })
215
216 it('Should fail with another user token', async function () {
217 await makeGetRequest({
218 url: server.url,
219 token: userAccessToken,
220 path: path + privateVideo.shortUUID + '/captions',
221 expectedStatus: HttpStatusCode.FORBIDDEN_403
222 })
223 })
224
225 it('Should success with the correct parameters', async function () {
226 await makeGetRequest({ url: server.url, path: path + video.shortUUID + '/captions', expectedStatus: HttpStatusCode.OK_200 })
227
228 await makeGetRequest({
229 url: server.url,
230 path: path + privateVideo.shortUUID + '/captions',
231 token: server.accessToken,
232 expectedStatus: HttpStatusCode.OK_200
233 })
234 })
235 })
236
237 describe('When deleting video caption', function () {
238 it('Should fail without a valid uuid', async function () {
239 await makeDeleteRequest({
240 url: server.url,
241 path: path + '4da6fde3-88f7-4d16-b119-108df563d0b06/captions/fr',
242 token: server.accessToken
243 })
244 })
245
246 it('Should fail with an unknown id', async function () {
247 await makeDeleteRequest({
248 url: server.url,
249 path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/captions/fr',
250 token: server.accessToken,
251 expectedStatus: HttpStatusCode.NOT_FOUND_404
252 })
253 })
254
255 it('Should fail with an invalid language', async function () {
256 await makeDeleteRequest({
257 url: server.url,
258 path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/captions/16',
259 token: server.accessToken
260 })
261 })
262
263 it('Should fail with a missing language', async function () {
264 const captionPath = path + video.shortUUID + '/captions'
265 await makeDeleteRequest({ url: server.url, path: captionPath, token: server.accessToken })
266 })
267
268 it('Should fail with an unknown language', async function () {
269 const captionPath = path + video.shortUUID + '/captions/15'
270 await makeDeleteRequest({ url: server.url, path: captionPath, token: server.accessToken })
271 })
272
273 it('Should fail without access token', async function () {
274 const captionPath = path + video.shortUUID + '/captions/fr'
275 await makeDeleteRequest({ url: server.url, path: captionPath, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
276 })
277
278 it('Should fail with a bad access token', async function () {
279 const captionPath = path + video.shortUUID + '/captions/fr'
280 await makeDeleteRequest({ url: server.url, path: captionPath, token: 'coucou', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
281 })
282
283 it('Should fail with another user', async function () {
284 const captionPath = path + video.shortUUID + '/captions/fr'
285 await makeDeleteRequest({
286 url: server.url,
287 path: captionPath,
288 token: userAccessToken,
289 expectedStatus: HttpStatusCode.FORBIDDEN_403
290 })
291 })
292
293 it('Should success with the correct parameters', async function () {
294 const captionPath = path + video.shortUUID + '/captions/fr'
295 await makeDeleteRequest({
296 url: server.url,
297 path: captionPath,
298 token: server.accessToken,
299 expectedStatus: HttpStatusCode.NO_CONTENT_204
300 })
301 })
302 })
303
304 after(async function () {
305 await cleanupTests([ server ])
306 })
307})
diff --git a/packages/tests/src/api/check-params/video-channel-syncs.ts b/packages/tests/src/api/check-params/video-channel-syncs.ts
new file mode 100644
index 000000000..d95f3319a
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-channel-syncs.ts
@@ -0,0 +1,319 @@
1import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
2import { FIXTURE_URLS } from '@tests/shared/tests.js'
3import { HttpStatusCode, VideoChannelSyncCreate } from '@peertube/peertube-models'
4import {
5 ChannelSyncsCommand,
6 createSingleServer,
7 makePostBodyRequest,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 setDefaultVideoChannel
11} from '@peertube/peertube-server-commands'
12
13describe('Test video channel sync API validator', () => {
14 const path = '/api/v1/video-channel-syncs'
15 let server: PeerTubeServer
16 let command: ChannelSyncsCommand
17 let rootChannelId: number
18 let rootChannelSyncId: number
19 const userInfo = {
20 accessToken: '',
21 username: 'user1',
22 id: -1,
23 channelId: -1,
24 syncId: -1
25 }
26
27 async function withChannelSyncDisabled<T> (callback: () => Promise<T>): Promise<void> {
28 try {
29 await server.config.disableChannelSync()
30 await callback()
31 } finally {
32 await server.config.enableChannelSync()
33 }
34 }
35
36 async function withMaxSyncsPerUser<T> (maxSync: number, callback: () => Promise<T>): Promise<void> {
37 const origConfig = await server.config.getCustomConfig()
38
39 await server.config.updateExistingSubConfig({
40 newConfig: {
41 import: {
42 videoChannelSynchronization: {
43 maxPerUser: maxSync
44 }
45 }
46 }
47 })
48
49 try {
50 await callback()
51 } finally {
52 await server.config.updateCustomConfig({ newCustomConfig: origConfig })
53 }
54 }
55
56 before(async function () {
57 this.timeout(30_000)
58
59 server = await createSingleServer(1)
60
61 await setAccessTokensToServers([ server ])
62 await setDefaultVideoChannel([ server ])
63
64 command = server.channelSyncs
65
66 rootChannelId = server.store.channel.id
67
68 {
69 userInfo.accessToken = await server.users.generateUserAndToken(userInfo.username)
70
71 const { videoChannels, id: userId } = await server.users.getMyInfo({ token: userInfo.accessToken })
72 userInfo.id = userId
73 userInfo.channelId = videoChannels[0].id
74 }
75
76 await server.config.enableChannelSync()
77 })
78
79 describe('When creating a sync', function () {
80 let baseCorrectParams: VideoChannelSyncCreate
81
82 before(function () {
83 baseCorrectParams = {
84 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
85 videoChannelId: rootChannelId
86 }
87 })
88
89 it('Should fail when sync is disabled', async function () {
90 await withChannelSyncDisabled(async () => {
91 await command.create({
92 token: server.accessToken,
93 attributes: baseCorrectParams,
94 expectedStatus: HttpStatusCode.FORBIDDEN_403
95 })
96 })
97 })
98
99 it('Should fail with nothing', async function () {
100 const fields = {}
101 await makePostBodyRequest({
102 url: server.url,
103 path,
104 token: server.accessToken,
105 fields,
106 expectedStatus: HttpStatusCode.BAD_REQUEST_400
107 })
108 })
109
110 it('Should fail with no authentication', async function () {
111 await command.create({
112 token: null,
113 attributes: baseCorrectParams,
114 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
115 })
116 })
117
118 it('Should fail without a target url', async function () {
119 const attributes: VideoChannelSyncCreate = {
120 ...baseCorrectParams,
121 externalChannelUrl: null
122 }
123 await command.create({
124 token: server.accessToken,
125 attributes,
126 expectedStatus: HttpStatusCode.BAD_REQUEST_400
127 })
128 })
129
130 it('Should fail without a channelId', async function () {
131 const attributes: VideoChannelSyncCreate = {
132 ...baseCorrectParams,
133 videoChannelId: null
134 }
135 await command.create({
136 token: server.accessToken,
137 attributes,
138 expectedStatus: HttpStatusCode.BAD_REQUEST_400
139 })
140 })
141
142 it('Should fail with a channelId refering nothing', async function () {
143 const attributes: VideoChannelSyncCreate = {
144 ...baseCorrectParams,
145 videoChannelId: 42
146 }
147 await command.create({
148 token: server.accessToken,
149 attributes,
150 expectedStatus: HttpStatusCode.NOT_FOUND_404
151 })
152 })
153
154 it('Should fail to create a sync when the user does not own the channel', async function () {
155 await command.create({
156 token: userInfo.accessToken,
157 attributes: baseCorrectParams,
158 expectedStatus: HttpStatusCode.FORBIDDEN_403
159 })
160 })
161
162 it('Should succeed to create a sync with root and for another user\'s channel', async function () {
163 const { videoChannelSync } = await command.create({
164 token: server.accessToken,
165 attributes: {
166 ...baseCorrectParams,
167 videoChannelId: userInfo.channelId
168 },
169 expectedStatus: HttpStatusCode.OK_200
170 })
171 userInfo.syncId = videoChannelSync.id
172 })
173
174 it('Should succeed with the correct parameters', async function () {
175 const { videoChannelSync } = await command.create({
176 token: server.accessToken,
177 attributes: baseCorrectParams,
178 expectedStatus: HttpStatusCode.OK_200
179 })
180 rootChannelSyncId = videoChannelSync.id
181 })
182
183 it('Should fail when the user exceeds allowed number of synchronizations', async function () {
184 await withMaxSyncsPerUser(1, async () => {
185 await command.create({
186 token: server.accessToken,
187 attributes: {
188 ...baseCorrectParams,
189 videoChannelId: userInfo.channelId
190 },
191 expectedStatus: HttpStatusCode.BAD_REQUEST_400
192 })
193 })
194 })
195 })
196
197 describe('When listing my channel syncs', function () {
198 const myPath = '/api/v1/accounts/root/video-channel-syncs'
199
200 it('Should fail with a bad start pagination', async function () {
201 await checkBadStartPagination(server.url, myPath, server.accessToken)
202 })
203
204 it('Should fail with a bad count pagination', async function () {
205 await checkBadCountPagination(server.url, myPath, server.accessToken)
206 })
207
208 it('Should fail with an incorrect sort', async function () {
209 await checkBadSortPagination(server.url, myPath, server.accessToken)
210 })
211
212 it('Should succeed with the correct parameters', async function () {
213 await command.listByAccount({
214 accountName: 'root',
215 token: server.accessToken,
216 expectedStatus: HttpStatusCode.OK_200
217 })
218 })
219
220 it('Should fail with no authentication', async function () {
221 await command.listByAccount({
222 accountName: 'root',
223 token: null,
224 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
225 })
226 })
227
228 it('Should fail when a simple user lists another user\'s synchronizations', async function () {
229 await command.listByAccount({
230 accountName: 'root',
231 token: userInfo.accessToken,
232 expectedStatus: HttpStatusCode.FORBIDDEN_403
233 })
234 })
235
236 it('Should succeed when root lists another user\'s synchronizations', async function () {
237 await command.listByAccount({
238 accountName: userInfo.username,
239 token: server.accessToken,
240 expectedStatus: HttpStatusCode.OK_200
241 })
242 })
243
244 it('Should succeed even with synchronization disabled', async function () {
245 await withChannelSyncDisabled(async function () {
246 await command.listByAccount({
247 accountName: 'root',
248 token: server.accessToken,
249 expectedStatus: HttpStatusCode.OK_200
250 })
251 })
252 })
253 })
254
255 describe('When triggering deletion', function () {
256 it('should fail with no authentication', async function () {
257 await command.delete({
258 channelSyncId: userInfo.syncId,
259 token: null,
260 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
261 })
262 })
263
264 it('Should fail when channelSyncId does not refer to any sync', async function () {
265 await command.delete({
266 channelSyncId: 42,
267 token: server.accessToken,
268 expectedStatus: HttpStatusCode.NOT_FOUND_404
269 })
270 })
271
272 it('Should fail when sync is not owned by the user', async function () {
273 await command.delete({
274 channelSyncId: rootChannelSyncId,
275 token: userInfo.accessToken,
276 expectedStatus: HttpStatusCode.FORBIDDEN_403
277 })
278 })
279
280 it('Should succeed when root delete a sync they do not own', async function () {
281 await command.delete({
282 channelSyncId: userInfo.syncId,
283 token: server.accessToken,
284 expectedStatus: HttpStatusCode.NO_CONTENT_204
285 })
286 })
287
288 it('should succeed when user delete a sync they own', async function () {
289 const { videoChannelSync } = await command.create({
290 attributes: {
291 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
292 videoChannelId: userInfo.channelId
293 },
294 token: server.accessToken,
295 expectedStatus: HttpStatusCode.OK_200
296 })
297
298 await command.delete({
299 channelSyncId: videoChannelSync.id,
300 token: server.accessToken,
301 expectedStatus: HttpStatusCode.NO_CONTENT_204
302 })
303 })
304
305 it('Should succeed even when synchronization is disabled', async function () {
306 await withChannelSyncDisabled(async function () {
307 await command.delete({
308 channelSyncId: rootChannelSyncId,
309 token: server.accessToken,
310 expectedStatus: HttpStatusCode.NO_CONTENT_204
311 })
312 })
313 })
314 })
315
316 after(async function () {
317 await server?.kill()
318 })
319})
diff --git a/packages/tests/src/api/check-params/video-channels.ts b/packages/tests/src/api/check-params/video-channels.ts
new file mode 100644
index 000000000..84b962b19
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-channels.ts
@@ -0,0 +1,379 @@
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 { HttpStatusCode, VideoChannelUpdate } from '@peertube/peertube-models'
6import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
7import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
8import {
9 ChannelsCommand,
10 cleanupTests,
11 createSingleServer,
12 makeGetRequest,
13 makePostBodyRequest,
14 makePutBodyRequest,
15 makeUploadRequest,
16 PeerTubeServer,
17 setAccessTokensToServers
18} from '@peertube/peertube-server-commands'
19
20describe('Test video channels API validator', function () {
21 const videoChannelPath = '/api/v1/video-channels'
22 let server: PeerTubeServer
23 const userInfo = {
24 accessToken: '',
25 channelName: 'fake_channel',
26 id: -1,
27 videoQuota: -1,
28 videoQuotaDaily: -1
29 }
30 let command: ChannelsCommand
31
32 // ---------------------------------------------------------------
33
34 before(async function () {
35 this.timeout(30000)
36
37 server = await createSingleServer(1)
38
39 await setAccessTokensToServers([ server ])
40
41 const userCreds = {
42 username: 'fake',
43 password: 'fake_password'
44 }
45
46 {
47 const user = await server.users.create({ username: userCreds.username, password: userCreds.password })
48 userInfo.id = user.id
49 userInfo.accessToken = await server.login.getAccessToken(userCreds)
50 }
51
52 command = server.channels
53 })
54
55 describe('When listing a video channels', function () {
56 it('Should fail with a bad start pagination', async function () {
57 await checkBadStartPagination(server.url, videoChannelPath, server.accessToken)
58 })
59
60 it('Should fail with a bad count pagination', async function () {
61 await checkBadCountPagination(server.url, videoChannelPath, server.accessToken)
62 })
63
64 it('Should fail with an incorrect sort', async function () {
65 await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
66 })
67 })
68
69 describe('When listing account video channels', function () {
70 const accountChannelPath = '/api/v1/accounts/fake/video-channels'
71
72 it('Should fail with a bad start pagination', async function () {
73 await checkBadStartPagination(server.url, accountChannelPath, server.accessToken)
74 })
75
76 it('Should fail with a bad count pagination', async function () {
77 await checkBadCountPagination(server.url, accountChannelPath, server.accessToken)
78 })
79
80 it('Should fail with an incorrect sort', async function () {
81 await checkBadSortPagination(server.url, accountChannelPath, server.accessToken)
82 })
83
84 it('Should fail with a unknown account', async function () {
85 await server.channels.listByAccount({ accountName: 'unknown', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
86 })
87
88 it('Should succeed with the correct parameters', async function () {
89 await makeGetRequest({
90 url: server.url,
91 path: accountChannelPath,
92 expectedStatus: HttpStatusCode.OK_200
93 })
94 })
95 })
96
97 describe('When adding a video channel', function () {
98 const baseCorrectParams = {
99 name: 'super_channel',
100 displayName: 'hello',
101 description: 'super description',
102 support: 'super support text'
103 }
104
105 it('Should fail with a non authenticated user', async function () {
106 await makePostBodyRequest({
107 url: server.url,
108 path: videoChannelPath,
109 token: 'none',
110 fields: baseCorrectParams,
111 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
112 })
113 })
114
115 it('Should fail with nothing', async function () {
116 const fields = {}
117 await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
118 })
119
120 it('Should fail without a name', async function () {
121 const fields = omit(baseCorrectParams, [ 'name' ])
122 await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
123 })
124
125 it('Should fail with a bad name', async function () {
126 const fields = { ...baseCorrectParams, name: 'super name' }
127 await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
128 })
129
130 it('Should fail without a name', async function () {
131 const fields = omit(baseCorrectParams, [ 'displayName' ])
132 await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
133 })
134
135 it('Should fail with a long name', async function () {
136 const fields = { ...baseCorrectParams, displayName: 'super'.repeat(25) }
137 await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
138 })
139
140 it('Should fail with a long description', async function () {
141 const fields = { ...baseCorrectParams, description: 'super'.repeat(201) }
142 await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
143 })
144
145 it('Should fail with a long support text', async function () {
146 const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
147 await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
148 })
149
150 it('Should succeed with the correct parameters', async function () {
151 await makePostBodyRequest({
152 url: server.url,
153 path: videoChannelPath,
154 token: server.accessToken,
155 fields: baseCorrectParams,
156 expectedStatus: HttpStatusCode.OK_200
157 })
158 })
159
160 it('Should fail when adding a channel with the same username', async function () {
161 await makePostBodyRequest({
162 url: server.url,
163 path: videoChannelPath,
164 token: server.accessToken,
165 fields: baseCorrectParams,
166 expectedStatus: HttpStatusCode.CONFLICT_409
167 })
168 })
169 })
170
171 describe('When updating a video channel', function () {
172 const baseCorrectParams: VideoChannelUpdate = {
173 displayName: 'hello',
174 description: 'super description',
175 support: 'toto',
176 bulkVideosSupportUpdate: false
177 }
178 let path: string
179
180 before(async function () {
181 path = videoChannelPath + '/super_channel'
182 })
183
184 it('Should fail with a non authenticated user', async function () {
185 await makePutBodyRequest({
186 url: server.url,
187 path,
188 token: 'hi',
189 fields: baseCorrectParams,
190 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
191 })
192 })
193
194 it('Should fail with another authenticated user', async function () {
195 await makePutBodyRequest({
196 url: server.url,
197 path,
198 token: userInfo.accessToken,
199 fields: baseCorrectParams,
200 expectedStatus: HttpStatusCode.FORBIDDEN_403
201 })
202 })
203
204 it('Should fail with a long name', async function () {
205 const fields = { ...baseCorrectParams, displayName: 'super'.repeat(25) }
206 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
207 })
208
209 it('Should fail with a long description', async function () {
210 const fields = { ...baseCorrectParams, description: 'super'.repeat(201) }
211 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
212 })
213
214 it('Should fail with a long support text', async function () {
215 const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
216 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
217 })
218
219 it('Should fail with a bad bulkVideosSupportUpdate field', async function () {
220 const fields = { ...baseCorrectParams, bulkVideosSupportUpdate: 'super' }
221 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
222 })
223
224 it('Should succeed with the correct parameters', async function () {
225 await makePutBodyRequest({
226 url: server.url,
227 path,
228 token: server.accessToken,
229 fields: baseCorrectParams,
230 expectedStatus: HttpStatusCode.NO_CONTENT_204
231 })
232 })
233 })
234
235 describe('When updating video channel avatars/banners', function () {
236 const types = [ 'avatar', 'banner' ]
237 let path: string
238
239 before(async function () {
240 path = videoChannelPath + '/super_channel'
241 })
242
243 it('Should fail with an incorrect input file', async function () {
244 for (const type of types) {
245 const fields = {}
246 const attaches = {
247 [type + 'file']: buildAbsoluteFixturePath('video_short.mp4')
248 }
249
250 await makeUploadRequest({ url: server.url, path: `${path}/${type}/pick`, token: server.accessToken, fields, attaches })
251 }
252 })
253
254 it('Should fail with a big file', async function () {
255 for (const type of types) {
256 const fields = {}
257 const attaches = {
258 [type + 'file']: buildAbsoluteFixturePath('avatar-big.png')
259 }
260 await makeUploadRequest({ url: server.url, path: `${path}/${type}/pick`, token: server.accessToken, fields, attaches })
261 }
262 })
263
264 it('Should fail with an unauthenticated user', async function () {
265 for (const type of types) {
266 const fields = {}
267 const attaches = {
268 [type + 'file']: buildAbsoluteFixturePath('avatar.png')
269 }
270 await makeUploadRequest({
271 url: server.url,
272 path: `${path}/${type}/pick`,
273 fields,
274 attaches,
275 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
276 })
277 }
278 })
279
280 it('Should succeed with the correct params', async function () {
281 for (const type of types) {
282 const fields = {}
283 const attaches = {
284 [type + 'file']: buildAbsoluteFixturePath('avatar.png')
285 }
286 await makeUploadRequest({
287 url: server.url,
288 path: `${path}/${type}/pick`,
289 token: server.accessToken,
290 fields,
291 attaches,
292 expectedStatus: HttpStatusCode.OK_200
293 })
294 }
295 })
296 })
297
298 describe('When getting a video channel', function () {
299 it('Should return the list of the video channels with nothing', async function () {
300 const res = await makeGetRequest({
301 url: server.url,
302 path: videoChannelPath,
303 expectedStatus: HttpStatusCode.OK_200
304 })
305
306 expect(res.body.data).to.be.an('array')
307 })
308
309 it('Should return 404 with an incorrect video channel', async function () {
310 await makeGetRequest({
311 url: server.url,
312 path: videoChannelPath + '/super_channel2',
313 expectedStatus: HttpStatusCode.NOT_FOUND_404
314 })
315 })
316
317 it('Should succeed with the correct parameters', async function () {
318 await makeGetRequest({
319 url: server.url,
320 path: videoChannelPath + '/super_channel',
321 expectedStatus: HttpStatusCode.OK_200
322 })
323 })
324 })
325
326 describe('When getting channel followers', function () {
327 const path = '/api/v1/video-channels/super_channel/followers'
328
329 it('Should fail with a bad start pagination', async function () {
330 await checkBadStartPagination(server.url, path, server.accessToken)
331 })
332
333 it('Should fail with a bad count pagination', async function () {
334 await checkBadCountPagination(server.url, path, server.accessToken)
335 })
336
337 it('Should fail with an incorrect sort', async function () {
338 await checkBadSortPagination(server.url, path, server.accessToken)
339 })
340
341 it('Should fail with a unauthenticated user', async function () {
342 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
343 })
344
345 it('Should fail with a another user', async function () {
346 await makeGetRequest({ url: server.url, path, token: userInfo.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
347 })
348
349 it('Should succeed with the correct params', async function () {
350 await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
351 })
352 })
353
354 describe('When deleting a video channel', function () {
355 it('Should fail with a non authenticated user', async function () {
356 await command.delete({ token: 'coucou', channelName: 'super_channel', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
357 })
358
359 it('Should fail with another authenticated user', async function () {
360 await command.delete({ token: userInfo.accessToken, channelName: 'super_channel', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
361 })
362
363 it('Should fail with an unknown video channel id', async function () {
364 await command.delete({ channelName: 'super_channel2', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
365 })
366
367 it('Should succeed with the correct parameters', async function () {
368 await command.delete({ channelName: 'super_channel' })
369 })
370
371 it('Should fail to delete the last user video channel', async function () {
372 await command.delete({ channelName: 'root_channel', expectedStatus: HttpStatusCode.CONFLICT_409 })
373 })
374 })
375
376 after(async function () {
377 await cleanupTests([ server ])
378 })
379})
diff --git a/packages/tests/src/api/check-params/video-comments.ts b/packages/tests/src/api/check-params/video-comments.ts
new file mode 100644
index 000000000..177361606
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-comments.ts
@@ -0,0 +1,484 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
5import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models'
6import {
7 cleanupTests,
8 createSingleServer,
9 makeDeleteRequest,
10 makeGetRequest,
11 makePostBodyRequest,
12 PeerTubeServer,
13 setAccessTokensToServers
14} from '@peertube/peertube-server-commands'
15
16describe('Test video comments API validator', function () {
17 let pathThread: string
18 let pathComment: string
19
20 let server: PeerTubeServer
21
22 let video: VideoCreateResult
23
24 let userAccessToken: string
25 let userAccessToken2: string
26
27 let commentId: number
28 let privateCommentId: number
29 let privateVideo: VideoCreateResult
30
31 // ---------------------------------------------------------------
32
33 before(async function () {
34 this.timeout(120000)
35
36 server = await createSingleServer(1)
37
38 await setAccessTokensToServers([ server ])
39
40 {
41 video = await server.videos.upload({ attributes: {} })
42 pathThread = '/api/v1/videos/' + video.uuid + '/comment-threads'
43 }
44
45 {
46 privateVideo = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PRIVATE } })
47 }
48
49 {
50 const created = await server.comments.createThread({ videoId: video.uuid, text: 'coucou' })
51 commentId = created.id
52 pathComment = '/api/v1/videos/' + video.uuid + '/comments/' + commentId
53 }
54
55 {
56 const created = await server.comments.createThread({ videoId: privateVideo.uuid, text: 'coucou' })
57 privateCommentId = created.id
58 }
59
60 {
61 const user = { username: 'user1', password: 'my super password' }
62 await server.users.create({ username: user.username, password: user.password })
63 userAccessToken = await server.login.getAccessToken(user)
64 }
65
66 {
67 const user = { username: 'user2', password: 'my super password' }
68 await server.users.create({ username: user.username, password: user.password })
69 userAccessToken2 = await server.login.getAccessToken(user)
70 }
71 })
72
73 describe('When listing video comment threads', function () {
74 it('Should fail with a bad start pagination', async function () {
75 await checkBadStartPagination(server.url, pathThread, server.accessToken)
76 })
77
78 it('Should fail with a bad count pagination', async function () {
79 await checkBadCountPagination(server.url, pathThread, server.accessToken)
80 })
81
82 it('Should fail with an incorrect sort', async function () {
83 await checkBadSortPagination(server.url, pathThread, server.accessToken)
84 })
85
86 it('Should fail with an incorrect video', async function () {
87 await makeGetRequest({
88 url: server.url,
89 path: '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comment-threads',
90 expectedStatus: HttpStatusCode.NOT_FOUND_404
91 })
92 })
93
94 it('Should fail with a private video without token', async function () {
95 await makeGetRequest({
96 url: server.url,
97 path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
98 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
99 })
100 })
101
102 it('Should fail with another user token', async function () {
103 await makeGetRequest({
104 url: server.url,
105 token: userAccessToken,
106 path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
107 expectedStatus: HttpStatusCode.FORBIDDEN_403
108 })
109 })
110
111 it('Should succeed with the correct params', async function () {
112 await makeGetRequest({
113 url: server.url,
114 token: server.accessToken,
115 path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
116 expectedStatus: HttpStatusCode.OK_200
117 })
118 })
119 })
120
121 describe('When listing comments of a thread', function () {
122 it('Should fail with an incorrect video', async function () {
123 await makeGetRequest({
124 url: server.url,
125 path: '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comment-threads/' + commentId,
126 expectedStatus: HttpStatusCode.NOT_FOUND_404
127 })
128 })
129
130 it('Should fail with an incorrect thread id', async function () {
131 await makeGetRequest({
132 url: server.url,
133 path: '/api/v1/videos/' + video.shortUUID + '/comment-threads/156',
134 expectedStatus: HttpStatusCode.NOT_FOUND_404
135 })
136 })
137
138 it('Should fail with a private video without token', async function () {
139 await makeGetRequest({
140 url: server.url,
141 path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads/' + privateCommentId,
142 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
143 })
144 })
145
146 it('Should fail with another user token', async function () {
147 await makeGetRequest({
148 url: server.url,
149 token: userAccessToken,
150 path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads/' + privateCommentId,
151 expectedStatus: HttpStatusCode.FORBIDDEN_403
152 })
153 })
154
155 it('Should success with the correct params', async function () {
156 await makeGetRequest({
157 url: server.url,
158 token: server.accessToken,
159 path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads/' + privateCommentId,
160 expectedStatus: HttpStatusCode.OK_200
161 })
162
163 await makeGetRequest({
164 url: server.url,
165 path: '/api/v1/videos/' + video.shortUUID + '/comment-threads/' + commentId,
166 expectedStatus: HttpStatusCode.OK_200
167 })
168 })
169 })
170
171 describe('When adding a video thread', function () {
172
173 it('Should fail with a non authenticated user', async function () {
174 const fields = {
175 text: 'text'
176 }
177 await makePostBodyRequest({
178 url: server.url,
179 path: pathThread,
180 token: 'none',
181 fields,
182 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
183 })
184 })
185
186 it('Should fail with nothing', async function () {
187 const fields = {}
188 await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields })
189 })
190
191 it('Should fail with a short comment', async function () {
192 const fields = {
193 text: ''
194 }
195 await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields })
196 })
197
198 it('Should fail with a long comment', async function () {
199 const fields = {
200 text: 'h'.repeat(10001)
201 }
202 await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields })
203 })
204
205 it('Should fail with an incorrect video', async function () {
206 const path = '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comment-threads'
207 const fields = { text: 'super comment' }
208
209 await makePostBodyRequest({
210 url: server.url,
211 path,
212 token: server.accessToken,
213 fields,
214 expectedStatus: HttpStatusCode.NOT_FOUND_404
215 })
216 })
217
218 it('Should fail with a private video of another user', async function () {
219 const fields = { text: 'super comment' }
220
221 await makePostBodyRequest({
222 url: server.url,
223 path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
224 token: userAccessToken,
225 fields,
226 expectedStatus: HttpStatusCode.FORBIDDEN_403
227 })
228 })
229
230 it('Should succeed with the correct parameters', async function () {
231 const fields = { text: 'super comment' }
232
233 await makePostBodyRequest({
234 url: server.url,
235 path: pathThread,
236 token: server.accessToken,
237 fields,
238 expectedStatus: HttpStatusCode.OK_200
239 })
240 })
241 })
242
243 describe('When adding a comment to a thread', function () {
244
245 it('Should fail with a non authenticated user', async function () {
246 const fields = {
247 text: 'text'
248 }
249 await makePostBodyRequest({
250 url: server.url,
251 path: pathComment,
252 token: 'none',
253 fields,
254 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
255 })
256 })
257
258 it('Should fail with nothing', async function () {
259 const fields = {}
260 await makePostBodyRequest({ url: server.url, path: pathComment, token: server.accessToken, fields })
261 })
262
263 it('Should fail with a short comment', async function () {
264 const fields = {
265 text: ''
266 }
267 await makePostBodyRequest({ url: server.url, path: pathComment, token: server.accessToken, fields })
268 })
269
270 it('Should fail with a long comment', async function () {
271 const fields = {
272 text: 'h'.repeat(10001)
273 }
274 await makePostBodyRequest({ url: server.url, path: pathComment, token: server.accessToken, fields })
275 })
276
277 it('Should fail with an incorrect video', async function () {
278 const path = '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comments/' + commentId
279 const fields = {
280 text: 'super comment'
281 }
282 await makePostBodyRequest({
283 url: server.url,
284 path,
285 token: server.accessToken,
286 fields,
287 expectedStatus: HttpStatusCode.NOT_FOUND_404
288 })
289 })
290
291 it('Should fail with a private video of another user', async function () {
292 const fields = { text: 'super comment' }
293
294 await makePostBodyRequest({
295 url: server.url,
296 path: '/api/v1/videos/' + privateVideo.uuid + '/comments/' + privateCommentId,
297 token: userAccessToken,
298 fields,
299 expectedStatus: HttpStatusCode.FORBIDDEN_403
300 })
301 })
302
303 it('Should fail with an incorrect comment', async function () {
304 const path = '/api/v1/videos/' + video.uuid + '/comments/124'
305 const fields = {
306 text: 'super comment'
307 }
308 await makePostBodyRequest({
309 url: server.url,
310 path,
311 token: server.accessToken,
312 fields,
313 expectedStatus: HttpStatusCode.NOT_FOUND_404
314 })
315 })
316
317 it('Should succeed with the correct parameters', async function () {
318 const fields = {
319 text: 'super comment'
320 }
321 await makePostBodyRequest({
322 url: server.url,
323 path: pathComment,
324 token: server.accessToken,
325 fields,
326 expectedStatus: HttpStatusCode.OK_200
327 })
328 })
329 })
330
331 describe('When removing video comments', function () {
332 it('Should fail with a non authenticated user', async function () {
333 await makeDeleteRequest({ url: server.url, path: pathComment, token: 'none', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
334 })
335
336 it('Should fail with another user', async function () {
337 await makeDeleteRequest({
338 url: server.url,
339 path: pathComment,
340 token: userAccessToken,
341 expectedStatus: HttpStatusCode.FORBIDDEN_403
342 })
343 })
344
345 it('Should fail with an incorrect video', async function () {
346 const path = '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comments/' + commentId
347 await makeDeleteRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
348 })
349
350 it('Should fail with an incorrect comment', async function () {
351 const path = '/api/v1/videos/' + video.uuid + '/comments/124'
352 await makeDeleteRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
353 })
354
355 it('Should succeed with the same user', async function () {
356 let commentToDelete: number
357
358 {
359 const created = await server.comments.createThread({ videoId: video.uuid, token: userAccessToken, text: 'hello' })
360 commentToDelete = created.id
361 }
362
363 const path = '/api/v1/videos/' + video.uuid + '/comments/' + commentToDelete
364
365 await makeDeleteRequest({ url: server.url, path, token: userAccessToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
366 await makeDeleteRequest({ url: server.url, path, token: userAccessToken, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
367 })
368
369 it('Should succeed with the owner of the video', async function () {
370 let commentToDelete: number
371 let anotherVideoUUID: string
372
373 {
374 const { uuid } = await server.videos.upload({ token: userAccessToken, attributes: { name: 'video' } })
375 anotherVideoUUID = uuid
376 }
377
378 {
379 const created = await server.comments.createThread({ videoId: anotherVideoUUID, text: 'hello' })
380 commentToDelete = created.id
381 }
382
383 const path = '/api/v1/videos/' + anotherVideoUUID + '/comments/' + commentToDelete
384
385 await makeDeleteRequest({ url: server.url, path, token: userAccessToken2, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
386 await makeDeleteRequest({ url: server.url, path, token: userAccessToken, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
387 })
388
389 it('Should succeed with the correct parameters', async function () {
390 await makeDeleteRequest({
391 url: server.url,
392 path: pathComment,
393 token: server.accessToken,
394 expectedStatus: HttpStatusCode.NO_CONTENT_204
395 })
396 })
397 })
398
399 describe('When a video has comments disabled', function () {
400 before(async function () {
401 video = await server.videos.upload({ attributes: { commentsEnabled: false } })
402 pathThread = '/api/v1/videos/' + video.uuid + '/comment-threads'
403 })
404
405 it('Should return an empty thread list', async function () {
406 const res = await makeGetRequest({
407 url: server.url,
408 path: pathThread,
409 expectedStatus: HttpStatusCode.OK_200
410 })
411 expect(res.body.total).to.equal(0)
412 expect(res.body.data).to.have.lengthOf(0)
413 })
414
415 it('Should return an thread comments list')
416
417 it('Should return conflict on thread add', async function () {
418 const fields = {
419 text: 'super comment'
420 }
421 await makePostBodyRequest({
422 url: server.url,
423 path: pathThread,
424 token: server.accessToken,
425 fields,
426 expectedStatus: HttpStatusCode.CONFLICT_409
427 })
428 })
429
430 it('Should return conflict on comment thread add')
431 })
432
433 describe('When listing admin comments threads', function () {
434 const path = '/api/v1/videos/comments'
435
436 it('Should fail with a bad start pagination', async function () {
437 await checkBadStartPagination(server.url, path, server.accessToken)
438 })
439
440 it('Should fail with a bad count pagination', async function () {
441 await checkBadCountPagination(server.url, path, server.accessToken)
442 })
443
444 it('Should fail with an incorrect sort', async function () {
445 await checkBadSortPagination(server.url, path, server.accessToken)
446 })
447
448 it('Should fail with a non authenticated user', async function () {
449 await makeGetRequest({
450 url: server.url,
451 path,
452 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
453 })
454 })
455
456 it('Should fail with a non admin user', async function () {
457 await makeGetRequest({
458 url: server.url,
459 path,
460 token: userAccessToken,
461 expectedStatus: HttpStatusCode.FORBIDDEN_403
462 })
463 })
464
465 it('Should succeed with the correct params', async function () {
466 await makeGetRequest({
467 url: server.url,
468 path,
469 token: server.accessToken,
470 query: {
471 isLocal: false,
472 search: 'toto',
473 searchAccount: 'toto',
474 searchVideo: 'toto'
475 },
476 expectedStatus: HttpStatusCode.OK_200
477 })
478 })
479 })
480
481 after(async function () {
482 await cleanupTests([ server ])
483 })
484})
diff --git a/packages/tests/src/api/check-params/video-files.ts b/packages/tests/src/api/check-params/video-files.ts
new file mode 100644
index 000000000..b5819ff19
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-files.ts
@@ -0,0 +1,195 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { getAllFiles } from '@peertube/peertube-core-utils'
4import { HttpStatusCode, UserRole, VideoDetails, VideoPrivacy } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 makeRawRequest,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 waitJobs
13} from '@peertube/peertube-server-commands'
14
15describe('Test videos files', function () {
16 let servers: PeerTubeServer[]
17
18 let userToken: string
19 let moderatorToken: string
20
21 // ---------------------------------------------------------------
22
23 before(async function () {
24 this.timeout(300_000)
25
26 servers = await createMultipleServers(2)
27 await setAccessTokensToServers(servers)
28
29 await doubleFollow(servers[0], servers[1])
30
31 userToken = await servers[0].users.generateUserAndToken('user', UserRole.USER)
32 moderatorToken = await servers[0].users.generateUserAndToken('moderator', UserRole.MODERATOR)
33 })
34
35 describe('Getting metadata', function () {
36 let video: VideoDetails
37
38 before(async function () {
39 const { uuid } = await servers[0].videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
40 video = await servers[0].videos.getWithToken({ id: uuid })
41 })
42
43 it('Should not get metadata of private video without token', async function () {
44 for (const file of getAllFiles(video)) {
45 await makeRawRequest({ url: file.metadataUrl, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
46 }
47 })
48
49 it('Should not get metadata of private video without the appropriate token', async function () {
50 for (const file of getAllFiles(video)) {
51 await makeRawRequest({ url: file.metadataUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
52 }
53 })
54
55 it('Should get metadata of private video with the appropriate token', async function () {
56 for (const file of getAllFiles(video)) {
57 await makeRawRequest({ url: file.metadataUrl, token: servers[0].accessToken, expectedStatus: HttpStatusCode.OK_200 })
58 }
59 })
60 })
61
62 describe('Deleting files', function () {
63 let webVideoId: string
64 let hlsId: string
65 let remoteId: string
66
67 let validId1: string
68 let validId2: string
69
70 let hlsFileId: number
71 let webVideoFileId: number
72
73 let remoteHLSFileId: number
74 let remoteWebVideoFileId: number
75
76 before(async function () {
77 this.timeout(300_000)
78
79 {
80 const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' })
81 await waitJobs(servers)
82
83 const video = await servers[1].videos.get({ id: uuid })
84 remoteId = video.uuid
85 remoteHLSFileId = video.streamingPlaylists[0].files[0].id
86 remoteWebVideoFileId = video.files[0].id
87 }
88
89 {
90 await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
91
92 {
93 const { uuid } = await servers[0].videos.quickUpload({ name: 'both 1' })
94 await waitJobs(servers)
95
96 const video = await servers[0].videos.get({ id: uuid })
97 validId1 = video.uuid
98 hlsFileId = video.streamingPlaylists[0].files[0].id
99 webVideoFileId = video.files[0].id
100 }
101
102 {
103 const { uuid } = await servers[0].videos.quickUpload({ name: 'both 2' })
104 validId2 = uuid
105 }
106 }
107
108 await waitJobs(servers)
109
110 {
111 await servers[0].config.enableTranscoding({ hls: true, webVideo: false })
112 const { uuid } = await servers[0].videos.quickUpload({ name: 'hls' })
113 hlsId = uuid
114 }
115
116 await waitJobs(servers)
117
118 {
119 await servers[0].config.enableTranscoding({ webVideo: true, hls: false })
120 const { uuid } = await servers[0].videos.quickUpload({ name: 'web-video' })
121 webVideoId = uuid
122 }
123
124 await waitJobs(servers)
125 })
126
127 it('Should not delete files of a unknown video', async function () {
128 const expectedStatus = HttpStatusCode.NOT_FOUND_404
129
130 await servers[0].videos.removeHLSPlaylist({ videoId: 404, expectedStatus })
131 await servers[0].videos.removeAllWebVideoFiles({ videoId: 404, expectedStatus })
132
133 await servers[0].videos.removeHLSFile({ videoId: 404, fileId: hlsFileId, expectedStatus })
134 await servers[0].videos.removeWebVideoFile({ videoId: 404, fileId: webVideoFileId, expectedStatus })
135 })
136
137 it('Should not delete unknown files', async function () {
138 const expectedStatus = HttpStatusCode.NOT_FOUND_404
139
140 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: webVideoFileId, expectedStatus })
141 await servers[0].videos.removeWebVideoFile({ videoId: validId1, fileId: hlsFileId, expectedStatus })
142 })
143
144 it('Should not delete files of a remote video', async function () {
145 const expectedStatus = HttpStatusCode.BAD_REQUEST_400
146
147 await servers[0].videos.removeHLSPlaylist({ videoId: remoteId, expectedStatus })
148 await servers[0].videos.removeAllWebVideoFiles({ videoId: remoteId, expectedStatus })
149
150 await servers[0].videos.removeHLSFile({ videoId: remoteId, fileId: remoteHLSFileId, expectedStatus })
151 await servers[0].videos.removeWebVideoFile({ videoId: remoteId, fileId: remoteWebVideoFileId, expectedStatus })
152 })
153
154 it('Should not delete files by a non admin user', async function () {
155 const expectedStatus = HttpStatusCode.FORBIDDEN_403
156
157 await servers[0].videos.removeHLSPlaylist({ videoId: validId1, token: userToken, expectedStatus })
158 await servers[0].videos.removeHLSPlaylist({ videoId: validId1, token: moderatorToken, expectedStatus })
159
160 await servers[0].videos.removeAllWebVideoFiles({ videoId: validId1, token: userToken, expectedStatus })
161 await servers[0].videos.removeAllWebVideoFiles({ videoId: validId1, token: moderatorToken, expectedStatus })
162
163 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId, token: userToken, expectedStatus })
164 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId, token: moderatorToken, expectedStatus })
165
166 await servers[0].videos.removeWebVideoFile({ videoId: validId1, fileId: webVideoFileId, token: userToken, expectedStatus })
167 await servers[0].videos.removeWebVideoFile({ videoId: validId1, fileId: webVideoFileId, token: moderatorToken, expectedStatus })
168 })
169
170 it('Should not delete files if the files are not available', async function () {
171 await servers[0].videos.removeHLSPlaylist({ videoId: hlsId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
172 await servers[0].videos.removeAllWebVideoFiles({ videoId: webVideoId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
173
174 await servers[0].videos.removeHLSFile({ videoId: hlsId, fileId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
175 await servers[0].videos.removeWebVideoFile({ videoId: webVideoId, fileId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
176 })
177
178 it('Should not delete files if no both versions are available', async function () {
179 await servers[0].videos.removeHLSPlaylist({ videoId: hlsId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
180 await servers[0].videos.removeAllWebVideoFiles({ videoId: webVideoId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
181 })
182
183 it('Should delete files if both versions are available', async function () {
184 await servers[0].videos.removeHLSFile({ videoId: validId1, fileId: hlsFileId })
185 await servers[0].videos.removeWebVideoFile({ videoId: validId1, fileId: webVideoFileId })
186
187 await servers[0].videos.removeHLSPlaylist({ videoId: validId1 })
188 await servers[0].videos.removeAllWebVideoFiles({ videoId: validId2 })
189 })
190 })
191
192 after(async function () {
193 await cleanupTests(servers)
194 })
195})
diff --git a/packages/tests/src/api/check-params/video-imports.ts b/packages/tests/src/api/check-params/video-imports.ts
new file mode 100644
index 000000000..e078cedd6
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-imports.ts
@@ -0,0 +1,433 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { omit } from '@peertube/peertube-core-utils'
4import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models'
5import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
6import { FIXTURE_URLS } from '@tests/shared/tests.js'
7import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
8import {
9 cleanupTests,
10 createSingleServer,
11 makeGetRequest,
12 makePostBodyRequest,
13 makeUploadRequest,
14 PeerTubeServer,
15 setAccessTokensToServers,
16 setDefaultVideoChannel,
17 waitJobs
18} from '@peertube/peertube-server-commands'
19
20describe('Test video imports API validator', function () {
21 const path = '/api/v1/videos/imports'
22 let server: PeerTubeServer
23 let userAccessToken = ''
24 let channelId: number
25
26 // ---------------------------------------------------------------
27
28 before(async function () {
29 this.timeout(30000)
30
31 server = await createSingleServer(1)
32
33 await setAccessTokensToServers([ server ])
34 await setDefaultVideoChannel([ server ])
35
36 const username = 'user1'
37 const password = 'my super password'
38 await server.users.create({ username, password })
39 userAccessToken = await server.login.getAccessToken({ username, password })
40
41 {
42 const { videoChannels } = await server.users.getMyInfo()
43 channelId = videoChannels[0].id
44 }
45 })
46
47 describe('When listing my video imports', function () {
48 const myPath = '/api/v1/users/me/videos/imports'
49
50 it('Should fail with a bad start pagination', async function () {
51 await checkBadStartPagination(server.url, myPath, server.accessToken)
52 })
53
54 it('Should fail with a bad count pagination', async function () {
55 await checkBadCountPagination(server.url, myPath, server.accessToken)
56 })
57
58 it('Should fail with an incorrect sort', async function () {
59 await checkBadSortPagination(server.url, myPath, server.accessToken)
60 })
61
62 it('Should fail with a bad videoChannelSyncId param', async function () {
63 await makeGetRequest({
64 url: server.url,
65 path: myPath,
66 query: { videoChannelSyncId: 'toto' },
67 token: server.accessToken
68 })
69 })
70
71 it('Should success with the correct parameters', async function () {
72 await makeGetRequest({ url: server.url, path: myPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
73 })
74 })
75
76 describe('When adding a video import', function () {
77 let baseCorrectParams
78
79 before(function () {
80 baseCorrectParams = {
81 targetUrl: FIXTURE_URLS.goodVideo,
82 name: 'my super name',
83 category: 5,
84 licence: 1,
85 language: 'pt',
86 nsfw: false,
87 commentsEnabled: true,
88 downloadEnabled: true,
89 waitTranscoding: true,
90 description: 'my super description',
91 support: 'my super support text',
92 tags: [ 'tag1', 'tag2' ],
93 privacy: VideoPrivacy.PUBLIC,
94 channelId
95 }
96 })
97
98 it('Should fail with nothing', async function () {
99 const fields = {}
100 await makePostBodyRequest({
101 url: server.url,
102 path,
103 token: server.accessToken,
104 fields,
105 expectedStatus: HttpStatusCode.BAD_REQUEST_400
106 })
107 })
108
109 it('Should fail without a target url', async function () {
110 const fields = omit(baseCorrectParams, [ 'targetUrl' ])
111 await makePostBodyRequest({
112 url: server.url,
113 path,
114 token: server.accessToken,
115 fields,
116 expectedStatus: HttpStatusCode.BAD_REQUEST_400
117 })
118 })
119
120 it('Should fail with a bad target url', async function () {
121 const fields = { ...baseCorrectParams, targetUrl: 'htt://hello' }
122
123 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
124 })
125
126 it('Should fail with localhost', async function () {
127 const fields = { ...baseCorrectParams, targetUrl: 'http://localhost:8000' }
128
129 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
130 })
131
132 it('Should fail with a private IP target urls', async function () {
133 const targetUrls = [
134 'http://127.0.0.1:8000',
135 'http://127.0.0.1',
136 'http://127.0.0.1/hello',
137 'https://192.168.1.42',
138 'http://192.168.1.42',
139 'http://127.0.0.1.cpy.re'
140 ]
141
142 for (const targetUrl of targetUrls) {
143 const fields = { ...baseCorrectParams, targetUrl }
144
145 await makePostBodyRequest({
146 url: server.url,
147 path,
148 token: server.accessToken,
149 fields,
150 expectedStatus: HttpStatusCode.FORBIDDEN_403
151 })
152 }
153 })
154
155 it('Should fail with a long name', async function () {
156 const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
157
158 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
159 })
160
161 it('Should fail with a bad category', async function () {
162 const fields = { ...baseCorrectParams, category: 125 }
163
164 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
165 })
166
167 it('Should fail with a bad licence', async function () {
168 const fields = { ...baseCorrectParams, licence: 125 }
169
170 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
171 })
172
173 it('Should fail with a bad language', async function () {
174 const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
175
176 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
177 })
178
179 it('Should fail with a long description', async function () {
180 const fields = { ...baseCorrectParams, description: 'super'.repeat(2500) }
181
182 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
183 })
184
185 it('Should fail with a long support text', async function () {
186 const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
187
188 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
189 })
190
191 it('Should fail without a channel', async function () {
192 const fields = omit(baseCorrectParams, [ 'channelId' ])
193
194 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
195 })
196
197 it('Should fail with a bad channel', async function () {
198 const fields = { ...baseCorrectParams, channelId: 545454 }
199
200 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
201 })
202
203 it('Should fail with another user channel', async function () {
204 const user = {
205 username: 'fake',
206 password: 'fake_password'
207 }
208 await server.users.create({ username: user.username, password: user.password })
209
210 const accessTokenUser = await server.login.getAccessToken(user)
211 const { videoChannels } = await server.users.getMyInfo({ token: accessTokenUser })
212 const customChannelId = videoChannels[0].id
213
214 const fields = { ...baseCorrectParams, channelId: customChannelId }
215
216 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
217 })
218
219 it('Should fail with too many tags', async function () {
220 const fields = { ...baseCorrectParams, tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }
221
222 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
223 })
224
225 it('Should fail with a tag length too low', async function () {
226 const fields = { ...baseCorrectParams, tags: [ 'tag1', 't' ] }
227
228 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
229 })
230
231 it('Should fail with a tag length too big', async function () {
232 const fields = { ...baseCorrectParams, tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }
233
234 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
235 })
236
237 it('Should fail with an incorrect thumbnail file', async function () {
238 const fields = baseCorrectParams
239 const attaches = {
240 thumbnailfile: buildAbsoluteFixturePath('video_short.mp4')
241 }
242
243 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
244 })
245
246 it('Should fail with a big thumbnail file', async function () {
247 const fields = baseCorrectParams
248 const attaches = {
249 thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png')
250 }
251
252 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
253 })
254
255 it('Should fail with an incorrect preview file', async function () {
256 const fields = baseCorrectParams
257 const attaches = {
258 previewfile: buildAbsoluteFixturePath('video_short.mp4')
259 }
260
261 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
262 })
263
264 it('Should fail with a big preview file', async function () {
265 const fields = baseCorrectParams
266 const attaches = {
267 previewfile: buildAbsoluteFixturePath('custom-preview-big.png')
268 }
269
270 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
271 })
272
273 it('Should fail with an invalid torrent file', async function () {
274 const fields = omit(baseCorrectParams, [ 'targetUrl' ])
275 const attaches = {
276 torrentfile: buildAbsoluteFixturePath('avatar-big.png')
277 }
278
279 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
280 })
281
282 it('Should fail with an invalid magnet URI', async function () {
283 let fields = omit(baseCorrectParams, [ 'targetUrl' ])
284 fields = { ...fields, magnetUri: 'blabla' }
285
286 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
287 })
288
289 it('Should succeed with the correct parameters', async function () {
290 this.timeout(120000)
291
292 await makePostBodyRequest({
293 url: server.url,
294 path,
295 token: server.accessToken,
296 fields: baseCorrectParams,
297 expectedStatus: HttpStatusCode.OK_200
298 })
299 })
300
301 it('Should forbid to import http videos', async function () {
302 await server.config.updateCustomSubConfig({
303 newConfig: {
304 import: {
305 videos: {
306 http: {
307 enabled: false
308 },
309 torrent: {
310 enabled: true
311 }
312 }
313 }
314 }
315 })
316
317 await makePostBodyRequest({
318 url: server.url,
319 path,
320 token: server.accessToken,
321 fields: baseCorrectParams,
322 expectedStatus: HttpStatusCode.CONFLICT_409
323 })
324 })
325
326 it('Should forbid to import torrent videos', async function () {
327 await server.config.updateCustomSubConfig({
328 newConfig: {
329 import: {
330 videos: {
331 http: {
332 enabled: true
333 },
334 torrent: {
335 enabled: false
336 }
337 }
338 }
339 }
340 })
341
342 let fields = omit(baseCorrectParams, [ 'targetUrl' ])
343 fields = { ...fields, magnetUri: FIXTURE_URLS.magnet }
344
345 await makePostBodyRequest({
346 url: server.url,
347 path,
348 token: server.accessToken,
349 fields,
350 expectedStatus: HttpStatusCode.CONFLICT_409
351 })
352
353 fields = omit(fields, [ 'magnetUri' ])
354 const attaches = {
355 torrentfile: buildAbsoluteFixturePath('video-720p.torrent')
356 }
357
358 await makeUploadRequest({
359 url: server.url,
360 path,
361 token: server.accessToken,
362 fields,
363 attaches,
364 expectedStatus: HttpStatusCode.CONFLICT_409
365 })
366 })
367 })
368
369 describe('Deleting/cancelling a video import', function () {
370 let importId: number
371
372 async function importVideo () {
373 const attributes = { channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
374 const res = await server.imports.importVideo({ attributes })
375
376 return res.id
377 }
378
379 before(async function () {
380 importId = await importVideo()
381 })
382
383 it('Should fail with an invalid import id', async function () {
384 await server.imports.cancel({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
385 await server.imports.delete({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
386 })
387
388 it('Should fail with an unknown import id', async function () {
389 await server.imports.cancel({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
390 await server.imports.delete({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
391 })
392
393 it('Should fail without token', async function () {
394 await server.imports.cancel({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
395 await server.imports.delete({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
396 })
397
398 it('Should fail with another user token', async function () {
399 await server.imports.cancel({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
400 await server.imports.delete({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
401 })
402
403 it('Should fail to cancel non pending import', async function () {
404 this.timeout(60000)
405
406 await waitJobs([ server ])
407
408 await server.imports.cancel({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
409 })
410
411 it('Should succeed to delete an import', async function () {
412 await server.imports.delete({ importId })
413 })
414
415 it('Should fail to delete a pending import', async function () {
416 await server.jobs.pauseJobQueue()
417
418 importId = await importVideo()
419
420 await server.imports.delete({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
421 })
422
423 it('Should succeed to cancel an import', async function () {
424 importId = await importVideo()
425
426 await server.imports.cancel({ importId })
427 })
428 })
429
430 after(async function () {
431 await cleanupTests([ server ])
432 })
433})
diff --git a/packages/tests/src/api/check-params/video-passwords.ts b/packages/tests/src/api/check-params/video-passwords.ts
new file mode 100644
index 000000000..3f57ebe74
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-passwords.ts
@@ -0,0 +1,604 @@
1import { expect } from 'chai'
2import {
3 HttpStatusCode,
4 HttpStatusCodeType,
5 PeerTubeProblemDocument,
6 ServerErrorCode,
7 VideoCreateResult,
8 VideoPrivacy
9} from '@peertube/peertube-models'
10import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
11import {
12 cleanupTests,
13 createSingleServer,
14 makePostBodyRequest,
15 PeerTubeServer,
16 setAccessTokensToServers
17} from '@peertube/peertube-server-commands'
18import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
19import { FIXTURE_URLS } from '@tests/shared/tests.js'
20import { checkUploadVideoParam } from '@tests/shared/videos.js'
21
22describe('Test video passwords validator', function () {
23 let path: string
24 let server: PeerTubeServer
25 let userAccessToken = ''
26 let video: VideoCreateResult
27 let channelId: number
28 let publicVideo: VideoCreateResult
29 let commentId: number
30 // ---------------------------------------------------------------
31
32 before(async function () {
33 this.timeout(50000)
34
35 server = await createSingleServer(1)
36
37 await setAccessTokensToServers([ server ])
38
39 await server.config.updateCustomSubConfig({
40 newConfig: {
41 live: {
42 enabled: true,
43 latencySetting: {
44 enabled: false
45 },
46 allowReplay: false
47 },
48 import: {
49 videos: {
50 http:{
51 enabled: true
52 }
53 }
54 }
55 }
56 })
57
58 userAccessToken = await server.users.generateUserAndToken('user1')
59
60 {
61 const body = await server.users.getMyInfo()
62 channelId = body.videoChannels[0].id
63 }
64
65 {
66 video = await server.videos.quickUpload({
67 name: 'password protected video',
68 privacy: VideoPrivacy.PASSWORD_PROTECTED,
69 videoPasswords: [ 'password1', 'password2' ]
70 })
71 }
72 path = '/api/v1/videos/'
73 })
74
75 async function checkVideoPasswordOptions (options: {
76 server: PeerTubeServer
77 token: string
78 videoPasswords: string[]
79 expectedStatus: HttpStatusCodeType
80 mode: 'uploadLegacy' | 'uploadResumable' | 'import' | 'updateVideo' | 'updatePasswords' | 'live'
81 }) {
82 const { server, token, videoPasswords, expectedStatus = HttpStatusCode.OK_200, mode } = options
83 const attaches = {
84 fixture: buildAbsoluteFixturePath('video_short.webm')
85 }
86 const baseCorrectParams = {
87 name: 'my super name',
88 category: 5,
89 licence: 1,
90 language: 'pt',
91 nsfw: false,
92 commentsEnabled: true,
93 downloadEnabled: true,
94 waitTranscoding: true,
95 description: 'my super description',
96 support: 'my super support text',
97 tags: [ 'tag1', 'tag2' ],
98 privacy: VideoPrivacy.PASSWORD_PROTECTED,
99 channelId,
100 originallyPublishedAt: new Date().toISOString()
101 }
102 if (mode === 'uploadLegacy') {
103 const fields = { ...baseCorrectParams, videoPasswords }
104 return checkUploadVideoParam({ server, token, attributes: { ...fields, ...attaches }, expectedStatus, mode: 'legacy' })
105 }
106
107 if (mode === 'uploadResumable') {
108 const fields = { ...baseCorrectParams, videoPasswords }
109 return checkUploadVideoParam({ server, token, attributes: { ...fields, ...attaches }, expectedStatus, mode: 'resumable' })
110 }
111
112 if (mode === 'import') {
113 const attributes = { ...baseCorrectParams, targetUrl: FIXTURE_URLS.goodVideo, videoPasswords }
114 return server.imports.importVideo({ attributes, expectedStatus })
115 }
116
117 if (mode === 'updateVideo') {
118 const attributes = { ...baseCorrectParams, videoPasswords }
119 return server.videos.update({ token, expectedStatus, id: video.id, attributes })
120 }
121
122 if (mode === 'updatePasswords') {
123 return server.videoPasswords.updateAll({ token, expectedStatus, videoId: video.id, passwords: videoPasswords })
124 }
125
126 if (mode === 'live') {
127 const fields = { ...baseCorrectParams, videoPasswords }
128
129 return server.live.create({ fields, expectedStatus })
130 }
131 }
132
133 function validateVideoPasswordList (mode: 'uploadLegacy' | 'uploadResumable' | 'import' | 'updateVideo' | 'updatePasswords' | 'live') {
134
135 it('Should fail with a password protected privacy without providing a password', async function () {
136 await checkVideoPasswordOptions({
137 server,
138 token: server.accessToken,
139 videoPasswords: undefined,
140 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
141 mode
142 })
143 })
144
145 it('Should fail with a password protected privacy and an empty password list', async function () {
146 const videoPasswords = []
147
148 await checkVideoPasswordOptions({
149 server,
150 token: server.accessToken,
151 videoPasswords,
152 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
153 mode
154 })
155 })
156
157 it('Should fail with a password protected privacy and a too short password', async function () {
158 const videoPasswords = [ 'p' ]
159
160 await checkVideoPasswordOptions({
161 server,
162 token: server.accessToken,
163 videoPasswords,
164 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
165 mode
166 })
167 })
168
169 it('Should fail with a password protected privacy and a too long password', async function () {
170 const videoPasswords = [ 'Very very very very very very very very very very very very very very very very very very long password' ]
171
172 await checkVideoPasswordOptions({
173 server,
174 token: server.accessToken,
175 videoPasswords,
176 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
177 mode
178 })
179 })
180
181 it('Should fail with a password protected privacy and an empty password', async function () {
182 const videoPasswords = [ '' ]
183
184 await checkVideoPasswordOptions({
185 server,
186 token: server.accessToken,
187 videoPasswords,
188 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
189 mode
190 })
191 })
192
193 it('Should fail with a password protected privacy and duplicated passwords', async function () {
194 const videoPasswords = [ 'password', 'password' ]
195
196 await checkVideoPasswordOptions({
197 server,
198 token: server.accessToken,
199 videoPasswords,
200 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
201 mode
202 })
203 })
204
205 if (mode === 'updatePasswords') {
206 it('Should fail for an unauthenticated user', async function () {
207 const videoPasswords = [ 'password' ]
208 await checkVideoPasswordOptions({
209 server,
210 token: null,
211 videoPasswords,
212 expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
213 mode
214 })
215 })
216
217 it('Should fail for an unauthorized user', async function () {
218 const videoPasswords = [ 'password' ]
219 await checkVideoPasswordOptions({
220 server,
221 token: userAccessToken,
222 videoPasswords,
223 expectedStatus: HttpStatusCode.FORBIDDEN_403,
224 mode
225 })
226 })
227 }
228
229 it('Should succeed with a password protected privacy and correct passwords', async function () {
230 const videoPasswords = [ 'password1', 'password2' ]
231 const expectedStatus = mode === 'updatePasswords' || mode === 'updateVideo'
232 ? HttpStatusCode.NO_CONTENT_204
233 : HttpStatusCode.OK_200
234
235 await checkVideoPasswordOptions({ server, token: server.accessToken, videoPasswords, expectedStatus, mode })
236 })
237 }
238
239 describe('When adding or updating a video', function () {
240 describe('Resumable upload', function () {
241 validateVideoPasswordList('uploadResumable')
242 })
243
244 describe('Legacy upload', function () {
245 validateVideoPasswordList('uploadLegacy')
246 })
247
248 describe('When importing a video', function () {
249 validateVideoPasswordList('import')
250 })
251
252 describe('When updating a video', function () {
253 validateVideoPasswordList('updateVideo')
254 })
255
256 describe('When updating the password list of a video', function () {
257 validateVideoPasswordList('updatePasswords')
258 })
259
260 describe('When creating a live', function () {
261 validateVideoPasswordList('live')
262 })
263 })
264
265 async function checkVideoAccessOptions (options: {
266 server: PeerTubeServer
267 token?: string
268 videoPassword?: string
269 expectedStatus: HttpStatusCodeType
270 mode: 'get' | 'getWithPassword' | 'getWithToken' | 'listCaptions' | 'createThread' | 'listThreads' | 'replyThread' | 'rate' | 'token'
271 }) {
272 const { server, token = null, videoPassword, expectedStatus, mode } = options
273
274 if (mode === 'get') {
275 return server.videos.get({ id: video.id, expectedStatus })
276 }
277
278 if (mode === 'getWithToken') {
279 return server.videos.getWithToken({
280 id: video.id,
281 token,
282 expectedStatus
283 })
284 }
285
286 if (mode === 'getWithPassword') {
287 return server.videos.getWithPassword({
288 id: video.id,
289 token,
290 expectedStatus,
291 password: videoPassword
292 })
293 }
294
295 if (mode === 'rate') {
296 return server.videos.rate({
297 id: video.id,
298 token,
299 expectedStatus,
300 rating: 'like',
301 videoPassword
302 })
303 }
304
305 if (mode === 'createThread') {
306 const fields = { text: 'super comment' }
307 const headers = videoPassword !== undefined && videoPassword !== null
308 ? { 'x-peertube-video-password': videoPassword }
309 : undefined
310 const body = await makePostBodyRequest({
311 url: server.url,
312 path: path + video.uuid + '/comment-threads',
313 token,
314 fields,
315 headers,
316 expectedStatus
317 })
318 return JSON.parse(body.text)
319 }
320
321 if (mode === 'replyThread') {
322 const fields = { text: 'super reply' }
323 const headers = videoPassword !== undefined && videoPassword !== null
324 ? { 'x-peertube-video-password': videoPassword }
325 : undefined
326 return makePostBodyRequest({
327 url: server.url,
328 path: path + video.uuid + '/comments/' + commentId,
329 token,
330 fields,
331 headers,
332 expectedStatus
333 })
334 }
335 if (mode === 'listThreads') {
336 return server.comments.listThreads({
337 videoId: video.id,
338 token,
339 expectedStatus,
340 videoPassword
341 })
342 }
343
344 if (mode === 'listCaptions') {
345 return server.captions.list({
346 videoId: video.id,
347 token,
348 expectedStatus,
349 videoPassword
350 })
351 }
352
353 if (mode === 'token') {
354 return server.videoToken.create({
355 videoId: video.id,
356 token,
357 expectedStatus,
358 videoPassword
359 })
360 }
361 }
362
363 function checkVideoError (error: any, mode: 'providePassword' | 'incorrectPassword') {
364 const serverCode = mode === 'providePassword'
365 ? ServerErrorCode.VIDEO_REQUIRES_PASSWORD
366 : ServerErrorCode.INCORRECT_VIDEO_PASSWORD
367
368 const message = mode === 'providePassword'
369 ? 'Please provide a password to access this password protected video'
370 : 'Incorrect video password. Access to the video is denied.'
371
372 if (!error.code) {
373 error = JSON.parse(error.text)
374 }
375
376 expect(error.code).to.equal(serverCode)
377 expect(error.detail).to.equal(message)
378 expect(error.error).to.equal(message)
379
380 expect(error.status).to.equal(HttpStatusCode.FORBIDDEN_403)
381 }
382
383 function validateVideoAccess (mode: 'get' | 'listCaptions' | 'createThread' | 'listThreads' | 'replyThread' | 'rate' | 'token') {
384 const requiresUserAuth = [ 'createThread', 'replyThread', 'rate' ].includes(mode)
385 let tokens: string[]
386 if (!requiresUserAuth) {
387 it('Should fail without providing a password for an unlogged user', async function () {
388 const body = await checkVideoAccessOptions({ server, expectedStatus: HttpStatusCode.FORBIDDEN_403, mode })
389 const error = body as unknown as PeerTubeProblemDocument
390
391 checkVideoError(error, 'providePassword')
392 })
393 }
394
395 it('Should fail without providing a password for an unauthorised user', async function () {
396 const tmp = mode === 'get' ? 'getWithToken' : mode
397
398 const body = await checkVideoAccessOptions({
399 server,
400 token: userAccessToken,
401 expectedStatus: HttpStatusCode.FORBIDDEN_403,
402 mode: tmp
403 })
404
405 const error = body as unknown as PeerTubeProblemDocument
406
407 checkVideoError(error, 'providePassword')
408 })
409
410 it('Should fail if a wrong password is entered', async function () {
411 const tmp = mode === 'get' ? 'getWithPassword' : mode
412 tokens = [ userAccessToken, server.accessToken ]
413
414 if (!requiresUserAuth) tokens.push(null)
415
416 for (const token of tokens) {
417 const body = await checkVideoAccessOptions({
418 server,
419 token,
420 videoPassword: 'toto',
421 expectedStatus: HttpStatusCode.FORBIDDEN_403,
422 mode: tmp
423 })
424 const error = body as unknown as PeerTubeProblemDocument
425
426 checkVideoError(error, 'incorrectPassword')
427 }
428 })
429
430 it('Should fail if an empty password is entered', async function () {
431 const tmp = mode === 'get' ? 'getWithPassword' : mode
432
433 for (const token of tokens) {
434 const body = await checkVideoAccessOptions({
435 server,
436 token,
437 videoPassword: '',
438 expectedStatus: HttpStatusCode.FORBIDDEN_403,
439 mode: tmp
440 })
441 const error = body as unknown as PeerTubeProblemDocument
442
443 checkVideoError(error, 'incorrectPassword')
444 }
445 })
446
447 it('Should fail if an inccorect password containing the correct password is entered', async function () {
448 const tmp = mode === 'get' ? 'getWithPassword' : mode
449
450 for (const token of tokens) {
451 const body = await checkVideoAccessOptions({
452 server,
453 token,
454 videoPassword: 'password11',
455 expectedStatus: HttpStatusCode.FORBIDDEN_403,
456 mode: tmp
457 })
458 const error = body as unknown as PeerTubeProblemDocument
459
460 checkVideoError(error, 'incorrectPassword')
461 }
462 })
463
464 it('Should succeed without providing a password for an authorised user', async function () {
465 const tmp = mode === 'get' ? 'getWithToken' : mode
466 const expectedStatus = mode === 'rate' ? HttpStatusCode.NO_CONTENT_204 : HttpStatusCode.OK_200
467
468 const body = await checkVideoAccessOptions({ server, token: server.accessToken, expectedStatus, mode: tmp })
469
470 if (mode === 'createThread') commentId = body.comment.id
471 })
472
473 it('Should succeed using correct passwords', async function () {
474 const tmp = mode === 'get' ? 'getWithPassword' : mode
475 const expectedStatus = mode === 'rate' ? HttpStatusCode.NO_CONTENT_204 : HttpStatusCode.OK_200
476
477 for (const token of tokens) {
478 await checkVideoAccessOptions({ server, videoPassword: 'password1', token, expectedStatus, mode: tmp })
479 await checkVideoAccessOptions({ server, videoPassword: 'password2', token, expectedStatus, mode: tmp })
480 }
481 })
482 }
483
484 describe('When accessing password protected video', function () {
485
486 describe('For getting a password protected video', function () {
487 validateVideoAccess('get')
488 })
489
490 describe('For rating a video', function () {
491 validateVideoAccess('rate')
492 })
493
494 describe('For creating a thread', function () {
495 validateVideoAccess('createThread')
496 })
497
498 describe('For replying to a thread', function () {
499 validateVideoAccess('replyThread')
500 })
501
502 describe('For listing threads', function () {
503 validateVideoAccess('listThreads')
504 })
505
506 describe('For getting captions', function () {
507 validateVideoAccess('listCaptions')
508 })
509
510 describe('For creating video file token', function () {
511 validateVideoAccess('token')
512 })
513 })
514
515 describe('When listing passwords', function () {
516 it('Should fail with a bad start pagination', async function () {
517 await checkBadStartPagination(server.url, path + video.uuid + '/passwords', server.accessToken)
518 })
519
520 it('Should fail with a bad count pagination', async function () {
521 await checkBadCountPagination(server.url, path + video.uuid + '/passwords', server.accessToken)
522 })
523
524 it('Should fail with an incorrect sort', async function () {
525 await checkBadSortPagination(server.url, path + video.uuid + '/passwords', server.accessToken)
526 })
527
528 it('Should fail for unauthenticated user', async function () {
529 await server.videoPasswords.list({
530 token: null,
531 expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
532 videoId: video.id
533 })
534 })
535
536 it('Should fail for unauthorized user', async function () {
537 await server.videoPasswords.list({
538 token: userAccessToken,
539 expectedStatus: HttpStatusCode.FORBIDDEN_403,
540 videoId: video.id
541 })
542 })
543
544 it('Should succeed with the correct parameters', async function () {
545 await server.videoPasswords.list({
546 token: server.accessToken,
547 expectedStatus: HttpStatusCode.OK_200,
548 videoId: video.id
549 })
550 })
551 })
552
553 describe('When deleting a password', async function () {
554 const passwords = (await server.videoPasswords.list({ videoId: video.id })).data
555
556 it('Should fail with wrong password id', async function () {
557 await server.videoPasswords.remove({ id: -1, videoId: video.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
558 })
559
560 it('Should fail for unauthenticated user', async function () {
561 await server.videoPasswords.remove({
562 id: passwords[0].id,
563 token: null,
564 videoId: video.id,
565 expectedStatus: HttpStatusCode.FORBIDDEN_403
566 })
567 })
568
569 it('Should fail for unauthorized user', async function () {
570 await server.videoPasswords.remove({
571 id: passwords[0].id,
572 token: userAccessToken,
573 videoId: video.id,
574 expectedStatus: HttpStatusCode.BAD_REQUEST_400
575 })
576 })
577
578 it('Should fail for non password protected video', async function () {
579 publicVideo = await server.videos.quickUpload({ name: 'public video' })
580 await server.videoPasswords.remove({ id: passwords[0].id, videoId: publicVideo.id, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
581 })
582
583 it('Should fail for password not linked to correct video', async function () {
584 const video2 = await server.videos.quickUpload({
585 name: 'password protected video',
586 privacy: VideoPrivacy.PASSWORD_PROTECTED,
587 videoPasswords: [ 'password1', 'password2' ]
588 })
589 await server.videoPasswords.remove({ id: passwords[0].id, videoId: video2.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
590 })
591
592 it('Should succeed with correct parameter', async function () {
593 await server.videoPasswords.remove({ id: passwords[0].id, videoId: video.id, expectedStatus: HttpStatusCode.NO_CONTENT_204 })
594 })
595
596 it('Should fail for last password of a video', async function () {
597 await server.videoPasswords.remove({ id: passwords[1].id, videoId: video.id, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
598 })
599 })
600
601 after(async function () {
602 await cleanupTests([ server ])
603 })
604})
diff --git a/packages/tests/src/api/check-params/video-playlists.ts b/packages/tests/src/api/check-params/video-playlists.ts
new file mode 100644
index 000000000..7f5be18d4
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-playlists.ts
@@ -0,0 +1,695 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import {
5 HttpStatusCode,
6 VideoPlaylistCreate,
7 VideoPlaylistCreateResult,
8 VideoPlaylistElementCreate,
9 VideoPlaylistElementUpdate,
10 VideoPlaylistPrivacy,
11 VideoPlaylistReorder,
12 VideoPlaylistType
13} from '@peertube/peertube-models'
14import {
15 cleanupTests,
16 createSingleServer,
17 makeGetRequest,
18 PeerTubeServer,
19 PlaylistsCommand,
20 setAccessTokensToServers,
21 setDefaultVideoChannel
22} from '@peertube/peertube-server-commands'
23
24describe('Test video playlists API validator', function () {
25 let server: PeerTubeServer
26 let userAccessToken: string
27
28 let playlist: VideoPlaylistCreateResult
29 let privatePlaylistUUID: string
30
31 let watchLaterPlaylistId: number
32 let videoId: number
33 let elementId: number
34
35 let command: PlaylistsCommand
36
37 // ---------------------------------------------------------------
38
39 before(async function () {
40 this.timeout(30000)
41
42 server = await createSingleServer(1)
43
44 await setAccessTokensToServers([ server ])
45 await setDefaultVideoChannel([ server ])
46
47 userAccessToken = await server.users.generateUserAndToken('user1')
48 videoId = (await server.videos.quickUpload({ name: 'video 1' })).id
49
50 command = server.playlists
51
52 {
53 const { data } = await command.listByAccount({
54 token: server.accessToken,
55 handle: 'root',
56 start: 0,
57 count: 5,
58 playlistType: VideoPlaylistType.WATCH_LATER
59 })
60 watchLaterPlaylistId = data[0].id
61 }
62
63 {
64 playlist = await command.create({
65 attributes: {
66 displayName: 'super playlist',
67 privacy: VideoPlaylistPrivacy.PUBLIC,
68 videoChannelId: server.store.channel.id
69 }
70 })
71 }
72
73 {
74 const created = await command.create({
75 attributes: {
76 displayName: 'private',
77 privacy: VideoPlaylistPrivacy.PRIVATE
78 }
79 })
80 privatePlaylistUUID = created.uuid
81 }
82 })
83
84 describe('When listing playlists', function () {
85 const globalPath = '/api/v1/video-playlists'
86 const accountPath = '/api/v1/accounts/root/video-playlists'
87 const videoChannelPath = '/api/v1/video-channels/root_channel/video-playlists'
88
89 it('Should fail with a bad start pagination', async function () {
90 await checkBadStartPagination(server.url, globalPath, server.accessToken)
91 await checkBadStartPagination(server.url, accountPath, server.accessToken)
92 await checkBadStartPagination(server.url, videoChannelPath, server.accessToken)
93 })
94
95 it('Should fail with a bad count pagination', async function () {
96 await checkBadCountPagination(server.url, globalPath, server.accessToken)
97 await checkBadCountPagination(server.url, accountPath, server.accessToken)
98 await checkBadCountPagination(server.url, videoChannelPath, server.accessToken)
99 })
100
101 it('Should fail with an incorrect sort', async function () {
102 await checkBadSortPagination(server.url, globalPath, server.accessToken)
103 await checkBadSortPagination(server.url, accountPath, server.accessToken)
104 await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
105 })
106
107 it('Should fail with a bad playlist type', async function () {
108 await makeGetRequest({ url: server.url, path: globalPath, query: { playlistType: 3 } })
109 await makeGetRequest({ url: server.url, path: accountPath, query: { playlistType: 3 } })
110 await makeGetRequest({ url: server.url, path: videoChannelPath, query: { playlistType: 3 } })
111 })
112
113 it('Should fail with a bad account parameter', async function () {
114 const accountPath = '/api/v1/accounts/root2/video-playlists'
115
116 await makeGetRequest({
117 url: server.url,
118 path: accountPath,
119 expectedStatus: HttpStatusCode.NOT_FOUND_404,
120 token: server.accessToken
121 })
122 })
123
124 it('Should fail with a bad video channel parameter', async function () {
125 const accountPath = '/api/v1/video-channels/bad_channel/video-playlists'
126
127 await makeGetRequest({
128 url: server.url,
129 path: accountPath,
130 expectedStatus: HttpStatusCode.NOT_FOUND_404,
131 token: server.accessToken
132 })
133 })
134
135 it('Should success with the correct parameters', async function () {
136 await makeGetRequest({ url: server.url, path: globalPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
137 await makeGetRequest({ url: server.url, path: accountPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
138 await makeGetRequest({
139 url: server.url,
140 path: videoChannelPath,
141 expectedStatus: HttpStatusCode.OK_200,
142 token: server.accessToken
143 })
144 })
145 })
146
147 describe('When listing videos of a playlist', function () {
148 const path = '/api/v1/video-playlists/'
149
150 it('Should fail with a bad start pagination', async function () {
151 await checkBadStartPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
152 })
153
154 it('Should fail with a bad count pagination', async function () {
155 await checkBadCountPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
156 })
157
158 it('Should success with the correct parameters', async function () {
159 await makeGetRequest({ url: server.url, path: path + playlist.shortUUID + '/videos', expectedStatus: HttpStatusCode.OK_200 })
160 })
161 })
162
163 describe('When getting a video playlist', function () {
164 it('Should fail with a bad id or uuid', async function () {
165 await command.get({ playlistId: 'toto', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
166 })
167
168 it('Should fail with an unknown playlist', async function () {
169 await command.get({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
170 })
171
172 it('Should fail to get an unlisted playlist with the number id', async function () {
173 const playlist = await command.create({
174 attributes: {
175 displayName: 'super playlist',
176 videoChannelId: server.store.channel.id,
177 privacy: VideoPlaylistPrivacy.UNLISTED
178 }
179 })
180
181 await command.get({ playlistId: playlist.id, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
182 await command.get({ playlistId: playlist.uuid, expectedStatus: HttpStatusCode.OK_200 })
183 })
184
185 it('Should succeed with the correct params', async function () {
186 await command.get({ playlistId: playlist.uuid, expectedStatus: HttpStatusCode.OK_200 })
187 })
188 })
189
190 describe('When creating/updating a video playlist', function () {
191 const getBase = (
192 attributes?: Partial<VideoPlaylistCreate>,
193 wrapper?: Partial<Parameters<PlaylistsCommand['create']>[0]>
194 ) => {
195 return {
196 attributes: {
197 displayName: 'display name',
198 privacy: VideoPlaylistPrivacy.UNLISTED,
199 thumbnailfile: 'custom-thumbnail.jpg',
200 videoChannelId: server.store.channel.id,
201
202 ...attributes
203 },
204
205 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
206
207 ...wrapper
208 }
209 }
210 const getUpdate = (params: any, playlistId: number | string) => {
211 return { ...params, playlistId }
212 }
213
214 it('Should fail with an unauthenticated user', async function () {
215 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
216
217 await command.create(params)
218 await command.update(getUpdate(params, playlist.shortUUID))
219 })
220
221 it('Should fail without displayName', async function () {
222 const params = getBase({ displayName: undefined })
223
224 await command.create(params)
225 })
226
227 it('Should fail with an incorrect display name', async function () {
228 const params = getBase({ displayName: 's'.repeat(300) })
229
230 await command.create(params)
231 await command.update(getUpdate(params, playlist.shortUUID))
232 })
233
234 it('Should fail with an incorrect description', async function () {
235 const params = getBase({ description: 't' })
236
237 await command.create(params)
238 await command.update(getUpdate(params, playlist.shortUUID))
239 })
240
241 it('Should fail with an incorrect privacy', async function () {
242 const params = getBase({ privacy: 45 as any })
243
244 await command.create(params)
245 await command.update(getUpdate(params, playlist.shortUUID))
246 })
247
248 it('Should fail with an unknown video channel id', async function () {
249 const params = getBase({ videoChannelId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
250
251 await command.create(params)
252 await command.update(getUpdate(params, playlist.shortUUID))
253 })
254
255 it('Should fail with an incorrect thumbnail file', async function () {
256 const params = getBase({ thumbnailfile: 'video_short.mp4' })
257
258 await command.create(params)
259 await command.update(getUpdate(params, playlist.shortUUID))
260 })
261
262 it('Should fail with a thumbnail file too big', async function () {
263 const params = getBase({ thumbnailfile: 'custom-preview-big.png' })
264
265 await command.create(params)
266 await command.update(getUpdate(params, playlist.shortUUID))
267 })
268
269 it('Should fail to set "public" a playlist not assigned to a channel', async function () {
270 const params = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: undefined })
271 const params2 = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: 'null' as any })
272 const params3 = getBase({ privacy: undefined, videoChannelId: 'null' as any })
273
274 await command.create(params)
275 await command.create(params2)
276 await command.update(getUpdate(params, privatePlaylistUUID))
277 await command.update(getUpdate(params2, playlist.shortUUID))
278 await command.update(getUpdate(params3, playlist.shortUUID))
279 })
280
281 it('Should fail with an unknown playlist to update', async function () {
282 await command.update(getUpdate(
283 getBase({}, { expectedStatus: HttpStatusCode.NOT_FOUND_404 }),
284 42
285 ))
286 })
287
288 it('Should fail to update a playlist of another user', async function () {
289 await command.update(getUpdate(
290 getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }),
291 playlist.shortUUID
292 ))
293 })
294
295 it('Should fail to update the watch later playlist', async function () {
296 await command.update(getUpdate(
297 getBase({}, { expectedStatus: HttpStatusCode.BAD_REQUEST_400 }),
298 watchLaterPlaylistId
299 ))
300 })
301
302 it('Should succeed with the correct params', async function () {
303 {
304 const params = getBase({}, { expectedStatus: HttpStatusCode.OK_200 })
305 await command.create(params)
306 }
307
308 {
309 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
310 await command.update(getUpdate(params, playlist.shortUUID))
311 }
312 })
313 })
314
315 describe('When adding an element in a playlist', function () {
316 const getBase = (
317 attributes?: Partial<VideoPlaylistElementCreate>,
318 wrapper?: Partial<Parameters<PlaylistsCommand['addElement']>[0]>
319 ) => {
320 return {
321 attributes: {
322 videoId,
323 startTimestamp: 2,
324 stopTimestamp: 3,
325
326 ...attributes
327 },
328
329 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
330 playlistId: playlist.id,
331
332 ...wrapper
333 }
334 }
335
336 it('Should fail with an unauthenticated user', async function () {
337 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
338 await command.addElement(params)
339 })
340
341 it('Should fail with the playlist of another user', async function () {
342 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
343 await command.addElement(params)
344 })
345
346 it('Should fail with an unknown or incorrect playlist id', async function () {
347 {
348 const params = getBase({}, { playlistId: 'toto' })
349 await command.addElement(params)
350 }
351
352 {
353 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
354 await command.addElement(params)
355 }
356 })
357
358 it('Should fail with an unknown or incorrect video id', async function () {
359 const params = getBase({ videoId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
360 await command.addElement(params)
361 })
362
363 it('Should fail with a bad start/stop timestamp', async function () {
364 {
365 const params = getBase({ startTimestamp: -42 })
366 await command.addElement(params)
367 }
368
369 {
370 const params = getBase({ stopTimestamp: 'toto' as any })
371 await command.addElement(params)
372 }
373 })
374
375 it('Succeed with the correct params', async function () {
376 const params = getBase({}, { expectedStatus: HttpStatusCode.OK_200 })
377 const created = await command.addElement(params)
378 elementId = created.id
379 })
380 })
381
382 describe('When updating an element in a playlist', function () {
383 const getBase = (
384 attributes?: Partial<VideoPlaylistElementUpdate>,
385 wrapper?: Partial<Parameters<PlaylistsCommand['updateElement']>[0]>
386 ) => {
387 return {
388 attributes: {
389 startTimestamp: 1,
390 stopTimestamp: 2,
391
392 ...attributes
393 },
394
395 elementId,
396 playlistId: playlist.id,
397 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
398
399 ...wrapper
400 }
401 }
402
403 it('Should fail with an unauthenticated user', async function () {
404 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
405 await command.updateElement(params)
406 })
407
408 it('Should fail with the playlist of another user', async function () {
409 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
410 await command.updateElement(params)
411 })
412
413 it('Should fail with an unknown or incorrect playlist id', async function () {
414 {
415 const params = getBase({}, { playlistId: 'toto' })
416 await command.updateElement(params)
417 }
418
419 {
420 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
421 await command.updateElement(params)
422 }
423 })
424
425 it('Should fail with an unknown or incorrect playlistElement id', async function () {
426 {
427 const params = getBase({}, { elementId: 'toto' })
428 await command.updateElement(params)
429 }
430
431 {
432 const params = getBase({}, { elementId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
433 await command.updateElement(params)
434 }
435 })
436
437 it('Should fail with a bad start/stop timestamp', async function () {
438 {
439 const params = getBase({ startTimestamp: 'toto' as any })
440 await command.updateElement(params)
441 }
442
443 {
444 const params = getBase({ stopTimestamp: -42 })
445 await command.updateElement(params)
446 }
447 })
448
449 it('Should fail with an unknown element', async function () {
450 const params = getBase({}, { elementId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
451 await command.updateElement(params)
452 })
453
454 it('Succeed with the correct params', async function () {
455 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
456 await command.updateElement(params)
457 })
458 })
459
460 describe('When reordering elements of a playlist', function () {
461 let videoId3: number
462 let videoId4: number
463
464 const getBase = (
465 attributes?: Partial<VideoPlaylistReorder>,
466 wrapper?: Partial<Parameters<PlaylistsCommand['reorderElements']>[0]>
467 ) => {
468 return {
469 attributes: {
470 startPosition: 1,
471 insertAfterPosition: 2,
472 reorderLength: 3,
473
474 ...attributes
475 },
476
477 playlistId: playlist.shortUUID,
478 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
479
480 ...wrapper
481 }
482 }
483
484 before(async function () {
485 videoId3 = (await server.videos.quickUpload({ name: 'video 3' })).id
486 videoId4 = (await server.videos.quickUpload({ name: 'video 4' })).id
487
488 for (const id of [ videoId3, videoId4 ]) {
489 await command.addElement({ playlistId: playlist.shortUUID, attributes: { videoId: id } })
490 }
491 })
492
493 it('Should fail with an unauthenticated user', async function () {
494 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
495 await command.reorderElements(params)
496 })
497
498 it('Should fail with the playlist of another user', async function () {
499 const params = getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
500 await command.reorderElements(params)
501 })
502
503 it('Should fail with an invalid playlist', async function () {
504 {
505 const params = getBase({}, { playlistId: 'toto' })
506 await command.reorderElements(params)
507 }
508
509 {
510 const params = getBase({}, { playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
511 await command.reorderElements(params)
512 }
513 })
514
515 it('Should fail with an invalid start position', async function () {
516 {
517 const params = getBase({ startPosition: -1 })
518 await command.reorderElements(params)
519 }
520
521 {
522 const params = getBase({ startPosition: 'toto' as any })
523 await command.reorderElements(params)
524 }
525
526 {
527 const params = getBase({ startPosition: 42 })
528 await command.reorderElements(params)
529 }
530 })
531
532 it('Should fail with an invalid insert after position', async function () {
533 {
534 const params = getBase({ insertAfterPosition: 'toto' as any })
535 await command.reorderElements(params)
536 }
537
538 {
539 const params = getBase({ insertAfterPosition: -2 })
540 await command.reorderElements(params)
541 }
542
543 {
544 const params = getBase({ insertAfterPosition: 42 })
545 await command.reorderElements(params)
546 }
547 })
548
549 it('Should fail with an invalid reorder length', async function () {
550 {
551 const params = getBase({ reorderLength: 'toto' as any })
552 await command.reorderElements(params)
553 }
554
555 {
556 const params = getBase({ reorderLength: -2 })
557 await command.reorderElements(params)
558 }
559
560 {
561 const params = getBase({ reorderLength: 42 })
562 await command.reorderElements(params)
563 }
564 })
565
566 it('Succeed with the correct params', async function () {
567 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
568 await command.reorderElements(params)
569 })
570 })
571
572 describe('When checking exists in playlist endpoint', function () {
573 const path = '/api/v1/users/me/video-playlists/videos-exist'
574
575 it('Should fail with an unauthenticated user', async function () {
576 await makeGetRequest({
577 url: server.url,
578 path,
579 query: { videoIds: [ 1, 2 ] },
580 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
581 })
582 })
583
584 it('Should fail with invalid video ids', async function () {
585 await makeGetRequest({
586 url: server.url,
587 token: server.accessToken,
588 path,
589 query: { videoIds: 'toto' }
590 })
591
592 await makeGetRequest({
593 url: server.url,
594 token: server.accessToken,
595 path,
596 query: { videoIds: [ 'toto' ] }
597 })
598
599 await makeGetRequest({
600 url: server.url,
601 token: server.accessToken,
602 path,
603 query: { videoIds: [ 1, 'toto' ] }
604 })
605 })
606
607 it('Should succeed with the correct params', async function () {
608 await makeGetRequest({
609 url: server.url,
610 token: server.accessToken,
611 path,
612 query: { videoIds: [ 1, 2 ] },
613 expectedStatus: HttpStatusCode.OK_200
614 })
615 })
616 })
617
618 describe('When deleting an element in a playlist', function () {
619 const getBase = (wrapper: Partial<Parameters<PlaylistsCommand['removeElement']>[0]>) => {
620 return {
621 elementId,
622 playlistId: playlist.uuid,
623 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
624
625 ...wrapper
626 }
627 }
628
629 it('Should fail with an unauthenticated user', async function () {
630 const params = getBase({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
631 await command.removeElement(params)
632 })
633
634 it('Should fail with the playlist of another user', async function () {
635 const params = getBase({ token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
636 await command.removeElement(params)
637 })
638
639 it('Should fail with an unknown or incorrect playlist id', async function () {
640 {
641 const params = getBase({ playlistId: 'toto' })
642 await command.removeElement(params)
643 }
644
645 {
646 const params = getBase({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
647 await command.removeElement(params)
648 }
649 })
650
651 it('Should fail with an unknown or incorrect video id', async function () {
652 {
653 const params = getBase({ elementId: 'toto' as any })
654 await command.removeElement(params)
655 }
656
657 {
658 const params = getBase({ elementId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
659 await command.removeElement(params)
660 }
661 })
662
663 it('Should fail with an unknown element', async function () {
664 const params = getBase({ elementId: 888, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
665 await command.removeElement(params)
666 })
667
668 it('Succeed with the correct params', async function () {
669 const params = getBase({ expectedStatus: HttpStatusCode.NO_CONTENT_204 })
670 await command.removeElement(params)
671 })
672 })
673
674 describe('When deleting a playlist', function () {
675 it('Should fail with an unknown playlist', async function () {
676 await command.delete({ playlistId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
677 })
678
679 it('Should fail with a playlist of another user', async function () {
680 await command.delete({ token: userAccessToken, playlistId: playlist.uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
681 })
682
683 it('Should fail with the watch later playlist', async function () {
684 await command.delete({ playlistId: watchLaterPlaylistId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
685 })
686
687 it('Should succeed with the correct params', async function () {
688 await command.delete({ playlistId: playlist.uuid })
689 })
690 })
691
692 after(async function () {
693 await cleanupTests([ server ])
694 })
695})
diff --git a/packages/tests/src/api/check-params/video-source.ts b/packages/tests/src/api/check-params/video-source.ts
new file mode 100644
index 000000000..918182b8d
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-source.ts
@@ -0,0 +1,154 @@
1import { HttpStatusCode } from '@peertube/peertube-models'
2import {
3 cleanupTests,
4 createSingleServer,
5 PeerTubeServer,
6 setAccessTokensToServers,
7 setDefaultVideoChannel,
8 waitJobs
9} from '@peertube/peertube-server-commands'
10
11describe('Test video sources API validator', function () {
12 let server: PeerTubeServer = null
13 let uuid: string
14 let userToken: string
15
16 before(async function () {
17 this.timeout(120000)
18
19 server = await createSingleServer(1)
20 await setAccessTokensToServers([ server ])
21 await setDefaultVideoChannel([ server ])
22
23 userToken = await server.users.generateUserAndToken('user1')
24 })
25
26 describe('When getting latest source', function () {
27
28 before(async function () {
29 const created = await server.videos.quickUpload({ name: 'video' })
30 uuid = created.uuid
31 })
32
33 it('Should fail without a valid uuid', async function () {
34 await server.videos.getSource({ id: '4da6fde3-88f7-4d16-b119-108df563d0b0', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
35 })
36
37 it('Should receive 404 when passing a non existing video id', async function () {
38 await server.videos.getSource({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
39 })
40
41 it('Should not get the source as unauthenticated', async function () {
42 await server.videos.getSource({ id: uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401, token: null })
43 })
44
45 it('Should not get the source with another user', async function () {
46 await server.videos.getSource({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: userToken })
47 })
48
49 it('Should succeed with the correct parameters get the source as another user', async function () {
50 await server.videos.getSource({ id: uuid })
51 })
52 })
53
54 describe('When updating source video file', function () {
55 let userAccessToken: string
56 let userId: number
57
58 let videoId: string
59 let userVideoId: string
60
61 before(async function () {
62 const res = await server.users.generate('user2')
63 userAccessToken = res.token
64 userId = res.userId
65
66 const { uuid } = await server.videos.quickUpload({ name: 'video' })
67 videoId = uuid
68
69 await waitJobs([ server ])
70 })
71
72 it('Should fail if not enabled on the instance', async function () {
73 await server.config.disableFileUpdate()
74
75 await server.videos.replaceSourceFile({ videoId, fixture: 'video_short.mp4', expectedStatus: HttpStatusCode.FORBIDDEN_403 })
76 })
77
78 it('Should fail on an unknown video', async function () {
79 await server.config.enableFileUpdate()
80
81 await server.videos.replaceSourceFile({ videoId: 404, fixture: 'video_short.mp4', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
82 })
83
84 it('Should fail with an invalid video', async function () {
85 await server.config.enableLive({ allowReplay: false })
86
87 const { video } = await server.live.quickCreate({ saveReplay: false, permanentLive: true })
88 await server.videos.replaceSourceFile({
89 videoId: video.uuid,
90 fixture: 'video_short.mp4',
91 expectedStatus: HttpStatusCode.BAD_REQUEST_400
92 })
93 })
94
95 it('Should fail without token', async function () {
96 await server.videos.replaceSourceFile({
97 token: null,
98 videoId,
99 fixture: 'video_short.mp4',
100 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
101 })
102 })
103
104 it('Should fail with another user', async function () {
105 await server.videos.replaceSourceFile({
106 token: userAccessToken,
107 videoId,
108 fixture: 'video_short.mp4',
109 expectedStatus: HttpStatusCode.FORBIDDEN_403
110 })
111 })
112
113 it('Should fail with an incorrect input file', async function () {
114 await server.videos.replaceSourceFile({
115 fixture: 'video_short_fake.webm',
116 videoId,
117 completedExpectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422
118 })
119
120 await server.videos.replaceSourceFile({
121 fixture: 'video_short.mkv',
122 videoId,
123 expectedStatus: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415
124 })
125 })
126
127 it('Should fail if quota is exceeded', async function () {
128 this.timeout(60000)
129
130 const { uuid } = await server.videos.quickUpload({ name: 'user video' })
131 userVideoId = uuid
132 await waitJobs([ server ])
133
134 await server.users.update({ userId, videoQuota: 1 })
135 await server.videos.replaceSourceFile({
136 token: userAccessToken,
137 videoId: uuid,
138 fixture: 'video_short.mp4',
139 expectedStatus: HttpStatusCode.FORBIDDEN_403
140 })
141 })
142
143 it('Should succeed with the correct params', async function () {
144 this.timeout(60000)
145
146 await server.users.update({ userId, videoQuota: 1000 * 1000 * 1000 })
147 await server.videos.replaceSourceFile({ videoId: userVideoId, fixture: 'video_short.mp4' })
148 })
149 })
150
151 after(async function () {
152 await cleanupTests([ server ])
153 })
154})
diff --git a/packages/tests/src/api/check-params/video-storyboards.ts b/packages/tests/src/api/check-params/video-storyboards.ts
new file mode 100644
index 000000000..f83b541d8
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-storyboards.ts
@@ -0,0 +1,45 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models'
4import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@peertube/peertube-server-commands'
5
6describe('Test video storyboards API validator', function () {
7 let server: PeerTubeServer
8
9 let publicVideo: { uuid: string }
10 let privateVideo: { uuid: string }
11
12 // ---------------------------------------------------------------
13
14 before(async function () {
15 this.timeout(120000)
16
17 server = await createSingleServer(1)
18 await setAccessTokensToServers([ server ])
19
20 publicVideo = await server.videos.quickUpload({ name: 'public' })
21 privateVideo = await server.videos.quickUpload({ name: 'private', privacy: VideoPrivacy.PRIVATE })
22 })
23
24 it('Should fail without a valid uuid', async function () {
25 await server.storyboard.list({ id: '4da6fde3-88f7-4d16-b119-108df563d0b0', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
26 })
27
28 it('Should receive 404 when passing a non existing video id', async function () {
29 await server.storyboard.list({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
30 })
31
32 it('Should not get the private storyboard without the appropriate token', async function () {
33 await server.storyboard.list({ id: privateVideo.uuid, expectedStatus: HttpStatusCode.UNAUTHORIZED_401, token: null })
34 await server.storyboard.list({ id: publicVideo.uuid, expectedStatus: HttpStatusCode.OK_200, token: null })
35 })
36
37 it('Should succeed with the correct parameters', async function () {
38 await server.storyboard.list({ id: privateVideo.uuid })
39 await server.storyboard.list({ id: publicVideo.uuid })
40 })
41
42 after(async function () {
43 await cleanupTests([ server ])
44 })
45})
diff --git a/packages/tests/src/api/check-params/video-studio.ts b/packages/tests/src/api/check-params/video-studio.ts
new file mode 100644
index 000000000..ae83f3590
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-studio.ts
@@ -0,0 +1,392 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode, HttpStatusCodeType, VideoStudioTask } from '@peertube/peertube-models'
4import {
5 cleanupTests,
6 createSingleServer,
7 PeerTubeServer,
8 setAccessTokensToServers,
9 VideoStudioCommand,
10 waitJobs
11} from '@peertube/peertube-server-commands'
12
13describe('Test video studio API validator', function () {
14 let server: PeerTubeServer
15 let command: VideoStudioCommand
16 let userAccessToken: string
17 let videoUUID: string
18
19 // ---------------------------------------------------------------
20
21 before(async function () {
22 this.timeout(120_000)
23
24 server = await createSingleServer(1)
25
26 await setAccessTokensToServers([ server ])
27 userAccessToken = await server.users.generateUserAndToken('user1')
28
29 await server.config.enableMinimumTranscoding()
30
31 const { uuid } = await server.videos.quickUpload({ name: 'video' })
32 videoUUID = uuid
33
34 command = server.videoStudio
35
36 await waitJobs([ server ])
37 })
38
39 describe('Task creation', function () {
40
41 describe('Config settings', function () {
42
43 it('Should fail if studio is disabled', async function () {
44 await server.config.updateExistingSubConfig({
45 newConfig: {
46 videoStudio: {
47 enabled: false
48 }
49 }
50 })
51
52 await command.createEditionTasks({
53 videoId: videoUUID,
54 tasks: VideoStudioCommand.getComplexTask(),
55 expectedStatus: HttpStatusCode.BAD_REQUEST_400
56 })
57 })
58
59 it('Should fail to enable studio if transcoding is disabled', async function () {
60 await server.config.updateExistingSubConfig({
61 newConfig: {
62 videoStudio: {
63 enabled: true
64 },
65 transcoding: {
66 enabled: false
67 }
68 },
69 expectedStatus: HttpStatusCode.BAD_REQUEST_400
70 })
71 })
72
73 it('Should succeed to enable video studio', async function () {
74 await server.config.updateExistingSubConfig({
75 newConfig: {
76 videoStudio: {
77 enabled: true
78 },
79 transcoding: {
80 enabled: true
81 }
82 }
83 })
84 })
85 })
86
87 describe('Common tasks', function () {
88
89 it('Should fail without token', async function () {
90 await command.createEditionTasks({
91 token: null,
92 videoId: videoUUID,
93 tasks: VideoStudioCommand.getComplexTask(),
94 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
95 })
96 })
97
98 it('Should fail with another user token', async function () {
99 await command.createEditionTasks({
100 token: userAccessToken,
101 videoId: videoUUID,
102 tasks: VideoStudioCommand.getComplexTask(),
103 expectedStatus: HttpStatusCode.FORBIDDEN_403
104 })
105 })
106
107 it('Should fail with an invalid video', async function () {
108 await command.createEditionTasks({
109 videoId: 'tintin',
110 tasks: VideoStudioCommand.getComplexTask(),
111 expectedStatus: HttpStatusCode.BAD_REQUEST_400
112 })
113 })
114
115 it('Should fail with an unknown video', async function () {
116 await command.createEditionTasks({
117 videoId: 42,
118 tasks: VideoStudioCommand.getComplexTask(),
119 expectedStatus: HttpStatusCode.NOT_FOUND_404
120 })
121 })
122
123 it('Should fail with an already in transcoding state video', async function () {
124 this.timeout(60000)
125
126 const { uuid } = await server.videos.quickUpload({ name: 'transcoded video' })
127 await waitJobs([ server ])
128
129 await server.jobs.pauseJobQueue()
130 await server.videos.runTranscoding({ videoId: uuid, transcodingType: 'hls' })
131
132 await command.createEditionTasks({
133 videoId: uuid,
134 tasks: VideoStudioCommand.getComplexTask(),
135 expectedStatus: HttpStatusCode.CONFLICT_409
136 })
137
138 await server.jobs.resumeJobQueue()
139 })
140
141 it('Should fail with a bad complex task', async function () {
142 await command.createEditionTasks({
143 videoId: videoUUID,
144 tasks: [
145 {
146 name: 'cut',
147 options: {
148 start: 1,
149 end: 2
150 }
151 },
152 {
153 name: 'hadock',
154 options: {
155 start: 1,
156 end: 2
157 }
158 }
159 ] as any,
160 expectedStatus: HttpStatusCode.BAD_REQUEST_400
161 })
162 })
163
164 it('Should fail without task', async function () {
165 await command.createEditionTasks({
166 videoId: videoUUID,
167 tasks: [],
168 expectedStatus: HttpStatusCode.BAD_REQUEST_400
169 })
170 })
171
172 it('Should fail with too many tasks', async function () {
173 const tasks: VideoStudioTask[] = []
174
175 for (let i = 0; i < 110; i++) {
176 tasks.push({
177 name: 'cut',
178 options: {
179 start: 1
180 }
181 })
182 }
183
184 await command.createEditionTasks({
185 videoId: videoUUID,
186 tasks,
187 expectedStatus: HttpStatusCode.BAD_REQUEST_400
188 })
189 })
190
191 it('Should succeed with correct parameters', async function () {
192 await server.jobs.pauseJobQueue()
193
194 await command.createEditionTasks({
195 videoId: videoUUID,
196 tasks: VideoStudioCommand.getComplexTask(),
197 expectedStatus: HttpStatusCode.NO_CONTENT_204
198 })
199 })
200
201 it('Should fail with a video that is already waiting for edition', async function () {
202 this.timeout(120000)
203
204 await command.createEditionTasks({
205 videoId: videoUUID,
206 tasks: VideoStudioCommand.getComplexTask(),
207 expectedStatus: HttpStatusCode.CONFLICT_409
208 })
209
210 await server.jobs.resumeJobQueue()
211
212 await waitJobs([ server ])
213 })
214 })
215
216 describe('Cut task', function () {
217
218 async function cut (start: number, end: number, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
219 await command.createEditionTasks({
220 videoId: videoUUID,
221 tasks: [
222 {
223 name: 'cut',
224 options: {
225 start,
226 end
227 }
228 }
229 ],
230 expectedStatus
231 })
232 }
233
234 it('Should fail with bad start/end', async function () {
235 const invalid = [
236 'tintin',
237 -1,
238 undefined
239 ]
240
241 for (const value of invalid) {
242 await cut(value as any, undefined)
243 await cut(undefined, value as any)
244 }
245 })
246
247 it('Should fail with the same start/end', async function () {
248 await cut(2, 2)
249 })
250
251 it('Should fail with inconsistents start/end', async function () {
252 await cut(2, 1)
253 })
254
255 it('Should fail without start and end', async function () {
256 await cut(undefined, undefined)
257 })
258
259 it('Should succeed with the correct params', async function () {
260 this.timeout(120000)
261
262 await cut(0, 2, HttpStatusCode.NO_CONTENT_204)
263
264 await waitJobs([ server ])
265 })
266 })
267
268 describe('Watermark task', function () {
269
270 async function addWatermark (file: string, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
271 await command.createEditionTasks({
272 videoId: videoUUID,
273 tasks: [
274 {
275 name: 'add-watermark',
276 options: {
277 file
278 }
279 }
280 ],
281 expectedStatus
282 })
283 }
284
285 it('Should fail without waterkmark', async function () {
286 await addWatermark(undefined)
287 })
288
289 it('Should fail with an invalid watermark', async function () {
290 await addWatermark('video_short.mp4')
291 })
292
293 it('Should succeed with the correct params', async function () {
294 this.timeout(120000)
295
296 await addWatermark('custom-thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
297
298 await waitJobs([ server ])
299 })
300 })
301
302 describe('Intro/Outro task', function () {
303
304 async function addIntroOutro (
305 type: 'add-intro' | 'add-outro',
306 file: string,
307 expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400
308 ) {
309 await command.createEditionTasks({
310 videoId: videoUUID,
311 tasks: [
312 {
313 name: type,
314 options: {
315 file
316 }
317 }
318 ],
319 expectedStatus
320 })
321 }
322
323 it('Should fail without file', async function () {
324 await addIntroOutro('add-intro', undefined)
325 await addIntroOutro('add-outro', undefined)
326 })
327
328 it('Should fail with an invalid file', async function () {
329 await addIntroOutro('add-intro', 'custom-thumbnail.jpg')
330 await addIntroOutro('add-outro', 'custom-thumbnail.jpg')
331 })
332
333 it('Should fail with a file that does not contain video stream', async function () {
334 await addIntroOutro('add-intro', 'sample.ogg')
335 await addIntroOutro('add-outro', 'sample.ogg')
336
337 })
338
339 it('Should succeed with the correct params', async function () {
340 this.timeout(120000)
341
342 await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
343 await waitJobs([ server ])
344
345 await addIntroOutro('add-outro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
346 await waitJobs([ server ])
347 })
348
349 it('Should check total quota when creating the task', async function () {
350 this.timeout(120000)
351
352 const user = await server.users.create({ username: 'user_quota_1' })
353 const token = await server.login.getAccessToken('user_quota_1')
354 const { uuid } = await server.videos.quickUpload({ token, name: 'video_quota_1', fixture: 'video_short.mp4' })
355
356 const addIntroOutroByUser = (type: 'add-intro' | 'add-outro', expectedStatus: HttpStatusCodeType) => {
357 return command.createEditionTasks({
358 token,
359 videoId: uuid,
360 tasks: [
361 {
362 name: type,
363 options: {
364 file: 'video_short.mp4'
365 }
366 }
367 ],
368 expectedStatus
369 })
370 }
371
372 await waitJobs([ server ])
373
374 const { videoQuotaUsed } = await server.users.getMyQuotaUsed({ token })
375 await server.users.update({ userId: user.id, videoQuota: Math.round(videoQuotaUsed * 2.5) })
376
377 // Still valid
378 await addIntroOutroByUser('add-intro', HttpStatusCode.NO_CONTENT_204)
379
380 await waitJobs([ server ])
381
382 // Too much quota
383 await addIntroOutroByUser('add-intro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
384 await addIntroOutroByUser('add-outro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
385 })
386 })
387 })
388
389 after(async function () {
390 await cleanupTests([ server ])
391 })
392})
diff --git a/packages/tests/src/api/check-params/video-token.ts b/packages/tests/src/api/check-params/video-token.ts
new file mode 100644
index 000000000..5f838102d
--- /dev/null
+++ b/packages/tests/src/api/check-params/video-token.ts
@@ -0,0 +1,70 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models'
4import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@peertube/peertube-server-commands'
5
6describe('Test video tokens', function () {
7 let server: PeerTubeServer
8 let privateVideoId: string
9 let passwordProtectedVideoId: string
10 let userToken: string
11
12 const videoPassword = 'password'
13
14 // ---------------------------------------------------------------
15
16 before(async function () {
17 this.timeout(300_000)
18
19 server = await createSingleServer(1)
20 await setAccessTokensToServers([ server ])
21 {
22 const { uuid } = await server.videos.quickUpload({ name: 'private video', privacy: VideoPrivacy.PRIVATE })
23 privateVideoId = uuid
24 }
25 {
26 const { uuid } = await server.videos.quickUpload({
27 name: 'password protected video',
28 privacy: VideoPrivacy.PASSWORD_PROTECTED,
29 videoPasswords: [ videoPassword ]
30 })
31 passwordProtectedVideoId = uuid
32 }
33 userToken = await server.users.generateUserAndToken('user1')
34 })
35
36 it('Should not generate tokens on private video for unauthenticated user', async function () {
37 await server.videoToken.create({ videoId: privateVideoId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
38 })
39
40 it('Should not generate tokens of unknown video', async function () {
41 await server.videoToken.create({ videoId: 404, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
42 })
43
44 it('Should not generate tokens with incorrect password', async function () {
45 await server.videoToken.create({
46 videoId: passwordProtectedVideoId,
47 token: null,
48 expectedStatus: HttpStatusCode.FORBIDDEN_403,
49 videoPassword: 'incorrectPassword'
50 })
51 })
52
53 it('Should not generate tokens of a non owned video', async function () {
54 await server.videoToken.create({ videoId: privateVideoId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
55 })
56
57 it('Should generate token', async function () {
58 await server.videoToken.create({ videoId: privateVideoId })
59 })
60
61 it('Should generate token on password protected video', async function () {
62 await server.videoToken.create({ videoId: passwordProtectedVideoId, videoPassword, token: null })
63 await server.videoToken.create({ videoId: passwordProtectedVideoId, videoPassword, token: userToken })
64 await server.videoToken.create({ videoId: passwordProtectedVideoId, videoPassword })
65 })
66
67 after(async function () {
68 await cleanupTests([ server ])
69 })
70})
diff --git a/packages/tests/src/api/check-params/videos-common-filters.ts b/packages/tests/src/api/check-params/videos-common-filters.ts
new file mode 100644
index 000000000..dbae3010c
--- /dev/null
+++ b/packages/tests/src/api/check-params/videos-common-filters.ts
@@ -0,0 +1,171 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import {
4 HttpStatusCode,
5 HttpStatusCodeType,
6 UserRole,
7 VideoInclude,
8 VideoIncludeType,
9 VideoPrivacy,
10 VideoPrivacyType
11} from '@peertube/peertube-models'
12import {
13 cleanupTests,
14 createSingleServer,
15 makeGetRequest,
16 PeerTubeServer,
17 setAccessTokensToServers,
18 setDefaultVideoChannel
19} from '@peertube/peertube-server-commands'
20
21describe('Test video filters validators', function () {
22 let server: PeerTubeServer
23 let userAccessToken: string
24 let moderatorAccessToken: string
25
26 // ---------------------------------------------------------------
27
28 before(async function () {
29 this.timeout(30000)
30
31 server = await createSingleServer(1)
32
33 await setAccessTokensToServers([ server ])
34 await setDefaultVideoChannel([ server ])
35
36 const user = { username: 'user1', password: 'my super password' }
37 await server.users.create({ username: user.username, password: user.password })
38 userAccessToken = await server.login.getAccessToken(user)
39
40 const moderator = { username: 'moderator', password: 'my super password' }
41 await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
42
43 moderatorAccessToken = await server.login.getAccessToken(moderator)
44 })
45
46 describe('When setting video filters', function () {
47
48 const validIncludes = [
49 VideoInclude.NONE,
50 VideoInclude.BLOCKED_OWNER,
51 VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED
52 ]
53
54 async function testEndpoints (options: {
55 token?: string
56 isLocal?: boolean
57 include?: VideoIncludeType
58 privacyOneOf?: VideoPrivacyType[]
59 expectedStatus: HttpStatusCodeType
60 excludeAlreadyWatched?: boolean
61 unauthenticatedUser?: boolean
62 }) {
63 const paths = [
64 '/api/v1/video-channels/root_channel/videos',
65 '/api/v1/accounts/root/videos',
66 '/api/v1/videos',
67 '/api/v1/search/videos'
68 ]
69
70 for (const path of paths) {
71 const token = options.unauthenticatedUser
72 ? undefined
73 : options.token || server.accessToken
74
75 await makeGetRequest({
76 url: server.url,
77 path,
78 token,
79 query: {
80 isLocal: options.isLocal,
81 privacyOneOf: options.privacyOneOf,
82 include: options.include,
83 excludeAlreadyWatched: options.excludeAlreadyWatched
84 },
85 expectedStatus: options.expectedStatus
86 })
87 }
88 }
89
90 it('Should fail with a bad privacyOneOf', async function () {
91 await testEndpoints({ privacyOneOf: [ 'toto' ] as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
92 })
93
94 it('Should succeed with a good privacyOneOf', async function () {
95 await testEndpoints({ privacyOneOf: [ VideoPrivacy.INTERNAL ], expectedStatus: HttpStatusCode.OK_200 })
96 })
97
98 it('Should fail to use privacyOneOf with a simple user', async function () {
99 await testEndpoints({
100 privacyOneOf: [ VideoPrivacy.INTERNAL ],
101 token: userAccessToken,
102 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
103 })
104 })
105
106 it('Should fail with a bad include', async function () {
107 await testEndpoints({ include: 'toto' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
108 })
109
110 it('Should succeed with a good include', async function () {
111 for (const include of validIncludes) {
112 await testEndpoints({ include, expectedStatus: HttpStatusCode.OK_200 })
113 }
114 })
115
116 it('Should fail to include more videos with a simple user', async function () {
117 for (const include of validIncludes) {
118 await testEndpoints({ token: userAccessToken, include, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
119 }
120 })
121
122 it('Should succeed to list all local/all with a moderator', async function () {
123 for (const include of validIncludes) {
124 await testEndpoints({ token: moderatorAccessToken, include, expectedStatus: HttpStatusCode.OK_200 })
125 }
126 })
127
128 it('Should succeed to list all local/all with an admin', async function () {
129 for (const include of validIncludes) {
130 await testEndpoints({ token: server.accessToken, include, expectedStatus: HttpStatusCode.OK_200 })
131 }
132 })
133
134 // Because we cannot authenticate the user on the RSS endpoint
135 it('Should fail on the feeds endpoint with the all filter', async function () {
136 for (const include of [ VideoInclude.NOT_PUBLISHED_STATE ]) {
137 await makeGetRequest({
138 url: server.url,
139 path: '/feeds/videos.json',
140 expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
141 query: {
142 include
143 }
144 })
145 }
146 })
147
148 it('Should succeed on the feeds endpoint with the local filter', async function () {
149 await makeGetRequest({
150 url: server.url,
151 path: '/feeds/videos.json',
152 expectedStatus: HttpStatusCode.OK_200,
153 query: {
154 isLocal: true
155 }
156 })
157 })
158
159 it('Should fail when trying to exclude already watched videos for an unlogged user', async function () {
160 await testEndpoints({ excludeAlreadyWatched: true, unauthenticatedUser: true, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
161 })
162
163 it('Should succeed when trying to exclude already watched videos for a logged user', async function () {
164 await testEndpoints({ token: userAccessToken, excludeAlreadyWatched: true, expectedStatus: HttpStatusCode.OK_200 })
165 })
166 })
167
168 after(async function () {
169 await cleanupTests([ server ])
170 })
171})
diff --git a/packages/tests/src/api/check-params/videos-history.ts b/packages/tests/src/api/check-params/videos-history.ts
new file mode 100644
index 000000000..65d1e9fac
--- /dev/null
+++ b/packages/tests/src/api/check-params/videos-history.ts
@@ -0,0 +1,145 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { checkBadCountPagination, checkBadStartPagination } from '@tests/shared/checks.js'
4import { HttpStatusCode } from '@peertube/peertube-models'
5import {
6 cleanupTests,
7 createSingleServer,
8 makeDeleteRequest,
9 makeGetRequest,
10 makePostBodyRequest,
11 makePutBodyRequest,
12 PeerTubeServer,
13 setAccessTokensToServers
14} from '@peertube/peertube-server-commands'
15
16describe('Test videos history API validator', function () {
17 const myHistoryPath = '/api/v1/users/me/history/videos'
18 const myHistoryRemove = myHistoryPath + '/remove'
19 let viewPath: string
20 let server: PeerTubeServer
21 let videoId: number
22
23 // ---------------------------------------------------------------
24
25 before(async function () {
26 this.timeout(30000)
27
28 server = await createSingleServer(1)
29
30 await setAccessTokensToServers([ server ])
31
32 const { id, uuid } = await server.videos.upload()
33 viewPath = '/api/v1/videos/' + uuid + '/views'
34 videoId = id
35 })
36
37 describe('When notifying a user is watching a video', function () {
38
39 it('Should fail with a bad token', async function () {
40 const fields = { currentTime: 5 }
41 await makePutBodyRequest({ url: server.url, path: viewPath, fields, token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
42 })
43
44 it('Should succeed with the correct parameters', async function () {
45 const fields = { currentTime: 5 }
46
47 await makePutBodyRequest({
48 url: server.url,
49 path: viewPath,
50 fields,
51 token: server.accessToken,
52 expectedStatus: HttpStatusCode.NO_CONTENT_204
53 })
54 })
55 })
56
57 describe('When listing user videos history', function () {
58 it('Should fail with a bad start pagination', async function () {
59 await checkBadStartPagination(server.url, myHistoryPath, server.accessToken)
60 })
61
62 it('Should fail with a bad count pagination', async function () {
63 await checkBadCountPagination(server.url, myHistoryPath, server.accessToken)
64 })
65
66 it('Should fail with an unauthenticated user', async function () {
67 await makeGetRequest({ url: server.url, path: myHistoryPath, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
68 })
69
70 it('Should succeed with the correct params', async function () {
71 await makeGetRequest({ url: server.url, token: server.accessToken, path: myHistoryPath, expectedStatus: HttpStatusCode.OK_200 })
72 })
73 })
74
75 describe('When removing a specific user video history element', function () {
76 let path: string
77
78 before(function () {
79 path = myHistoryPath + '/' + videoId
80 })
81
82 it('Should fail with an unauthenticated user', async function () {
83 await makeDeleteRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
84 })
85
86 it('Should fail with a bad videoId parameter', async function () {
87 await makeDeleteRequest({
88 url: server.url,
89 token: server.accessToken,
90 path: myHistoryRemove + '/hi',
91 expectedStatus: HttpStatusCode.BAD_REQUEST_400
92 })
93 })
94
95 it('Should succeed with the correct parameters', async function () {
96 await makeDeleteRequest({
97 url: server.url,
98 token: server.accessToken,
99 path,
100 expectedStatus: HttpStatusCode.NO_CONTENT_204
101 })
102 })
103 })
104
105 describe('When removing all user videos history', function () {
106 it('Should fail with an unauthenticated user', async function () {
107 await makePostBodyRequest({ url: server.url, path: myHistoryPath + '/remove', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
108 })
109
110 it('Should fail with a bad beforeDate parameter', async function () {
111 const body = { beforeDate: '15' }
112 await makePostBodyRequest({
113 url: server.url,
114 token: server.accessToken,
115 path: myHistoryRemove,
116 fields: body,
117 expectedStatus: HttpStatusCode.BAD_REQUEST_400
118 })
119 })
120
121 it('Should succeed with a valid beforeDate param', async function () {
122 const body = { beforeDate: new Date().toISOString() }
123 await makePostBodyRequest({
124 url: server.url,
125 token: server.accessToken,
126 path: myHistoryRemove,
127 fields: body,
128 expectedStatus: HttpStatusCode.NO_CONTENT_204
129 })
130 })
131
132 it('Should succeed without body', async function () {
133 await makePostBodyRequest({
134 url: server.url,
135 token: server.accessToken,
136 path: myHistoryRemove,
137 expectedStatus: HttpStatusCode.NO_CONTENT_204
138 })
139 })
140 })
141
142 after(async function () {
143 await cleanupTests([ server ])
144 })
145})
diff --git a/packages/tests/src/api/check-params/videos-overviews.ts b/packages/tests/src/api/check-params/videos-overviews.ts
new file mode 100644
index 000000000..ba6f6ac69
--- /dev/null
+++ b/packages/tests/src/api/check-params/videos-overviews.ts
@@ -0,0 +1,31 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { cleanupTests, createSingleServer, PeerTubeServer } from '@peertube/peertube-server-commands'
4
5describe('Test videos overview API validator', function () {
6 let server: PeerTubeServer
7
8 // ---------------------------------------------------------------
9
10 before(async function () {
11 this.timeout(30000)
12
13 server = await createSingleServer(1)
14 })
15
16 describe('When getting videos overview', function () {
17
18 it('Should fail with a bad pagination', async function () {
19 await server.overviews.getVideos({ page: 0, expectedStatus: 400 })
20 await server.overviews.getVideos({ page: 100, expectedStatus: 400 })
21 })
22
23 it('Should succeed with a good pagination', async function () {
24 await server.overviews.getVideos({ page: 1 })
25 })
26 })
27
28 after(async function () {
29 await cleanupTests([ server ])
30 })
31})
diff --git a/packages/tests/src/api/check-params/videos.ts b/packages/tests/src/api/check-params/videos.ts
new file mode 100644
index 000000000..c349ed9fe
--- /dev/null
+++ b/packages/tests/src/api/check-params/videos.ts
@@ -0,0 +1,883 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { join } from 'path'
5import { omit, randomInt } from '@peertube/peertube-core-utils'
6import { HttpStatusCode, PeerTubeProblemDocument, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models'
7import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
8import {
9 cleanupTests,
10 createSingleServer,
11 makeDeleteRequest,
12 makeGetRequest,
13 makePutBodyRequest,
14 makeUploadRequest,
15 PeerTubeServer,
16 setAccessTokensToServers
17} from '@peertube/peertube-server-commands'
18import { checkBadStartPagination, checkBadCountPagination, checkBadSortPagination } from '@tests/shared/checks.js'
19import { checkUploadVideoParam } from '@tests/shared/videos.js'
20
21describe('Test videos API validator', function () {
22 const path = '/api/v1/videos/'
23 let server: PeerTubeServer
24 let userAccessToken = ''
25 let accountName: string
26 let channelId: number
27 let channelName: string
28 let video: VideoCreateResult
29 let privateVideo: VideoCreateResult
30
31 // ---------------------------------------------------------------
32
33 before(async function () {
34 this.timeout(30000)
35
36 server = await createSingleServer(1)
37
38 await setAccessTokensToServers([ server ])
39
40 userAccessToken = await server.users.generateUserAndToken('user1')
41
42 {
43 const body = await server.users.getMyInfo()
44 channelId = body.videoChannels[0].id
45 channelName = body.videoChannels[0].name
46 accountName = body.account.name + '@' + body.account.host
47 }
48
49 {
50 privateVideo = await server.videos.quickUpload({ name: 'private video', privacy: VideoPrivacy.PRIVATE })
51 }
52 })
53
54 describe('When listing videos', function () {
55 it('Should fail with a bad start pagination', async function () {
56 await checkBadStartPagination(server.url, path)
57 })
58
59 it('Should fail with a bad count pagination', async function () {
60 await checkBadCountPagination(server.url, path)
61 })
62
63 it('Should fail with an incorrect sort', async function () {
64 await checkBadSortPagination(server.url, path)
65 })
66
67 it('Should fail with a bad skipVideos query', async function () {
68 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200, query: { skipCount: 'toto' } })
69 })
70
71 it('Should success with the correct parameters', async function () {
72 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200, query: { skipCount: false } })
73 })
74 })
75
76 describe('When searching a video', function () {
77
78 it('Should fail with nothing', async function () {
79 await makeGetRequest({
80 url: server.url,
81 path: join(path, 'search'),
82 expectedStatus: HttpStatusCode.BAD_REQUEST_400
83 })
84 })
85
86 it('Should fail with a bad start pagination', async function () {
87 await checkBadStartPagination(server.url, join(path, 'search', 'test'))
88 })
89
90 it('Should fail with a bad count pagination', async function () {
91 await checkBadCountPagination(server.url, join(path, 'search', 'test'))
92 })
93
94 it('Should fail with an incorrect sort', async function () {
95 await checkBadSortPagination(server.url, join(path, 'search', 'test'))
96 })
97
98 it('Should success with the correct parameters', async function () {
99 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200 })
100 })
101 })
102
103 describe('When listing my videos', function () {
104 const path = '/api/v1/users/me/videos'
105
106 it('Should fail with a bad start pagination', async function () {
107 await checkBadStartPagination(server.url, path, server.accessToken)
108 })
109
110 it('Should fail with a bad count pagination', async function () {
111 await checkBadCountPagination(server.url, path, server.accessToken)
112 })
113
114 it('Should fail with an incorrect sort', async function () {
115 await checkBadSortPagination(server.url, path, server.accessToken)
116 })
117
118 it('Should fail with an invalid channel', async function () {
119 await makeGetRequest({ url: server.url, token: server.accessToken, path, query: { channelId: 'toto' } })
120 })
121
122 it('Should fail with an unknown channel', async function () {
123 await makeGetRequest({
124 url: server.url,
125 token: server.accessToken,
126 path,
127 query: { channelId: 89898 },
128 expectedStatus: HttpStatusCode.NOT_FOUND_404
129 })
130 })
131
132 it('Should success with the correct parameters', async function () {
133 await makeGetRequest({ url: server.url, token: server.accessToken, path, expectedStatus: HttpStatusCode.OK_200 })
134 })
135 })
136
137 describe('When listing account videos', function () {
138 let path: string
139
140 before(async function () {
141 path = '/api/v1/accounts/' + accountName + '/videos'
142 })
143
144 it('Should fail with a bad start pagination', async function () {
145 await checkBadStartPagination(server.url, path, server.accessToken)
146 })
147
148 it('Should fail with a bad count pagination', async function () {
149 await checkBadCountPagination(server.url, path, server.accessToken)
150 })
151
152 it('Should fail with an incorrect sort', async function () {
153 await checkBadSortPagination(server.url, path, server.accessToken)
154 })
155
156 it('Should success with the correct parameters', async function () {
157 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200 })
158 })
159 })
160
161 describe('When listing video channel videos', function () {
162 let path: string
163
164 before(async function () {
165 path = '/api/v1/video-channels/' + channelName + '/videos'
166 })
167
168 it('Should fail with a bad start pagination', async function () {
169 await checkBadStartPagination(server.url, path, server.accessToken)
170 })
171
172 it('Should fail with a bad count pagination', async function () {
173 await checkBadCountPagination(server.url, path, server.accessToken)
174 })
175
176 it('Should fail with an incorrect sort', async function () {
177 await checkBadSortPagination(server.url, path, server.accessToken)
178 })
179
180 it('Should success with the correct parameters', async function () {
181 await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.OK_200 })
182 })
183 })
184
185 describe('When adding a video', function () {
186 let baseCorrectParams
187 const baseCorrectAttaches = {
188 fixture: buildAbsoluteFixturePath('video_short.webm')
189 }
190
191 before(function () {
192 // Put in before to have channelId
193 baseCorrectParams = {
194 name: 'my super name',
195 category: 5,
196 licence: 1,
197 language: 'pt',
198 nsfw: false,
199 commentsEnabled: true,
200 downloadEnabled: true,
201 waitTranscoding: true,
202 description: 'my super description',
203 support: 'my super support text',
204 tags: [ 'tag1', 'tag2' ],
205 privacy: VideoPrivacy.PUBLIC,
206 channelId,
207 originallyPublishedAt: new Date().toISOString()
208 }
209 })
210
211 function runSuite (mode: 'legacy' | 'resumable') {
212
213 const baseOptions = () => {
214 return {
215 server,
216 token: server.accessToken,
217 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
218 mode
219 }
220 }
221
222 it('Should fail with nothing', async function () {
223 const fields = {}
224 const attaches = {}
225 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
226 })
227
228 it('Should fail without name', async function () {
229 const fields = omit(baseCorrectParams, [ 'name' ])
230 const attaches = baseCorrectAttaches
231
232 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
233 })
234
235 it('Should fail with a long name', async function () {
236 const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
237 const attaches = baseCorrectAttaches
238
239 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
240 })
241
242 it('Should fail with a bad category', async function () {
243 const fields = { ...baseCorrectParams, category: 125 }
244 const attaches = baseCorrectAttaches
245
246 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
247 })
248
249 it('Should fail with a bad licence', async function () {
250 const fields = { ...baseCorrectParams, licence: 125 }
251 const attaches = baseCorrectAttaches
252
253 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
254 })
255
256 it('Should fail with a bad language', async function () {
257 const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
258 const attaches = baseCorrectAttaches
259
260 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
261 })
262
263 it('Should fail with a long description', async function () {
264 const fields = { ...baseCorrectParams, description: 'super'.repeat(2500) }
265 const attaches = baseCorrectAttaches
266
267 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
268 })
269
270 it('Should fail with a long support text', async function () {
271 const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
272 const attaches = baseCorrectAttaches
273
274 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
275 })
276
277 it('Should fail without a channel', async function () {
278 const fields = omit(baseCorrectParams, [ 'channelId' ])
279 const attaches = baseCorrectAttaches
280
281 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
282 })
283
284 it('Should fail with a bad channel', async function () {
285 const fields = { ...baseCorrectParams, channelId: 545454 }
286 const attaches = baseCorrectAttaches
287
288 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
289 })
290
291 it('Should fail with another user channel', async function () {
292 const user = {
293 username: 'fake' + randomInt(0, 1500),
294 password: 'fake_password'
295 }
296 await server.users.create({ username: user.username, password: user.password })
297
298 const accessTokenUser = await server.login.getAccessToken(user)
299 const { videoChannels } = await server.users.getMyInfo({ token: accessTokenUser })
300 const customChannelId = videoChannels[0].id
301
302 const fields = { ...baseCorrectParams, channelId: customChannelId }
303 const attaches = baseCorrectAttaches
304
305 await checkUploadVideoParam({
306 ...baseOptions(),
307 token: userAccessToken,
308 attributes: { ...fields, ...attaches }
309 })
310 })
311
312 it('Should fail with too many tags', async function () {
313 const fields = { ...baseCorrectParams, tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }
314 const attaches = baseCorrectAttaches
315
316 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
317 })
318
319 it('Should fail with a tag length too low', async function () {
320 const fields = { ...baseCorrectParams, tags: [ 'tag1', 't' ] }
321 const attaches = baseCorrectAttaches
322
323 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
324 })
325
326 it('Should fail with a tag length too big', async function () {
327 const fields = { ...baseCorrectParams, tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }
328 const attaches = baseCorrectAttaches
329
330 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
331 })
332
333 it('Should fail with a bad schedule update (miss updateAt)', async function () {
334 const fields = { ...baseCorrectParams, scheduleUpdate: { privacy: VideoPrivacy.PUBLIC } }
335 const attaches = baseCorrectAttaches
336
337 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
338 })
339
340 it('Should fail with a bad schedule update (wrong updateAt)', async function () {
341 const fields = {
342 ...baseCorrectParams,
343
344 scheduleUpdate: {
345 privacy: VideoPrivacy.PUBLIC,
346 updateAt: 'toto'
347 }
348 }
349 const attaches = baseCorrectAttaches
350
351 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
352 })
353
354 it('Should fail with a bad originally published at attribute', async function () {
355 const fields = { ...baseCorrectParams, originallyPublishedAt: 'toto' }
356 const attaches = baseCorrectAttaches
357
358 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
359 })
360
361 it('Should fail without an input file', async function () {
362 const fields = baseCorrectParams
363 const attaches = {}
364 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
365 })
366
367 it('Should fail with an incorrect input file', async function () {
368 const fields = baseCorrectParams
369 let attaches = { fixture: buildAbsoluteFixturePath('video_short_fake.webm') }
370
371 await checkUploadVideoParam({
372 ...baseOptions(),
373 attributes: { ...fields, ...attaches },
374 // 200 for the init request, 422 when the file has finished being uploaded
375 expectedStatus: undefined,
376 completedExpectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422
377 })
378
379 attaches = { fixture: buildAbsoluteFixturePath('video_short.mkv') }
380 await checkUploadVideoParam({
381 ...baseOptions(),
382 attributes: { ...fields, ...attaches },
383 expectedStatus: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415
384 })
385 })
386
387 it('Should fail with an incorrect thumbnail file', async function () {
388 const fields = baseCorrectParams
389 const attaches = {
390 thumbnailfile: buildAbsoluteFixturePath('video_short.mp4'),
391 fixture: buildAbsoluteFixturePath('video_short.mp4')
392 }
393
394 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
395 })
396
397 it('Should fail with a big thumbnail file', async function () {
398 const fields = baseCorrectParams
399 const attaches = {
400 thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png'),
401 fixture: buildAbsoluteFixturePath('video_short.mp4')
402 }
403
404 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
405 })
406
407 it('Should fail with an incorrect preview file', async function () {
408 const fields = baseCorrectParams
409 const attaches = {
410 previewfile: buildAbsoluteFixturePath('video_short.mp4'),
411 fixture: buildAbsoluteFixturePath('video_short.mp4')
412 }
413
414 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
415 })
416
417 it('Should fail with a big preview file', async function () {
418 const fields = baseCorrectParams
419 const attaches = {
420 previewfile: buildAbsoluteFixturePath('custom-preview-big.png'),
421 fixture: buildAbsoluteFixturePath('video_short.mp4')
422 }
423
424 await checkUploadVideoParam({ ...baseOptions(), attributes: { ...fields, ...attaches } })
425 })
426
427 it('Should report the appropriate error', async function () {
428 const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
429 const attaches = baseCorrectAttaches
430
431 const attributes = { ...fields, ...attaches }
432 const body = await checkUploadVideoParam({ ...baseOptions(), attributes })
433
434 const error = body as unknown as PeerTubeProblemDocument
435
436 if (mode === 'legacy') {
437 expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadLegacy')
438 } else {
439 expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/uploadResumableInit')
440 }
441
442 expect(error.type).to.equal('about:blank')
443 expect(error.title).to.equal('Bad Request')
444
445 expect(error.detail).to.equal('Incorrect request parameters: language')
446 expect(error.error).to.equal('Incorrect request parameters: language')
447
448 expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
449 expect(error['invalid-params'].language).to.exist
450 })
451
452 it('Should succeed with the correct parameters', async function () {
453 this.timeout(30000)
454
455 const fields = baseCorrectParams
456
457 {
458 const attaches = baseCorrectAttaches
459 await checkUploadVideoParam({
460 ...baseOptions(),
461 attributes: { ...fields, ...attaches },
462 expectedStatus: HttpStatusCode.OK_200
463 })
464 }
465
466 {
467 const attaches = {
468 ...baseCorrectAttaches,
469
470 videofile: buildAbsoluteFixturePath('video_short.mp4')
471 }
472
473 await checkUploadVideoParam({
474 ...baseOptions(),
475 attributes: { ...fields, ...attaches },
476 expectedStatus: HttpStatusCode.OK_200
477 })
478 }
479
480 {
481 const attaches = {
482 ...baseCorrectAttaches,
483
484 videofile: buildAbsoluteFixturePath('video_short.ogv')
485 }
486
487 await checkUploadVideoParam({
488 ...baseOptions(),
489 attributes: { ...fields, ...attaches },
490 expectedStatus: HttpStatusCode.OK_200
491 })
492 }
493 })
494 }
495
496 describe('Resumable upload', function () {
497 runSuite('resumable')
498 })
499
500 describe('Legacy upload', function () {
501 runSuite('legacy')
502 })
503 })
504
505 describe('When updating a video', function () {
506 const baseCorrectParams = {
507 name: 'my super name',
508 category: 5,
509 licence: 2,
510 language: 'pt',
511 nsfw: false,
512 commentsEnabled: false,
513 downloadEnabled: false,
514 description: 'my super description',
515 privacy: VideoPrivacy.PUBLIC,
516 tags: [ 'tag1', 'tag2' ]
517 }
518
519 before(async function () {
520 const { data } = await server.videos.list()
521 video = data[0]
522 })
523
524 it('Should fail with nothing', async function () {
525 const fields = {}
526 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
527 })
528
529 it('Should fail without a valid uuid', async function () {
530 const fields = baseCorrectParams
531 await makePutBodyRequest({ url: server.url, path: path + 'blabla', token: server.accessToken, fields })
532 })
533
534 it('Should fail with an unknown id', async function () {
535 const fields = baseCorrectParams
536
537 await makePutBodyRequest({
538 url: server.url,
539 path: path + '4da6fde3-88f7-4d16-b119-108df5630b06',
540 token: server.accessToken,
541 fields,
542 expectedStatus: HttpStatusCode.NOT_FOUND_404
543 })
544 })
545
546 it('Should fail with a long name', async function () {
547 const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
548
549 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
550 })
551
552 it('Should fail with a bad category', async function () {
553 const fields = { ...baseCorrectParams, category: 125 }
554
555 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
556 })
557
558 it('Should fail with a bad licence', async function () {
559 const fields = { ...baseCorrectParams, licence: 125 }
560
561 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
562 })
563
564 it('Should fail with a bad language', async function () {
565 const fields = { ...baseCorrectParams, language: 'a'.repeat(15) }
566
567 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
568 })
569
570 it('Should fail with a long description', async function () {
571 const fields = { ...baseCorrectParams, description: 'super'.repeat(2500) }
572
573 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
574 })
575
576 it('Should fail with a long support text', async function () {
577 const fields = { ...baseCorrectParams, support: 'super'.repeat(201) }
578
579 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
580 })
581
582 it('Should fail with a bad channel', async function () {
583 const fields = { ...baseCorrectParams, channelId: 545454 }
584
585 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
586 })
587
588 it('Should fail with too many tags', async function () {
589 const fields = { ...baseCorrectParams, tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }
590
591 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
592 })
593
594 it('Should fail with a tag length too low', async function () {
595 const fields = { ...baseCorrectParams, tags: [ 'tag1', 't' ] }
596
597 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
598 })
599
600 it('Should fail with a tag length too big', async function () {
601 const fields = { ...baseCorrectParams, tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }
602
603 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
604 })
605
606 it('Should fail with a bad schedule update (miss updateAt)', async function () {
607 const fields = { ...baseCorrectParams, scheduleUpdate: { privacy: VideoPrivacy.PUBLIC } }
608
609 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
610 })
611
612 it('Should fail with a bad schedule update (wrong updateAt)', async function () {
613 const fields = { ...baseCorrectParams, scheduleUpdate: { updateAt: 'toto', privacy: VideoPrivacy.PUBLIC } }
614
615 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
616 })
617
618 it('Should fail with a bad originally published at param', async function () {
619 const fields = { ...baseCorrectParams, originallyPublishedAt: 'toto' }
620
621 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
622 })
623
624 it('Should fail with an incorrect thumbnail file', async function () {
625 const fields = baseCorrectParams
626 const attaches = {
627 thumbnailfile: buildAbsoluteFixturePath('video_short.mp4')
628 }
629
630 await makeUploadRequest({
631 url: server.url,
632 method: 'PUT',
633 path: path + video.shortUUID,
634 token: server.accessToken,
635 fields,
636 attaches
637 })
638 })
639
640 it('Should fail with a big thumbnail file', async function () {
641 const fields = baseCorrectParams
642 const attaches = {
643 thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png')
644 }
645
646 await makeUploadRequest({
647 url: server.url,
648 method: 'PUT',
649 path: path + video.shortUUID,
650 token: server.accessToken,
651 fields,
652 attaches
653 })
654 })
655
656 it('Should fail with an incorrect preview file', async function () {
657 const fields = baseCorrectParams
658 const attaches = {
659 previewfile: buildAbsoluteFixturePath('video_short.mp4')
660 }
661
662 await makeUploadRequest({
663 url: server.url,
664 method: 'PUT',
665 path: path + video.shortUUID,
666 token: server.accessToken,
667 fields,
668 attaches
669 })
670 })
671
672 it('Should fail with a big preview file', async function () {
673 const fields = baseCorrectParams
674 const attaches = {
675 previewfile: buildAbsoluteFixturePath('custom-preview-big.png')
676 }
677
678 await makeUploadRequest({
679 url: server.url,
680 method: 'PUT',
681 path: path + video.shortUUID,
682 token: server.accessToken,
683 fields,
684 attaches
685 })
686 })
687
688 it('Should fail with a video of another user without the appropriate right', async function () {
689 const fields = baseCorrectParams
690
691 await makePutBodyRequest({
692 url: server.url,
693 path: path + video.shortUUID,
694 token: userAccessToken,
695 fields,
696 expectedStatus: HttpStatusCode.FORBIDDEN_403
697 })
698 })
699
700 it('Should fail with a video of another server')
701
702 it('Shoud report the appropriate error', async function () {
703 const fields = { ...baseCorrectParams, licence: 125 }
704
705 const res = await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
706 const error = res.body as PeerTubeProblemDocument
707
708 expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/putVideo')
709
710 expect(error.type).to.equal('about:blank')
711 expect(error.title).to.equal('Bad Request')
712
713 expect(error.detail).to.equal('Incorrect request parameters: licence')
714 expect(error.error).to.equal('Incorrect request parameters: licence')
715
716 expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
717 expect(error['invalid-params'].licence).to.exist
718 })
719
720 it('Should succeed with the correct parameters', async function () {
721 const fields = baseCorrectParams
722
723 await makePutBodyRequest({
724 url: server.url,
725 path: path + video.shortUUID,
726 token: server.accessToken,
727 fields,
728 expectedStatus: HttpStatusCode.NO_CONTENT_204
729 })
730 })
731 })
732
733 describe('When getting a video', function () {
734 it('Should return the list of the videos with nothing', async function () {
735 const res = await makeGetRequest({
736 url: server.url,
737 path,
738 expectedStatus: HttpStatusCode.OK_200
739 })
740
741 expect(res.body.data).to.be.an('array')
742 expect(res.body.data.length).to.equal(6)
743 })
744
745 it('Should fail without a correct uuid', async function () {
746 await server.videos.get({ id: 'coucou', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
747 })
748
749 it('Should return 404 with an incorrect video', async function () {
750 await server.videos.get({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
751 })
752
753 it('Shoud report the appropriate error', async function () {
754 const body = await server.videos.get({ id: 'hi', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
755 const error = body as unknown as PeerTubeProblemDocument
756
757 expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo')
758
759 expect(error.type).to.equal('about:blank')
760 expect(error.title).to.equal('Bad Request')
761
762 expect(error.detail).to.equal('Incorrect request parameters: id')
763 expect(error.error).to.equal('Incorrect request parameters: id')
764
765 expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
766 expect(error['invalid-params'].id).to.exist
767 })
768
769 it('Should succeed with the correct parameters', async function () {
770 await server.videos.get({ id: video.shortUUID })
771 })
772 })
773
774 describe('When rating a video', function () {
775 let videoId: number
776
777 before(async function () {
778 const { data } = await server.videos.list()
779 videoId = data[0].id
780 })
781
782 it('Should fail without a valid uuid', async function () {
783 const fields = {
784 rating: 'like'
785 }
786 await makePutBodyRequest({ url: server.url, path: path + 'blabla/rate', token: server.accessToken, fields })
787 })
788
789 it('Should fail with an unknown id', async function () {
790 const fields = {
791 rating: 'like'
792 }
793 await makePutBodyRequest({
794 url: server.url,
795 path: path + '4da6fde3-88f7-4d16-b119-108df5630b06/rate',
796 token: server.accessToken,
797 fields,
798 expectedStatus: HttpStatusCode.NOT_FOUND_404
799 })
800 })
801
802 it('Should fail with a wrong rating', async function () {
803 const fields = {
804 rating: 'likes'
805 }
806 await makePutBodyRequest({ url: server.url, path: path + videoId + '/rate', token: server.accessToken, fields })
807 })
808
809 it('Should fail with a private video of another user', async function () {
810 const fields = {
811 rating: 'like'
812 }
813 await makePutBodyRequest({
814 url: server.url,
815 path: path + privateVideo.uuid + '/rate',
816 token: userAccessToken,
817 fields,
818 expectedStatus: HttpStatusCode.FORBIDDEN_403
819 })
820 })
821
822 it('Should succeed with the correct parameters', async function () {
823 const fields = {
824 rating: 'like'
825 }
826 await makePutBodyRequest({
827 url: server.url,
828 path: path + videoId + '/rate',
829 token: server.accessToken,
830 fields,
831 expectedStatus: HttpStatusCode.NO_CONTENT_204
832 })
833 })
834 })
835
836 describe('When removing a video', function () {
837 it('Should have 404 with nothing', async function () {
838 await makeDeleteRequest({
839 url: server.url,
840 path,
841 expectedStatus: HttpStatusCode.BAD_REQUEST_400
842 })
843 })
844
845 it('Should fail without a correct uuid', async function () {
846 await server.videos.remove({ id: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
847 })
848
849 it('Should fail with a video which does not exist', async function () {
850 await server.videos.remove({ id: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 })
851 })
852
853 it('Should fail with a video of another user without the appropriate right', async function () {
854 await server.videos.remove({ token: userAccessToken, id: video.uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
855 })
856
857 it('Should fail with a video of another server')
858
859 it('Shoud report the appropriate error', async function () {
860 const body = await server.videos.remove({ id: 'hello', expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
861 const error = body as PeerTubeProblemDocument
862
863 expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/delVideo')
864
865 expect(error.type).to.equal('about:blank')
866 expect(error.title).to.equal('Bad Request')
867
868 expect(error.detail).to.equal('Incorrect request parameters: id')
869 expect(error.error).to.equal('Incorrect request parameters: id')
870
871 expect(error.status).to.equal(HttpStatusCode.BAD_REQUEST_400)
872 expect(error['invalid-params'].id).to.exist
873 })
874
875 it('Should succeed with the correct parameters', async function () {
876 await server.videos.remove({ id: video.uuid })
877 })
878 })
879
880 after(async function () {
881 await cleanupTests([ server ])
882 })
883})
diff --git a/packages/tests/src/api/check-params/views.ts b/packages/tests/src/api/check-params/views.ts
new file mode 100644
index 000000000..c454d4b80
--- /dev/null
+++ b/packages/tests/src/api/check-params/views.ts
@@ -0,0 +1,227 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models'
4import {
5 cleanupTests,
6 createMultipleServers,
7 doubleFollow,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 setDefaultVideoChannel
11} from '@peertube/peertube-server-commands'
12
13describe('Test videos views', function () {
14 let servers: PeerTubeServer[]
15 let liveVideoId: string
16 let videoId: string
17 let remoteVideoId: string
18 let userAccessToken: string
19
20 before(async function () {
21 this.timeout(120000)
22
23 servers = await createMultipleServers(2)
24 await setAccessTokensToServers(servers)
25 await setDefaultVideoChannel(servers)
26
27 await servers[0].config.enableLive({ allowReplay: false, transcoding: false });
28
29 ({ uuid: videoId } = await servers[0].videos.quickUpload({ name: 'video' }));
30 ({ uuid: remoteVideoId } = await servers[1].videos.quickUpload({ name: 'video' }));
31 ({ uuid: liveVideoId } = await servers[0].live.create({
32 fields: {
33 name: 'live',
34 privacy: VideoPrivacy.PUBLIC,
35 channelId: servers[0].store.channel.id
36 }
37 }))
38
39 userAccessToken = await servers[0].users.generateUserAndToken('user')
40
41 await doubleFollow(servers[0], servers[1])
42 })
43
44 describe('When viewing a video', async function () {
45
46 it('Should fail without current time', async function () {
47 await servers[0].views.view({ id: videoId, currentTime: undefined, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
48 })
49
50 it('Should fail with an invalid current time', async function () {
51 await servers[0].views.view({ id: videoId, currentTime: -1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
52 await servers[0].views.view({ id: videoId, currentTime: 10, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
53 })
54
55 it('Should succeed with correct parameters', async function () {
56 await servers[0].views.view({ id: videoId, currentTime: 1 })
57 })
58 })
59
60 describe('When getting overall stats', function () {
61
62 it('Should fail with a remote video', async function () {
63 await servers[0].videoStats.getOverallStats({ videoId: remoteVideoId, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
64 })
65
66 it('Should fail without token', async function () {
67 await servers[0].videoStats.getOverallStats({ videoId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
68 })
69
70 it('Should fail with another token', async function () {
71 await servers[0].videoStats.getOverallStats({
72 videoId,
73 token: userAccessToken,
74 expectedStatus: HttpStatusCode.FORBIDDEN_403
75 })
76 })
77
78 it('Should fail with an invalid start date', async function () {
79 await servers[0].videoStats.getOverallStats({
80 videoId,
81 startDate: 'fake' as any,
82 endDate: new Date().toISOString(),
83 expectedStatus: HttpStatusCode.BAD_REQUEST_400
84 })
85 })
86
87 it('Should fail with an invalid end date', async function () {
88 await servers[0].videoStats.getOverallStats({
89 videoId,
90 startDate: new Date().toISOString(),
91 endDate: 'fake' as any,
92 expectedStatus: HttpStatusCode.BAD_REQUEST_400
93 })
94 })
95
96 it('Should succeed with the correct parameters', async function () {
97 await servers[0].videoStats.getOverallStats({
98 videoId,
99 startDate: new Date().toISOString(),
100 endDate: new Date().toISOString()
101 })
102 })
103 })
104
105 describe('When getting timeserie stats', function () {
106
107 it('Should fail with a remote video', async function () {
108 await servers[0].videoStats.getTimeserieStats({
109 videoId: remoteVideoId,
110 metric: 'viewers',
111 expectedStatus: HttpStatusCode.FORBIDDEN_403
112 })
113 })
114
115 it('Should fail without token', async function () {
116 await servers[0].videoStats.getTimeserieStats({
117 videoId,
118 token: null,
119 metric: 'viewers',
120 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
121 })
122 })
123
124 it('Should fail with another token', async function () {
125 await servers[0].videoStats.getTimeserieStats({
126 videoId,
127 token: userAccessToken,
128 metric: 'viewers',
129 expectedStatus: HttpStatusCode.FORBIDDEN_403
130 })
131 })
132
133 it('Should fail with an invalid metric', async function () {
134 await servers[0].videoStats.getTimeserieStats({ videoId, metric: 'hello' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
135 })
136
137 it('Should fail with an invalid start date', async function () {
138 await servers[0].videoStats.getTimeserieStats({
139 videoId,
140 metric: 'viewers',
141 startDate: 'fake' as any,
142 endDate: new Date(),
143 expectedStatus: HttpStatusCode.BAD_REQUEST_400
144 })
145 })
146
147 it('Should fail with an invalid end date', async function () {
148 await servers[0].videoStats.getTimeserieStats({
149 videoId,
150 metric: 'viewers',
151 startDate: new Date(),
152 endDate: 'fake' as any,
153 expectedStatus: HttpStatusCode.BAD_REQUEST_400
154 })
155 })
156
157 it('Should fail if start date is specified but not end date', async function () {
158 await servers[0].videoStats.getTimeserieStats({
159 videoId,
160 metric: 'viewers',
161 startDate: new Date(),
162 expectedStatus: HttpStatusCode.BAD_REQUEST_400
163 })
164 })
165
166 it('Should fail if end date is specified but not start date', async function () {
167 await servers[0].videoStats.getTimeserieStats({
168 videoId,
169 metric: 'viewers',
170 endDate: new Date(),
171 expectedStatus: HttpStatusCode.BAD_REQUEST_400
172 })
173 })
174
175 it('Should fail with a too big interval', async function () {
176 await servers[0].videoStats.getTimeserieStats({
177 videoId,
178 metric: 'viewers',
179 startDate: new Date('2000-04-07T08:31:57.126Z'),
180 endDate: new Date(),
181 expectedStatus: HttpStatusCode.BAD_REQUEST_400
182 })
183 })
184
185 it('Should succeed with the correct parameters', async function () {
186 await servers[0].videoStats.getTimeserieStats({ videoId, metric: 'viewers' })
187 })
188 })
189
190 describe('When getting retention stats', function () {
191
192 it('Should fail with a remote video', async function () {
193 await servers[0].videoStats.getRetentionStats({
194 videoId: remoteVideoId,
195 expectedStatus: HttpStatusCode.FORBIDDEN_403
196 })
197 })
198
199 it('Should fail without token', async function () {
200 await servers[0].videoStats.getRetentionStats({
201 videoId,
202 token: null,
203 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
204 })
205 })
206
207 it('Should fail with another token', async function () {
208 await servers[0].videoStats.getRetentionStats({
209 videoId,
210 token: userAccessToken,
211 expectedStatus: HttpStatusCode.FORBIDDEN_403
212 })
213 })
214
215 it('Should fail on live video', async function () {
216 await servers[0].videoStats.getRetentionStats({ videoId: liveVideoId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
217 })
218
219 it('Should succeed with the correct parameters', async function () {
220 await servers[0].videoStats.getRetentionStats({ videoId })
221 })
222 })
223
224 after(async function () {
225 await cleanupTests(servers)
226 })
227})