diff --git a/packages/server/api/src/app/benchmark/create-benchmark.service.ts b/packages/server/api/src/app/benchmark/create-benchmark.service.ts new file mode 100644 index 0000000000..25ee8a251d --- /dev/null +++ b/packages/server/api/src/app/benchmark/create-benchmark.service.ts @@ -0,0 +1,58 @@ +import { + BenchmarkCreationResult, + BenchmarkProviders, + ContentType, + Folder, + openOpsId, +} from '@openops/shared'; +import { flowFolderService } from '../flows/folder/folder.service'; +import { throwValidationError } from './errors'; + +function getBenchmarkFolderDisplayName(provider: string): string { + const normalizedProvider = provider.toLowerCase(); + switch (normalizedProvider) { + case BenchmarkProviders.AWS: + return 'AWS Benchmark'; + default: + throwValidationError(`Unknown provider: ${provider}`); + } +} + +async function ensureBenchmarkFolder( + projectId: string, + displayName: string, +): Promise { + return flowFolderService.getOrCreate({ + projectId, + request: { + displayName, + contentType: ContentType.WORKFLOW, + }, + }); +} + +export async function createBenchmark(params: { + provider: string; + projectId: string; +}): Promise { + const { provider, projectId } = params; + + const benchmarkFolder = await ensureBenchmarkFolder( + projectId, + getBenchmarkFolderDisplayName(provider), + ); + + return { + benchmarkId: openOpsId(), + folderId: benchmarkFolder.id, + provider, + workflows: [], + webhookPayload: { + webhookBaseUrl: '', + workflows: [], + cleanupWorkflows: [], + accounts: [], + regions: [], + }, + }; +} diff --git a/packages/server/api/src/app/flows/folder/folder.service.ts b/packages/server/api/src/app/flows/folder/folder.service.ts index 2478a91afd..65fb997603 100644 --- a/packages/server/api/src/app/flows/folder/folder.service.ts +++ b/packages/server/api/src/app/flows/folder/folder.service.ts @@ -86,36 +86,22 @@ export const flowFolderService = { params: { folderName: request.displayName }, }); } - - const folderId = openOpsId(); - const parentFolder = await flowFolderService.getParentFolder( - projectId, - request.parentFolderId, - ); - - await folderRepo().upsert( + return createFolder(params); + }, + async getOrCreate(params: UpsertParams): Promise { + const { projectId, request } = params; + const requestContentType = request.contentType ?? ContentType.WORKFLOW; + const folderWithDisplayName = await this.getOneByDisplayNameCaseInsensitive( { - id: folderId, projectId, - parentFolder, displayName: request.displayName, contentType: requestContentType, }, - ['projectId', 'contentType', 'displayName'], ); - - const folder = await folderRepo().findOneByOrFail({ - projectId, - id: folderId, - }); - - return { - ...folder, - numberOfFlows: 0, - flows: undefined, - subfolders: undefined, - parentFolderId: request.parentFolderId, - }; + if (!isNil(folderWithDisplayName)) { + return folderWithDisplayName; + } + return createFolder(params); }, async getParentFolder( projectId: string, @@ -225,6 +211,41 @@ export const flowFolderService = { }, }; +async function createFolder(params: UpsertParams): Promise { + const { projectId, request } = params; + const requestContentType = request.contentType ?? ContentType.WORKFLOW; + + const folderId = openOpsId(); + const parentFolder = await flowFolderService.getParentFolder( + projectId, + request.parentFolderId, + ); + + await folderRepo().upsert( + { + id: folderId, + projectId, + parentFolder, + displayName: request.displayName, + contentType: requestContentType, + }, + ['projectId', 'contentType', 'displayName'], + ); + + const folder = await folderRepo().findOneByOrFail({ + projectId, + id: folderId, + }); + + return { + ...folder, + numberOfFlows: 0, + flows: undefined, + subfolders: undefined, + parentFolderId: request.parentFolderId, + }; +} + type DeleteParams = { projectId: ProjectId; folderId: FolderId; diff --git a/packages/server/api/test/unit/benchmark/create-benchmark.service.test.ts b/packages/server/api/test/unit/benchmark/create-benchmark.service.test.ts new file mode 100644 index 0000000000..11423b61c7 --- /dev/null +++ b/packages/server/api/test/unit/benchmark/create-benchmark.service.test.ts @@ -0,0 +1,88 @@ +import { ContentType, type Folder } from '@openops/shared'; +import { createBenchmark } from '../../../src/app/benchmark/create-benchmark.service'; +import { flowFolderService } from '../../../src/app/flows/folder/folder.service'; + +jest.mock('../../../src/app/flows/folder/folder.service', () => ({ + flowFolderService: { + getOrCreate: jest.fn(), + }, +})); + +const flowFolderServiceMock = flowFolderService as jest.Mocked< + typeof flowFolderService +>; + +describe('create-benchmark.service', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('createBenchmark with provider aws calls getOrCreate with displayName AWS Benchmark', async () => { + const projectId = 'project-1'; + const folder: Folder = { + id: 'folder-1', + projectId, + displayName: 'AWS Benchmark', + created: '', + updated: '', + contentType: ContentType.WORKFLOW, + }; + flowFolderServiceMock.getOrCreate.mockResolvedValue(folder); + + await createBenchmark({ provider: 'aws', projectId }); + + expect(flowFolderServiceMock.getOrCreate).toHaveBeenCalledWith({ + projectId, + request: { + displayName: 'AWS Benchmark', + contentType: ContentType.WORKFLOW, + }, + }); + }); + + it('createBenchmark throws for unknown provider', async () => { + const projectId = 'project-1'; + await expect( + createBenchmark({ provider: 'gcp', projectId }), + ).rejects.toThrow('Unknown provider: gcp'); + expect(flowFolderServiceMock.getOrCreate).not.toHaveBeenCalled(); + }); + + it('createBenchmark returns BenchmarkCreationResult', async () => { + const projectId = 'project-1'; + const folder: Folder = { + id: 'folder-2', + projectId, + displayName: 'AWS Benchmark', + created: '', + updated: '', + contentType: ContentType.WORKFLOW, + }; + + flowFolderServiceMock.getOrCreate.mockResolvedValue(folder); + + const result = await createBenchmark({ + provider: 'aws', + projectId, + }); + + expect(flowFolderServiceMock.getOrCreate).toHaveBeenCalledWith({ + projectId, + request: { + displayName: 'AWS Benchmark', + contentType: ContentType.WORKFLOW, + }, + }); + expect(result.folderId).toBe(folder.id); + expect(result.workflows).toEqual([]); + expect(result.benchmarkId).toBeDefined(); + expect(result.provider).toBe('aws'); + expect(result.webhookPayload).toEqual({ + webhookBaseUrl: '', + workflows: [], + cleanupWorkflows: [], + accounts: [], + regions: [], + }); + }); +});