Skip to content

Commit 7518567

Browse files
authored
Merge pull request #7 from OpenPathfinder/feat/check
2 parents 12055fd + e0bd9a7 commit 7518567

File tree

6 files changed

+196
-7
lines changed

6 files changed

+196
-7
lines changed

src/__tests__/cli-commands.test.ts

Lines changed: 105 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 } from '../cli-commands.js'
3+
import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists, printChecks } from '../cli-commands.js'
44
import { getPackageJson } from '../utils.js'
5-
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIErrorResponse, APIChecklistItem } from '../types.js'
6-
import { mockApiHealthResponse, mockAPIProjectResponse, mockAPIGithubOrgResponse, mockAPIChecklistResponse } from './fixtures.js'
5+
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIErrorResponse, APIChecklistItem, APICheckItem } from '../types.js'
6+
import { mockApiHealthResponse, mockAPIProjectResponse, mockAPIGithubOrgResponse, mockAPIChecklistResponse, mockAPICheckResponse } from './fixtures.js'
77
import nock from 'nock'
88

99
const pkg = getPackageJson()
@@ -278,4 +278,106 @@ describe('CLI Commands', () => {
278278
expect(result.messages[0]).toBe('No compliance checklists found')
279279
})
280280
})
281+
282+
describe('printChecks', () => {
283+
let mockChecks: APICheckItem[]
284+
285+
beforeEach(() => {
286+
nock.cleanAll()
287+
mockChecks = [...mockAPICheckResponse]
288+
})
289+
290+
it('should retrieve and format check items successfully', async () => {
291+
// Mock API call
292+
nock('http://localhost:3000')
293+
.get('/api/v1/compliance-check')
294+
.reply(200, mockChecks)
295+
296+
// Execute the function
297+
const result = await printChecks()
298+
299+
// Verify the result
300+
expect(result.success).toBe(true)
301+
expect(result.messages[0]).toBe('Compliance checks available:')
302+
expect(result.messages[1]).toContain(mockChecks[0].code_name)
303+
expect(result.messages[1]).toContain(mockChecks[0].description)
304+
expect(result.messages[1]).toContain(mockChecks[0].details_url)
305+
expect(result.messages).toHaveLength(2) // Header + 1 check item
306+
expect(nock.isDone()).toBe(true) // Verify all mocked endpoints were called
307+
})
308+
309+
it('should handle multiple check items', async () => {
310+
// Add a second check item
311+
const secondCheck = {
312+
...mockChecks[0],
313+
id: 456,
314+
title: 'Second Check',
315+
code_name: 'secondCheck',
316+
description: 'Another check description',
317+
details_url: 'https://openpathfinder.com/docs/checks/secondCheck'
318+
}
319+
mockChecks.push(secondCheck)
320+
321+
// Mock API call
322+
nock('http://localhost:3000')
323+
.get('/api/v1/compliance-check')
324+
.reply(200, mockChecks)
325+
326+
// Execute the function
327+
const result = await printChecks()
328+
329+
// Verify the result
330+
expect(result.success).toBe(true)
331+
expect(result.messages[0]).toBe('Compliance checks available:')
332+
expect(result.messages[1]).toContain(mockChecks[0].code_name)
333+
expect(result.messages[2]).toContain(mockChecks[1].code_name)
334+
expect(result.messages).toHaveLength(3) // Header + 2 check items
335+
})
336+
337+
it('should handle API errors gracefully', async () => {
338+
// Mock API error
339+
nock('http://localhost:3000')
340+
.get('/api/v1/compliance-check')
341+
.reply(500, { errors: [{ message: 'Internal server error' }] } as APIErrorResponse)
342+
343+
// Execute the function
344+
const result = await printChecks()
345+
346+
// Verify the result
347+
expect(result.success).toBe(false)
348+
expect(result.messages[0]).toContain('❌ Failed to retrieve compliance check items')
349+
expect(result.messages).toHaveLength(1)
350+
})
351+
352+
it('should handle network errors gracefully', async () => {
353+
// Mock network error
354+
nock('http://localhost:3000')
355+
.get('/api/v1/compliance-check')
356+
.replyWithError('Network error')
357+
358+
// Execute the function
359+
const result = await printChecks()
360+
361+
// Verify the result
362+
expect(result.success).toBe(false)
363+
expect(result.messages[0]).toContain('❌ Failed to retrieve compliance check items')
364+
expect(result.messages[0]).toContain('Network error')
365+
expect(result.messages).toHaveLength(1)
366+
})
367+
368+
it('should handle empty check response', async () => {
369+
// Mock empty response
370+
nock('http://localhost:3000')
371+
.get('/api/v1/compliance-check')
372+
.reply(200, [])
373+
374+
// Execute the function
375+
const result = await printChecks()
376+
377+
// Verify the result
378+
expect(result.success).toBe(true)
379+
expect(result.messages).toHaveLength(1) // Only the header message
380+
expect(result.messages[0]).toBe('No compliance checks found')
381+
})
382+
})
281383
})

src/__tests__/fixtures.ts

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

