Skip to content

Commit 46a4b31

Browse files
committed
test(cli): add unit tests for CVE to GHSA conversion utility
Add comprehensive test coverage for cve-to-ghsa.mts: - Successful CVE to GHSA ID conversion - No GHSA found error handling - Missing GHSA ID in response - CResult error handling - Generic error handling - Cache TTL verification (30 days)
1 parent e4d2ea8 commit 46a4b31

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* Unit tests for CVE to GHSA conversion utility.
3+
*
4+
* Purpose:
5+
* Tests the CVE to GHSA ID conversion using GitHub API.
6+
*
7+
* Test Coverage:
8+
* - convertCveToGhsa function
9+
* - Successful conversion
10+
* - No GHSA found for CVE
11+
* - API errors
12+
*
13+
* Related Files:
14+
* - src/utils/cve-to-ghsa.mts (implementation)
15+
*/
16+
17+
import { beforeEach, describe, expect, it, vi } from 'vitest'
18+
19+
// Mock the github utility.
20+
const mockGetOctokit = vi.hoisted(() =>
21+
vi.fn(() => ({
22+
rest: {
23+
securityAdvisories: {
24+
listGlobalAdvisories: vi.fn(),
25+
},
26+
},
27+
})),
28+
)
29+
30+
const mockCacheFetch = vi.hoisted(() => vi.fn())
31+
const mockWithGitHubRetry = vi.hoisted(() => vi.fn())
32+
const mockHandleGitHubApiError = vi.hoisted(() =>
33+
vi.fn(() => ({
34+
ok: false,
35+
message: 'GitHub API error',
36+
})),
37+
)
38+
39+
vi.mock('../../../src/utils/git/github.mjs', () => ({
40+
getOctokit: mockGetOctokit,
41+
cacheFetch: mockCacheFetch,
42+
withGitHubRetry: mockWithGitHubRetry,
43+
handleGitHubApiError: mockHandleGitHubApiError,
44+
}))
45+
46+
describe('cve-to-ghsa', () => {
47+
beforeEach(() => {
48+
vi.clearAllMocks()
49+
vi.resetModules()
50+
})
51+
52+
describe('convertCveToGhsa', () => {
53+
it('returns GHSA ID for valid CVE', async () => {
54+
mockCacheFetch.mockResolvedValue({
55+
data: [{ ghsa_id: 'GHSA-xxxx-yyyy-zzzz' }],
56+
})
57+
58+
const { convertCveToGhsa } = await import(
59+
'../../../src/utils/cve-to-ghsa.mts'
60+
)
61+
62+
const result = await convertCveToGhsa('CVE-2021-44228')
63+
64+
expect(result.ok).toBe(true)
65+
if (result.ok) {
66+
expect(result.data).toBe('GHSA-xxxx-yyyy-zzzz')
67+
}
68+
})
69+
70+
it('returns error when no GHSA found for CVE', async () => {
71+
mockCacheFetch.mockResolvedValue({
72+
data: [],
73+
})
74+
75+
const { convertCveToGhsa } = await import(
76+
'../../../src/utils/cve-to-ghsa.mts'
77+
)
78+
79+
const result = await convertCveToGhsa('CVE-2021-99999')
80+
81+
expect(result.ok).toBe(false)
82+
if (!result.ok) {
83+
expect(result.message).toContain('No GHSA found for CVE')
84+
}
85+
})
86+
87+
it('returns error when GHSA ID is missing in response', async () => {
88+
mockCacheFetch.mockResolvedValue({
89+
data: [{ ghsa_id: undefined }],
90+
})
91+
92+
const { convertCveToGhsa } = await import(
93+
'../../../src/utils/cve-to-ghsa.mts'
94+
)
95+
96+
const result = await convertCveToGhsa('CVE-2021-12345')
97+
98+
expect(result.ok).toBe(false)
99+
if (!result.ok) {
100+
expect(result.message).toContain('No GHSA ID found in response')
101+
}
102+
})
103+
104+
it('handles CResult error from API', async () => {
105+
const apiError = {
106+
ok: false,
107+
message: 'API rate limit exceeded',
108+
}
109+
mockCacheFetch.mockRejectedValue(apiError)
110+
111+
const { convertCveToGhsa } = await import(
112+
'../../../src/utils/cve-to-ghsa.mts'
113+
)
114+
115+
const result = await convertCveToGhsa('CVE-2021-44228')
116+
117+
expect(result.ok).toBe(false)
118+
if (!result.ok) {
119+
expect(result.message).toBe('API rate limit exceeded')
120+
}
121+
})
122+
123+
it('handles generic errors', async () => {
124+
mockCacheFetch.mockRejectedValue(new Error('Network error'))
125+
126+
const { convertCveToGhsa } = await import(
127+
'../../../src/utils/cve-to-ghsa.mts'
128+
)
129+
130+
const result = await convertCveToGhsa('CVE-2021-44228')
131+
132+
expect(result.ok).toBe(false)
133+
expect(mockHandleGitHubApiError).toHaveBeenCalled()
134+
})
135+
136+
it('uses 30-day cache TTL', async () => {
137+
mockCacheFetch.mockResolvedValue({
138+
data: [{ ghsa_id: 'GHSA-xxxx-yyyy-zzzz' }],
139+
})
140+
141+
const { convertCveToGhsa } = await import(
142+
'../../../src/utils/cve-to-ghsa.mts'
143+
)
144+
145+
await convertCveToGhsa('CVE-2021-44228')
146+
147+
// Verify cache was called with correct TTL (30 days in ms).
148+
const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000
149+
expect(mockCacheFetch).toHaveBeenCalledWith(
150+
expect.stringContaining('cve-to-ghsa::CVE-2021-44228'),
151+
expect.any(Function),
152+
thirtyDaysMs,
153+
)
154+
})
155+
})
156+
})

0 commit comments

Comments
 (0)