From b15ad10a55e4bfaa8e8498f99ee823804c055171 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 24 Apr 2025 12:56:36 +0000 Subject: [PATCH] feat(api): add create folder endpoint for users --- .../api/v1/users/create-folder.ee.js | 14 ++++ .../api/v1/users/create-folder.ee.test.js | 65 +++++++++++++++++++ .../api/v1/folders/create-folder.test.js | 2 +- .../models/__snapshots__/folder.test.js.snap | 3 + packages/backend/src/models/folder.js | 2 + .../backend/src/routes/api/v1/users.ee.js | 2 + .../mocks/rest/api/v1/users/create-folder.js | 21 ++++++ packages/backend/vitest.config.js | 4 +- 8 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 packages/backend/src/controllers/api/v1/users/create-folder.ee.js create mode 100644 packages/backend/src/controllers/api/v1/users/create-folder.ee.test.js create mode 100644 packages/backend/test/mocks/rest/api/v1/users/create-folder.js diff --git a/packages/backend/src/controllers/api/v1/users/create-folder.ee.js b/packages/backend/src/controllers/api/v1/users/create-folder.ee.js new file mode 100644 index 00000000..fbfeb955 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/create-folder.ee.js @@ -0,0 +1,14 @@ +import User from '../../../../models/user.js'; +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + const user = await User.query() + .findById(request.params.userId) + .throwIfNotFound(); + + const folder = await user.$relatedQuery('folders').insertAndFetch({ + name: request.body.name, + }); + + renderObject(response, folder, { status: 201 }); +}; diff --git a/packages/backend/src/controllers/api/v1/users/create-folder.ee.test.js b/packages/backend/src/controllers/api/v1/users/create-folder.ee.test.js new file mode 100644 index 00000000..0938aa55 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/create-folder.ee.test.js @@ -0,0 +1,65 @@ +import Crypto from 'node:crypto'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import app from '../../../../app.js'; +import { createApiToken } from '../../../../../test/factories/api-token.js'; +import { createUser } from '../../../../../test/factories/user.js'; +import createFolderMock from '../../../../../test/mocks/rest/api/v1/users/create-folder.js'; +import * as license from '../../../../helpers/license.ee.js'; + +describe('POST /api/v1/users/:userId/folders', () => { + let currentUser, token; + + beforeEach(async () => { + vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); + currentUser = await createUser(); + + token = (await createApiToken()).token; + }); + + it('should return created folder for the given user', async () => { + const response = await request(app) + .post(`/api/v1/users/${currentUser.id}/folders`) + .set('x-api-token', token) + .send({ + name: 'Test Folder', + }) + .expect(201); + + const refetchedFolder = await currentUser + .$relatedQuery('folders') + .findById(response.body.data.id); + + const expectedPayload = await createFolderMock(refetchedFolder); + + expect(response.body).toMatchObject(expectedPayload); + }); + + it('should return unprocessable entity response for invalid data', async () => { + const response = await request(app) + .post(`/api/v1/users/${currentUser.id}/folders`) + .set('x-api-token', token) + .send({}) + .expect(422); + + expect(response.body.errors.name).toStrictEqual([ + "must have required property 'name'", + ]); + }); + + it('should respond with HTTP 404 for non-existent user', async () => { + const notExistingUserId = Crypto.randomUUID(); + + await request(app) + .get(`/api/v1/users/${notExistingUserId}/folders`) + .set('x-api-token', token) + .expect(404); + }); + + it('should return bad request response for invalid user UUID', async () => { + await request(app) + .get(`/api/v1/users/invalidUserUUID/folders`) + .set('x-api-token', token) + .expect(400); + }); +}); diff --git a/packages/backend/src/controllers/internal/api/v1/folders/create-folder.test.js b/packages/backend/src/controllers/internal/api/v1/folders/create-folder.test.js index 00d5ae27..51a11e78 100644 --- a/packages/backend/src/controllers/internal/api/v1/folders/create-folder.test.js +++ b/packages/backend/src/controllers/internal/api/v1/folders/create-folder.test.js @@ -16,7 +16,7 @@ describe('POST /internal/api/v1/folders', () => { token = await createAuthTokenByUserId(currentUser.id); }); - it('should return created flow', async () => { + it('should return created folder', async () => { await createPermission({ action: 'manage', subject: 'Flow', diff --git a/packages/backend/src/models/__snapshots__/folder.test.js.snap b/packages/backend/src/models/__snapshots__/folder.test.js.snap index ede97dfe..61e21305 100644 --- a/packages/backend/src/models/__snapshots__/folder.test.js.snap +++ b/packages/backend/src/models/__snapshots__/folder.test.js.snap @@ -22,6 +22,9 @@ exports[`Folder model > jsonSchema should have correct validations 1`] = ` "type": "string", }, }, + "required": [ + "name", + ], "type": "object", } `; diff --git a/packages/backend/src/models/folder.js b/packages/backend/src/models/folder.js index a59c0525..c45f8de4 100644 --- a/packages/backend/src/models/folder.js +++ b/packages/backend/src/models/folder.js @@ -7,6 +7,8 @@ class Folder extends Base { static jsonSchema = { type: 'object', + required: ['name'], + properties: { id: { type: 'string', format: 'uuid' }, name: { type: 'string', minLength: 1 }, diff --git a/packages/backend/src/routes/api/v1/users.ee.js b/packages/backend/src/routes/api/v1/users.ee.js index f82b361c..b8ac2fed 100644 --- a/packages/backend/src/routes/api/v1/users.ee.js +++ b/packages/backend/src/routes/api/v1/users.ee.js @@ -1,8 +1,10 @@ import { Router } from 'express'; import getFoldersAction from '../../../controllers/api/v1/users/get-folders.ee.js'; +import createFolderAction from '../../../controllers/api/v1/users/create-folder.ee.js'; const router = Router(); router.get('/:userId/folders', getFoldersAction); +router.post('/:userId/folders', createFolderAction); export default router; diff --git a/packages/backend/test/mocks/rest/api/v1/users/create-folder.js b/packages/backend/test/mocks/rest/api/v1/users/create-folder.js new file mode 100644 index 00000000..8967a493 --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/users/create-folder.js @@ -0,0 +1,21 @@ +const createFolderMock = async (flow) => { + const data = { + id: flow.id, + name: flow.name, + createdAt: flow.createdAt.getTime(), + updatedAt: flow.updatedAt.getTime(), + }; + + return { + data: data, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'Folder', + }, + }; +}; + +export default createFolderMock; diff --git a/packages/backend/vitest.config.js b/packages/backend/vitest.config.js index a0336845..739e2a23 100644 --- a/packages/backend/vitest.config.js +++ b/packages/backend/vitest.config.js @@ -29,8 +29,8 @@ export default defineConfig({ thresholds: { autoUpdate: true, statements: 99.39, - branches: 98.28, - functions: 99.03, + branches: 98.29, + functions: 99.04, lines: 99.39, }, },