33
export const mockApiHealthResponse: APIHealthResponse = {
44
status: 'ok',
@@ -104,3 +104,20 @@ export const mockAPIChecklistResponse: APIChecklistItem[] = [{
104104
created_at: new Date().toISOString(),
105105
updated_at: new Date().toISOString()
106106
}]
107+
108+
export const mockAPICheckResponse: APICheckItem[] = [{
109+
id: 53,
110+
title: 'Refresh dependencies with annual releases',
111+
description: 'Ensure dependencies are refreshed through a new release at least once annually',
112+
default_section_number: '5',
113+
default_section_name: 'vulnerability management',
114+
code_name: 'annualDependencyRefresh',
115+
default_priority_group: 'P14',
116+
is_c_scrm: true,
117+
implementation_status: 'completed',
118+
implementation_type: 'manual',
119+
implementation_details_reference: 'https://github.com/OpenPathfinder/visionBoard/issues/112',
120+
details_url: 'https://openpathfinder.com/docs/checks/annualDependencyRefresh',
121+
created_at: '2025-02-21T18:53:00.485Z',
122+
updated_at: '2025-02-21T18:53:00.485Z'
123+
}]

src/api-client.ts

Lines changed: 10 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 } from './types.js'
3+
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIChecklistItem, APICheckItem } from './types.js'
44

55
export const apiClient = () => {
66
const config = getConfig()
@@ -61,3 +61,12 @@ export const getAllChecklistItems = async (): Promise<APIChecklistItem[]> => {
6161
}
6262
return response.body as APIChecklistItem[]
6363
}
64+
65+
export const getAllChecks = async (): Promise<APICheckItem[]> => {
66+
const client = apiClient()
67+
const response = await client.get('compliance-check', { responseType: 'json' })
68+
if (response.statusCode !== 200) {
69+
throw new Error(`Failed to get the data from the API: ${response.statusCode} ${response.body}`)
70+
}
71+
return response.body as APICheckItem[]
72+
}

src/cli-commands.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CommandResult } from './types.js'
22
import { isApiAvailable, isApiCompatible, getPackageJson } from './utils.js'
3-
import { getAPIDetails, createProject, addGithubOrgToProject, getAllChecklistItems } from './api-client.js'
3+
import { getAPIDetails, createProject, addGithubOrgToProject, getAllChecklistItems, getAllChecks } from './api-client.js'
44

55
const pkg = getPackageJson()
66

@@ -83,3 +83,30 @@ export const printChecklists = async (): Promise<CommandResult> => {
8383
success
8484
}
8585
}
86+
87+
export const printChecks = async (): Promise<CommandResult> => {
88+
const messages: string[] = []
89+
let success = true
90+
try {
91+
const checks = await getAllChecks()
92+
if (checks.length === 0) {
93+
messages.push('No compliance checks found')
94+
return {
95+
messages,
96+
success
97+
}
98+
}
99+
messages.push('Compliance checks available:')
100+
checks.forEach((check) => {
101+
messages.push(`- ${check.code_name}: ${check.description}. ${check.details_url}`)
102+
})
103+
} catch (error) {
104+
messages.push(`❌ Failed to retrieve compliance check items: ${error instanceof Error ? error.message : 'Unknown error'}`)
105+
success = false
106+
}
107+
108+
return {
109+
messages,
110+
success
111+
}
112+
}

src/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Command } from 'commander'
44
// @ts-ignore
55
import { stringToArray } from '@ulisesgascon/string-to-array'
66
import { handleCommandResult } from './utils.js'
7-
import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists } from './cli-commands.js'
7+
import { getVersion, runDoctor, addProjectWithGithubOrgs, printChecklists, printChecks } from './cli-commands.js'
88

99
const program = new Command()
1010

@@ -35,6 +35,18 @@ checklist
3535
handleCommandResult(result)
3636
})
3737

38+
const check = program
39+
.command('compliance-check')
40+
.description('Compliance check management')
41+
42+
check
43+
.command('list')
44+
.description('Print all available compliance checks')
45+
.action(async () => {
46+
const result = await printChecks()
47+
handleCommandResult(result)
48+
})
49+
3850
const project = program
3951
.command('project')
4052
.description('Project management')

src/types.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,28 @@ export interface APIChecklistItem {
119119
updated_at: string
120120
}
121121

122+
type PriorityGroup = 'P0' | 'P1' | 'P2' | 'P3' | 'P4' | 'P5' | 'P6' | 'P7' | 'P8' | 'P9' | 'P10' | 'P11' | 'P12' | 'P13' | 'P14' | 'R0' | 'R1' | 'R2' | 'R3' | 'R4' | 'R5' | 'R6' | 'R7' | 'R8' | 'R9' | 'R10' | 'R11' | 'R12' | 'R13' | 'R14';
123+
124+
/**
125+
* Check Schema
126+
*/
127+
export type APICheckItem = {
128+
id: number;
129+
title: string;
130+
description: string;
131+
default_section_number: string;
132+
default_section_name: string;
133+
code_name: string;
134+
default_priority_group: PriorityGroup;
135+
is_c_scrm: boolean;
136+
implementation_status: 'pending' | 'completed';
137+
implementation_type: string | null;
138+
implementation_details_reference: string | null;
139+
details_url: string;
140+
created_at: string;
141+
updated_at: string;
142+
};
143+
122144
/**
123145
* Error object as defined in the OpenAPI schema
124146
*/

0 commit comments

Comments
 (0)