diff --git a/packages/backend/src/controllers/api/v1/admin/templates/create-template.ee.test.js b/packages/backend/src/controllers/api/v1/admin/templates/create-template.ee.test.js new file mode 100644 index 00000000..08a89d3c --- /dev/null +++ b/packages/backend/src/controllers/api/v1/admin/templates/create-template.ee.test.js @@ -0,0 +1,135 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import Crypto from 'crypto'; +import app from '../../../../../app.js'; +import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; +import Template from '../../../../../models/template.ee.js'; +import { createRole } from '../../../../../../test/factories/role.js'; +import { createUser } from '../../../../../../test/factories/user.js'; +import { createFlow } from '../../../../../../test/factories/flow.js'; +import { createStep } from '../../../../../../test/factories/step.js'; +import createTemplateMock from '../../../../../../test/mocks/rest/api/v1/admin/templates/create-template.ee.js'; +import * as license from '../../../../../helpers/license.ee.js'; + +describe('POST /api/v1/admin/templates', () => { + let currentUser, token, role; + + beforeEach(async () => { + vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); + + role = await createRole({ name: 'Admin' }); + currentUser = await createUser({ roleId: role.id }); + + token = await createAuthTokenByUserId(currentUser.id); + }); + + it('should return the created template', async () => { + const currentUserFlow = await createFlow({ userId: currentUser.id }); + + const triggerStep = await createStep({ + flowId: currentUserFlow.id, + type: 'trigger', + appKey: 'webhook', + key: 'catchRawWebhook', + name: 'Catch raw webhook', + parameters: { + workSynchronously: true, + }, + position: 1, + webhookPath: `/webhooks/flows/${currentUserFlow.id}/sync`, + }); + + await createStep({ + flowId: currentUserFlow.id, + type: 'action', + appKey: 'formatter', + key: 'text', + name: 'Text', + parameters: { + input: `hello {{step.${triggerStep.id}.query.sample}} world`, + transform: 'capitalize', + }, + position: 2, + }); + + const templatePayload = { + name: 'Sample Template Name', + flowId: currentUserFlow.id, + }; + + const response = await request(app) + .post('/api/v1/admin/templates') + .set('Authorization', token) + .send(templatePayload) + .expect(201); + + const refetchedTemplate = await Template.query().findById( + response.body.data.id + ); + + const expectedPayload = await createTemplateMock(refetchedTemplate); + + expect(response.body).toStrictEqual(expectedPayload); + }); + + it('should return not found response for invalid flow ID', async () => { + const invalidFlowId = Crypto.randomUUID(); + + await request(app) + .post('/api/v1/admin/templates') + .set('Authorization', token) + .send({ + name: 'Sample Template Name', + flowId: invalidFlowId, + }) + .expect(404); + }); + + it('should return unprocessable entity response for invalid name', async () => { + const currentUserFlow = await createFlow({ userId: currentUser.id }); + + const triggerStep = await createStep({ + flowId: currentUserFlow.id, + type: 'trigger', + appKey: 'webhook', + key: 'catchRawWebhook', + name: 'Catch raw webhook', + parameters: { + workSynchronously: true, + }, + position: 1, + webhookPath: `/webhooks/flows/${currentUserFlow.id}/sync`, + }); + + await createStep({ + flowId: currentUserFlow.id, + type: 'action', + appKey: 'formatter', + key: 'text', + name: 'Text', + parameters: { + input: `hello {{step.${triggerStep.id}.query.sample}} world`, + transform: 'capitalize', + }, + position: 2, + }); + + const templatePayload = { + name: '', + flowId: currentUserFlow.id, + }; + + const response = await request(app) + .post('/api/v1/admin/templates') + .set('Authorization', token) + .send(templatePayload) + .expect(422); + + expect(response.body).toStrictEqual({ + errors: { + name: ['must NOT have fewer than 1 characters'], + }, + meta: { type: 'ModelValidation' }, + }); + }); +}); diff --git a/packages/backend/src/models/template.ee.js b/packages/backend/src/models/template.ee.js index aad0192c..26f87313 100644 --- a/packages/backend/src/models/template.ee.js +++ b/packages/backend/src/models/template.ee.js @@ -17,7 +17,7 @@ class Template extends Base { }, }; - static async create(name, flowId) { + static async create({ name, flowId }) { const flow = await Flow.query().findById(flowId).throwIfNotFound(); const flowData = await flow.export(); diff --git a/packages/backend/src/models/template.ee.test.js b/packages/backend/src/models/template.ee.test.js index ce85e336..9ebb4daf 100644 --- a/packages/backend/src/models/template.ee.test.js +++ b/packages/backend/src/models/template.ee.test.js @@ -19,7 +19,7 @@ describe('Template model', () => { const templateName = 'Test Template'; await expect( - Template.create(templateName, nonExistentFlowId) + Template.create({ name: templateName, flowId: nonExistentFlowId }) ).rejects.toThrowError('NotFoundError'); }); @@ -27,7 +27,10 @@ describe('Template model', () => { const flow = await createFlow(); const templateName = 'Test Template'; - const template = await Template.create(templateName, flow.id); + const template = await Template.create({ + name: templateName, + flowId: flow.id, + }); expect(template.name).toStrictEqual(templateName); }); @@ -54,7 +57,10 @@ describe('Template model', () => { }); const templateName = 'Test Template'; - const template = await Template.create(templateName, flow.id); + const template = await Template.create({ + name: templateName, + flowId: flow.id, + }); const exportedFlowData = await flow.export(); diff --git a/packages/backend/src/routes/api/v1/admin/templates.ee.js b/packages/backend/src/routes/api/v1/admin/templates.ee.js index 749d202d..34b5c137 100644 --- a/packages/backend/src/routes/api/v1/admin/templates.ee.js +++ b/packages/backend/src/routes/api/v1/admin/templates.ee.js @@ -1,11 +1,18 @@ import { Router } from 'express'; import { authenticateUser } from '../../../../helpers/authentication.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js'; +import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import createTemplateAction from '../../../../controllers/api/v1/admin/templates/create-template.ee.js'; const router = Router(); -router.post('/', authenticateUser, authorizeAdmin, createTemplateAction); +router.post( + '/', + authenticateUser, + authorizeAdmin, + checkIsEnterprise, + createTemplateAction +); export default router; diff --git a/packages/backend/test/mocks/rest/api/v1/admin/templates/create-template.ee.js b/packages/backend/test/mocks/rest/api/v1/admin/templates/create-template.ee.js new file mode 100644 index 00000000..2081a5b9 --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/admin/templates/create-template.ee.js @@ -0,0 +1,22 @@ +const createTemplateMock = async (template) => { + const data = { + id: template.id, + name: template.name, + createdAt: template.createdAt.getTime(), + updatedAt: template.updatedAt.getTime(), + flowData: template.flowData, + }; + + return { + data: data, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'Template', + }, + }; +}; + +export default createTemplateMock;