diff options
author | Chocobozzz <me@florianbigard.com> | 2021-10-19 15:02:43 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-10-20 09:25:44 +0200 |
commit | 906f46d0849fac3e2b30b7bdd57759e189d6a35d (patch) | |
tree | 3fbe0023c046ade74995010ae21f6e2eece1ddf5 | |
parent | 4beda9e12adc7b1f3b178cecd6863ebf3cf431f1 (diff) | |
download | PeerTube-906f46d0849fac3e2b30b7bdd57759e189d6a35d.tar.gz PeerTube-906f46d0849fac3e2b30b7bdd57759e189d6a35d.tar.zst PeerTube-906f46d0849fac3e2b30b7bdd57759e189d6a35d.zip |
Split check user params tests
-rw-r--r-- | server/controllers/api/accounts.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/shared/utils.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-channels.ts | 2 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 2 | ||||
-rw-r--r-- | server/tests/api/check-params/index.ts | 2 | ||||
-rw-r--r-- | server/tests/api/check-params/my-user.ts | 495 | ||||
-rw-r--r-- | server/tests/api/check-params/users-admin.ts | 477 | ||||
-rw-r--r-- | server/tests/api/check-params/users.ts | 911 | ||||
-rw-r--r-- | server/tests/api/users/user-subscriptions.ts | 18 | ||||
-rw-r--r-- | shared/extra-utils/users/users-command.ts | 8 | ||||
-rw-r--r-- | support/doc/api/openapi.yaml | 69 |
12 files changed, 1080 insertions, 910 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index 77edfa7c2..8eb880d59 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -221,7 +221,7 @@ async function listAccountFollowers (req: express.Request, res: express.Response | |||
221 | count: req.query.count, | 221 | count: req.query.count, |
222 | sort: req.query.sort, | 222 | sort: req.query.sort, |
223 | search: req.query.search, | 223 | search: req.query.search, |
224 | state: 'accepted', | 224 | state: 'accepted' |
225 | }) | 225 | }) |
226 | 226 | ||
227 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 227 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index f370c7004..7bf7a68c9 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -362,7 +362,7 @@ async function listVideoChannelFollowers (req: express.Request, res: express.Res | |||
362 | count: req.query.count, | 362 | count: req.query.count, |
363 | sort: req.query.sort, | 363 | sort: req.query.sort, |
364 | search: req.query.search, | 364 | search: req.query.search, |
365 | state: 'accepted', | 365 | state: 'accepted' |
366 | }) | 366 | }) |
367 | 367 | ||
368 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 368 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
diff --git a/server/middlewares/validators/shared/utils.ts b/server/middlewares/validators/shared/utils.ts index 8e451a24c..104eace91 100644 --- a/server/middlewares/validators/shared/utils.ts +++ b/server/middlewares/validators/shared/utils.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { param, query, validationResult } from 'express-validator' | 2 | import { param, validationResult } from 'express-validator' |
3 | import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc' | 3 | import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | 5 | ||
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index ec107fa51..c4705192a 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -61,7 +61,7 @@ const videoChannelsUpdateValidator = [ | |||
61 | .optional() | 61 | .optional() |
62 | .custom(isBooleanValid).withMessage('Should have a valid bulkVideosSupportUpdate boolean field'), | 62 | .custom(isBooleanValid).withMessage('Should have a valid bulkVideosSupportUpdate boolean field'), |
63 | 63 | ||
64 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 64 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
65 | logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) | 65 | logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) |
66 | 66 | ||
67 | if (areValidationErrors(req, res)) return | 67 | if (areValidationErrors(req, res)) return |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index a151ad61c..4151dc5b5 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -582,7 +582,6 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
582 | }) | 582 | }) |
583 | } | 583 | } |
584 | 584 | ||
585 | |||
586 | static listAllByAccount (accountId: number) { | 585 | static listAllByAccount (accountId: number) { |
587 | const query = { | 586 | const query = { |
588 | limit: VIDEO_CHANNELS.MAX_PER_USER, | 587 | limit: VIDEO_CHANNELS.MAX_PER_USER, |
@@ -601,7 +600,6 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
601 | return VideoChannelModel.findAll(query) | 600 | return VideoChannelModel.findAll(query) |
602 | } | 601 | } |
603 | 602 | ||
604 | |||
605 | static loadAndPopulateAccount (id: number, transaction?: Transaction): Promise<MChannelBannerAccountDefault> { | 603 | static loadAndPopulateAccount (id: number, transaction?: Transaction): Promise<MChannelBannerAccountDefault> { |
606 | return VideoChannelModel.unscoped() | 604 | return VideoChannelModel.unscoped() |
607 | .scope([ ScopeNames.WITH_ACTOR_BANNER, ScopeNames.WITH_ACCOUNT ]) | 605 | .scope([ ScopeNames.WITH_ACTOR_BANNER, ScopeNames.WITH_ACCOUNT ]) |
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index ce2335e42..a14e4d3e0 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts | |||
@@ -9,6 +9,7 @@ import './debug' | |||
9 | import './follows' | 9 | import './follows' |
10 | import './jobs' | 10 | import './jobs' |
11 | import './logs' | 11 | import './logs' |
12 | import './my-user' | ||
12 | import './live' | 13 | import './live' |
13 | import './plugins' | 14 | import './plugins' |
14 | import './redundancy' | 15 | import './redundancy' |
@@ -17,6 +18,7 @@ import './services' | |||
17 | import './upload-quota' | 18 | import './upload-quota' |
18 | import './user-notifications' | 19 | import './user-notifications' |
19 | import './user-subscriptions' | 20 | import './user-subscriptions' |
21 | import './users-admin' | ||
20 | import './users' | 22 | import './users' |
21 | import './video-blacklist' | 23 | import './video-blacklist' |
22 | import './video-captions' | 24 | import './video-captions' |
diff --git a/server/tests/api/check-params/my-user.ts b/server/tests/api/check-params/my-user.ts new file mode 100644 index 000000000..d35284d60 --- /dev/null +++ b/server/tests/api/check-params/my-user.ts | |||
@@ -0,0 +1,495 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { | ||
5 | buildAbsoluteFixturePath, | ||
6 | checkBadCountPagination, | ||
7 | checkBadSortPagination, | ||
8 | checkBadStartPagination, | ||
9 | cleanupTests, | ||
10 | createSingleServer, | ||
11 | makeGetRequest, | ||
12 | makePutBodyRequest, | ||
13 | makeUploadRequest, | ||
14 | MockSmtpServer, | ||
15 | PeerTubeServer, | ||
16 | setAccessTokensToServers, | ||
17 | UsersCommand | ||
18 | } from '@shared/extra-utils' | ||
19 | import { HttpStatusCode, UserRole, VideoCreateResult } from '@shared/models' | ||
20 | |||
21 | describe('Test my user API validators', function () { | ||
22 | const path = '/api/v1/users/' | ||
23 | let userId: number | ||
24 | let rootId: number | ||
25 | let moderatorId: number | ||
26 | let video: VideoCreateResult | ||
27 | let server: PeerTubeServer | ||
28 | let userToken = '' | ||
29 | let moderatorToken = '' | ||
30 | |||
31 | // --------------------------------------------------------------- | ||
32 | |||
33 | before(async function () { | ||
34 | this.timeout(30000) | ||
35 | |||
36 | { | ||
37 | server = await createSingleServer(1) | ||
38 | await setAccessTokensToServers([ server ]) | ||
39 | } | ||
40 | |||
41 | { | ||
42 | const result = await server.users.generate('user1') | ||
43 | userToken = result.token | ||
44 | userId = result.userId | ||
45 | } | ||
46 | |||
47 | { | ||
48 | const result = await server.users.generate('moderator1', UserRole.MODERATOR) | ||
49 | moderatorToken = result.token | ||
50 | } | ||
51 | |||
52 | { | ||
53 | const result = await server.users.generate('moderator2', UserRole.MODERATOR) | ||
54 | moderatorId = result.userId | ||
55 | } | ||
56 | |||
57 | { | ||
58 | video = await server.videos.upload() | ||
59 | } | ||
60 | }) | ||
61 | |||
62 | describe('When updating my account', function () { | ||
63 | |||
64 | it('Should fail with an invalid email attribute', async function () { | ||
65 | const fields = { | ||
66 | email: 'blabla' | ||
67 | } | ||
68 | |||
69 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: server.accessToken, fields }) | ||
70 | }) | ||
71 | |||
72 | it('Should fail with a too small password', async function () { | ||
73 | const fields = { | ||
74 | currentPassword: 'password', | ||
75 | password: 'bla' | ||
76 | } | ||
77 | |||
78 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
79 | }) | ||
80 | |||
81 | it('Should fail with a too long password', async function () { | ||
82 | const fields = { | ||
83 | currentPassword: 'password', | ||
84 | password: 'super'.repeat(61) | ||
85 | } | ||
86 | |||
87 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
88 | }) | ||
89 | |||
90 | it('Should fail without the current password', async function () { | ||
91 | const fields = { | ||
92 | currentPassword: 'password', | ||
93 | password: 'super'.repeat(61) | ||
94 | } | ||
95 | |||
96 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
97 | }) | ||
98 | |||
99 | it('Should fail with an invalid current password', async function () { | ||
100 | const fields = { | ||
101 | currentPassword: 'my super password fail', | ||
102 | password: 'super'.repeat(61) | ||
103 | } | ||
104 | |||
105 | await makePutBodyRequest({ | ||
106 | url: server.url, | ||
107 | path: path + 'me', | ||
108 | token: userToken, | ||
109 | fields, | ||
110 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
111 | }) | ||
112 | }) | ||
113 | |||
114 | it('Should fail with an invalid NSFW policy attribute', async function () { | ||
115 | const fields = { | ||
116 | nsfwPolicy: 'hello' | ||
117 | } | ||
118 | |||
119 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
120 | }) | ||
121 | |||
122 | it('Should fail with an invalid autoPlayVideo attribute', async function () { | ||
123 | const fields = { | ||
124 | autoPlayVideo: -1 | ||
125 | } | ||
126 | |||
127 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
128 | }) | ||
129 | |||
130 | it('Should fail with an invalid autoPlayNextVideo attribute', async function () { | ||
131 | const fields = { | ||
132 | autoPlayNextVideo: -1 | ||
133 | } | ||
134 | |||
135 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
136 | }) | ||
137 | |||
138 | it('Should fail with an invalid videosHistoryEnabled attribute', async function () { | ||
139 | const fields = { | ||
140 | videosHistoryEnabled: -1 | ||
141 | } | ||
142 | |||
143 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
144 | }) | ||
145 | |||
146 | it('Should fail with an non authenticated user', async function () { | ||
147 | const fields = { | ||
148 | currentPassword: 'password', | ||
149 | password: 'my super password' | ||
150 | } | ||
151 | |||
152 | await makePutBodyRequest({ | ||
153 | url: server.url, | ||
154 | path: path + 'me', | ||
155 | token: 'super token', | ||
156 | fields, | ||
157 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
158 | }) | ||
159 | }) | ||
160 | |||
161 | it('Should fail with a too long description', async function () { | ||
162 | const fields = { | ||
163 | description: 'super'.repeat(201) | ||
164 | } | ||
165 | |||
166 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
167 | }) | ||
168 | |||
169 | it('Should fail with an invalid videoLanguages attribute', async function () { | ||
170 | { | ||
171 | const fields = { | ||
172 | videoLanguages: 'toto' | ||
173 | } | ||
174 | |||
175 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
176 | } | ||
177 | |||
178 | { | ||
179 | const languages = [] | ||
180 | for (let i = 0; i < 1000; i++) { | ||
181 | languages.push('fr') | ||
182 | } | ||
183 | |||
184 | const fields = { | ||
185 | videoLanguages: languages | ||
186 | } | ||
187 | |||
188 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
189 | } | ||
190 | }) | ||
191 | |||
192 | it('Should fail with an invalid theme', async function () { | ||
193 | const fields = { theme: 'invalid' } | ||
194 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
195 | }) | ||
196 | |||
197 | it('Should fail with an unknown theme', async function () { | ||
198 | const fields = { theme: 'peertube-theme-unknown' } | ||
199 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
200 | }) | ||
201 | |||
202 | it('Should fail with invalid no modal attributes', async function () { | ||
203 | const keys = [ | ||
204 | 'noInstanceConfigWarningModal', | ||
205 | 'noAccountSetupWarningModal', | ||
206 | 'noWelcomeModal' | ||
207 | ] | ||
208 | |||
209 | for (const key of keys) { | ||
210 | const fields = { | ||
211 | [key]: -1 | ||
212 | } | ||
213 | |||
214 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
215 | } | ||
216 | }) | ||
217 | |||
218 | it('Should succeed to change password with the correct params', async function () { | ||
219 | const fields = { | ||
220 | currentPassword: 'password', | ||
221 | password: 'my super password', | ||
222 | nsfwPolicy: 'blur', | ||
223 | autoPlayVideo: false, | ||
224 | email: 'super_email@example.com', | ||
225 | theme: 'default', | ||
226 | noInstanceConfigWarningModal: true, | ||
227 | noWelcomeModal: true, | ||
228 | noAccountSetupWarningModal: true | ||
229 | } | ||
230 | |||
231 | await makePutBodyRequest({ | ||
232 | url: server.url, | ||
233 | path: path + 'me', | ||
234 | token: userToken, | ||
235 | fields, | ||
236 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
237 | }) | ||
238 | }) | ||
239 | |||
240 | it('Should succeed without password change with the correct params', async function () { | ||
241 | const fields = { | ||
242 | nsfwPolicy: 'blur', | ||
243 | autoPlayVideo: false | ||
244 | } | ||
245 | |||
246 | await makePutBodyRequest({ | ||
247 | url: server.url, | ||
248 | path: path + 'me', | ||
249 | token: userToken, | ||
250 | fields, | ||
251 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
252 | }) | ||
253 | }) | ||
254 | }) | ||
255 | |||
256 | describe('When updating my avatar', function () { | ||
257 | it('Should fail without an incorrect input file', async function () { | ||
258 | const fields = {} | ||
259 | const attaches = { | ||
260 | avatarfile: buildAbsoluteFixturePath('video_short.mp4') | ||
261 | } | ||
262 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) | ||
263 | }) | ||
264 | |||
265 | it('Should fail with a big file', async function () { | ||
266 | const fields = {} | ||
267 | const attaches = { | ||
268 | avatarfile: buildAbsoluteFixturePath('avatar-big.png') | ||
269 | } | ||
270 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) | ||
271 | }) | ||
272 | |||
273 | it('Should fail with an unauthenticated user', async function () { | ||
274 | const fields = {} | ||
275 | const attaches = { | ||
276 | avatarfile: buildAbsoluteFixturePath('avatar.png') | ||
277 | } | ||
278 | await makeUploadRequest({ | ||
279 | url: server.url, | ||
280 | path: path + '/me/avatar/pick', | ||
281 | fields, | ||
282 | attaches, | ||
283 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
284 | }) | ||
285 | }) | ||
286 | |||
287 | it('Should succeed with the correct params', async function () { | ||
288 | const fields = {} | ||
289 | const attaches = { | ||
290 | avatarfile: buildAbsoluteFixturePath('avatar.png') | ||
291 | } | ||
292 | await makeUploadRequest({ | ||
293 | url: server.url, | ||
294 | path: path + '/me/avatar/pick', | ||
295 | token: server.accessToken, | ||
296 | fields, | ||
297 | attaches, | ||
298 | expectedStatus: HttpStatusCode.OK_200 | ||
299 | }) | ||
300 | }) | ||
301 | }) | ||
302 | |||
303 | describe('When managing my scoped tokens', function () { | ||
304 | |||
305 | it('Should fail to get my scoped tokens with an non authenticated user', async function () { | ||
306 | await server.users.getMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
307 | }) | ||
308 | |||
309 | it('Should fail to get my scoped tokens with a bad token', async function () { | ||
310 | await server.users.getMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
311 | |||
312 | }) | ||
313 | |||
314 | it('Should succeed to get my scoped tokens', async function () { | ||
315 | await server.users.getMyScopedTokens() | ||
316 | }) | ||
317 | |||
318 | it('Should fail to renew my scoped tokens with an non authenticated user', async function () { | ||
319 | await server.users.renewMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
320 | }) | ||
321 | |||
322 | it('Should fail to renew my scoped tokens with a bad token', async function () { | ||
323 | await server.users.renewMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
324 | }) | ||
325 | |||
326 | it('Should succeed to renew my scoped tokens', async function () { | ||
327 | await server.users.renewMyScopedTokens() | ||
328 | }) | ||
329 | }) | ||
330 | |||
331 | describe('When getting my information', function () { | ||
332 | it('Should fail with a non authenticated user', async function () { | ||
333 | await server.users.getMyInfo({ token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
334 | }) | ||
335 | |||
336 | it('Should success with the correct parameters', async function () { | ||
337 | await server.users.getMyInfo({ token: userToken }) | ||
338 | }) | ||
339 | }) | ||
340 | |||
341 | describe('When getting my video rating', function () { | ||
342 | let command: UsersCommand | ||
343 | |||
344 | before(function () { | ||
345 | command = server.users | ||
346 | }) | ||
347 | |||
348 | it('Should fail with a non authenticated user', async function () { | ||
349 | await command.getMyRating({ token: 'fake_token', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
350 | }) | ||
351 | |||
352 | it('Should fail with an incorrect video uuid', async function () { | ||
353 | await command.getMyRating({ videoId: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
354 | }) | ||
355 | |||
356 | it('Should fail with an unknown video', async function () { | ||
357 | await command.getMyRating({ videoId: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 }) | ||
358 | }) | ||
359 | |||
360 | it('Should succeed with the correct parameters', async function () { | ||
361 | await command.getMyRating({ videoId: video.id }) | ||
362 | await command.getMyRating({ videoId: video.uuid }) | ||
363 | await command.getMyRating({ videoId: video.shortUUID }) | ||
364 | }) | ||
365 | }) | ||
366 | |||
367 | describe('When retrieving my global ratings', function () { | ||
368 | const path = '/api/v1/accounts/user1/ratings' | ||
369 | |||
370 | it('Should fail with a bad start pagination', async function () { | ||
371 | await checkBadStartPagination(server.url, path, userToken) | ||
372 | }) | ||
373 | |||
374 | it('Should fail with a bad count pagination', async function () { | ||
375 | await checkBadCountPagination(server.url, path, userToken) | ||
376 | }) | ||
377 | |||
378 | it('Should fail with an incorrect sort', async function () { | ||
379 | await checkBadSortPagination(server.url, path, userToken) | ||
380 | }) | ||
381 | |||
382 | it('Should fail with a unauthenticated user', async function () { | ||
383 | await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
384 | }) | ||
385 | |||
386 | it('Should fail with a another user', async function () { | ||
387 | await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
388 | }) | ||
389 | |||
390 | it('Should fail with a bad type', async function () { | ||
391 | await makeGetRequest({ | ||
392 | url: server.url, | ||
393 | path, | ||
394 | token: userToken, | ||
395 | query: { rating: 'toto ' }, | ||
396 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
397 | }) | ||
398 | }) | ||
399 | |||
400 | it('Should succeed with the correct params', async function () { | ||
401 | await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
402 | }) | ||
403 | }) | ||
404 | |||
405 | describe('When getting my global followers', function () { | ||
406 | const path = '/api/v1/accounts/user1/followers' | ||
407 | |||
408 | it('Should fail with a bad start pagination', async function () { | ||
409 | await checkBadStartPagination(server.url, path, userToken) | ||
410 | }) | ||
411 | |||
412 | it('Should fail with a bad count pagination', async function () { | ||
413 | await checkBadCountPagination(server.url, path, userToken) | ||
414 | }) | ||
415 | |||
416 | it('Should fail with an incorrect sort', async function () { | ||
417 | await checkBadSortPagination(server.url, path, userToken) | ||
418 | }) | ||
419 | |||
420 | it('Should fail with a unauthenticated user', async function () { | ||
421 | await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
422 | }) | ||
423 | |||
424 | it('Should fail with a another user', async function () { | ||
425 | await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
426 | }) | ||
427 | |||
428 | it('Should succeed with the correct params', async function () { | ||
429 | await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
430 | }) | ||
431 | }) | ||
432 | |||
433 | describe('When blocking/unblocking/removing user', function () { | ||
434 | |||
435 | it('Should fail with an incorrect id', async function () { | ||
436 | const options = { userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } | ||
437 | |||
438 | await server.users.remove(options) | ||
439 | await server.users.banUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
440 | await server.users.unbanUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
441 | }) | ||
442 | |||
443 | it('Should fail with the root user', async function () { | ||
444 | const options = { userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } | ||
445 | |||
446 | await server.users.remove(options) | ||
447 | await server.users.banUser(options) | ||
448 | await server.users.unbanUser(options) | ||
449 | }) | ||
450 | |||
451 | it('Should return 404 with a non existing id', async function () { | ||
452 | const options = { userId: 4545454, expectedStatus: HttpStatusCode.NOT_FOUND_404 } | ||
453 | |||
454 | await server.users.remove(options) | ||
455 | await server.users.banUser(options) | ||
456 | await server.users.unbanUser(options) | ||
457 | }) | ||
458 | |||
459 | it('Should fail with a non admin user', async function () { | ||
460 | const options = { userId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } | ||
461 | |||
462 | await server.users.remove(options) | ||
463 | await server.users.banUser(options) | ||
464 | await server.users.unbanUser(options) | ||
465 | }) | ||
466 | |||
467 | it('Should fail on a moderator with a moderator', async function () { | ||
468 | const options = { userId: moderatorId, token: moderatorToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } | ||
469 | |||
470 | await server.users.remove(options) | ||
471 | await server.users.banUser(options) | ||
472 | await server.users.unbanUser(options) | ||
473 | }) | ||
474 | |||
475 | it('Should succeed on a user with a moderator', async function () { | ||
476 | const options = { userId, token: moderatorToken } | ||
477 | |||
478 | await server.users.banUser(options) | ||
479 | await server.users.unbanUser(options) | ||
480 | }) | ||
481 | }) | ||
482 | |||
483 | describe('When deleting our account', function () { | ||
484 | |||
485 | it('Should fail with with the root account', async function () { | ||
486 | await server.users.deleteMe({ expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
487 | }) | ||
488 | }) | ||
489 | |||
490 | after(async function () { | ||
491 | MockSmtpServer.Instance.kill() | ||
492 | |||
493 | await cleanupTests([ server ]) | ||
494 | }) | ||
495 | }) | ||
diff --git a/server/tests/api/check-params/users-admin.ts b/server/tests/api/check-params/users-admin.ts new file mode 100644 index 000000000..f71414a6b --- /dev/null +++ b/server/tests/api/check-params/users-admin.ts | |||
@@ -0,0 +1,477 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { omit } from 'lodash' | ||
5 | import { | ||
6 | checkBadCountPagination, | ||
7 | checkBadSortPagination, | ||
8 | checkBadStartPagination, | ||
9 | cleanupTests, | ||
10 | createSingleServer, | ||
11 | killallServers, | ||
12 | makeGetRequest, | ||
13 | makePostBodyRequest, | ||
14 | makePutBodyRequest, | ||
15 | MockSmtpServer, | ||
16 | PeerTubeServer, | ||
17 | setAccessTokensToServers | ||
18 | } from '@shared/extra-utils' | ||
19 | import { HttpStatusCode, UserAdminFlag, UserRole } from '@shared/models' | ||
20 | |||
21 | describe('Test users admin API validators', function () { | ||
22 | const path = '/api/v1/users/' | ||
23 | let userId: number | ||
24 | let rootId: number | ||
25 | let moderatorId: number | ||
26 | let server: PeerTubeServer | ||
27 | let userToken = '' | ||
28 | let moderatorToken = '' | ||
29 | let emailPort: number | ||
30 | |||
31 | // --------------------------------------------------------------- | ||
32 | |||
33 | before(async function () { | ||
34 | this.timeout(30000) | ||
35 | |||
36 | const emails: object[] = [] | ||
37 | emailPort = await MockSmtpServer.Instance.collectEmails(emails) | ||
38 | |||
39 | { | ||
40 | server = await createSingleServer(1) | ||
41 | |||
42 | await setAccessTokensToServers([ server ]) | ||
43 | } | ||
44 | |||
45 | { | ||
46 | const result = await server.users.generate('user1') | ||
47 | userToken = result.token | ||
48 | userId = result.userId | ||
49 | } | ||
50 | |||
51 | { | ||
52 | const result = await server.users.generate('moderator1', UserRole.MODERATOR) | ||
53 | moderatorToken = result.token | ||
54 | } | ||
55 | |||
56 | { | ||
57 | const result = await server.users.generate('moderator2', UserRole.MODERATOR) | ||
58 | moderatorId = result.userId | ||
59 | } | ||
60 | }) | ||
61 | |||
62 | describe('When listing users', function () { | ||
63 | it('Should fail with a bad start pagination', async function () { | ||
64 | await checkBadStartPagination(server.url, path, server.accessToken) | ||
65 | }) | ||
66 | |||
67 | it('Should fail with a bad count pagination', async function () { | ||
68 | await checkBadCountPagination(server.url, path, server.accessToken) | ||
69 | }) | ||
70 | |||
71 | it('Should fail with an incorrect sort', async function () { | ||
72 | await checkBadSortPagination(server.url, path, server.accessToken) | ||
73 | }) | ||
74 | |||
75 | it('Should fail with a non authenticated user', async function () { | ||
76 | await makeGetRequest({ | ||
77 | url: server.url, | ||
78 | path, | ||
79 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
80 | }) | ||
81 | }) | ||
82 | |||
83 | it('Should fail with a non admin user', async function () { | ||
84 | await makeGetRequest({ | ||
85 | url: server.url, | ||
86 | path, | ||
87 | token: userToken, | ||
88 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
89 | }) | ||
90 | }) | ||
91 | }) | ||
92 | |||
93 | describe('When adding a new user', function () { | ||
94 | const baseCorrectParams = { | ||
95 | username: 'user2', | ||
96 | email: 'test@example.com', | ||
97 | password: 'my super password', | ||
98 | videoQuota: -1, | ||
99 | videoQuotaDaily: -1, | ||
100 | role: UserRole.USER, | ||
101 | adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST | ||
102 | } | ||
103 | |||
104 | it('Should fail with a too small username', async function () { | ||
105 | const fields = { ...baseCorrectParams, username: '' } | ||
106 | |||
107 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
108 | }) | ||
109 | |||
110 | it('Should fail with a too long username', async function () { | ||
111 | const fields = { ...baseCorrectParams, username: 'super'.repeat(50) } | ||
112 | |||
113 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
114 | }) | ||
115 | |||
116 | it('Should fail with a not lowercase username', async function () { | ||
117 | const fields = { ...baseCorrectParams, username: 'Toto' } | ||
118 | |||
119 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
120 | }) | ||
121 | |||
122 | it('Should fail with an incorrect username', async function () { | ||
123 | const fields = { ...baseCorrectParams, username: 'my username' } | ||
124 | |||
125 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
126 | }) | ||
127 | |||
128 | it('Should fail with a missing email', async function () { | ||
129 | const fields = omit(baseCorrectParams, 'email') | ||
130 | |||
131 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
132 | }) | ||
133 | |||
134 | it('Should fail with an invalid email', async function () { | ||
135 | const fields = { ...baseCorrectParams, email: 'test_example.com' } | ||
136 | |||
137 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
138 | }) | ||
139 | |||
140 | it('Should fail with a too small password', async function () { | ||
141 | const fields = { ...baseCorrectParams, password: 'bla' } | ||
142 | |||
143 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
144 | }) | ||
145 | |||
146 | it('Should fail with a too long password', async function () { | ||
147 | const fields = { ...baseCorrectParams, password: 'super'.repeat(61) } | ||
148 | |||
149 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
150 | }) | ||
151 | |||
152 | it('Should fail with empty password and no smtp configured', async function () { | ||
153 | const fields = { ...baseCorrectParams, password: '' } | ||
154 | |||
155 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
156 | }) | ||
157 | |||
158 | it('Should succeed with no password on a server with smtp enabled', async function () { | ||
159 | this.timeout(20000) | ||
160 | |||
161 | await killallServers([ server ]) | ||
162 | |||
163 | const config = { | ||
164 | smtp: { | ||
165 | hostname: 'localhost', | ||
166 | port: emailPort | ||
167 | } | ||
168 | } | ||
169 | await server.run(config) | ||
170 | |||
171 | const fields = { | ||
172 | ...baseCorrectParams, | ||
173 | |||
174 | password: '', | ||
175 | username: 'create_password', | ||
176 | email: 'create_password@example.com' | ||
177 | } | ||
178 | |||
179 | await makePostBodyRequest({ | ||
180 | url: server.url, | ||
181 | path: path, | ||
182 | token: server.accessToken, | ||
183 | fields, | ||
184 | expectedStatus: HttpStatusCode.OK_200 | ||
185 | }) | ||
186 | }) | ||
187 | |||
188 | it('Should fail with invalid admin flags', async function () { | ||
189 | const fields = { ...baseCorrectParams, adminFlags: 'toto' } | ||
190 | |||
191 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
192 | }) | ||
193 | |||
194 | it('Should fail with an non authenticated user', async function () { | ||
195 | await makePostBodyRequest({ | ||
196 | url: server.url, | ||
197 | path, | ||
198 | token: 'super token', | ||
199 | fields: baseCorrectParams, | ||
200 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
201 | }) | ||
202 | }) | ||
203 | |||
204 | it('Should fail if we add a user with the same username', async function () { | ||
205 | const fields = { ...baseCorrectParams, username: 'user1' } | ||
206 | |||
207 | await makePostBodyRequest({ | ||
208 | url: server.url, | ||
209 | path, | ||
210 | token: server.accessToken, | ||
211 | fields, | ||
212 | expectedStatus: HttpStatusCode.CONFLICT_409 | ||
213 | }) | ||
214 | }) | ||
215 | |||
216 | it('Should fail if we add a user with the same email', async function () { | ||
217 | const fields = { ...baseCorrectParams, email: 'user1@example.com' } | ||
218 | |||
219 | await makePostBodyRequest({ | ||
220 | url: server.url, | ||
221 | path, | ||
222 | token: server.accessToken, | ||
223 | fields, | ||
224 | expectedStatus: HttpStatusCode.CONFLICT_409 | ||
225 | }) | ||
226 | }) | ||
227 | |||
228 | it('Should fail without a videoQuota', async function () { | ||
229 | const fields = omit(baseCorrectParams, 'videoQuota') | ||
230 | |||
231 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
232 | }) | ||
233 | |||
234 | it('Should fail without a videoQuotaDaily', async function () { | ||
235 | const fields = omit(baseCorrectParams, 'videoQuotaDaily') | ||
236 | |||
237 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
238 | }) | ||
239 | |||
240 | it('Should fail with an invalid videoQuota', async function () { | ||
241 | const fields = { ...baseCorrectParams, videoQuota: -5 } | ||
242 | |||
243 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
244 | }) | ||
245 | |||
246 | it('Should fail with an invalid videoQuotaDaily', async function () { | ||
247 | const fields = { ...baseCorrectParams, videoQuotaDaily: -7 } | ||
248 | |||
249 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
250 | }) | ||
251 | |||
252 | it('Should fail without a user role', async function () { | ||
253 | const fields = omit(baseCorrectParams, 'role') | ||
254 | |||
255 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
256 | }) | ||
257 | |||
258 | it('Should fail with an invalid user role', async function () { | ||
259 | const fields = { ...baseCorrectParams, role: 88989 } | ||
260 | |||
261 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
262 | }) | ||
263 | |||
264 | it('Should fail with a "peertube" username', async function () { | ||
265 | const fields = { ...baseCorrectParams, username: 'peertube' } | ||
266 | |||
267 | await makePostBodyRequest({ | ||
268 | url: server.url, | ||
269 | path, | ||
270 | token: server.accessToken, | ||
271 | fields, | ||
272 | expectedStatus: HttpStatusCode.CONFLICT_409 | ||
273 | }) | ||
274 | }) | ||
275 | |||
276 | it('Should fail to create a moderator or an admin with a moderator', async function () { | ||
277 | for (const role of [ UserRole.MODERATOR, UserRole.ADMINISTRATOR ]) { | ||
278 | const fields = { ...baseCorrectParams, role } | ||
279 | |||
280 | await makePostBodyRequest({ | ||
281 | url: server.url, | ||
282 | path, | ||
283 | token: moderatorToken, | ||
284 | fields, | ||
285 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
286 | }) | ||
287 | } | ||
288 | }) | ||
289 | |||
290 | it('Should succeed to create a user with a moderator', async function () { | ||
291 | const fields = { ...baseCorrectParams, username: 'a4656', email: 'a4656@example.com', role: UserRole.USER } | ||
292 | |||
293 | await makePostBodyRequest({ | ||
294 | url: server.url, | ||
295 | path, | ||
296 | token: moderatorToken, | ||
297 | fields, | ||
298 | expectedStatus: HttpStatusCode.OK_200 | ||
299 | }) | ||
300 | }) | ||
301 | |||
302 | it('Should succeed with the correct params', async function () { | ||
303 | await makePostBodyRequest({ | ||
304 | url: server.url, | ||
305 | path, | ||
306 | token: server.accessToken, | ||
307 | fields: baseCorrectParams, | ||
308 | expectedStatus: HttpStatusCode.OK_200 | ||
309 | }) | ||
310 | }) | ||
311 | |||
312 | it('Should fail with a non admin user', async function () { | ||
313 | const user = { username: 'user1' } | ||
314 | userToken = await server.login.getAccessToken(user) | ||
315 | |||
316 | const fields = { | ||
317 | username: 'user3', | ||
318 | email: 'test@example.com', | ||
319 | password: 'my super password', | ||
320 | videoQuota: 42000000 | ||
321 | } | ||
322 | await makePostBodyRequest({ url: server.url, path, token: userToken, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
323 | }) | ||
324 | }) | ||
325 | |||
326 | describe('When getting a user', function () { | ||
327 | |||
328 | it('Should fail with an non authenticated user', async function () { | ||
329 | await makeGetRequest({ | ||
330 | url: server.url, | ||
331 | path: path + userId, | ||
332 | token: 'super token', | ||
333 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
334 | }) | ||
335 | }) | ||
336 | |||
337 | it('Should fail with a non admin user', async function () { | ||
338 | await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
339 | }) | ||
340 | |||
341 | it('Should succeed with the correct params', async function () { | ||
342 | await makeGetRequest({ url: server.url, path: path + userId, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
343 | }) | ||
344 | }) | ||
345 | |||
346 | describe('When updating a user', function () { | ||
347 | |||
348 | it('Should fail with an invalid email attribute', async function () { | ||
349 | const fields = { | ||
350 | email: 'blabla' | ||
351 | } | ||
352 | |||
353 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
354 | }) | ||
355 | |||
356 | it('Should fail with an invalid emailVerified attribute', async function () { | ||
357 | const fields = { | ||
358 | emailVerified: 'yes' | ||
359 | } | ||
360 | |||
361 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
362 | }) | ||
363 | |||
364 | it('Should fail with an invalid videoQuota attribute', async function () { | ||
365 | const fields = { | ||
366 | videoQuota: -90 | ||
367 | } | ||
368 | |||
369 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
370 | }) | ||
371 | |||
372 | it('Should fail with an invalid user role attribute', async function () { | ||
373 | const fields = { | ||
374 | role: 54878 | ||
375 | } | ||
376 | |||
377 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
378 | }) | ||
379 | |||
380 | it('Should fail with a too small password', async function () { | ||
381 | const fields = { | ||
382 | currentPassword: 'password', | ||
383 | password: 'bla' | ||
384 | } | ||
385 | |||
386 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
387 | }) | ||
388 | |||
389 | it('Should fail with a too long password', async function () { | ||
390 | const fields = { | ||
391 | currentPassword: 'password', | ||
392 | password: 'super'.repeat(61) | ||
393 | } | ||
394 | |||
395 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
396 | }) | ||
397 | |||
398 | it('Should fail with an non authenticated user', async function () { | ||
399 | const fields = { | ||
400 | videoQuota: 42 | ||
401 | } | ||
402 | |||
403 | await makePutBodyRequest({ | ||
404 | url: server.url, | ||
405 | path: path + userId, | ||
406 | token: 'super token', | ||
407 | fields, | ||
408 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
409 | }) | ||
410 | }) | ||
411 | |||
412 | it('Should fail when updating root role', async function () { | ||
413 | const fields = { | ||
414 | role: UserRole.MODERATOR | ||
415 | } | ||
416 | |||
417 | await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields }) | ||
418 | }) | ||
419 | |||
420 | it('Should fail with invalid admin flags', async function () { | ||
421 | const fields = { adminFlags: 'toto' } | ||
422 | |||
423 | await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
424 | }) | ||
425 | |||
426 | it('Should fail to update an admin with a moderator', async function () { | ||
427 | const fields = { | ||
428 | videoQuota: 42 | ||
429 | } | ||
430 | |||
431 | await makePutBodyRequest({ | ||
432 | url: server.url, | ||
433 | path: path + moderatorId, | ||
434 | token: moderatorToken, | ||
435 | fields, | ||
436 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
437 | }) | ||
438 | }) | ||
439 | |||
440 | it('Should succeed to update a user with a moderator', async function () { | ||
441 | const fields = { | ||
442 | videoQuota: 42 | ||
443 | } | ||
444 | |||
445 | await makePutBodyRequest({ | ||
446 | url: server.url, | ||
447 | path: path + userId, | ||
448 | token: moderatorToken, | ||
449 | fields, | ||
450 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
451 | }) | ||
452 | }) | ||
453 | |||
454 | it('Should succeed with the correct params', async function () { | ||
455 | const fields = { | ||
456 | email: 'email@example.com', | ||
457 | emailVerified: true, | ||
458 | videoQuota: 42, | ||
459 | role: UserRole.USER | ||
460 | } | ||
461 | |||
462 | await makePutBodyRequest({ | ||
463 | url: server.url, | ||
464 | path: path + userId, | ||
465 | token: server.accessToken, | ||
466 | fields, | ||
467 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
468 | }) | ||
469 | }) | ||
470 | }) | ||
471 | |||
472 | after(async function () { | ||
473 | MockSmtpServer.Instance.kill() | ||
474 | |||
475 | await cleanupTests([ server ]) | ||
476 | }) | ||
477 | }) | ||
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 517e2f423..5f9cbc5eb 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -3,925 +3,36 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
5 | import { | 5 | import { |
6 | buildAbsoluteFixturePath, | ||
7 | checkBadCountPagination, | ||
8 | checkBadSortPagination, | ||
9 | checkBadStartPagination, | ||
10 | cleanupTests, | 6 | cleanupTests, |
11 | createSingleServer, | 7 | createSingleServer, |
12 | killallServers, | ||
13 | makeGetRequest, | ||
14 | makePostBodyRequest, | 8 | makePostBodyRequest, |
15 | makePutBodyRequest, | ||
16 | makeUploadRequest, | ||
17 | MockSmtpServer, | 9 | MockSmtpServer, |
18 | PeerTubeServer, | 10 | PeerTubeServer, |
19 | setAccessTokensToServers, | 11 | setAccessTokensToServers |
20 | UsersCommand | ||
21 | } from '@shared/extra-utils' | 12 | } from '@shared/extra-utils' |
22 | import { HttpStatusCode, UserAdminFlag, UserRole, VideoCreateResult } from '@shared/models' | 13 | import { HttpStatusCode, UserRole } from '@shared/models' |
23 | 14 | ||
24 | describe('Test users API validators', function () { | 15 | describe('Test users API validators', function () { |
25 | const path = '/api/v1/users/' | 16 | const path = '/api/v1/users/' |
26 | let userId: number | ||
27 | let rootId: number | ||
28 | let moderatorId: number | ||
29 | let video: VideoCreateResult | ||
30 | let server: PeerTubeServer | 17 | let server: PeerTubeServer |
31 | let serverWithRegistrationDisabled: PeerTubeServer | 18 | let serverWithRegistrationDisabled: PeerTubeServer |
32 | let userToken = '' | ||
33 | let moderatorToken = '' | ||
34 | let emailPort: number | ||
35 | let overrideConfig: Object | ||
36 | 19 | ||
37 | // --------------------------------------------------------------- | 20 | // --------------------------------------------------------------- |
38 | 21 | ||
39 | before(async function () { | 22 | before(async function () { |
40 | this.timeout(30000) | 23 | this.timeout(30000) |
41 | 24 | ||
42 | const emails: object[] = [] | 25 | const res = await Promise.all([ |
43 | emailPort = await MockSmtpServer.Instance.collectEmails(emails) | 26 | createSingleServer(1, { signup: { limit: 3 } }), |
27 | createSingleServer(2) | ||
28 | ]) | ||
44 | 29 | ||
45 | overrideConfig = { signup: { limit: 8 } } | 30 | server = res[0] |
31 | serverWithRegistrationDisabled = res[1] | ||
46 | 32 | ||
47 | { | 33 | await setAccessTokensToServers([ server ]) |
48 | const res = await Promise.all([ | ||
49 | createSingleServer(1, overrideConfig), | ||
50 | createSingleServer(2) | ||
51 | ]) | ||
52 | 34 | ||
53 | server = res[0] | 35 | await server.users.generate('moderator2', UserRole.MODERATOR) |
54 | serverWithRegistrationDisabled = res[1] | ||
55 | |||
56 | await setAccessTokensToServers([ server ]) | ||
57 | } | ||
58 | |||
59 | { | ||
60 | const user = { username: 'user1' } | ||
61 | await server.users.create({ ...user }) | ||
62 | userToken = await server.login.getAccessToken(user) | ||
63 | } | ||
64 | |||
65 | { | ||
66 | const moderator = { username: 'moderator1' } | ||
67 | await server.users.create({ ...moderator, role: UserRole.MODERATOR }) | ||
68 | moderatorToken = await server.login.getAccessToken(moderator) | ||
69 | } | ||
70 | |||
71 | { | ||
72 | const moderator = { username: 'moderator2' } | ||
73 | await server.users.create({ ...moderator, role: UserRole.MODERATOR }) | ||
74 | } | ||
75 | |||
76 | { | ||
77 | video = await server.videos.upload() | ||
78 | } | ||
79 | |||
80 | { | ||
81 | const { data } = await server.users.list() | ||
82 | userId = data.find(u => u.username === 'user1').id | ||
83 | rootId = data.find(u => u.username === 'root').id | ||
84 | moderatorId = data.find(u => u.username === 'moderator2').id | ||
85 | } | ||
86 | }) | ||
87 | |||
88 | describe('When listing users', function () { | ||
89 | it('Should fail with a bad start pagination', async function () { | ||
90 | await checkBadStartPagination(server.url, path, server.accessToken) | ||
91 | }) | ||
92 | |||
93 | it('Should fail with a bad count pagination', async function () { | ||
94 | await checkBadCountPagination(server.url, path, server.accessToken) | ||
95 | }) | ||
96 | |||
97 | it('Should fail with an incorrect sort', async function () { | ||
98 | await checkBadSortPagination(server.url, path, server.accessToken) | ||
99 | }) | ||
100 | |||
101 | it('Should fail with a non authenticated user', async function () { | ||
102 | await makeGetRequest({ | ||
103 | url: server.url, | ||
104 | path, | ||
105 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
106 | }) | ||
107 | }) | ||
108 | |||
109 | it('Should fail with a non admin user', async function () { | ||
110 | await makeGetRequest({ | ||
111 | url: server.url, | ||
112 | path, | ||
113 | token: userToken, | ||
114 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
115 | }) | ||
116 | }) | ||
117 | }) | ||
118 | |||
119 | describe('When adding a new user', function () { | ||
120 | const baseCorrectParams = { | ||
121 | username: 'user2', | ||
122 | email: 'test@example.com', | ||
123 | password: 'my super password', | ||
124 | videoQuota: -1, | ||
125 | videoQuotaDaily: -1, | ||
126 | role: UserRole.USER, | ||
127 | adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST | ||
128 | } | ||
129 | |||
130 | it('Should fail with a too small username', async function () { | ||
131 | const fields = { ...baseCorrectParams, username: '' } | ||
132 | |||
133 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
134 | }) | ||
135 | |||
136 | it('Should fail with a too long username', async function () { | ||
137 | const fields = { ...baseCorrectParams, username: 'super'.repeat(50) } | ||
138 | |||
139 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
140 | }) | ||
141 | |||
142 | it('Should fail with a not lowercase username', async function () { | ||
143 | const fields = { ...baseCorrectParams, username: 'Toto' } | ||
144 | |||
145 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
146 | }) | ||
147 | |||
148 | it('Should fail with an incorrect username', async function () { | ||
149 | const fields = { ...baseCorrectParams, username: 'my username' } | ||
150 | |||
151 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
152 | }) | ||
153 | |||
154 | it('Should fail with a missing email', async function () { | ||
155 | const fields = omit(baseCorrectParams, 'email') | ||
156 | |||
157 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
158 | }) | ||
159 | |||
160 | it('Should fail with an invalid email', async function () { | ||
161 | const fields = { ...baseCorrectParams, email: 'test_example.com' } | ||
162 | |||
163 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
164 | }) | ||
165 | |||
166 | it('Should fail with a too small password', async function () { | ||
167 | const fields = { ...baseCorrectParams, password: 'bla' } | ||
168 | |||
169 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
170 | }) | ||
171 | |||
172 | it('Should fail with a too long password', async function () { | ||
173 | const fields = { ...baseCorrectParams, password: 'super'.repeat(61) } | ||
174 | |||
175 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
176 | }) | ||
177 | |||
178 | it('Should fail with empty password and no smtp configured', async function () { | ||
179 | const fields = { ...baseCorrectParams, password: '' } | ||
180 | |||
181 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
182 | }) | ||
183 | |||
184 | it('Should succeed with no password on a server with smtp enabled', async function () { | ||
185 | this.timeout(20000) | ||
186 | |||
187 | await killallServers([ server ]) | ||
188 | |||
189 | const config = { | ||
190 | ...overrideConfig, | ||
191 | |||
192 | smtp: { | ||
193 | hostname: 'localhost', | ||
194 | port: emailPort | ||
195 | } | ||
196 | } | ||
197 | await server.run(config) | ||
198 | |||
199 | const fields = { | ||
200 | ...baseCorrectParams, | ||
201 | |||
202 | password: '', | ||
203 | username: 'create_password', | ||
204 | email: 'create_password@example.com' | ||
205 | } | ||
206 | |||
207 | await makePostBodyRequest({ | ||
208 | url: server.url, | ||
209 | path: path, | ||
210 | token: server.accessToken, | ||
211 | fields, | ||
212 | expectedStatus: HttpStatusCode.OK_200 | ||
213 | }) | ||
214 | }) | ||
215 | |||
216 | it('Should fail with invalid admin flags', async function () { | ||
217 | const fields = { ...baseCorrectParams, adminFlags: 'toto' } | ||
218 | |||
219 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
220 | }) | ||
221 | |||
222 | it('Should fail with an non authenticated user', async function () { | ||
223 | await makePostBodyRequest({ | ||
224 | url: server.url, | ||
225 | path, | ||
226 | token: 'super token', | ||
227 | fields: baseCorrectParams, | ||
228 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
229 | }) | ||
230 | }) | ||
231 | |||
232 | it('Should fail if we add a user with the same username', async function () { | ||
233 | const fields = { ...baseCorrectParams, username: 'user1' } | ||
234 | |||
235 | await makePostBodyRequest({ | ||
236 | url: server.url, | ||
237 | path, | ||
238 | token: server.accessToken, | ||
239 | fields, | ||
240 | expectedStatus: HttpStatusCode.CONFLICT_409 | ||
241 | }) | ||
242 | }) | ||
243 | |||
244 | it('Should fail if we add a user with the same email', async function () { | ||
245 | const fields = { ...baseCorrectParams, email: 'user1@example.com' } | ||
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 without a videoQuota', async function () { | ||
257 | const fields = omit(baseCorrectParams, 'videoQuota') | ||
258 | |||
259 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
260 | }) | ||
261 | |||
262 | it('Should fail without a videoQuotaDaily', async function () { | ||
263 | const fields = omit(baseCorrectParams, 'videoQuotaDaily') | ||
264 | |||
265 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
266 | }) | ||
267 | |||
268 | it('Should fail with an invalid videoQuota', async function () { | ||
269 | const fields = { ...baseCorrectParams, videoQuota: -5 } | ||
270 | |||
271 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
272 | }) | ||
273 | |||
274 | it('Should fail with an invalid videoQuotaDaily', async function () { | ||
275 | const fields = { ...baseCorrectParams, videoQuotaDaily: -7 } | ||
276 | |||
277 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
278 | }) | ||
279 | |||
280 | it('Should fail without a user role', async function () { | ||
281 | const fields = omit(baseCorrectParams, 'role') | ||
282 | |||
283 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
284 | }) | ||
285 | |||
286 | it('Should fail with an invalid user role', async function () { | ||
287 | const fields = { ...baseCorrectParams, role: 88989 } | ||
288 | |||
289 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
290 | }) | ||
291 | |||
292 | it('Should fail with a "peertube" username', async function () { | ||
293 | const fields = { ...baseCorrectParams, username: 'peertube' } | ||
294 | |||
295 | await makePostBodyRequest({ | ||
296 | url: server.url, | ||
297 | path, | ||
298 | token: server.accessToken, | ||
299 | fields, | ||
300 | expectedStatus: HttpStatusCode.CONFLICT_409 | ||
301 | }) | ||
302 | }) | ||
303 | |||
304 | it('Should fail to create a moderator or an admin with a moderator', async function () { | ||
305 | for (const role of [ UserRole.MODERATOR, UserRole.ADMINISTRATOR ]) { | ||
306 | const fields = { ...baseCorrectParams, role } | ||
307 | |||
308 | await makePostBodyRequest({ | ||
309 | url: server.url, | ||
310 | path, | ||
311 | token: moderatorToken, | ||
312 | fields, | ||
313 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
314 | }) | ||
315 | } | ||
316 | }) | ||
317 | |||
318 | it('Should succeed to create a user with a moderator', async function () { | ||
319 | const fields = { ...baseCorrectParams, username: 'a4656', email: 'a4656@example.com', role: UserRole.USER } | ||
320 | |||
321 | await makePostBodyRequest({ | ||
322 | url: server.url, | ||
323 | path, | ||
324 | token: moderatorToken, | ||
325 | fields, | ||
326 | expectedStatus: HttpStatusCode.OK_200 | ||
327 | }) | ||
328 | }) | ||
329 | |||
330 | it('Should succeed with the correct params', async function () { | ||
331 | await makePostBodyRequest({ | ||
332 | url: server.url, | ||
333 | path, | ||
334 | token: server.accessToken, | ||
335 | fields: baseCorrectParams, | ||
336 | expectedStatus: HttpStatusCode.OK_200 | ||
337 | }) | ||
338 | }) | ||
339 | |||
340 | it('Should fail with a non admin user', async function () { | ||
341 | const user = { username: 'user1' } | ||
342 | userToken = await server.login.getAccessToken(user) | ||
343 | |||
344 | const fields = { | ||
345 | username: 'user3', | ||
346 | email: 'test@example.com', | ||
347 | password: 'my super password', | ||
348 | videoQuota: 42000000 | ||
349 | } | ||
350 | await makePostBodyRequest({ url: server.url, path, token: userToken, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
351 | }) | ||
352 | }) | ||
353 | |||
354 | describe('When updating my account', function () { | ||
355 | |||
356 | it('Should fail with an invalid email attribute', async function () { | ||
357 | const fields = { | ||
358 | email: 'blabla' | ||
359 | } | ||
360 | |||
361 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: server.accessToken, fields }) | ||
362 | }) | ||
363 | |||
364 | it('Should fail with a too small password', async function () { | ||
365 | const fields = { | ||
366 | currentPassword: 'password', | ||
367 | password: 'bla' | ||
368 | } | ||
369 | |||
370 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
371 | }) | ||
372 | |||
373 | it('Should fail with a too long password', async function () { | ||
374 | const fields = { | ||
375 | currentPassword: 'password', | ||
376 | password: 'super'.repeat(61) | ||
377 | } | ||
378 | |||
379 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
380 | }) | ||
381 | |||
382 | it('Should fail without the current password', async function () { | ||
383 | const fields = { | ||
384 | currentPassword: 'password', | ||
385 | password: 'super'.repeat(61) | ||
386 | } | ||
387 | |||
388 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
389 | }) | ||
390 | |||
391 | it('Should fail with an invalid current password', async function () { | ||
392 | const fields = { | ||
393 | currentPassword: 'my super password fail', | ||
394 | password: 'super'.repeat(61) | ||
395 | } | ||
396 | |||
397 | await makePutBodyRequest({ | ||
398 | url: server.url, | ||
399 | path: path + 'me', | ||
400 | token: userToken, | ||
401 | fields, | ||
402 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
403 | }) | ||
404 | }) | ||
405 | |||
406 | it('Should fail with an invalid NSFW policy attribute', async function () { | ||
407 | const fields = { | ||
408 | nsfwPolicy: 'hello' | ||
409 | } | ||
410 | |||
411 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
412 | }) | ||
413 | |||
414 | it('Should fail with an invalid autoPlayVideo attribute', async function () { | ||
415 | const fields = { | ||
416 | autoPlayVideo: -1 | ||
417 | } | ||
418 | |||
419 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
420 | }) | ||
421 | |||
422 | it('Should fail with an invalid autoPlayNextVideo attribute', async function () { | ||
423 | const fields = { | ||
424 | autoPlayNextVideo: -1 | ||
425 | } | ||
426 | |||
427 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
428 | }) | ||
429 | |||
430 | it('Should fail with an invalid videosHistoryEnabled attribute', async function () { | ||
431 | const fields = { | ||
432 | videosHistoryEnabled: -1 | ||
433 | } | ||
434 | |||
435 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
436 | }) | ||
437 | |||
438 | it('Should fail with an non authenticated user', async function () { | ||
439 | const fields = { | ||
440 | currentPassword: 'password', | ||
441 | password: 'my super password' | ||
442 | } | ||
443 | |||
444 | await makePutBodyRequest({ | ||
445 | url: server.url, | ||
446 | path: path + 'me', | ||
447 | token: 'super token', | ||
448 | fields, | ||
449 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
450 | }) | ||
451 | }) | ||
452 | |||
453 | it('Should fail with a too long description', async function () { | ||
454 | const fields = { | ||
455 | description: 'super'.repeat(201) | ||
456 | } | ||
457 | |||
458 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
459 | }) | ||
460 | |||
461 | it('Should fail with an invalid videoLanguages attribute', async function () { | ||
462 | { | ||
463 | const fields = { | ||
464 | videoLanguages: 'toto' | ||
465 | } | ||
466 | |||
467 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
468 | } | ||
469 | |||
470 | { | ||
471 | const languages = [] | ||
472 | for (let i = 0; i < 1000; i++) { | ||
473 | languages.push('fr') | ||
474 | } | ||
475 | |||
476 | const fields = { | ||
477 | videoLanguages: languages | ||
478 | } | ||
479 | |||
480 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
481 | } | ||
482 | }) | ||
483 | |||
484 | it('Should fail with an invalid theme', async function () { | ||
485 | const fields = { theme: 'invalid' } | ||
486 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
487 | }) | ||
488 | |||
489 | it('Should fail with an unknown theme', async function () { | ||
490 | const fields = { theme: 'peertube-theme-unknown' } | ||
491 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
492 | }) | ||
493 | |||
494 | it('Should fail with invalid no modal attributes', async function () { | ||
495 | const keys = [ | ||
496 | 'noInstanceConfigWarningModal', | ||
497 | 'noAccountSetupWarningModal', | ||
498 | 'noWelcomeModal' | ||
499 | ] | ||
500 | |||
501 | for (const key of keys) { | ||
502 | const fields = { | ||
503 | [key]: -1 | ||
504 | } | ||
505 | |||
506 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) | ||
507 | } | ||
508 | }) | ||
509 | |||
510 | it('Should succeed to change password with the correct params', async function () { | ||
511 | const fields = { | ||
512 | currentPassword: 'password', | ||
513 | password: 'my super password', | ||
514 | nsfwPolicy: 'blur', | ||
515 | autoPlayVideo: false, | ||
516 | email: 'super_email@example.com', | ||
517 | theme: 'default', | ||
518 | noInstanceConfigWarningModal: true, | ||
519 | noWelcomeModal: true, | ||
520 | noAccountSetupWarningModal: true | ||
521 | } | ||
522 | |||
523 | await makePutBodyRequest({ | ||
524 | url: server.url, | ||
525 | path: path + 'me', | ||
526 | token: userToken, | ||
527 | fields, | ||
528 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
529 | }) | ||
530 | }) | ||
531 | |||
532 | it('Should succeed without password change with the correct params', async function () { | ||
533 | const fields = { | ||
534 | nsfwPolicy: 'blur', | ||
535 | autoPlayVideo: false | ||
536 | } | ||
537 | |||
538 | await makePutBodyRequest({ | ||
539 | url: server.url, | ||
540 | path: path + 'me', | ||
541 | token: userToken, | ||
542 | fields, | ||
543 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
544 | }) | ||
545 | }) | ||
546 | }) | ||
547 | |||
548 | describe('When updating my avatar', function () { | ||
549 | it('Should fail without an incorrect input file', async function () { | ||
550 | const fields = {} | ||
551 | const attaches = { | ||
552 | avatarfile: buildAbsoluteFixturePath('video_short.mp4') | ||
553 | } | ||
554 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) | ||
555 | }) | ||
556 | |||
557 | it('Should fail with a big file', async function () { | ||
558 | const fields = {} | ||
559 | const attaches = { | ||
560 | avatarfile: buildAbsoluteFixturePath('avatar-big.png') | ||
561 | } | ||
562 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) | ||
563 | }) | ||
564 | |||
565 | it('Should fail with an unauthenticated user', async function () { | ||
566 | const fields = {} | ||
567 | const attaches = { | ||
568 | avatarfile: buildAbsoluteFixturePath('avatar.png') | ||
569 | } | ||
570 | await makeUploadRequest({ | ||
571 | url: server.url, | ||
572 | path: path + '/me/avatar/pick', | ||
573 | fields, | ||
574 | attaches, | ||
575 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
576 | }) | ||
577 | }) | ||
578 | |||
579 | it('Should succeed with the correct params', async function () { | ||
580 | const fields = {} | ||
581 | const attaches = { | ||
582 | avatarfile: buildAbsoluteFixturePath('avatar.png') | ||
583 | } | ||
584 | await makeUploadRequest({ | ||
585 | url: server.url, | ||
586 | path: path + '/me/avatar/pick', | ||
587 | token: server.accessToken, | ||
588 | fields, | ||
589 | attaches, | ||
590 | expectedStatus: HttpStatusCode.OK_200 | ||
591 | }) | ||
592 | }) | ||
593 | }) | ||
594 | |||
595 | describe('When managing my scoped tokens', function () { | ||
596 | |||
597 | it('Should fail to get my scoped tokens with an non authenticated user', async function () { | ||
598 | await server.users.getMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
599 | }) | ||
600 | |||
601 | it('Should fail to get my scoped tokens with a bad token', async function () { | ||
602 | await server.users.getMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
603 | |||
604 | }) | ||
605 | |||
606 | it('Should succeed to get my scoped tokens', async function () { | ||
607 | await server.users.getMyScopedTokens() | ||
608 | }) | ||
609 | |||
610 | it('Should fail to renew my scoped tokens with an non authenticated user', async function () { | ||
611 | await server.users.renewMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
612 | }) | ||
613 | |||
614 | it('Should fail to renew my scoped tokens with a bad token', async function () { | ||
615 | await server.users.renewMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
616 | }) | ||
617 | |||
618 | it('Should succeed to renew my scoped tokens', async function () { | ||
619 | await server.users.renewMyScopedTokens() | ||
620 | }) | ||
621 | }) | ||
622 | |||
623 | describe('When getting a user', function () { | ||
624 | |||
625 | it('Should fail with an non authenticated user', async function () { | ||
626 | await makeGetRequest({ | ||
627 | url: server.url, | ||
628 | path: path + userId, | ||
629 | token: 'super token', | ||
630 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
631 | }) | ||
632 | }) | ||
633 | |||
634 | it('Should fail with a non admin user', async function () { | ||
635 | await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
636 | }) | ||
637 | |||
638 | it('Should succeed with the correct params', async function () { | ||
639 | await makeGetRequest({ url: server.url, path: path + userId, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
640 | }) | ||
641 | }) | ||
642 | |||
643 | describe('When updating a user', function () { | ||
644 | |||
645 | it('Should fail with an invalid email attribute', async function () { | ||
646 | const fields = { | ||
647 | email: 'blabla' | ||
648 | } | ||
649 | |||
650 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
651 | }) | ||
652 | |||
653 | it('Should fail with an invalid emailVerified attribute', async function () { | ||
654 | const fields = { | ||
655 | emailVerified: 'yes' | ||
656 | } | ||
657 | |||
658 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
659 | }) | ||
660 | |||
661 | it('Should fail with an invalid videoQuota attribute', async function () { | ||
662 | const fields = { | ||
663 | videoQuota: -90 | ||
664 | } | ||
665 | |||
666 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
667 | }) | ||
668 | |||
669 | it('Should fail with an invalid user role attribute', async function () { | ||
670 | const fields = { | ||
671 | role: 54878 | ||
672 | } | ||
673 | |||
674 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
675 | }) | ||
676 | |||
677 | it('Should fail with a too small password', async function () { | ||
678 | const fields = { | ||
679 | currentPassword: 'password', | ||
680 | password: 'bla' | ||
681 | } | ||
682 | |||
683 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
684 | }) | ||
685 | |||
686 | it('Should fail with a too long password', async function () { | ||
687 | const fields = { | ||
688 | currentPassword: 'password', | ||
689 | password: 'super'.repeat(61) | ||
690 | } | ||
691 | |||
692 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
693 | }) | ||
694 | |||
695 | it('Should fail with an non authenticated user', async function () { | ||
696 | const fields = { | ||
697 | videoQuota: 42 | ||
698 | } | ||
699 | |||
700 | await makePutBodyRequest({ | ||
701 | url: server.url, | ||
702 | path: path + userId, | ||
703 | token: 'super token', | ||
704 | fields, | ||
705 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
706 | }) | ||
707 | }) | ||
708 | |||
709 | it('Should fail when updating root role', async function () { | ||
710 | const fields = { | ||
711 | role: UserRole.MODERATOR | ||
712 | } | ||
713 | |||
714 | await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields }) | ||
715 | }) | ||
716 | |||
717 | it('Should fail with invalid admin flags', async function () { | ||
718 | const fields = { adminFlags: 'toto' } | ||
719 | |||
720 | await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
721 | }) | ||
722 | |||
723 | it('Should fail to update an admin with a moderator', async function () { | ||
724 | const fields = { | ||
725 | videoQuota: 42 | ||
726 | } | ||
727 | |||
728 | await makePutBodyRequest({ | ||
729 | url: server.url, | ||
730 | path: path + moderatorId, | ||
731 | token: moderatorToken, | ||
732 | fields, | ||
733 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
734 | }) | ||
735 | }) | ||
736 | |||
737 | it('Should succeed to update a user with a moderator', async function () { | ||
738 | const fields = { | ||
739 | videoQuota: 42 | ||
740 | } | ||
741 | |||
742 | await makePutBodyRequest({ | ||
743 | url: server.url, | ||
744 | path: path + userId, | ||
745 | token: moderatorToken, | ||
746 | fields, | ||
747 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
748 | }) | ||
749 | }) | ||
750 | |||
751 | it('Should succeed with the correct params', async function () { | ||
752 | const fields = { | ||
753 | email: 'email@example.com', | ||
754 | emailVerified: true, | ||
755 | videoQuota: 42, | ||
756 | role: UserRole.USER | ||
757 | } | ||
758 | |||
759 | await makePutBodyRequest({ | ||
760 | url: server.url, | ||
761 | path: path + userId, | ||
762 | token: server.accessToken, | ||
763 | fields, | ||
764 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
765 | }) | ||
766 | }) | ||
767 | }) | ||
768 | |||
769 | describe('When getting my information', function () { | ||
770 | it('Should fail with a non authenticated user', async function () { | ||
771 | await server.users.getMyInfo({ token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
772 | }) | ||
773 | |||
774 | it('Should success with the correct parameters', async function () { | ||
775 | await server.users.getMyInfo({ token: userToken }) | ||
776 | }) | ||
777 | }) | ||
778 | |||
779 | describe('When getting my video rating', function () { | ||
780 | let command: UsersCommand | ||
781 | |||
782 | before(function () { | ||
783 | command = server.users | ||
784 | }) | ||
785 | |||
786 | it('Should fail with a non authenticated user', async function () { | ||
787 | await command.getMyRating({ token: 'fake_token', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
788 | }) | ||
789 | |||
790 | it('Should fail with an incorrect video uuid', async function () { | ||
791 | await command.getMyRating({ videoId: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
792 | }) | ||
793 | |||
794 | it('Should fail with an unknown video', async function () { | ||
795 | await command.getMyRating({ videoId: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 }) | ||
796 | }) | ||
797 | |||
798 | it('Should succeed with the correct parameters', async function () { | ||
799 | await command.getMyRating({ videoId: video.id }) | ||
800 | await command.getMyRating({ videoId: video.uuid }) | ||
801 | await command.getMyRating({ videoId: video.shortUUID }) | ||
802 | }) | ||
803 | }) | ||
804 | |||
805 | describe('When retrieving my global ratings', function () { | ||
806 | const path = '/api/v1/accounts/user1/ratings' | ||
807 | |||
808 | it('Should fail with a bad start pagination', async function () { | ||
809 | await checkBadStartPagination(server.url, path, userToken) | ||
810 | }) | ||
811 | |||
812 | it('Should fail with a bad count pagination', async function () { | ||
813 | await checkBadCountPagination(server.url, path, userToken) | ||
814 | }) | ||
815 | |||
816 | it('Should fail with an incorrect sort', async function () { | ||
817 | await checkBadSortPagination(server.url, path, userToken) | ||
818 | }) | ||
819 | |||
820 | it('Should fail with a unauthenticated user', async function () { | ||
821 | await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
822 | }) | ||
823 | |||
824 | it('Should fail with a another user', async function () { | ||
825 | await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
826 | }) | ||
827 | |||
828 | it('Should fail with a bad type', async function () { | ||
829 | await makeGetRequest({ | ||
830 | url: server.url, | ||
831 | path, | ||
832 | token: userToken, | ||
833 | query: { rating: 'toto ' }, | ||
834 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
835 | }) | ||
836 | }) | ||
837 | |||
838 | it('Should succeed with the correct params', async function () { | ||
839 | await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
840 | }) | ||
841 | }) | ||
842 | |||
843 | describe('When getting my global followers', function () { | ||
844 | const path = '/api/v1/accounts/user1/followers' | ||
845 | |||
846 | it('Should fail with a bad start pagination', async function () { | ||
847 | await checkBadStartPagination(server.url, path, userToken) | ||
848 | }) | ||
849 | |||
850 | it('Should fail with a bad count pagination', async function () { | ||
851 | await checkBadCountPagination(server.url, path, userToken) | ||
852 | }) | ||
853 | |||
854 | it('Should fail with an incorrect sort', async function () { | ||
855 | await checkBadSortPagination(server.url, path, userToken) | ||
856 | }) | ||
857 | |||
858 | it('Should fail with a unauthenticated user', async function () { | ||
859 | await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) | ||
860 | }) | ||
861 | |||
862 | it('Should fail with a another user', async function () { | ||
863 | await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
864 | }) | ||
865 | |||
866 | it('Should succeed with the correct params', async function () { | ||
867 | await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
868 | }) | ||
869 | }) | ||
870 | |||
871 | describe('When blocking/unblocking/removing user', function () { | ||
872 | |||
873 | it('Should fail with an incorrect id', async function () { | ||
874 | const options = { userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } | ||
875 | |||
876 | await server.users.remove(options) | ||
877 | await server.users.banUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
878 | await server.users.unbanUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
879 | }) | ||
880 | |||
881 | it('Should fail with the root user', async function () { | ||
882 | const options = { userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } | ||
883 | |||
884 | await server.users.remove(options) | ||
885 | await server.users.banUser(options) | ||
886 | await server.users.unbanUser(options) | ||
887 | }) | ||
888 | |||
889 | it('Should return 404 with a non existing id', async function () { | ||
890 | const options = { userId: 4545454, expectedStatus: HttpStatusCode.NOT_FOUND_404 } | ||
891 | |||
892 | await server.users.remove(options) | ||
893 | await server.users.banUser(options) | ||
894 | await server.users.unbanUser(options) | ||
895 | }) | ||
896 | |||
897 | it('Should fail with a non admin user', async function () { | ||
898 | const options = { userId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } | ||
899 | |||
900 | await server.users.remove(options) | ||
901 | await server.users.banUser(options) | ||
902 | await server.users.unbanUser(options) | ||
903 | }) | ||
904 | |||
905 | it('Should fail on a moderator with a moderator', async function () { | ||
906 | const options = { userId: moderatorId, token: moderatorToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } | ||
907 | |||
908 | await server.users.remove(options) | ||
909 | await server.users.banUser(options) | ||
910 | await server.users.unbanUser(options) | ||
911 | }) | ||
912 | |||
913 | it('Should succeed on a user with a moderator', async function () { | ||
914 | const options = { userId, token: moderatorToken } | ||
915 | |||
916 | await server.users.banUser(options) | ||
917 | await server.users.unbanUser(options) | ||
918 | }) | ||
919 | }) | ||
920 | |||
921 | describe('When deleting our account', function () { | ||
922 | it('Should fail with with the root account', async function () { | ||
923 | await server.users.deleteMe({ expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
924 | }) | ||
925 | }) | 36 | }) |
926 | 37 | ||
927 | describe('When registering a new user', function () { | 38 | describe('When registering a new user', function () { |
@@ -1081,9 +192,11 @@ describe('Test users API validators', function () { | |||
1081 | }) | 192 | }) |
1082 | 193 | ||
1083 | describe('When registering multiple users on a server with users limit', function () { | 194 | describe('When registering multiple users on a server with users limit', function () { |
195 | |||
1084 | it('Should fail when after 3 registrations', async function () { | 196 | it('Should fail when after 3 registrations', async function () { |
1085 | await server.users.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | 197 | await server.users.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) |
1086 | }) | 198 | }) |
199 | |||
1087 | }) | 200 | }) |
1088 | 201 | ||
1089 | describe('When asking a password reset', function () { | 202 | describe('When asking a password reset', function () { |
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts index b49367be6..d1d192238 100644 --- a/server/tests/api/users/user-subscriptions.ts +++ b/server/tests/api/users/user-subscriptions.ts | |||
@@ -390,6 +390,8 @@ describe('Test users subscriptions', function () { | |||
390 | }) | 390 | }) |
391 | 391 | ||
392 | expect(total).to.equal(3) | 392 | expect(total).to.equal(3) |
393 | expect(data).to.have.lengthOf(3) | ||
394 | |||
393 | expect(data[0].following.host).to.equal(servers[2].host) | 395 | expect(data[0].following.host).to.equal(servers[2].host) |
394 | expect(data[0].following.name).to.equal('user3_channel') | 396 | expect(data[0].following.name).to.equal('user3_channel') |
395 | expect(data[0].follower.host).to.equal(servers[0].host) | 397 | expect(data[0].follower.host).to.equal(servers[0].host) |
@@ -416,6 +418,8 @@ describe('Test users subscriptions', function () { | |||
416 | }) | 418 | }) |
417 | 419 | ||
418 | expect(total).to.equal(3) | 420 | expect(total).to.equal(3) |
421 | expect(data).to.have.lengthOf(1) | ||
422 | |||
419 | expect(data[0].following.host).to.equal(servers[2].host) | 423 | expect(data[0].following.host).to.equal(servers[2].host) |
420 | expect(data[0].following.name).to.equal('user3_channel2') | 424 | expect(data[0].following.name).to.equal('user3_channel2') |
421 | expect(data[0].follower.host).to.equal(servers[2].host) | 425 | expect(data[0].follower.host).to.equal(servers[2].host) |
@@ -432,6 +436,8 @@ describe('Test users subscriptions', function () { | |||
432 | }) | 436 | }) |
433 | 437 | ||
434 | expect(total).to.equal(3) | 438 | expect(total).to.equal(3) |
439 | expect(data).to.have.lengthOf(1) | ||
440 | |||
435 | expect(data[0].following.host).to.equal(servers[2].host) | 441 | expect(data[0].following.host).to.equal(servers[2].host) |
436 | expect(data[0].following.name).to.equal('user3_channel') | 442 | expect(data[0].following.name).to.equal('user3_channel') |
437 | expect(data[0].follower.host).to.equal(servers[2].host) | 443 | expect(data[0].follower.host).to.equal(servers[2].host) |
@@ -447,6 +453,8 @@ describe('Test users subscriptions', function () { | |||
447 | }) | 453 | }) |
448 | 454 | ||
449 | expect(total).to.equal(1) | 455 | expect(total).to.equal(1) |
456 | expect(data).to.have.lengthOf(1) | ||
457 | |||
450 | expect(data[0].following.host).to.equal(servers[2].host) | 458 | expect(data[0].following.host).to.equal(servers[2].host) |
451 | expect(data[0].following.name).to.equal('user3_channel') | 459 | expect(data[0].following.name).to.equal('user3_channel') |
452 | expect(data[0].follower.host).to.equal(servers[0].host) | 460 | expect(data[0].follower.host).to.equal(servers[0].host) |
@@ -465,6 +473,8 @@ describe('Test users subscriptions', function () { | |||
465 | }) | 473 | }) |
466 | 474 | ||
467 | expect(total).to.equal(2) | 475 | expect(total).to.equal(2) |
476 | expect(data).to.have.lengthOf(2) | ||
477 | |||
468 | expect(data[0].following.host).to.equal(servers[2].host) | 478 | expect(data[0].following.host).to.equal(servers[2].host) |
469 | expect(data[0].following.name).to.equal('user3_channel') | 479 | expect(data[0].following.name).to.equal('user3_channel') |
470 | expect(data[0].follower.host).to.equal(servers[0].host) | 480 | expect(data[0].follower.host).to.equal(servers[0].host) |
@@ -486,6 +496,8 @@ describe('Test users subscriptions', function () { | |||
486 | }) | 496 | }) |
487 | 497 | ||
488 | expect(total).to.equal(2) | 498 | expect(total).to.equal(2) |
499 | expect(data).to.have.lengthOf(1) | ||
500 | |||
489 | expect(data[0].following.host).to.equal(servers[2].host) | 501 | expect(data[0].following.host).to.equal(servers[2].host) |
490 | expect(data[0].following.name).to.equal('user3_channel') | 502 | expect(data[0].following.name).to.equal('user3_channel') |
491 | expect(data[0].follower.host).to.equal(servers[2].host) | 503 | expect(data[0].follower.host).to.equal(servers[2].host) |
@@ -502,10 +514,12 @@ describe('Test users subscriptions', function () { | |||
502 | }) | 514 | }) |
503 | 515 | ||
504 | expect(total).to.equal(2) | 516 | expect(total).to.equal(2) |
517 | expect(data).to.have.lengthOf(1) | ||
518 | |||
505 | expect(data[0].following.host).to.equal(servers[2].host) | 519 | expect(data[0].following.host).to.equal(servers[2].host) |
506 | expect(data[0].following.name).to.equal('user3_channel') | 520 | expect(data[0].following.name).to.equal('user3_channel') |
507 | expect(data[0].follower.host).to.equal(servers[0].host) | 521 | expect(data[0].follower.host).to.equal(servers[0].host) |
508 | expect(data[0].follower.name).to.equal('root') | 522 | expect(data[0].follower.name).to.equal('user1') |
509 | } | 523 | } |
510 | 524 | ||
511 | { | 525 | { |
@@ -517,6 +531,8 @@ describe('Test users subscriptions', function () { | |||
517 | }) | 531 | }) |
518 | 532 | ||
519 | expect(total).to.equal(1) | 533 | expect(total).to.equal(1) |
534 | expect(data).to.have.lengthOf(1) | ||
535 | |||
520 | expect(data[0].following.host).to.equal(servers[2].host) | 536 | expect(data[0].following.host).to.equal(servers[2].host) |
521 | expect(data[0].following.name).to.equal('user3_channel') | 537 | expect(data[0].following.name).to.equal('user3_channel') |
522 | expect(data[0].follower.host).to.equal(servers[0].host) | 538 | expect(data[0].follower.host).to.equal(servers[0].host) |
diff --git a/shared/extra-utils/users/users-command.ts b/shared/extra-utils/users/users-command.ts index ddd20d041..2a10e4fc8 100644 --- a/shared/extra-utils/users/users-command.ts +++ b/shared/extra-utils/users/users-command.ts | |||
@@ -191,9 +191,9 @@ export class UsersCommand extends AbstractCommand { | |||
191 | })).then(res => res.user) | 191 | })).then(res => res.user) |
192 | } | 192 | } |
193 | 193 | ||
194 | async generate (username: string) { | 194 | async generate (username: string, role?: UserRole) { |
195 | const password = 'password' | 195 | const password = 'password' |
196 | const user = await this.create({ username, password }) | 196 | const user = await this.create({ username, password, role }) |
197 | 197 | ||
198 | const token = await this.server.login.getAccessToken({ username, password }) | 198 | const token = await this.server.login.getAccessToken({ username, password }) |
199 | 199 | ||
@@ -206,9 +206,9 @@ export class UsersCommand extends AbstractCommand { | |||
206 | } | 206 | } |
207 | } | 207 | } |
208 | 208 | ||
209 | async generateUserAndToken (username: string) { | 209 | async generateUserAndToken (username: string, role?: UserRole) { |
210 | const password = 'password' | 210 | const password = 'password' |
211 | await this.create({ username, password }) | 211 | await this.create({ username, password, role }) |
212 | 212 | ||
213 | return this.server.login.getAccessToken({ username, password }) | 213 | return this.server.login.getAccessToken({ username, password }) |
214 | } | 214 | } |
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index dee86d83d..e9e84b139 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -414,6 +414,36 @@ paths: | |||
414 | 414 | ||
415 | print(json) | 415 | print(json) |
416 | 416 | ||
417 | '/accounts/{name}/followers': | ||
418 | get: | ||
419 | tags: | ||
420 | - Accounts | ||
421 | summary: 'List followers of an account' | ||
422 | security: | ||
423 | - OAuth2: [] | ||
424 | operationId: getAccountFollowers | ||
425 | parameters: | ||
426 | - $ref: '#/components/parameters/name' | ||
427 | - $ref: '#/components/parameters/start' | ||
428 | - $ref: '#/components/parameters/count' | ||
429 | - $ref: '#/components/parameters/followersSort' | ||
430 | - $ref: '#/components/parameters/search' | ||
431 | responses: | ||
432 | '200': | ||
433 | description: successful operation | ||
434 | content: | ||
435 | application/json: | ||
436 | schema: | ||
437 | type: object | ||
438 | properties: | ||
439 | total: | ||
440 | type: integer | ||
441 | example: 1 | ||
442 | data: | ||
443 | type: array | ||
444 | items: | ||
445 | $ref: '#/components/schemas/Follow' | ||
446 | |||
417 | /accounts: | 447 | /accounts: |
418 | get: | 448 | get: |
419 | tags: | 449 | tags: |
@@ -2832,6 +2862,36 @@ paths: | |||
2832 | schema: | 2862 | schema: |
2833 | $ref: '#/components/schemas/VideoListResponse' | 2863 | $ref: '#/components/schemas/VideoListResponse' |
2834 | 2864 | ||
2865 | '/video-channels/{channelHandle}/followers': | ||
2866 | get: | ||
2867 | tags: | ||
2868 | - Video Channels | ||
2869 | summary: 'List followers of a video channel' | ||
2870 | security: | ||
2871 | - OAuth2: [] | ||
2872 | operationId: getVideoChannelFollowers | ||
2873 | parameters: | ||
2874 | - $ref: '#/components/parameters/channelHandle' | ||
2875 | - $ref: '#/components/parameters/start' | ||
2876 | - $ref: '#/components/parameters/count' | ||
2877 | - $ref: '#/components/parameters/followersSort' | ||
2878 | - $ref: '#/components/parameters/search' | ||
2879 | responses: | ||
2880 | '200': | ||
2881 | description: successful operation | ||
2882 | content: | ||
2883 | application/json: | ||
2884 | schema: | ||
2885 | type: object | ||
2886 | properties: | ||
2887 | total: | ||
2888 | type: integer | ||
2889 | example: 1 | ||
2890 | data: | ||
2891 | type: array | ||
2892 | items: | ||
2893 | $ref: '#/components/schemas/Follow' | ||
2894 | |||
2835 | '/video-channels/{channelHandle}/avatar/pick': | 2895 | '/video-channels/{channelHandle}/avatar/pick': |
2836 | post: | 2896 | post: |
2837 | summary: Update channel avatar | 2897 | summary: Update channel avatar |
@@ -4532,6 +4592,15 @@ components: | |||
4532 | type: string | 4592 | type: string |
4533 | enum: | 4593 | enum: |
4534 | - name | 4594 | - name |
4595 | followersSort: | ||
4596 | name: sort | ||
4597 | in: query | ||
4598 | required: false | ||
4599 | description: Sort followers by criteria | ||
4600 | schema: | ||
4601 | type: string | ||
4602 | enum: | ||
4603 | - createdAt | ||
4535 | name: | 4604 | name: |
4536 | name: name | 4605 | name: name |
4537 | in: path | 4606 | in: path |