From 91a171434139f021136ed5a82700613f9781188b Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Thu, 19 Jun 2025 19:57:11 +0200 Subject: [PATCH 1/4] feat: add API Response types for workflows --- src/types.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/types.ts b/src/types.ts index d366bdc..4a61acf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -141,6 +141,14 @@ export type APICheckItem = { updated_at: string; }; +/** + * Workflow Schema + */ +export interface APIWorkflowItem { + id: string; + description: string; +} + /** * Error object as defined in the OpenAPI schema */ From c11e723deccdd7fef9d18d87df64ab39a8f4c916 Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Thu, 19 Jun 2025 19:57:34 +0200 Subject: [PATCH 2/4] test: include workflows in fixtures --- src/__tests__/fixtures.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts index 72751f8..786e7de 100644 --- a/src/__tests__/fixtures.ts +++ b/src/__tests__/fixtures.ts @@ -1,4 +1,4 @@ -import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem } from '../types.js' +import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem, APIWorkflowItem } from '../types.js' export const mockApiHealthResponse: APIHealthResponse = { status: 'ok', @@ -121,3 +121,8 @@ export const mockAPICheckResponse: APICheckItem[] = [{ created_at: '2025-02-21T18:53:00.485Z', updated_at: '2025-02-21T18:53:00.485Z' }] + +export const mockAPIWorkflowResponse: APIWorkflowItem[] = [{ + id: 'update-stuff', + description: 'Test workflow description' +}] From f0984200cd97cb3dc3186b0d2d122dfbf756db18 Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Thu, 19 Jun 2025 19:58:00 +0200 Subject: [PATCH 3/4] feat: add api handler for workflows --- src/api-client.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/api-client.ts b/src/api-client.ts index d0485fd..99038bc 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -1,6 +1,6 @@ import { getConfig } from './utils.js' import { got } from 'got' -import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem } from './types.js' +import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem, APIWorkflowItem } from './types.js' export const apiClient = () => { const config = getConfig() @@ -70,3 +70,12 @@ export const getAllChecks = async (): Promise => { } return response.body as APICheckItem[] } + +export const getAllWorkflows = async (): Promise => { + const client = apiClient() + const response = await client.get('workflow', { responseType: 'json' }) + if (response.statusCode !== 200) { + throw new Error(`Failed to get the data from the API: ${response.statusCode} ${response.body}`) + } + return response.body as APIWorkflowItem[] +} From 1aa8fe16b4de3b04df3046a07687a9b483b1efe2 Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Thu, 19 Jun 2025 19:58:39 +0200 Subject: [PATCH 4/4] feat: add command `workflow list` --- src/__tests__/cli-commands.test.ts | 104 ++++++++++++++++++++++++++++- src/cli-commands.ts | 29 +++++++- src/index.ts | 14 +++- 3 files changed, 142 insertions(+), 5 deletions(-) diff --git a/src/__tests__/cli-commands.test.ts b/src/__tests__/cli-commands.test.ts index 155bf8c..352361d 100644 --- a/src/__tests__/cli-commands.test.ts +++ b/src/__tests__/cli-commands.test.ts @@ -1,9 +1,9 @@ /* eslint-env jest */ -import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists, printChecks } from '../cli-commands.js' +import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists, printChecks, printWorkflows } from '../cli-commands.js' import { getPackageJson } from '../utils.js' -import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIErrorResponse, APIChecklistItem, APICheckItem } from '../types.js' -import { mockApiHealthResponse, mockAPIProjectResponse, mockAPIGithubOrgResponse, mockAPIChecklistResponse, mockAPICheckResponse } from './fixtures.js' +import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIErrorResponse, APIChecklistItem, APICheckItem, APIWorkflowItem } from '../types.js' +import { mockApiHealthResponse, mockAPIProjectResponse, mockAPIGithubOrgResponse, mockAPIChecklistResponse, mockAPICheckResponse, mockAPIWorkflowResponse } from './fixtures.js' import nock from 'nock' const pkg = getPackageJson() @@ -380,4 +380,102 @@ describe('CLI Commands', () => { expect(result.messages[0]).toBe('No compliance checks found') }) }) + + describe('printWorkflows', () => { + let mockWorkflows: APIWorkflowItem[] + + beforeEach(() => { + nock.cleanAll() + mockWorkflows = [...mockAPIWorkflowResponse] + }) + + it('should retrieve and format workflow items successfully', async () => { + // Mock API call + nock('http://localhost:3000') + .get('/api/v1/workflow') + .reply(200, mockWorkflows) + + // Execute the function + const result = await printWorkflows() + + // Verify the result + expect(result.success).toBe(true) + expect(result.messages[0]).toBe('Compliance workflows available:') + expect(result.messages[1]).toContain(mockWorkflows[0].id) + expect(result.messages[1]).toContain(mockWorkflows[0].description) + expect(result.messages).toHaveLength(2) // Header + 1 workflow item + expect(nock.isDone()).toBe(true) // Verify all mocked endpoints were called + }) + + it('should handle multiple workflow items', async () => { + // Add a second workflow item + const secondWorkflow = { + ...mockWorkflows[0], + id: 'create-stuff', + description: 'Another workflow description' + } + mockWorkflows.push(secondWorkflow) + + // Mock API call + nock('http://localhost:3000') + .get('/api/v1/workflow') + .reply(200, mockWorkflows) + + // Execute the function + const result = await printWorkflows() + + // Verify the result + expect(result.success).toBe(true) + expect(result.messages[0]).toBe('Compliance workflows available:') + expect(result.messages[1]).toContain(mockWorkflows[0].id) + expect(result.messages[2]).toContain(mockWorkflows[1].id) + expect(result.messages).toHaveLength(3) // Header + 2 workflow items + }) + + it('should handle API errors gracefully', async () => { + // Mock API error + nock('http://localhost:3000') + .get('/api/v1/workflow') + .reply(500, { errors: [{ message: 'Internal server error' }] } as APIErrorResponse) + + // Execute the function + const result = await printWorkflows() + + // Verify the result + expect(result.success).toBe(false) + expect(result.messages[0]).toContain('❌ Failed to retrieve compliance workflow items') + expect(result.messages).toHaveLength(1) + }) + + it('should handle network errors gracefully', async () => { + // Mock network error + nock('http://localhost:3000') + .get('/api/v1/workflow') + .replyWithError('Network error') + + // Execute the function + const result = await printWorkflows() + + // Verify the result + expect(result.success).toBe(false) + expect(result.messages[0]).toContain('❌ Failed to retrieve compliance workflow items') + expect(result.messages[0]).toContain('Network error') + expect(result.messages).toHaveLength(1) + }) + + it('should handle empty workflow response', async () => { + // Mock empty response + nock('http://localhost:3000') + .get('/api/v1/workflow') + .reply(200, []) + + // Execute the function + const result = await printWorkflows() + + // Verify the result + expect(result.success).toBe(true) + expect(result.messages).toHaveLength(1) // Only the header message + expect(result.messages[0]).toBe('No compliance workflows found') + }) + }) }) diff --git a/src/cli-commands.ts b/src/cli-commands.ts index acd17b5..e1d48fc 100644 --- a/src/cli-commands.ts +++ b/src/cli-commands.ts @@ -1,6 +1,6 @@ import { CommandResult } from './types.js' import { isApiAvailable, isApiCompatible, getPackageJson } from './utils.js' -import { getAPIDetails, createProject, addGithubOrgToProject, getAllChecklistItems, getAllChecks } from './api-client.js' +import { getAPIDetails, createProject, addGithubOrgToProject, getAllChecklistItems, getAllChecks, getAllWorkflows } from './api-client.js' const pkg = getPackageJson() @@ -110,3 +110,30 @@ export const printChecks = async (): Promise => { success } } + +export const printWorkflows = async (): Promise => { + const messages: string[] = [] + let success = true + try { + const workflows = await getAllWorkflows() + if (workflows.length === 0) { + messages.push('No compliance workflows found') + return { + messages, + success + } + } + messages.push('Compliance workflows available:') + workflows.forEach((workflow) => { + messages.push(`- ${workflow.id}: ${workflow.description}`) + }) + } catch (error) { + messages.push(`❌ Failed to retrieve compliance workflow items: ${error instanceof Error ? error.message : 'Unknown error'}`) + success = false + } + + return { + messages, + success + } +} diff --git a/src/index.ts b/src/index.ts index 034ef77..9b16792 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { Command } from 'commander' // @ts-ignore import { stringToArray } from '@ulisesgascon/string-to-array' import { handleCommandResult } from './utils.js' -import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists, printChecks } from './cli-commands.js' +import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists, printChecks, printWorkflows } from './cli-commands.js' const program = new Command() @@ -23,6 +23,18 @@ program handleCommandResult(result) }) +const workflow = program + .command('workflow') + .description('Compliance workflow management') + +workflow + .command('list') + .description('Print all available compliance workflows') + .action(async () => { + const result = await printWorkflows() + handleCommandResult(result) + }) + const checklist = program .command('compliance-checklist') .description('Compliance checklist management')