Skip to content

Commit 45e4822

Browse files
committed
test(socket-fix): add comprehensive tests for PR management improvements
Adds test coverage for: - GHSA tracking (13 tests): load/save/mark/check functionality - PR lifecycle logging (8 tests): event logging with proper color coding - PR creation retry (7 tests): exponential backoff, retry limits, error handling All tests use mocking patterns consistent with existing test infrastructure.
1 parent bb33555 commit 45e4822

File tree

3 files changed

+733
-0
lines changed

3 files changed

+733
-0
lines changed
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
import { promises as fs } from 'node:fs'
2+
import path from 'node:path'
3+
4+
import { beforeEach, describe, expect, it, vi } from 'vitest'
5+
6+
import {
7+
getFixedGhsas,
8+
isGhsaFixed,
9+
loadGhsaTracker,
10+
markGhsaFixed,
11+
saveGhsaTracker,
12+
} from './ghsa-tracker.mts'
13+
14+
import type { GhsaTracker } from './ghsa-tracker.mts'
15+
16+
// Mock file system operations.
17+
vi.mock('node:fs', async () => {
18+
const actual = await vi.importActual<typeof import('node:fs')>('node:fs')
19+
return {
20+
...actual,
21+
promises: {
22+
...actual.promises,
23+
mkdir: vi.fn(),
24+
readFile: vi.fn(),
25+
writeFile: vi.fn(),
26+
},
27+
}
28+
})
29+
30+
vi.mock('@socketsecurity/lib/fs', () => ({
31+
readJson: vi.fn(),
32+
writeJson: vi.fn(),
33+
}))
34+
35+
describe('ghsa-tracker', () => {
36+
const mockCwd = '/test/repo'
37+
const trackerPath = path.join(mockCwd, '.socket/fixed-ghsas.json')
38+
39+
beforeEach(() => {
40+
vi.clearAllMocks()
41+
})
42+
43+
describe('loadGhsaTracker', () => {
44+
it('loads existing tracker file', async () => {
45+
const { readJson } = await import('@socketsecurity/lib/fs')
46+
const mockTracker: GhsaTracker = {
47+
version: 1,
48+
fixed: [
49+
{
50+
ghsaId: 'GHSA-1234-5678-90ab',
51+
fixedAt: '2025-01-01T00:00:00Z',
52+
prNumber: 123,
53+
branch: 'socket/fix/GHSA-1234-5678-90ab',
54+
},
55+
],
56+
}
57+
58+
vi.mocked(readJson).mockResolvedValue(mockTracker)
59+
60+
const result = await loadGhsaTracker(mockCwd)
61+
62+
expect(result).toEqual(mockTracker)
63+
expect(readJson).toHaveBeenCalledWith(trackerPath)
64+
})
65+
66+
it('creates new tracker when file does not exist', async () => {
67+
const { readJson } = await import('@socketsecurity/lib/fs')
68+
vi.mocked(readJson).mockRejectedValue(new Error('ENOENT'))
69+
70+
const result = await loadGhsaTracker(mockCwd)
71+
72+
expect(result).toEqual({
73+
version: 1,
74+
fixed: [],
75+
})
76+
})
77+
78+
it('handles null tracker data', async () => {
79+
const { readJson } = await import('@socketsecurity/lib/fs')
80+
vi.mocked(readJson).mockResolvedValue(null)
81+
82+
const result = await loadGhsaTracker(mockCwd)
83+
84+
expect(result).toEqual({
85+
version: 1,
86+
fixed: [],
87+
})
88+
})
89+
})
90+
91+
describe('saveGhsaTracker', () => {
92+
it('saves tracker to file', async () => {
93+
const { writeJson } = await import('@socketsecurity/lib/fs')
94+
const tracker: GhsaTracker = {
95+
version: 1,
96+
fixed: [
97+
{
98+
ghsaId: 'GHSA-1234-5678-90ab',
99+
fixedAt: '2025-01-01T00:00:00Z',
100+
prNumber: 123,
101+
branch: 'socket/fix/GHSA-1234-5678-90ab',
102+
},
103+
],
104+
}
105+
106+
await saveGhsaTracker(mockCwd, tracker)
107+
108+
expect(fs.mkdir).toHaveBeenCalledWith(
109+
path.dirname(trackerPath),
110+
{ recursive: true },
111+
)
112+
expect(writeJson).toHaveBeenCalledWith(trackerPath, tracker, {
113+
spaces: 2,
114+
})
115+
})
116+
})
117+
118+
describe('markGhsaFixed', () => {
119+
it('adds new GHSA fix record', async () => {
120+
const { readJson, writeJson } = await import('@socketsecurity/lib/fs')
121+
const existingTracker: GhsaTracker = {
122+
version: 1,
123+
fixed: [],
124+
}
125+
126+
vi.mocked(readJson).mockResolvedValue(existingTracker)
127+
128+
await markGhsaFixed(mockCwd, 'GHSA-1234-5678-90ab', 123)
129+
130+
expect(writeJson).toHaveBeenCalledWith(
131+
trackerPath,
132+
expect.objectContaining({
133+
version: 1,
134+
fixed: expect.arrayContaining([
135+
expect.objectContaining({
136+
ghsaId: 'GHSA-1234-5678-90ab',
137+
prNumber: 123,
138+
branch: 'socket/fix/GHSA-1234-5678-90ab',
139+
}),
140+
]),
141+
}),
142+
{ spaces: 2 },
143+
)
144+
})
145+
146+
it('replaces existing GHSA fix record', async () => {
147+
const { readJson, writeJson } = await import('@socketsecurity/lib/fs')
148+
const existingTracker: GhsaTracker = {
149+
version: 1,
150+
fixed: [
151+
{
152+
ghsaId: 'GHSA-1234-5678-90ab',
153+
fixedAt: '2025-01-01T00:00:00Z',
154+
prNumber: 100,
155+
branch: 'socket/fix/GHSA-1234-5678-90ab',
156+
},
157+
],
158+
}
159+
160+
vi.mocked(readJson).mockResolvedValue(existingTracker)
161+
162+
await markGhsaFixed(mockCwd, 'GHSA-1234-5678-90ab', 200)
163+
164+
expect(writeJson).toHaveBeenCalledWith(
165+
trackerPath,
166+
expect.objectContaining({
167+
version: 1,
168+
fixed: [
169+
expect.objectContaining({
170+
ghsaId: 'GHSA-1234-5678-90ab',
171+
prNumber: 200,
172+
}),
173+
],
174+
}),
175+
{ spaces: 2 },
176+
)
177+
178+
// Verify only one record exists (old one was removed).
179+
const savedTracker = vi.mocked(writeJson).mock.calls[0]![1] as GhsaTracker
180+
expect(savedTracker.fixed).toHaveLength(1)
181+
})
182+
183+
it('sorts records by fixedAt descending', async () => {
184+
const { readJson, writeJson } = await import('@socketsecurity/lib/fs')
185+
const existingTracker: GhsaTracker = {
186+
version: 1,
187+
fixed: [
188+
{
189+
ghsaId: 'GHSA-old',
190+
fixedAt: '2025-01-01T00:00:00Z',
191+
prNumber: 100,
192+
branch: 'socket/fix/GHSA-old',
193+
},
194+
],
195+
}
196+
197+
vi.mocked(readJson).mockResolvedValue(existingTracker)
198+
199+
// Add a new record with a later timestamp.
200+
await markGhsaFixed(mockCwd, 'GHSA-new', 200)
201+
202+
const savedTracker = vi.mocked(writeJson).mock.calls[0]![1] as GhsaTracker
203+
expect(savedTracker.fixed[0]!.ghsaId).toBe('GHSA-new')
204+
expect(savedTracker.fixed[1]!.ghsaId).toBe('GHSA-old')
205+
})
206+
207+
it('handles errors gracefully', async () => {
208+
const { readJson } = await import('@socketsecurity/lib/fs')
209+
vi.mocked(readJson).mockRejectedValue(new Error('Permission denied'))
210+
211+
// Should not throw.
212+
await expect(
213+
markGhsaFixed(mockCwd, 'GHSA-1234-5678-90ab', 123),
214+
).resolves.toBeUndefined()
215+
})
216+
})
217+
218+
describe('isGhsaFixed', () => {
219+
it('returns true for fixed GHSA', async () => {
220+
const { readJson } = await import('@socketsecurity/lib/fs')
221+
const tracker: GhsaTracker = {
222+
version: 1,
223+
fixed: [
224+
{
225+
ghsaId: 'GHSA-1234-5678-90ab',
226+
fixedAt: '2025-01-01T00:00:00Z',
227+
prNumber: 123,
228+
branch: 'socket/fix/GHSA-1234-5678-90ab',
229+
},
230+
],
231+
}
232+
233+
vi.mocked(readJson).mockResolvedValue(tracker)
234+
235+
const result = await isGhsaFixed(mockCwd, 'GHSA-1234-5678-90ab')
236+
237+
expect(result).toBe(true)
238+
})
239+
240+
it('returns false for unfixed GHSA', async () => {
241+
const { readJson } = await import('@socketsecurity/lib/fs')
242+
const tracker: GhsaTracker = {
243+
version: 1,
244+
fixed: [],
245+
}
246+
247+
vi.mocked(readJson).mockResolvedValue(tracker)
248+
249+
const result = await isGhsaFixed(mockCwd, 'GHSA-9999-9999-9999')
250+
251+
expect(result).toBe(false)
252+
})
253+
254+
it('returns false on error', async () => {
255+
const { readJson } = await import('@socketsecurity/lib/fs')
256+
vi.mocked(readJson).mockRejectedValue(new Error('Read error'))
257+
258+
const result = await isGhsaFixed(mockCwd, 'GHSA-1234-5678-90ab')
259+
260+
expect(result).toBe(false)
261+
})
262+
})
263+
264+
describe('getFixedGhsas', () => {
265+
it('returns all fixed GHSA records', async () => {
266+
const { readJson } = await import('@socketsecurity/lib/fs')
267+
const tracker: GhsaTracker = {
268+
version: 1,
269+
fixed: [
270+
{
271+
ghsaId: 'GHSA-1111-1111-1111',
272+
fixedAt: '2025-01-01T00:00:00Z',
273+
prNumber: 100,
274+
branch: 'socket/fix/GHSA-1111-1111-1111',
275+
},
276+
{
277+
ghsaId: 'GHSA-2222-2222-2222',
278+
fixedAt: '2025-01-02T00:00:00Z',
279+
prNumber: 200,
280+
branch: 'socket/fix/GHSA-2222-2222-2222',
281+
},
282+
],
283+
}
284+
285+
vi.mocked(readJson).mockResolvedValue(tracker)
286+
287+
const result = await getFixedGhsas(mockCwd)
288+
289+
expect(result).toEqual(tracker.fixed)
290+
expect(result).toHaveLength(2)
291+
})
292+
293+
it('returns empty array on error', async () => {
294+
const { readJson } = await import('@socketsecurity/lib/fs')
295+
vi.mocked(readJson).mockRejectedValue(new Error('Read error'))
296+
297+
const result = await getFixedGhsas(mockCwd)
298+
299+
expect(result).toEqual([])
300+
})
301+
})
302+
})

0 commit comments

Comments
 (0)