diff options
author | Chocobozzz <me@florianbigard.com> | 2023-07-31 14:34:36 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2023-08-11 15:02:33 +0200 |
commit | 3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch) | |
tree | e4510b39bdac9c318fdb4b47018d08f15368b8f0 /packages/tests/src/api/check-params | |
parent | 04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff) | |
download | PeerTube-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')
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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { AbuseCreate, AbuseState, HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { | ||
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 | |||
17 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { cleanupTests, createSingleServer, PeerTubeServer } from '@peertube/peertube-server-commands' | ||
6 | |||
7 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createMultipleServers, | ||
8 | doubleFollow, | ||
9 | makeDeleteRequest, | ||
10 | makeGetRequest, | ||
11 | makePostBodyRequest, | ||
12 | PeerTubeServer, | ||
13 | setAccessTokensToServers | ||
14 | } from '@peertube/peertube-server-commands' | ||
15 | |||
16 | describe('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 | |||
3 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | makePostBodyRequest, | ||
8 | PeerTubeServer, | ||
9 | setAccessTokensToServers | ||
10 | } from '@peertube/peertube-server-commands' | ||
11 | |||
12 | describe('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 | |||
3 | import { FIXTURE_URLS } from '@tests/shared/tests.js' | ||
4 | import { areHttpImportTestsDisabled } from '@peertube/peertube-node-utils' | ||
5 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
6 | import { | ||
7 | ChannelsCommand, | ||
8 | cleanupTests, | ||
9 | createSingleServer, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultVideoChannel | ||
13 | } from '@peertube/peertube-server-commands' | ||
14 | |||
15 | describe('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 */ | ||
2 | import merge from 'lodash-es/merge.js' | ||
3 | import { omit } from '@peertube/peertube-core-utils' | ||
4 | import { CustomConfig, HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makeDeleteRequest, | ||
9 | makeGetRequest, | ||
10 | makePutBodyRequest, | ||
11 | PeerTubeServer, | ||
12 | setAccessTokensToServers | ||
13 | } from '@peertube/peertube-server-commands' | ||
14 | |||
15 | describe('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 | |||
3 | import { MockSmtpServer } from '@tests/shared/mock-servers/index.js' | ||
4 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | ConfigCommand, | ||
8 | ContactFormCommand, | ||
9 | createSingleServer, | ||
10 | killallServers, | ||
11 | PeerTubeServer | ||
12 | } from '@peertube/peertube-server-commands' | ||
13 | |||
14 | describe('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 | |||
3 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | makeGetRequest, | ||
8 | makePutBodyRequest, | ||
9 | PeerTubeServer, | ||
10 | setAccessTokensToServers | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | describe('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 | |||
3 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | makeGetRequest, | ||
8 | PeerTubeServer, | ||
9 | setAccessTokensToServers | ||
10 | } from '@peertube/peertube-server-commands' | ||
11 | |||
12 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makeDeleteRequest, | ||
9 | makeGetRequest, | ||
10 | makePostBodyRequest, | ||
11 | PeerTubeServer, | ||
12 | setAccessTokensToServers | ||
13 | } from '@peertube/peertube-server-commands' | ||
14 | |||
15 | describe('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 @@ | |||
1 | import './abuses.js' | ||
2 | import './accounts.js' | ||
3 | import './blocklist.js' | ||
4 | import './bulk.js' | ||
5 | import './channel-import-videos.js' | ||
6 | import './config.js' | ||
7 | import './contact-form.js' | ||
8 | import './custom-pages.js' | ||
9 | import './debug.js' | ||
10 | import './follows.js' | ||
11 | import './jobs.js' | ||
12 | import './live.js' | ||
13 | import './logs.js' | ||
14 | import './metrics.js' | ||
15 | import './my-user.js' | ||
16 | import './plugins.js' | ||
17 | import './redundancy.js' | ||
18 | import './registrations.js' | ||
19 | import './runners.js' | ||
20 | import './search.js' | ||
21 | import './services.js' | ||
22 | import './transcoding.js' | ||
23 | import './two-factor.js' | ||
24 | import './upload-quota.js' | ||
25 | import './user-notifications.js' | ||
26 | import './user-subscriptions.js' | ||
27 | import './users-admin.js' | ||
28 | import './users-emails.js' | ||
29 | import './video-blacklist.js' | ||
30 | import './video-captions.js' | ||
31 | import './video-channel-syncs.js' | ||
32 | import './video-channels.js' | ||
33 | import './video-comments.js' | ||
34 | import './video-files.js' | ||
35 | import './video-imports.js' | ||
36 | import './video-playlists.js' | ||
37 | import './video-storyboards.js' | ||
38 | import './video-source.js' | ||
39 | import './video-studio.js' | ||
40 | import './video-token.js' | ||
41 | import './videos-common-filters.js' | ||
42 | import './videos-history.js' | ||
43 | import './videos-overviews.js' | ||
44 | import './videos.js' | ||
45 | import './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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makeGetRequest, | ||
9 | makePostBodyRequest, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers | ||
12 | } from '@peertube/peertube-server-commands' | ||
13 | |||
14 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { omit } from '@peertube/peertube-core-utils' | ||
5 | import { HttpStatusCode, LiveVideoLatencyMode, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models' | ||
6 | import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils' | ||
7 | import { | ||
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 | |||
19 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makeGetRequest, | ||
9 | PeerTubeServer, | ||
10 | setAccessTokensToServers | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | describe('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 | |||
3 | import { omit } from '@peertube/peertube-core-utils' | ||
4 | import { HttpStatusCode, PlaybackMetricCreate, VideoResolution } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makePostBodyRequest, | ||
9 | PeerTubeServer, | ||
10 | setAccessTokensToServers | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { MockSmtpServer } from '@tests/shared/mock-servers/index.js' | ||
5 | import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils' | ||
6 | import { HttpStatusCode, UserRole, VideoCreateResult } from '@peertube/peertube-models' | ||
7 | import { | ||
8 | cleanupTests, | ||
9 | createSingleServer, | ||
10 | makeGetRequest, | ||
11 | makePutBodyRequest, | ||
12 | makeUploadRequest, | ||
13 | PeerTubeServer, | ||
14 | setAccessTokensToServers, | ||
15 | UsersCommand | ||
16 | } from '@peertube/peertube-server-commands' | ||
17 | |||
18 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { HttpStatusCode, PeerTubePlugin, PluginType } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makeGetRequest, | ||
9 | makePostBodyRequest, | ||
10 | makePutBodyRequest, | ||
11 | PeerTubeServer, | ||
12 | setAccessTokensToServers | ||
13 | } from '@peertube/peertube-server-commands' | ||
14 | |||
15 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { HttpStatusCode, VideoCreateResult } from '@peertube/peertube-models' | ||
5 | import { | ||
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 | |||
18 | describe('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 @@ | |||
1 | import { omit } from '@peertube/peertube-core-utils' | ||
2 | import { HttpStatusCode, HttpStatusCodeType, UserRole } from '@peertube/peertube-models' | ||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | makePostBodyRequest, | ||
8 | PeerTubeServer, | ||
9 | setAccessTokensToServers, | ||
10 | setDefaultAccountAvatar, | ||
11 | setDefaultChannelAvatar | ||
12 | } from '@peertube/peertube-server-commands' | ||
13 | |||
14 | describe('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 */ | ||
2 | import { basename } from 'path' | ||
3 | import { | ||
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' | ||
15 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
16 | import { | ||
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 | |||
29 | const badUUID = '910ec12a-d9e6-458b-a274-0abb655f9464' | ||
30 | |||
31 | describe('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 | |||
3 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
4 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makeGetRequest, | ||
9 | PeerTubeServer, | ||
10 | setAccessTokensToServers | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | function 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 | |||
26 | describe('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 | |||
3 | import { | ||
4 | HttpStatusCode, | ||
5 | HttpStatusCodeType, | ||
6 | VideoCreateResult, | ||
7 | VideoPlaylistCreateResult, | ||
8 | VideoPlaylistPrivacy, | ||
9 | VideoPrivacy | ||
10 | } from '@peertube/peertube-models' | ||
11 | import { | ||
12 | cleanupTests, | ||
13 | createSingleServer, | ||
14 | makeGetRequest, | ||
15 | PeerTubeServer, | ||
16 | setAccessTokensToServers, | ||
17 | setDefaultVideoChannel | ||
18 | } from '@peertube/peertube-server-commands' | ||
19 | |||
20 | describe('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 | |||
193 | function 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 | |||
3 | import { HttpStatusCode, UserRole } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createMultipleServers, | ||
7 | doubleFollow, | ||
8 | PeerTubeServer, | ||
9 | setAccessTokensToServers, | ||
10 | waitJobs | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | describe('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 | |||
3 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | PeerTubeServer, | ||
8 | setAccessTokensToServers, | ||
9 | TwoFactorCommand | ||
10 | } from '@peertube/peertube-server-commands' | ||
11 | |||
12 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { FIXTURE_URLS } from '@tests/shared/tests.js' | ||
5 | import { randomInt } from '@peertube/peertube-core-utils' | ||
6 | import { HttpStatusCode, VideoImportState, VideoPrivacy } from '@peertube/peertube-models' | ||
7 | import { | ||
8 | cleanupTests, | ||
9 | createSingleServer, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | setDefaultVideoChannel, | ||
13 | VideosCommand, | ||
14 | waitJobs | ||
15 | } from '@peertube/peertube-server-commands' | ||
16 | |||
17 | describe('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 | |||
3 | import { io } from 'socket.io-client' | ||
4 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
5 | import { wait } from '@peertube/peertube-core-utils' | ||
6 | import { HttpStatusCode, UserNotificationSetting, UserNotificationSettingValue } from '@peertube/peertube-models' | ||
7 | import { | ||
8 | cleanupTests, | ||
9 | createSingleServer, | ||
10 | makeGetRequest, | ||
11 | makePostBodyRequest, | ||
12 | makePutBodyRequest, | ||
13 | PeerTubeServer, | ||
14 | setAccessTokensToServers | ||
15 | } from '@peertube/peertube-server-commands' | ||
16 | |||
17 | describe('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 | |||
3 | import { | ||
4 | cleanupTests, | ||
5 | createSingleServer, | ||
6 | makeDeleteRequest, | ||
7 | makeGetRequest, | ||
8 | makePostBodyRequest, | ||
9 | PeerTubeServer, | ||
10 | setAccessTokensToServers, | ||
11 | waitJobs | ||
12 | } from '@peertube/peertube-server-commands' | ||
13 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
14 | import { checkBadStartPagination, checkBadCountPagination, checkBadSortPagination } from '@tests/shared/checks.js' | ||
15 | |||
16 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { MockSmtpServer } from '@tests/shared/mock-servers/index.js' | ||
5 | import { omit } from '@peertube/peertube-core-utils' | ||
6 | import { HttpStatusCode, UserAdminFlag, UserRole } from '@peertube/peertube-models' | ||
7 | import { | ||
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 | |||
19 | describe('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 */ | ||
2 | import { HttpStatusCode, UserRole } from '@peertube/peertube-models' | ||
3 | import { | ||
4 | cleanupTests, | ||
5 | createSingleServer, | ||
6 | makePostBodyRequest, | ||
7 | PeerTubeServer, | ||
8 | setAccessTokensToServers | ||
9 | } from '@peertube/peertube-server-commands' | ||
10 | |||
11 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
5 | import { HttpStatusCode, VideoBlacklistType } from '@peertube/peertube-models' | ||
6 | import { | ||
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 | |||
18 | describe('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 | |||
3 | import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils' | ||
4 | import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makeDeleteRequest, | ||
9 | makeGetRequest, | ||
10 | makeUploadRequest, | ||
11 | PeerTubeServer, | ||
12 | setAccessTokensToServers | ||
13 | } from '@peertube/peertube-server-commands' | ||
14 | |||
15 | describe('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 @@ | |||
1 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
2 | import { FIXTURE_URLS } from '@tests/shared/tests.js' | ||
3 | import { HttpStatusCode, VideoChannelSyncCreate } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | ChannelSyncsCommand, | ||
6 | createSingleServer, | ||
7 | makePostBodyRequest, | ||
8 | PeerTubeServer, | ||
9 | setAccessTokensToServers, | ||
10 | setDefaultVideoChannel | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { omit } from '@peertube/peertube-core-utils' | ||
5 | import { HttpStatusCode, VideoChannelUpdate } from '@peertube/peertube-models' | ||
6 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
7 | import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils' | ||
8 | import { | ||
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 | |||
20 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
5 | import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models' | ||
6 | import { | ||
7 | cleanupTests, | ||
8 | createSingleServer, | ||
9 | makeDeleteRequest, | ||
10 | makeGetRequest, | ||
11 | makePostBodyRequest, | ||
12 | PeerTubeServer, | ||
13 | setAccessTokensToServers | ||
14 | } from '@peertube/peertube-server-commands' | ||
15 | |||
16 | describe('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 | |||
3 | import { getAllFiles } from '@peertube/peertube-core-utils' | ||
4 | import { HttpStatusCode, UserRole, VideoDetails, VideoPrivacy } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createMultipleServers, | ||
8 | doubleFollow, | ||
9 | makeRawRequest, | ||
10 | PeerTubeServer, | ||
11 | setAccessTokensToServers, | ||
12 | waitJobs | ||
13 | } from '@peertube/peertube-server-commands' | ||
14 | |||
15 | describe('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 | |||
3 | import { omit } from '@peertube/peertube-core-utils' | ||
4 | import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models' | ||
5 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
6 | import { FIXTURE_URLS } from '@tests/shared/tests.js' | ||
7 | import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils' | ||
8 | import { | ||
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 | |||
20 | describe('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 @@ | |||
1 | import { expect } from 'chai' | ||
2 | import { | ||
3 | HttpStatusCode, | ||
4 | HttpStatusCodeType, | ||
5 | PeerTubeProblemDocument, | ||
6 | ServerErrorCode, | ||
7 | VideoCreateResult, | ||
8 | VideoPrivacy | ||
9 | } from '@peertube/peertube-models' | ||
10 | import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils' | ||
11 | import { | ||
12 | cleanupTests, | ||
13 | createSingleServer, | ||
14 | makePostBodyRequest, | ||
15 | PeerTubeServer, | ||
16 | setAccessTokensToServers | ||
17 | } from '@peertube/peertube-server-commands' | ||
18 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
19 | import { FIXTURE_URLS } from '@tests/shared/tests.js' | ||
20 | import { checkUploadVideoParam } from '@tests/shared/videos.js' | ||
21 | |||
22 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { | ||
5 | HttpStatusCode, | ||
6 | VideoPlaylistCreate, | ||
7 | VideoPlaylistCreateResult, | ||
8 | VideoPlaylistElementCreate, | ||
9 | VideoPlaylistElementUpdate, | ||
10 | VideoPlaylistPrivacy, | ||
11 | VideoPlaylistReorder, | ||
12 | VideoPlaylistType | ||
13 | } from '@peertube/peertube-models' | ||
14 | import { | ||
15 | cleanupTests, | ||
16 | createSingleServer, | ||
17 | makeGetRequest, | ||
18 | PeerTubeServer, | ||
19 | PlaylistsCommand, | ||
20 | setAccessTokensToServers, | ||
21 | setDefaultVideoChannel | ||
22 | } from '@peertube/peertube-server-commands' | ||
23 | |||
24 | describe('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 @@ | |||
1 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
2 | import { | ||
3 | cleanupTests, | ||
4 | createSingleServer, | ||
5 | PeerTubeServer, | ||
6 | setAccessTokensToServers, | ||
7 | setDefaultVideoChannel, | ||
8 | waitJobs | ||
9 | } from '@peertube/peertube-server-commands' | ||
10 | |||
11 | describe('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 | |||
3 | import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models' | ||
4 | import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@peertube/peertube-server-commands' | ||
5 | |||
6 | describe('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 | |||
3 | import { HttpStatusCode, HttpStatusCodeType, VideoStudioTask } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createSingleServer, | ||
7 | PeerTubeServer, | ||
8 | setAccessTokensToServers, | ||
9 | VideoStudioCommand, | ||
10 | waitJobs | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | describe('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 | |||
3 | import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models' | ||
4 | import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@peertube/peertube-server-commands' | ||
5 | |||
6 | describe('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 | |||
3 | import { | ||
4 | HttpStatusCode, | ||
5 | HttpStatusCodeType, | ||
6 | UserRole, | ||
7 | VideoInclude, | ||
8 | VideoIncludeType, | ||
9 | VideoPrivacy, | ||
10 | VideoPrivacyType | ||
11 | } from '@peertube/peertube-models' | ||
12 | import { | ||
13 | cleanupTests, | ||
14 | createSingleServer, | ||
15 | makeGetRequest, | ||
16 | PeerTubeServer, | ||
17 | setAccessTokensToServers, | ||
18 | setDefaultVideoChannel | ||
19 | } from '@peertube/peertube-server-commands' | ||
20 | |||
21 | describe('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 | |||
3 | import { checkBadCountPagination, checkBadStartPagination } from '@tests/shared/checks.js' | ||
4 | import { HttpStatusCode } from '@peertube/peertube-models' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createSingleServer, | ||
8 | makeDeleteRequest, | ||
9 | makeGetRequest, | ||
10 | makePostBodyRequest, | ||
11 | makePutBodyRequest, | ||
12 | PeerTubeServer, | ||
13 | setAccessTokensToServers | ||
14 | } from '@peertube/peertube-server-commands' | ||
15 | |||
16 | describe('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 | |||
3 | import { cleanupTests, createSingleServer, PeerTubeServer } from '@peertube/peertube-server-commands' | ||
4 | |||
5 | describe('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 | |||
3 | import { expect } from 'chai' | ||
4 | import { join } from 'path' | ||
5 | import { omit, randomInt } from '@peertube/peertube-core-utils' | ||
6 | import { HttpStatusCode, PeerTubeProblemDocument, VideoCreateResult, VideoPrivacy } from '@peertube/peertube-models' | ||
7 | import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils' | ||
8 | import { | ||
9 | cleanupTests, | ||
10 | createSingleServer, | ||
11 | makeDeleteRequest, | ||
12 | makeGetRequest, | ||
13 | makePutBodyRequest, | ||
14 | makeUploadRequest, | ||
15 | PeerTubeServer, | ||
16 | setAccessTokensToServers | ||
17 | } from '@peertube/peertube-server-commands' | ||
18 | import { checkBadStartPagination, checkBadCountPagination, checkBadSortPagination } from '@tests/shared/checks.js' | ||
19 | import { checkUploadVideoParam } from '@tests/shared/videos.js' | ||
20 | |||
21 | describe('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 | |||
3 | import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models' | ||
4 | import { | ||
5 | cleanupTests, | ||
6 | createMultipleServers, | ||
7 | doubleFollow, | ||
8 | PeerTubeServer, | ||
9 | setAccessTokensToServers, | ||
10 | setDefaultVideoChannel | ||
11 | } from '@peertube/peertube-server-commands' | ||
12 | |||
13 | describe('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 | }) | ||