Skip to content

Commit e8dc599

Browse files
authored
Merge pull request #9 from OpenPathfinder/feat/workflow-run
2 parents 30eed17 + 87e783a commit e8dc599

File tree

9 files changed

+373
-23
lines changed

9 files changed

+373
-23
lines changed

package-lock.json

Lines changed: 102 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
},
4040
"dependencies": {
4141
"@ulisesgascon/string-to-array": "2.0.0",
42+
"ajv": "8.17.1",
43+
"ajv-formats": "3.0.1",
4244
"commander": "14.0.0",
4345
"got": "14.4.7",
4446
"nock": "14.0.5",

src/__tests__/cli-commands.test.ts

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint-env jest */
22

3-
import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists, printChecks, printWorkflows } from '../cli-commands.js'
3+
import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists, printChecks, printWorkflows, executeWorkflow } from '../cli-commands.js'
44
import { getPackageJson } from '../utils.js'
5-
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIErrorResponse, APIChecklistItem, APICheckItem, APIWorkflowItem } from '../types.js'
6-
import { mockApiHealthResponse, mockAPIProjectResponse, mockAPIGithubOrgResponse, mockAPIChecklistResponse, mockAPICheckResponse, mockAPIWorkflowResponse } from './fixtures.js'
5+
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIErrorResponse, APIChecklistItem, APICheckItem, APIWorkflowItem, APIWorkflowRunItem } from '../types.js'
6+
import { mockApiHealthResponse, mockAPIProjectResponse, mockAPIGithubOrgResponse, mockAPIChecklistResponse, mockAPICheckResponse, mockAPIWorkflowResponse, mockAPIWorkflowRunResponse } from './fixtures.js'
77
import nock from 'nock'
88

99
const pkg = getPackageJson()
@@ -432,6 +432,32 @@ describe('CLI Commands', () => {
432432
expect(result.messages).toHaveLength(3) // Header + 2 workflow items
433433
})
434434

435+
it('should handle disabled workflows', async () => {
436+
// Add a second workflow item
437+
const secondWorkflow = {
438+
...mockWorkflows[0],
439+
id: 'create-stuff',
440+
description: 'Another workflow description',
441+
isEnabled: false
442+
}
443+
mockWorkflows.push(secondWorkflow)
444+
445+
// Mock API call
446+
nock('http://localhost:3000')
447+
.get('/api/v1/workflow')
448+
.reply(200, mockWorkflows)
449+
450+
// Execute the function
451+
const result = await printWorkflows()
452+
453+
// Verify the result
454+
expect(result.success).toBe(true)
455+
expect(result.messages[0]).toBe('Compliance workflows available:')
456+
expect(result.messages[1]).toContain(mockWorkflows[0].id)
457+
expect(result.messages[1]).toContain(mockWorkflows[0].description)
458+
expect(result.messages).toHaveLength(2) // Header + 1 enabled workflow item
459+
})
460+
435461
it('should handle API errors gracefully', async () => {
436462
// Mock API error
437463
nock('http://localhost:3000')
@@ -478,4 +504,86 @@ describe('CLI Commands', () => {
478504
expect(result.messages[0]).toBe('No compliance workflows found')
479505
})
480506
})
507+
508+
describe('executeWorkflow', () => {
509+
let workflowRunResponse: APIWorkflowRunItem
510+
511+
beforeEach(() => {
512+
nock.cleanAll()
513+
514+
// Setup mock workflow run response
515+
workflowRunResponse = { ...mockAPIWorkflowRunResponse }
516+
})
517+
518+
it('should execute a workflow successfully', async () => {
519+
// Mock API call
520+
nock('http://localhost:3000')
521+
.post('/api/v1/workflow/update-stuff/run', { data: { projectId: 123 } })
522+
.reply(202, workflowRunResponse)
523+
524+
// Execute the function
525+
const result = await executeWorkflow('update-stuff', { projectId: 123 })
526+
527+
// Verify the result
528+
expect(result.success).toBe(true)
529+
expect(result.messages).toHaveLength(5) // 5 messages with details
530+
expect(result.messages[0]).toContain('Workflow executed successfully in 2.50 seconds')
531+
expect(result.messages[1]).toContain('Status: completed')
532+
expect(result.messages[2]).toContain('Started:')
533+
expect(result.messages[3]).toContain('Finished:')
534+
expect(result.messages[4]).toContain('Result:')
535+
expect(nock.isDone()).toBe(true) // Verify all mocked endpoints were called
536+
})
537+
538+
it('Should execute a workflow that was unsuccessful', async () => {
539+
// Mock API call
540+
nock('http://localhost:3000')
541+
.post('/api/v1/workflow/update-stuff/run', { data: { projectId: 123 } })
542+
.reply(202, { ...workflowRunResponse, status: 'failed', result: { message: 'Failed to execute workflow', success: false } })
543+
544+
// Execute the function
545+
const result = await executeWorkflow('update-stuff', { projectId: 123 })
546+
547+
// Verify the result
548+
expect(result.success).toBe(true)
549+
expect(result.messages).toHaveLength(5) // 5 messages with details
550+
expect(result.messages[0]).toContain('Workflow executed unsuccessfully in 2.50 seconds')
551+
expect(result.messages[1]).toContain('Status: failed')
552+
expect(result.messages[2]).toContain('Started:')
553+
expect(result.messages[3]).toContain('Finished:')
554+
expect(result.messages[4]).toContain('Result:')
555+
expect(nock.isDone()).toBe(true) // Verify all mocked endpoints were called
556+
})
557+
558+
it('should handle API errors gracefully', async () => {
559+
// Mock API error
560+
nock('http://localhost:3000')
561+
.post('/api/v1/workflow/invalid-workflow/run')
562+
.reply(404, { errors: [{ message: 'Workflow not found' }] } as APIErrorResponse)
563+
564+
// Execute the function
565+
const result = await executeWorkflow('invalid-workflow', {})
566+
567+
// Verify the result
568+
expect(result.success).toBe(false)
569+
expect(result.messages[0]).toContain('❌ Failed to execute the workflow')
570+
expect(result.messages).toHaveLength(1)
571+
})
572+
573+
it('should handle network errors gracefully', async () => {
574+
// Mock network error
575+
nock('http://localhost:3000')
576+
.post('/api/v1/workflow/update-stuff/run')
577+
.replyWithError('Network error')
578+
579+
// Execute the function
580+
const result = await executeWorkflow('update-stuff', {})
581+
582+
// Verify the result
583+
expect(result.success).toBe(false)
584+
expect(result.messages[0]).toContain('❌ Failed to execute the workflow')
585+
expect(result.messages[0]).toContain('Network error')
586+
expect(result.messages).toHaveLength(1)
587+
})
588+
})
481589
})

