From 906f46d0849fac3e2b30b7bdd57759e189d6a35d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 19 Oct 2021 15:02:43 +0200 Subject: [PATCH] Split check user params tests --- server/controllers/api/accounts.ts | 2 +- server/controllers/api/video-channel.ts | 2 +- server/middlewares/validators/shared/utils.ts | 2 +- .../validators/videos/video-channels.ts | 2 +- server/models/video/video-channel.ts | 2 - server/tests/api/check-params/index.ts | 2 + server/tests/api/check-params/my-user.ts | 495 ++++++++++ server/tests/api/check-params/users-admin.ts | 477 +++++++++ server/tests/api/check-params/users.ts | 911 +----------------- server/tests/api/users/user-subscriptions.ts | 18 +- shared/extra-utils/users/users-command.ts | 8 +- support/doc/api/openapi.yaml | 69 ++ 12 files changed, 1080 insertions(+), 910 deletions(-) create mode 100644 server/tests/api/check-params/my-user.ts create mode 100644 server/tests/api/check-params/users-admin.ts 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 count: req.query.count, sort: req.query.sort, search: req.query.search, - state: 'accepted', + state: 'accepted' }) 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 count: req.query.count, sort: req.query.sort, search: req.query.search, - state: 'accepted', + state: 'accepted' }) 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 @@ import express from 'express' -import { param, query, validationResult } from 'express-validator' +import { param, validationResult } from 'express-validator' import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc' import { logger } from '../../../helpers/logger' 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 = [ .optional() .custom(isBooleanValid).withMessage('Should have a valid bulkVideosSupportUpdate boolean field'), - async (req: express.Request, res: express.Response, next: express.NextFunction) => { + (req: express.Request, res: express.Response, next: express.NextFunction) => { logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) 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"` }) } - static listAllByAccount (accountId: number) { const query = { limit: VIDEO_CHANNELS.MAX_PER_USER, @@ -601,7 +600,6 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` return VideoChannelModel.findAll(query) } - static loadAndPopulateAccount (id: number, transaction?: Transaction): Promise { return VideoChannelModel.unscoped() .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' import './follows' import './jobs' import './logs' +import './my-user' import './live' import './plugins' import './redundancy' @@ -17,6 +18,7 @@ import './services' import './upload-quota' import './user-notifications' import './user-subscriptions' +import './users-admin' import './users' import './video-blacklist' 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 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import 'mocha' +import { + buildAbsoluteFixturePath, + checkBadCountPagination, + checkBadSortPagination, + checkBadStartPagination, + cleanupTests, + createSingleServer, + makeGetRequest, + makePutBodyRequest, + makeUploadRequest, + MockSmtpServer, + PeerTubeServer, + setAccessTokensToServers, + UsersCommand +} from '@shared/extra-utils' +import { HttpStatusCode, UserRole, VideoCreateResult } from '@shared/models' + +describe('Test my user API validators', function () { + const path = '/api/v1/users/' + let userId: number + let rootId: number + let moderatorId: number + let video: VideoCreateResult + let server: PeerTubeServer + let userToken = '' + let moderatorToken = '' + + // --------------------------------------------------------------- + + before(async function () { + this.timeout(30000) + + { + server = await createSingleServer(1) + await setAccessTokensToServers([ server ]) + } + + { + const result = await server.users.generate('user1') + userToken = result.token + userId = result.userId + } + + { + const result = await server.users.generate('moderator1', UserRole.MODERATOR) + moderatorToken = result.token + } + + { + const result = await server.users.generate('moderator2', UserRole.MODERATOR) + moderatorId = result.userId + } + + { + video = await server.videos.upload() + } + }) + + describe('When updating my account', function () { + + it('Should fail with an invalid email attribute', async function () { + const fields = { + email: 'blabla' + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: server.accessToken, fields }) + }) + + it('Should fail with a too small password', async function () { + const fields = { + currentPassword: 'password', + password: 'bla' + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with a too long password', async function () { + const fields = { + currentPassword: 'password', + password: 'super'.repeat(61) + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail without the current password', async function () { + const fields = { + currentPassword: 'password', + password: 'super'.repeat(61) + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with an invalid current password', async function () { + const fields = { + currentPassword: 'my super password fail', + password: 'super'.repeat(61) + } + + await makePutBodyRequest({ + url: server.url, + path: path + 'me', + token: userToken, + fields, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) + }) + + it('Should fail with an invalid NSFW policy attribute', async function () { + const fields = { + nsfwPolicy: 'hello' + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with an invalid autoPlayVideo attribute', async function () { + const fields = { + autoPlayVideo: -1 + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with an invalid autoPlayNextVideo attribute', async function () { + const fields = { + autoPlayNextVideo: -1 + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with an invalid videosHistoryEnabled attribute', async function () { + const fields = { + videosHistoryEnabled: -1 + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with an non authenticated user', async function () { + const fields = { + currentPassword: 'password', + password: 'my super password' + } + + await makePutBodyRequest({ + url: server.url, + path: path + 'me', + token: 'super token', + fields, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) + }) + + it('Should fail with a too long description', async function () { + const fields = { + description: 'super'.repeat(201) + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with an invalid videoLanguages attribute', async function () { + { + const fields = { + videoLanguages: 'toto' + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + } + + { + const languages = [] + for (let i = 0; i < 1000; i++) { + languages.push('fr') + } + + const fields = { + videoLanguages: languages + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + } + }) + + it('Should fail with an invalid theme', async function () { + const fields = { theme: 'invalid' } + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with an unknown theme', async function () { + const fields = { theme: 'peertube-theme-unknown' } + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + }) + + it('Should fail with invalid no modal attributes', async function () { + const keys = [ + 'noInstanceConfigWarningModal', + 'noAccountSetupWarningModal', + 'noWelcomeModal' + ] + + for (const key of keys) { + const fields = { + [key]: -1 + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) + } + }) + + it('Should succeed to change password with the correct params', async function () { + const fields = { + currentPassword: 'password', + password: 'my super password', + nsfwPolicy: 'blur', + autoPlayVideo: false, + email: 'super_email@example.com', + theme: 'default', + noInstanceConfigWarningModal: true, + noWelcomeModal: true, + noAccountSetupWarningModal: true + } + + await makePutBodyRequest({ + url: server.url, + path: path + 'me', + token: userToken, + fields, + expectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + }) + + it('Should succeed without password change with the correct params', async function () { + const fields = { + nsfwPolicy: 'blur', + autoPlayVideo: false + } + + await makePutBodyRequest({ + url: server.url, + path: path + 'me', + token: userToken, + fields, + expectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + }) + }) + + describe('When updating my avatar', function () { + it('Should fail without an incorrect input file', async function () { + const fields = {} + const attaches = { + avatarfile: buildAbsoluteFixturePath('video_short.mp4') + } + await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) + }) + + it('Should fail with a big file', async function () { + const fields = {} + const attaches = { + avatarfile: buildAbsoluteFixturePath('avatar-big.png') + } + await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) + }) + + it('Should fail with an unauthenticated user', async function () { + const fields = {} + const attaches = { + avatarfile: buildAbsoluteFixturePath('avatar.png') + } + await makeUploadRequest({ + url: server.url, + path: path + '/me/avatar/pick', + fields, + attaches, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) + }) + + it('Should succeed with the correct params', async function () { + const fields = {} + const attaches = { + avatarfile: buildAbsoluteFixturePath('avatar.png') + } + await makeUploadRequest({ + url: server.url, + path: path + '/me/avatar/pick', + token: server.accessToken, + fields, + attaches, + expectedStatus: HttpStatusCode.OK_200 + }) + }) + }) + + describe('When managing my scoped tokens', function () { + + it('Should fail to get my scoped tokens with an non authenticated user', async function () { + await server.users.getMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + }) + + it('Should fail to get my scoped tokens with a bad token', async function () { + await server.users.getMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + + }) + + it('Should succeed to get my scoped tokens', async function () { + await server.users.getMyScopedTokens() + }) + + it('Should fail to renew my scoped tokens with an non authenticated user', async function () { + await server.users.renewMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + }) + + it('Should fail to renew my scoped tokens with a bad token', async function () { + await server.users.renewMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + }) + + it('Should succeed to renew my scoped tokens', async function () { + await server.users.renewMyScopedTokens() + }) + }) + + describe('When getting my information', function () { + it('Should fail with a non authenticated user', async function () { + await server.users.getMyInfo({ token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + }) + + it('Should success with the correct parameters', async function () { + await server.users.getMyInfo({ token: userToken }) + }) + }) + + describe('When getting my video rating', function () { + let command: UsersCommand + + before(function () { + command = server.users + }) + + it('Should fail with a non authenticated user', async function () { + await command.getMyRating({ token: 'fake_token', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + }) + + it('Should fail with an incorrect video uuid', async function () { + await command.getMyRating({ videoId: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + }) + + it('Should fail with an unknown video', async function () { + await command.getMyRating({ videoId: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 }) + }) + + it('Should succeed with the correct parameters', async function () { + await command.getMyRating({ videoId: video.id }) + await command.getMyRating({ videoId: video.uuid }) + await command.getMyRating({ videoId: video.shortUUID }) + }) + }) + + describe('When retrieving my global ratings', function () { + const path = '/api/v1/accounts/user1/ratings' + + it('Should fail with a bad start pagination', async function () { + await checkBadStartPagination(server.url, path, userToken) + }) + + it('Should fail with a bad count pagination', async function () { + await checkBadCountPagination(server.url, path, userToken) + }) + + it('Should fail with an incorrect sort', async function () { + await checkBadSortPagination(server.url, path, userToken) + }) + + it('Should fail with a unauthenticated user', async function () { + await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + }) + + it('Should fail with a another user', async function () { + await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + + it('Should fail with a bad type', async function () { + await makeGetRequest({ + url: server.url, + path, + token: userToken, + query: { rating: 'toto ' }, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + + it('Should succeed with the correct params', async function () { + await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) + }) + }) + + describe('When getting my global followers', function () { + const path = '/api/v1/accounts/user1/followers' + + it('Should fail with a bad start pagination', async function () { + await checkBadStartPagination(server.url, path, userToken) + }) + + it('Should fail with a bad count pagination', async function () { + await checkBadCountPagination(server.url, path, userToken) + }) + + it('Should fail with an incorrect sort', async function () { + await checkBadSortPagination(server.url, path, userToken) + }) + + it('Should fail with a unauthenticated user', async function () { + await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + }) + + it('Should fail with a another user', async function () { + await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + + it('Should succeed with the correct params', async function () { + await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) + }) + }) + + describe('When blocking/unblocking/removing user', function () { + + it('Should fail with an incorrect id', async function () { + const options = { userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } + + await server.users.remove(options) + await server.users.banUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.users.unbanUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + }) + + it('Should fail with the root user', async function () { + const options = { userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } + + await server.users.remove(options) + await server.users.banUser(options) + await server.users.unbanUser(options) + }) + + it('Should return 404 with a non existing id', async function () { + const options = { userId: 4545454, expectedStatus: HttpStatusCode.NOT_FOUND_404 } + + await server.users.remove(options) + await server.users.banUser(options) + await server.users.unbanUser(options) + }) + + it('Should fail with a non admin user', async function () { + const options = { userId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } + + await server.users.remove(options) + await server.users.banUser(options) + await server.users.unbanUser(options) + }) + + it('Should fail on a moderator with a moderator', async function () { + const options = { userId: moderatorId, token: moderatorToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } + + await server.users.remove(options) + await server.users.banUser(options) + await server.users.unbanUser(options) + }) + + it('Should succeed on a user with a moderator', async function () { + const options = { userId, token: moderatorToken } + + await server.users.banUser(options) + await server.users.unbanUser(options) + }) + }) + + describe('When deleting our account', function () { + + it('Should fail with with the root account', async function () { + await server.users.deleteMe({ expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + }) + }) + + after(async function () { + MockSmtpServer.Instance.kill() + + await cleanupTests([ server ]) + }) +}) 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 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import 'mocha' +import { omit } from 'lodash' +import { + checkBadCountPagination, + checkBadSortPagination, + checkBadStartPagination, + cleanupTests, + createSingleServer, + killallServers, + makeGetRequest, + makePostBodyRequest, + makePutBodyRequest, + MockSmtpServer, + PeerTubeServer, + setAccessTokensToServers +} from '@shared/extra-utils' +import { HttpStatusCode, UserAdminFlag, UserRole } from '@shared/models' + +describe('Test users admin API validators', function () { + const path = '/api/v1/users/' + let userId: number + let rootId: number + let moderatorId: number + let server: PeerTubeServer + let userToken = '' + let moderatorToken = '' + let emailPort: number + + // --------------------------------------------------------------- + + before(async function () { + this.timeout(30000) + + const emails: object[] = [] + emailPort = await MockSmtpServer.Instance.collectEmails(emails) + + { + server = await createSingleServer(1) + + await setAccessTokensToServers([ server ]) + } + + { + const result = await server.users.generate('user1') + userToken = result.token + userId = result.userId + } + + { + const result = await server.users.generate('moderator1', UserRole.MODERATOR) + moderatorToken = result.token + } + + { + const result = await server.users.generate('moderator2', UserRole.MODERATOR) + moderatorId = result.userId + } + }) + + describe('When listing users', function () { + it('Should fail with a bad start pagination', async function () { + await checkBadStartPagination(server.url, path, server.accessToken) + }) + + it('Should fail with a bad count pagination', async function () { + await checkBadCountPagination(server.url, path, server.accessToken) + }) + + it('Should fail with an incorrect sort', async function () { + await checkBadSortPagination(server.url, path, server.accessToken) + }) + + it('Should fail with a non authenticated user', async function () { + await makeGetRequest({ + url: server.url, + path, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) + }) + + it('Should fail with a non admin user', async function () { + await makeGetRequest({ + url: server.url, + path, + token: userToken, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + }) + + describe('When adding a new user', function () { + const baseCorrectParams = { + username: 'user2', + email: 'test@example.com', + password: 'my super password', + videoQuota: -1, + videoQuotaDaily: -1, + role: UserRole.USER, + adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST + } + + it('Should fail with a too small username', async function () { + const fields = { ...baseCorrectParams, username: '' } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with a too long username', async function () { + const fields = { ...baseCorrectParams, username: 'super'.repeat(50) } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with a not lowercase username', async function () { + const fields = { ...baseCorrectParams, username: 'Toto' } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with an incorrect username', async function () { + const fields = { ...baseCorrectParams, username: 'my username' } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with a missing email', async function () { + const fields = omit(baseCorrectParams, 'email') + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with an invalid email', async function () { + const fields = { ...baseCorrectParams, email: 'test_example.com' } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with a too small password', async function () { + const fields = { ...baseCorrectParams, password: 'bla' } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with a too long password', async function () { + const fields = { ...baseCorrectParams, password: 'super'.repeat(61) } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with empty password and no smtp configured', async function () { + const fields = { ...baseCorrectParams, password: '' } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should succeed with no password on a server with smtp enabled', async function () { + this.timeout(20000) + + await killallServers([ server ]) + + const config = { + smtp: { + hostname: 'localhost', + port: emailPort + } + } + await server.run(config) + + const fields = { + ...baseCorrectParams, + + password: '', + username: 'create_password', + email: 'create_password@example.com' + } + + await makePostBodyRequest({ + url: server.url, + path: path, + token: server.accessToken, + fields, + expectedStatus: HttpStatusCode.OK_200 + }) + }) + + it('Should fail with invalid admin flags', async function () { + const fields = { ...baseCorrectParams, adminFlags: 'toto' } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with an non authenticated user', async function () { + await makePostBodyRequest({ + url: server.url, + path, + token: 'super token', + fields: baseCorrectParams, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) + }) + + it('Should fail if we add a user with the same username', async function () { + const fields = { ...baseCorrectParams, username: 'user1' } + + await makePostBodyRequest({ + url: server.url, + path, + token: server.accessToken, + fields, + expectedStatus: HttpStatusCode.CONFLICT_409 + }) + }) + + it('Should fail if we add a user with the same email', async function () { + const fields = { ...baseCorrectParams, email: 'user1@example.com' } + + await makePostBodyRequest({ + url: server.url, + path, + token: server.accessToken, + fields, + expectedStatus: HttpStatusCode.CONFLICT_409 + }) + }) + + it('Should fail without a videoQuota', async function () { + const fields = omit(baseCorrectParams, 'videoQuota') + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail without a videoQuotaDaily', async function () { + const fields = omit(baseCorrectParams, 'videoQuotaDaily') + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with an invalid videoQuota', async function () { + const fields = { ...baseCorrectParams, videoQuota: -5 } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with an invalid videoQuotaDaily', async function () { + const fields = { ...baseCorrectParams, videoQuotaDaily: -7 } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail without a user role', async function () { + const fields = omit(baseCorrectParams, 'role') + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with an invalid user role', async function () { + const fields = { ...baseCorrectParams, role: 88989 } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with a "peertube" username', async function () { + const fields = { ...baseCorrectParams, username: 'peertube' } + + await makePostBodyRequest({ + url: server.url, + path, + token: server.accessToken, + fields, + expectedStatus: HttpStatusCode.CONFLICT_409 + }) + }) + + it('Should fail to create a moderator or an admin with a moderator', async function () { + for (const role of [ UserRole.MODERATOR, UserRole.ADMINISTRATOR ]) { + const fields = { ...baseCorrectParams, role } + + await makePostBodyRequest({ + url: server.url, + path, + token: moderatorToken, + fields, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + } + }) + + it('Should succeed to create a user with a moderator', async function () { + const fields = { ...baseCorrectParams, username: 'a4656', email: 'a4656@example.com', role: UserRole.USER } + + await makePostBodyRequest({ + url: server.url, + path, + token: moderatorToken, + fields, + expectedStatus: HttpStatusCode.OK_200 + }) + }) + + it('Should succeed with the correct params', async function () { + await makePostBodyRequest({ + url: server.url, + path, + token: server.accessToken, + fields: baseCorrectParams, + expectedStatus: HttpStatusCode.OK_200 + }) + }) + + it('Should fail with a non admin user', async function () { + const user = { username: 'user1' } + userToken = await server.login.getAccessToken(user) + + const fields = { + username: 'user3', + email: 'test@example.com', + password: 'my super password', + videoQuota: 42000000 + } + await makePostBodyRequest({ url: server.url, path, token: userToken, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + }) + + describe('When getting a user', function () { + + it('Should fail with an non authenticated user', async function () { + await makeGetRequest({ + url: server.url, + path: path + userId, + token: 'super token', + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) + }) + + it('Should fail with a non admin user', async function () { + await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + + it('Should succeed with the correct params', async function () { + await makeGetRequest({ url: server.url, path: path + userId, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) + }) + }) + + describe('When updating a user', function () { + + it('Should fail with an invalid email attribute', async function () { + const fields = { + email: 'blabla' + } + + await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) + }) + + it('Should fail with an invalid emailVerified attribute', async function () { + const fields = { + emailVerified: 'yes' + } + + await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) + }) + + it('Should fail with an invalid videoQuota attribute', async function () { + const fields = { + videoQuota: -90 + } + + await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) + }) + + it('Should fail with an invalid user role attribute', async function () { + const fields = { + role: 54878 + } + + await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) + }) + + it('Should fail with a too small password', async function () { + const fields = { + currentPassword: 'password', + password: 'bla' + } + + await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) + }) + + it('Should fail with a too long password', async function () { + const fields = { + currentPassword: 'password', + password: 'super'.repeat(61) + } + + await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) + }) + + it('Should fail with an non authenticated user', async function () { + const fields = { + videoQuota: 42 + } + + await makePutBodyRequest({ + url: server.url, + path: path + userId, + token: 'super token', + fields, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) + }) + + it('Should fail when updating root role', async function () { + const fields = { + role: UserRole.MODERATOR + } + + await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields }) + }) + + it('Should fail with invalid admin flags', async function () { + const fields = { adminFlags: 'toto' } + + await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail to update an admin with a moderator', async function () { + const fields = { + videoQuota: 42 + } + + await makePutBodyRequest({ + url: server.url, + path: path + moderatorId, + token: moderatorToken, + fields, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should succeed to update a user with a moderator', async function () { + const fields = { + videoQuota: 42 + } + + await makePutBodyRequest({ + url: server.url, + path: path + userId, + token: moderatorToken, + fields, + expectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + }) + + it('Should succeed with the correct params', async function () { + const fields = { + email: 'email@example.com', + emailVerified: true, + videoQuota: 42, + role: UserRole.USER + } + + await makePutBodyRequest({ + url: server.url, + path: path + userId, + token: server.accessToken, + fields, + expectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + }) + }) + + after(async function () { + MockSmtpServer.Instance.kill() + + await cleanupTests([ server ]) + }) +}) 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 @@ import 'mocha' import { omit } from 'lodash' import { - buildAbsoluteFixturePath, - checkBadCountPagination, - checkBadSortPagination, - checkBadStartPagination, cleanupTests, createSingleServer, - killallServers, - makeGetRequest, makePostBodyRequest, - makePutBodyRequest, - makeUploadRequest, MockSmtpServer, PeerTubeServer, - setAccessTokensToServers, - UsersCommand + setAccessTokensToServers } from '@shared/extra-utils' -import { HttpStatusCode, UserAdminFlag, UserRole, VideoCreateResult } from '@shared/models' +import { HttpStatusCode, UserRole } from '@shared/models' describe('Test users API validators', function () { const path = '/api/v1/users/' - let userId: number - let rootId: number - let moderatorId: number - let video: VideoCreateResult let server: PeerTubeServer let serverWithRegistrationDisabled: PeerTubeServer - let userToken = '' - let moderatorToken = '' - let emailPort: number - let overrideConfig: Object // --------------------------------------------------------------- before(async function () { this.timeout(30000) - const emails: object[] = [] - emailPort = await MockSmtpServer.Instance.collectEmails(emails) + const res = await Promise.all([ + createSingleServer(1, { signup: { limit: 3 } }), + createSingleServer(2) + ]) - overrideConfig = { signup: { limit: 8 } } + server = res[0] + serverWithRegistrationDisabled = res[1] - { - const res = await Promise.all([ - createSingleServer(1, overrideConfig), - createSingleServer(2) - ]) + await setAccessTokensToServers([ server ]) - server = res[0] - serverWithRegistrationDisabled = res[1] - - await setAccessTokensToServers([ server ]) - } - - { - const user = { username: 'user1' } - await server.users.create({ ...user }) - userToken = await server.login.getAccessToken(user) - } - - { - const moderator = { username: 'moderator1' } - await server.users.create({ ...moderator, role: UserRole.MODERATOR }) - moderatorToken = await server.login.getAccessToken(moderator) - } - - { - const moderator = { username: 'moderator2' } - await server.users.create({ ...moderator, role: UserRole.MODERATOR }) - } - - { - video = await server.videos.upload() - } - - { - const { data } = await server.users.list() - userId = data.find(u => u.username === 'user1').id - rootId = data.find(u => u.username === 'root').id - moderatorId = data.find(u => u.username === 'moderator2').id - } - }) - - describe('When listing users', function () { - it('Should fail with a bad start pagination', async function () { - await checkBadStartPagination(server.url, path, server.accessToken) - }) - - it('Should fail with a bad count pagination', async function () { - await checkBadCountPagination(server.url, path, server.accessToken) - }) - - it('Should fail with an incorrect sort', async function () { - await checkBadSortPagination(server.url, path, server.accessToken) - }) - - it('Should fail with a non authenticated user', async function () { - await makeGetRequest({ - url: server.url, - path, - expectedStatus: HttpStatusCode.UNAUTHORIZED_401 - }) - }) - - it('Should fail with a non admin user', async function () { - await makeGetRequest({ - url: server.url, - path, - token: userToken, - expectedStatus: HttpStatusCode.FORBIDDEN_403 - }) - }) - }) - - describe('When adding a new user', function () { - const baseCorrectParams = { - username: 'user2', - email: 'test@example.com', - password: 'my super password', - videoQuota: -1, - videoQuotaDaily: -1, - role: UserRole.USER, - adminFlags: UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST - } - - it('Should fail with a too small username', async function () { - const fields = { ...baseCorrectParams, username: '' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with a too long username', async function () { - const fields = { ...baseCorrectParams, username: 'super'.repeat(50) } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with a not lowercase username', async function () { - const fields = { ...baseCorrectParams, username: 'Toto' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with an incorrect username', async function () { - const fields = { ...baseCorrectParams, username: 'my username' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with a missing email', async function () { - const fields = omit(baseCorrectParams, 'email') - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid email', async function () { - const fields = { ...baseCorrectParams, email: 'test_example.com' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with a too small password', async function () { - const fields = { ...baseCorrectParams, password: 'bla' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with a too long password', async function () { - const fields = { ...baseCorrectParams, password: 'super'.repeat(61) } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with empty password and no smtp configured', async function () { - const fields = { ...baseCorrectParams, password: '' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should succeed with no password on a server with smtp enabled', async function () { - this.timeout(20000) - - await killallServers([ server ]) - - const config = { - ...overrideConfig, - - smtp: { - hostname: 'localhost', - port: emailPort - } - } - await server.run(config) - - const fields = { - ...baseCorrectParams, - - password: '', - username: 'create_password', - email: 'create_password@example.com' - } - - await makePostBodyRequest({ - url: server.url, - path: path, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.OK_200 - }) - }) - - it('Should fail with invalid admin flags', async function () { - const fields = { ...baseCorrectParams, adminFlags: 'toto' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with an non authenticated user', async function () { - await makePostBodyRequest({ - url: server.url, - path, - token: 'super token', - fields: baseCorrectParams, - expectedStatus: HttpStatusCode.UNAUTHORIZED_401 - }) - }) - - it('Should fail if we add a user with the same username', async function () { - const fields = { ...baseCorrectParams, username: 'user1' } - - await makePostBodyRequest({ - url: server.url, - path, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.CONFLICT_409 - }) - }) - - it('Should fail if we add a user with the same email', async function () { - const fields = { ...baseCorrectParams, email: 'user1@example.com' } - - await makePostBodyRequest({ - url: server.url, - path, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.CONFLICT_409 - }) - }) - - it('Should fail without a videoQuota', async function () { - const fields = omit(baseCorrectParams, 'videoQuota') - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail without a videoQuotaDaily', async function () { - const fields = omit(baseCorrectParams, 'videoQuotaDaily') - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid videoQuota', async function () { - const fields = { ...baseCorrectParams, videoQuota: -5 } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid videoQuotaDaily', async function () { - const fields = { ...baseCorrectParams, videoQuotaDaily: -7 } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail without a user role', async function () { - const fields = omit(baseCorrectParams, 'role') - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid user role', async function () { - const fields = { ...baseCorrectParams, role: 88989 } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with a "peertube" username', async function () { - const fields = { ...baseCorrectParams, username: 'peertube' } - - await makePostBodyRequest({ - url: server.url, - path, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.CONFLICT_409 - }) - }) - - it('Should fail to create a moderator or an admin with a moderator', async function () { - for (const role of [ UserRole.MODERATOR, UserRole.ADMINISTRATOR ]) { - const fields = { ...baseCorrectParams, role } - - await makePostBodyRequest({ - url: server.url, - path, - token: moderatorToken, - fields, - expectedStatus: HttpStatusCode.FORBIDDEN_403 - }) - } - }) - - it('Should succeed to create a user with a moderator', async function () { - const fields = { ...baseCorrectParams, username: 'a4656', email: 'a4656@example.com', role: UserRole.USER } - - await makePostBodyRequest({ - url: server.url, - path, - token: moderatorToken, - fields, - expectedStatus: HttpStatusCode.OK_200 - }) - }) - - it('Should succeed with the correct params', async function () { - await makePostBodyRequest({ - url: server.url, - path, - token: server.accessToken, - fields: baseCorrectParams, - expectedStatus: HttpStatusCode.OK_200 - }) - }) - - it('Should fail with a non admin user', async function () { - const user = { username: 'user1' } - userToken = await server.login.getAccessToken(user) - - const fields = { - username: 'user3', - email: 'test@example.com', - password: 'my super password', - videoQuota: 42000000 - } - await makePostBodyRequest({ url: server.url, path, token: userToken, fields, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) - }) - }) - - describe('When updating my account', function () { - - it('Should fail with an invalid email attribute', async function () { - const fields = { - email: 'blabla' - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: server.accessToken, fields }) - }) - - it('Should fail with a too small password', async function () { - const fields = { - currentPassword: 'password', - password: 'bla' - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with a too long password', async function () { - const fields = { - currentPassword: 'password', - password: 'super'.repeat(61) - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail without the current password', async function () { - const fields = { - currentPassword: 'password', - password: 'super'.repeat(61) - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with an invalid current password', async function () { - const fields = { - currentPassword: 'my super password fail', - password: 'super'.repeat(61) - } - - await makePutBodyRequest({ - url: server.url, - path: path + 'me', - token: userToken, - fields, - expectedStatus: HttpStatusCode.UNAUTHORIZED_401 - }) - }) - - it('Should fail with an invalid NSFW policy attribute', async function () { - const fields = { - nsfwPolicy: 'hello' - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with an invalid autoPlayVideo attribute', async function () { - const fields = { - autoPlayVideo: -1 - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with an invalid autoPlayNextVideo attribute', async function () { - const fields = { - autoPlayNextVideo: -1 - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with an invalid videosHistoryEnabled attribute', async function () { - const fields = { - videosHistoryEnabled: -1 - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with an non authenticated user', async function () { - const fields = { - currentPassword: 'password', - password: 'my super password' - } - - await makePutBodyRequest({ - url: server.url, - path: path + 'me', - token: 'super token', - fields, - expectedStatus: HttpStatusCode.UNAUTHORIZED_401 - }) - }) - - it('Should fail with a too long description', async function () { - const fields = { - description: 'super'.repeat(201) - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with an invalid videoLanguages attribute', async function () { - { - const fields = { - videoLanguages: 'toto' - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - } - - { - const languages = [] - for (let i = 0; i < 1000; i++) { - languages.push('fr') - } - - const fields = { - videoLanguages: languages - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - } - }) - - it('Should fail with an invalid theme', async function () { - const fields = { theme: 'invalid' } - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with an unknown theme', async function () { - const fields = { theme: 'peertube-theme-unknown' } - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - }) - - it('Should fail with invalid no modal attributes', async function () { - const keys = [ - 'noInstanceConfigWarningModal', - 'noAccountSetupWarningModal', - 'noWelcomeModal' - ] - - for (const key of keys) { - const fields = { - [key]: -1 - } - - await makePutBodyRequest({ url: server.url, path: path + 'me', token: userToken, fields }) - } - }) - - it('Should succeed to change password with the correct params', async function () { - const fields = { - currentPassword: 'password', - password: 'my super password', - nsfwPolicy: 'blur', - autoPlayVideo: false, - email: 'super_email@example.com', - theme: 'default', - noInstanceConfigWarningModal: true, - noWelcomeModal: true, - noAccountSetupWarningModal: true - } - - await makePutBodyRequest({ - url: server.url, - path: path + 'me', - token: userToken, - fields, - expectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - }) - - it('Should succeed without password change with the correct params', async function () { - const fields = { - nsfwPolicy: 'blur', - autoPlayVideo: false - } - - await makePutBodyRequest({ - url: server.url, - path: path + 'me', - token: userToken, - fields, - expectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - }) - }) - - describe('When updating my avatar', function () { - it('Should fail without an incorrect input file', async function () { - const fields = {} - const attaches = { - avatarfile: buildAbsoluteFixturePath('video_short.mp4') - } - await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) - }) - - it('Should fail with a big file', async function () { - const fields = {} - const attaches = { - avatarfile: buildAbsoluteFixturePath('avatar-big.png') - } - await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) - }) - - it('Should fail with an unauthenticated user', async function () { - const fields = {} - const attaches = { - avatarfile: buildAbsoluteFixturePath('avatar.png') - } - await makeUploadRequest({ - url: server.url, - path: path + '/me/avatar/pick', - fields, - attaches, - expectedStatus: HttpStatusCode.UNAUTHORIZED_401 - }) - }) - - it('Should succeed with the correct params', async function () { - const fields = {} - const attaches = { - avatarfile: buildAbsoluteFixturePath('avatar.png') - } - await makeUploadRequest({ - url: server.url, - path: path + '/me/avatar/pick', - token: server.accessToken, - fields, - attaches, - expectedStatus: HttpStatusCode.OK_200 - }) - }) - }) - - describe('When managing my scoped tokens', function () { - - it('Should fail to get my scoped tokens with an non authenticated user', async function () { - await server.users.getMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - }) - - it('Should fail to get my scoped tokens with a bad token', async function () { - await server.users.getMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - - }) - - it('Should succeed to get my scoped tokens', async function () { - await server.users.getMyScopedTokens() - }) - - it('Should fail to renew my scoped tokens with an non authenticated user', async function () { - await server.users.renewMyScopedTokens({ token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - }) - - it('Should fail to renew my scoped tokens with a bad token', async function () { - await server.users.renewMyScopedTokens({ token: 'bad', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - }) - - it('Should succeed to renew my scoped tokens', async function () { - await server.users.renewMyScopedTokens() - }) - }) - - describe('When getting a user', function () { - - it('Should fail with an non authenticated user', async function () { - await makeGetRequest({ - url: server.url, - path: path + userId, - token: 'super token', - expectedStatus: HttpStatusCode.UNAUTHORIZED_401 - }) - }) - - it('Should fail with a non admin user', async function () { - await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) - }) - - it('Should succeed with the correct params', async function () { - await makeGetRequest({ url: server.url, path: path + userId, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) - }) - }) - - describe('When updating a user', function () { - - it('Should fail with an invalid email attribute', async function () { - const fields = { - email: 'blabla' - } - - await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid emailVerified attribute', async function () { - const fields = { - emailVerified: 'yes' - } - - await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid videoQuota attribute', async function () { - const fields = { - videoQuota: -90 - } - - await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid user role attribute', async function () { - const fields = { - role: 54878 - } - - await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) - }) - - it('Should fail with a too small password', async function () { - const fields = { - currentPassword: 'password', - password: 'bla' - } - - await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) - }) - - it('Should fail with a too long password', async function () { - const fields = { - currentPassword: 'password', - password: 'super'.repeat(61) - } - - await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) - }) - - it('Should fail with an non authenticated user', async function () { - const fields = { - videoQuota: 42 - } - - await makePutBodyRequest({ - url: server.url, - path: path + userId, - token: 'super token', - fields, - expectedStatus: HttpStatusCode.UNAUTHORIZED_401 - }) - }) - - it('Should fail when updating root role', async function () { - const fields = { - role: UserRole.MODERATOR - } - - await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields }) - }) - - it('Should fail with invalid admin flags', async function () { - const fields = { adminFlags: 'toto' } - - await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail to update an admin with a moderator', async function () { - const fields = { - videoQuota: 42 - } - - await makePutBodyRequest({ - url: server.url, - path: path + moderatorId, - token: moderatorToken, - fields, - expectedStatus: HttpStatusCode.FORBIDDEN_403 - }) - }) - - it('Should succeed to update a user with a moderator', async function () { - const fields = { - videoQuota: 42 - } - - await makePutBodyRequest({ - url: server.url, - path: path + userId, - token: moderatorToken, - fields, - expectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - }) - - it('Should succeed with the correct params', async function () { - const fields = { - email: 'email@example.com', - emailVerified: true, - videoQuota: 42, - role: UserRole.USER - } - - await makePutBodyRequest({ - url: server.url, - path: path + userId, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - }) - }) - - describe('When getting my information', function () { - it('Should fail with a non authenticated user', async function () { - await server.users.getMyInfo({ token: 'fake_token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - }) - - it('Should success with the correct parameters', async function () { - await server.users.getMyInfo({ token: userToken }) - }) - }) - - describe('When getting my video rating', function () { - let command: UsersCommand - - before(function () { - command = server.users - }) - - it('Should fail with a non authenticated user', async function () { - await command.getMyRating({ token: 'fake_token', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - }) - - it('Should fail with an incorrect video uuid', async function () { - await command.getMyRating({ videoId: 'blabla', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - }) - - it('Should fail with an unknown video', async function () { - await command.getMyRating({ videoId: '4da6fde3-88f7-4d16-b119-108df5630b06', expectedStatus: HttpStatusCode.NOT_FOUND_404 }) - }) - - it('Should succeed with the correct parameters', async function () { - await command.getMyRating({ videoId: video.id }) - await command.getMyRating({ videoId: video.uuid }) - await command.getMyRating({ videoId: video.shortUUID }) - }) - }) - - describe('When retrieving my global ratings', function () { - const path = '/api/v1/accounts/user1/ratings' - - it('Should fail with a bad start pagination', async function () { - await checkBadStartPagination(server.url, path, userToken) - }) - - it('Should fail with a bad count pagination', async function () { - await checkBadCountPagination(server.url, path, userToken) - }) - - it('Should fail with an incorrect sort', async function () { - await checkBadSortPagination(server.url, path, userToken) - }) - - it('Should fail with a unauthenticated user', async function () { - await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - }) - - it('Should fail with a another user', async function () { - await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) - }) - - it('Should fail with a bad type', async function () { - await makeGetRequest({ - url: server.url, - path, - token: userToken, - query: { rating: 'toto ' }, - expectedStatus: HttpStatusCode.BAD_REQUEST_400 - }) - }) - - it('Should succeed with the correct params', async function () { - await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) - }) - }) - - describe('When getting my global followers', function () { - const path = '/api/v1/accounts/user1/followers' - - it('Should fail with a bad start pagination', async function () { - await checkBadStartPagination(server.url, path, userToken) - }) - - it('Should fail with a bad count pagination', async function () { - await checkBadCountPagination(server.url, path, userToken) - }) - - it('Should fail with an incorrect sort', async function () { - await checkBadSortPagination(server.url, path, userToken) - }) - - it('Should fail with a unauthenticated user', async function () { - await makeGetRequest({ url: server.url, path, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) - }) - - it('Should fail with a another user', async function () { - await makeGetRequest({ url: server.url, path, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) - }) - - it('Should succeed with the correct params', async function () { - await makeGetRequest({ url: server.url, path, token: userToken, expectedStatus: HttpStatusCode.OK_200 }) - }) - }) - - describe('When blocking/unblocking/removing user', function () { - - it('Should fail with an incorrect id', async function () { - const options = { userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } - - await server.users.remove(options) - await server.users.banUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - await server.users.unbanUser({ userId: 'blabla' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - }) - - it('Should fail with the root user', async function () { - const options = { userId: rootId, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } - - await server.users.remove(options) - await server.users.banUser(options) - await server.users.unbanUser(options) - }) - - it('Should return 404 with a non existing id', async function () { - const options = { userId: 4545454, expectedStatus: HttpStatusCode.NOT_FOUND_404 } - - await server.users.remove(options) - await server.users.banUser(options) - await server.users.unbanUser(options) - }) - - it('Should fail with a non admin user', async function () { - const options = { userId, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } - - await server.users.remove(options) - await server.users.banUser(options) - await server.users.unbanUser(options) - }) - - it('Should fail on a moderator with a moderator', async function () { - const options = { userId: moderatorId, token: moderatorToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } - - await server.users.remove(options) - await server.users.banUser(options) - await server.users.unbanUser(options) - }) - - it('Should succeed on a user with a moderator', async function () { - const options = { userId, token: moderatorToken } - - await server.users.banUser(options) - await server.users.unbanUser(options) - }) - }) - - describe('When deleting our account', function () { - it('Should fail with with the root account', async function () { - await server.users.deleteMe({ expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - }) + await server.users.generate('moderator2', UserRole.MODERATOR) }) describe('When registering a new user', function () { @@ -1081,9 +192,11 @@ describe('Test users API validators', function () { }) describe('When registering multiple users on a server with users limit', function () { + it('Should fail when after 3 registrations', async function () { await server.users.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) + }) 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 () { }) expect(total).to.equal(3) + expect(data).to.have.lengthOf(3) + expect(data[0].following.host).to.equal(servers[2].host) expect(data[0].following.name).to.equal('user3_channel') expect(data[0].follower.host).to.equal(servers[0].host) @@ -416,6 +418,8 @@ describe('Test users subscriptions', function () { }) expect(total).to.equal(3) + expect(data).to.have.lengthOf(1) + expect(data[0].following.host).to.equal(servers[2].host) expect(data[0].following.name).to.equal('user3_channel2') expect(data[0].follower.host).to.equal(servers[2].host) @@ -432,6 +436,8 @@ describe('Test users subscriptions', function () { }) expect(total).to.equal(3) + expect(data).to.have.lengthOf(1) + expect(data[0].following.host).to.equal(servers[2].host) expect(data[0].following.name).to.equal('user3_channel') expect(data[0].follower.host).to.equal(servers[2].host) @@ -447,6 +453,8 @@ describe('Test users subscriptions', function () { }) expect(total).to.equal(1) + expect(data).to.have.lengthOf(1) + expect(data[0].following.host).to.equal(servers[2].host) expect(data[0].following.name).to.equal('user3_channel') expect(data[0].follower.host).to.equal(servers[0].host) @@ -465,6 +473,8 @@ describe('Test users subscriptions', function () { }) expect(total).to.equal(2) + expect(data).to.have.lengthOf(2) + expect(data[0].following.host).to.equal(servers[2].host) expect(data[0].following.name).to.equal('user3_channel') expect(data[0].follower.host).to.equal(servers[0].host) @@ -486,6 +496,8 @@ describe('Test users subscriptions', function () { }) expect(total).to.equal(2) + expect(data).to.have.lengthOf(1) + expect(data[0].following.host).to.equal(servers[2].host) expect(data[0].following.name).to.equal('user3_channel') expect(data[0].follower.host).to.equal(servers[2].host) @@ -502,10 +514,12 @@ describe('Test users subscriptions', function () { }) expect(total).to.equal(2) + expect(data).to.have.lengthOf(1) + expect(data[0].following.host).to.equal(servers[2].host) expect(data[0].following.name).to.equal('user3_channel') expect(data[0].follower.host).to.equal(servers[0].host) - expect(data[0].follower.name).to.equal('root') + expect(data[0].follower.name).to.equal('user1') } { @@ -517,6 +531,8 @@ describe('Test users subscriptions', function () { }) expect(total).to.equal(1) + expect(data).to.have.lengthOf(1) + expect(data[0].following.host).to.equal(servers[2].host) expect(data[0].following.name).to.equal('user3_channel') 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 { })).then(res => res.user) } - async generate (username: string) { + async generate (username: string, role?: UserRole) { const password = 'password' - const user = await this.create({ username, password }) + const user = await this.create({ username, password, role }) const token = await this.server.login.getAccessToken({ username, password }) @@ -206,9 +206,9 @@ export class UsersCommand extends AbstractCommand { } } - async generateUserAndToken (username: string) { + async generateUserAndToken (username: string, role?: UserRole) { const password = 'password' - await this.create({ username, password }) + await this.create({ username, password, role }) return this.server.login.getAccessToken({ username, password }) } 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: print(json) + '/accounts/{name}/followers': + get: + tags: + - Accounts + summary: 'List followers of an account' + security: + - OAuth2: [] + operationId: getAccountFollowers + parameters: + - $ref: '#/components/parameters/name' + - $ref: '#/components/parameters/start' + - $ref: '#/components/parameters/count' + - $ref: '#/components/parameters/followersSort' + - $ref: '#/components/parameters/search' + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + total: + type: integer + example: 1 + data: + type: array + items: + $ref: '#/components/schemas/Follow' + /accounts: get: tags: @@ -2832,6 +2862,36 @@ paths: schema: $ref: '#/components/schemas/VideoListResponse' + '/video-channels/{channelHandle}/followers': + get: + tags: + - Video Channels + summary: 'List followers of a video channel' + security: + - OAuth2: [] + operationId: getVideoChannelFollowers + parameters: + - $ref: '#/components/parameters/channelHandle' + - $ref: '#/components/parameters/start' + - $ref: '#/components/parameters/count' + - $ref: '#/components/parameters/followersSort' + - $ref: '#/components/parameters/search' + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + total: + type: integer + example: 1 + data: + type: array + items: + $ref: '#/components/schemas/Follow' + '/video-channels/{channelHandle}/avatar/pick': post: summary: Update channel avatar @@ -4532,6 +4592,15 @@ components: type: string enum: - name + followersSort: + name: sort + in: query + required: false + description: Sort followers by criteria + schema: + type: string + enum: + - createdAt name: name: name in: path -- 2.41.0