From 369f04fdbcf8b6e233528281a2486deb4fe52ac3 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 11 Sep 2024 13:06:39 +0000 Subject: [PATCH] feat: write REST API endpoint to register user --- .../api/v1/users/register-user.ee.js | 18 ++++ .../api/v1/users/register-user.ee.test.js | 96 +++++++++++++++++++ packages/backend/src/models/user.js | 15 +++ packages/backend/src/routes/api/v1/users.js | 2 + .../rest/api/v1/users/register-user.ee.js | 29 ++++++ 5 files changed, 160 insertions(+) create mode 100644 packages/backend/src/controllers/api/v1/users/register-user.ee.js create mode 100644 packages/backend/src/controllers/api/v1/users/register-user.ee.test.js create mode 100644 packages/backend/test/mocks/rest/api/v1/users/register-user.ee.js diff --git a/packages/backend/src/controllers/api/v1/users/register-user.ee.js b/packages/backend/src/controllers/api/v1/users/register-user.ee.js new file mode 100644 index 00000000..6ab54021 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/register-user.ee.js @@ -0,0 +1,18 @@ +import User from '../../../../models/user.js'; +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + const user = await User.registerUser(userParams(request)); + + renderObject(response, user, { status: 201 }); +}; + +const userParams = (request) => { + const { fullName, email, password } = request.body; + + return { + fullName, + email, + password, + }; +}; diff --git a/packages/backend/src/controllers/api/v1/users/register-user.ee.test.js b/packages/backend/src/controllers/api/v1/users/register-user.ee.test.js new file mode 100644 index 00000000..c8999c50 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/register-user.ee.test.js @@ -0,0 +1,96 @@ +import { beforeEach, describe, it, expect, vi } from 'vitest'; +import request from 'supertest'; +import app from '../../../../app.js'; +import User from '../../../../models/user.js'; +import appConfig from '../../../../config/app.js'; +import { createUser } from '../../../../../test/factories/user.js'; +import { createRole } from '../../../../../test/factories/role.js'; +import registerUserMock from '../../../../../test/mocks/rest/api/v1/users/register-user.ee.js'; + +describe('POST /api/v1/users/register', () => { + beforeEach(async () => { + vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); + }); + + it('should return registered user with valid data', async () => { + await createRole({ name: 'User' }); + + const userData = { + email: 'registered@sample.com', + fullName: 'Full Name', + password: 'samplePassword123', + }; + + const response = await request(app) + .post('/api/v1/users/register') + .send(userData) + .expect(201); + + const refetchedRegisteredUser = await User.query() + .findById(response.body.data.id) + .throwIfNotFound(); + + const expectedPayload = registerUserMock(refetchedRegisteredUser); + + expect(response.body).toStrictEqual(expectedPayload); + }); + + it('should return not found response without user role existing', async () => { + const userData = { + email: 'registered@sample.com', + fullName: 'Full Name', + password: 'samplePassword123', + }; + + await request(app) + .post('/api/v1/users/register') + .send(userData) + .expect(404); + }); + + it('should return unprocessable entity respones with already used email', async () => { + await createRole({ name: 'User' }); + await createUser({ + email: 'registered@sample.com', + }); + + const userData = { + email: 'registered@sample.com', + fullName: 'Full Name', + password: 'samplePassword123', + }; + + const response = await request(app) + .post('/api/v1/users/register') + .send(userData) + .expect(422); + + expect(response.body.errors).toStrictEqual({ + email: ["'email' must be unique."], + }); + + expect(response.body.meta).toStrictEqual({ + type: 'UniqueViolationError', + }); + }); + + it('should return unprocessable entity response with invalid user data', async () => { + await createRole({ name: 'User' }); + + const userData = { + email: null, + fullName: null, + }; + + const response = await request(app) + .post('/api/v1/users/register') + .send(userData) + .expect(422); + + expect(response.body.meta.type).toStrictEqual('ModelValidation'); + expect(response.body.errors).toStrictEqual({ + email: ['must be string'], + fullName: ['must be string'], + }); + }); +}); diff --git a/packages/backend/src/models/user.js b/packages/backend/src/models/user.js index 58ecb533..aafbd010 100644 --- a/packages/backend/src/models/user.js +++ b/packages/backend/src/models/user.js @@ -534,6 +534,21 @@ class User extends Base { return adminUser; } + static async registerUser(userData) { + const { fullName, email, password } = userData; + + const role = await Role.query().findOne({ name: 'User' }).throwIfNotFound(); + + const user = await User.query().insertAndFetch({ + fullName, + email, + password, + roleId: role.id, + }); + + return user; + } + async $beforeInsert(queryContext) { await super.$beforeInsert(queryContext); diff --git a/packages/backend/src/routes/api/v1/users.js b/packages/backend/src/routes/api/v1/users.js index 672b7d35..3619a96c 100644 --- a/packages/backend/src/routes/api/v1/users.js +++ b/packages/backend/src/routes/api/v1/users.js @@ -14,6 +14,7 @@ import getPlanAndUsageAction from '../../../controllers/api/v1/users/get-plan-an import acceptInvitationAction from '../../../controllers/api/v1/users/accept-invitation.js'; import forgotPasswordAction from '../../../controllers/api/v1/users/forgot-password.js'; import resetPasswordAction from '../../../controllers/api/v1/users/reset-password.js'; +import registerUserAction from '../../../controllers/api/v1/users/register-user.ee.js'; const router = Router(); @@ -54,5 +55,6 @@ router.get( router.post('/invitation', acceptInvitationAction); router.post('/forgot-password', forgotPasswordAction); router.post('/reset-password', resetPasswordAction); +router.post('/register', checkIsCloud, registerUserAction); export default router; diff --git a/packages/backend/test/mocks/rest/api/v1/users/register-user.ee.js b/packages/backend/test/mocks/rest/api/v1/users/register-user.ee.js new file mode 100644 index 00000000..cba1d815 --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/users/register-user.ee.js @@ -0,0 +1,29 @@ +import appConfig from '../../../../../../src/config/app.js'; + +const registerUserMock = (user) => { + const userData = { + createdAt: user.createdAt.getTime(), + email: user.email, + fullName: user.fullName, + id: user.id, + status: user.status, + updatedAt: user.updatedAt.getTime(), + }; + + if (appConfig.isCloud && user.trialExpiryDate) { + userData.trialExpiryDate = user.trial; + } + + return { + data: userData, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'User', + }, + }; +}; + +export default registerUserMock;