src/__tests__/fixtures.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem, APIWorkflowItem } from '../types.js'
1+
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem, APIWorkflowItem, APIWorkflowRunItem } from '../types.js'
22

33
export const mockApiHealthResponse: APIHealthResponse = {
44
status: 'ok',
@@ -124,5 +124,17 @@ export const mockAPICheckResponse: APICheckItem[] = [{
124124

125125
export const mockAPIWorkflowResponse: APIWorkflowItem[] = [{
126126
id: 'update-stuff',
127-
description: 'Test workflow description'
127+
description: 'Test workflow description',
128+
isEnabled: true,
129+
isRequiredAdditionalData: false,
130+
operations: null,
131+
schema: null
128132
}]
133+
134+
export const mockAPIWorkflowRunResponse: APIWorkflowRunItem = {
135+
status: 'completed',
136+
started: '2025-06-21T10:05:00.000Z',
137+
finished: '2025-06-21T10:05:02.500Z',
138+
completed: '2025-06-21T10:05:02.500Z',
139+
result: { success: true, message: 'Workflow completed successfully' }
140+
}

src/api-client.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getConfig } from './utils.js'
22
import { got } from 'got'
3-
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem, APIWorkflowItem } from './types.js'
3+
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem, APIWorkflowItem, APIWorkflowRunItem } from './types.js'
44

55
export const apiClient = () => {
66
const config = getConfig()
@@ -79,3 +79,16 @@ export const getAllWorkflows = async (): Promise<APIWorkflowItem[]> => {
7979
}
8080
return response.body as APIWorkflowItem[]
8181
}
82+
83+
export const runWorkflow = async (workflowId: string, data: any): Promise<APIWorkflowRunItem> => {
84+
const client = apiClient()
85+
const payload = data ? { data } : {}
86+
const response = await client.post(`workflow/${workflowId}/run`, {
87+
json: payload,
88+
responseType: 'json'
89+
})
90+
if (response.statusCode !== 202) {
91+
throw new Error(`Failed to run the workflow: ${response.statusCode} ${response.body}`)
92+
}
93+
return response.body as APIWorkflowRunItem
94+
}

0 commit comments

Comments
 (0)