Skip to content

Commit a13ac12

Browse files
committed
test(cli): expand test coverage for git providers and socket utils
Add comprehensive tests for: - GitHubProvider: updatePr, createPr error handling, addComment, listPrs pagination, author filtering, error handling - package-alert: logAlertsMap edge cases, hidden alerts aggregation, severity labels, translations fallback - sdk.mts: hooks, onFileValidation, userAgent, proxy handling Coverage: 64.37% -> 64.74%
1 parent be7757c commit a13ac12

File tree

5 files changed

+625
-12
lines changed

5 files changed

+625
-12
lines changed

packages/cli/test/unit/utils/git/providers.test.mts

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,337 @@ describe('GitHubProvider', () => {
297297

298298
expect(result).toBe(false)
299299
})
300+
301+
it('handles deletion exception gracefully', async () => {
302+
mockGitDeleteRemoteBranch.mockRejectedValue(new Error('Branch not found'))
303+
304+
const provider = new GitHubProvider()
305+
const result = await provider.deleteBranch('nonexistent-branch')
306+
307+
expect(result).toBe(false)
308+
})
309+
})
310+
311+
describe('updatePr', () => {
312+
it('updates PR by merging base into head', async () => {
313+
mockOctokit.repos.merge.mockResolvedValue({})
314+
mockOctokit.pulls.get.mockResolvedValue({
315+
data: { mergeable_state: 'clean' },
316+
})
317+
318+
const provider = new GitHubProvider()
319+
await provider.updatePr({
320+
owner: 'owner',
321+
repo: 'repo',
322+
prNumber: 123,
323+
head: 'feature-branch',
324+
base: 'main',
325+
})
326+
327+
expect(mockOctokit.repos.merge).toHaveBeenCalledWith({
328+
owner: 'owner',
329+
repo: 'repo',
330+
head: 'main',
331+
base: 'feature-branch',
332+
})
333+
})
334+
335+
it('adds conflict comment when PR becomes dirty', async () => {
336+
mockOctokit.repos.merge.mockResolvedValue({})
337+
mockOctokit.pulls.get.mockResolvedValue({
338+
data: { mergeable_state: 'dirty' },
339+
})
340+
mockOctokit.issues.createComment.mockResolvedValue({})
341+
342+
const provider = new GitHubProvider()
343+
await provider.updatePr({
344+
owner: 'owner',
345+
repo: 'repo',
346+
prNumber: 456,
347+
head: 'feature-branch',
348+
base: 'main',
349+
})
350+
351+
expect(mockOctokit.issues.createComment).toHaveBeenCalledWith(
352+
expect.objectContaining({
353+
issue_number: 456,
354+
body: expect.stringContaining('merge conflicts'),
355+
}),
356+
)
357+
})
358+
359+
it('throws when merge fails', async () => {
360+
mockWithGitHubRetry.mockResolvedValueOnce({
361+
ok: false,
362+
message: 'Merge failed',
363+
cause: 'Conflict',
364+
})
365+
366+
const provider = new GitHubProvider()
367+
await expect(
368+
provider.updatePr({
369+
owner: 'owner',
370+
repo: 'repo',
371+
prNumber: 789,
372+
head: 'feature',
373+
base: 'main',
374+
}),
375+
).rejects.toThrow()
376+
})
377+
378+
it('throws when PR details fetch fails', async () => {
379+
mockWithGitHubRetry
380+
.mockResolvedValueOnce({ ok: true, data: {} })
381+
.mockResolvedValueOnce({
382+
ok: false,
383+
message: 'PR not found',
384+
})
385+
386+
const provider = new GitHubProvider()
387+
await expect(
388+
provider.updatePr({
389+
owner: 'owner',
390+
repo: 'repo',
391+
prNumber: 999,
392+
head: 'feature',
393+
base: 'main',
394+
}),
395+
).rejects.toThrow()
396+
})
397+
})
398+
399+
describe('createPr error handling', () => {
400+
it('throws when API call fails', async () => {
401+
mockWithGitHubRetry.mockResolvedValueOnce({
402+
ok: false,
403+
message: 'Failed to create PR',
404+
cause: 'Repository not found',
405+
})
406+
407+
const provider = new GitHubProvider()
408+
await expect(
409+
provider.createPr({
410+
owner: 'owner',
411+
repo: 'nonexistent',
412+
title: 'Test',
413+
head: 'feature',
414+
base: 'main',
415+
body: 'Body',
416+
}),
417+
).rejects.toThrow('Repository not found')
418+
})
419+
420+
it('handles closed PR state', async () => {
421+
mockOctokit.pulls.create.mockResolvedValue({
422+
data: {
423+
number: 789,
424+
html_url: 'https://github.com/owner/repo/pull/789',
425+
state: 'closed',
426+
merged_at: null,
427+
},
428+
})
429+
430+
const provider = new GitHubProvider()
431+
const result = await provider.createPr({
432+
owner: 'owner',
433+
repo: 'repo',
434+
title: 'Test',
435+
head: 'feature',
436+
base: 'main',
437+
body: 'Body',
438+
})
439+
440+
expect(result.state).toBe('closed')
441+
})
442+
})
443+
444+
describe('addComment error handling', () => {
445+
it('throws when comment fails', async () => {
446+
mockWithGitHubRetry.mockResolvedValueOnce({
447+
ok: false,
448+
message: 'Comment failed',
449+
})
450+
451+
const provider = new GitHubProvider()
452+
await expect(
453+
provider.addComment({
454+
owner: 'owner',
455+
repo: 'repo',
456+
prNumber: 123,
457+
body: 'Test',
458+
}),
459+
).rejects.toThrow('Comment failed')
460+
})
461+
})
462+
463+
describe('listPrs advanced scenarios', () => {
464+
it('filters PRs by author', async () => {
465+
const mockResponse = {
466+
repository: {
467+
pullRequests: {
468+
pageInfo: { hasNextPage: false, endCursor: null },
469+
nodes: [
470+
{
471+
number: 1,
472+
title: 'PR by user1',
473+
author: { login: 'user1' },
474+
headRefName: 'f1',
475+
baseRefName: 'main',
476+
state: 'OPEN',
477+
mergeStateStatus: 'CLEAN',
478+
},
479+
{
480+
number: 2,
481+
title: 'PR by user2',
482+
author: { login: 'user2' },
483+
headRefName: 'f2',
484+
baseRefName: 'main',
485+
state: 'OPEN',
486+
mergeStateStatus: 'CLEAN',
487+
},
488+
],
489+
},
490+
},
491+
}
492+
493+
mockCacheFetch.mockResolvedValue(mockResponse)
494+
495+
const provider = new GitHubProvider()
496+
const results = await provider.listPrs({
497+
owner: 'owner',
498+
repo: 'repo',
499+
author: 'user1',
500+
})
501+
502+
expect(results).toHaveLength(1)
503+
expect(results[0]!.author).toBe('user1')
504+
})
505+
506+
it('handles PRs without author', async () => {
507+
const mockResponse = {
508+
repository: {
509+
pullRequests: {
510+
pageInfo: { hasNextPage: false, endCursor: null },
511+
nodes: [
512+
{
513+
number: 1,
514+
title: 'PR without author',
515+
// No author field.
516+
headRefName: 'f1',
517+
baseRefName: 'main',
518+
state: 'OPEN',
519+
mergeStateStatus: 'CLEAN',
520+
},
521+
],
522+
},
523+
},
524+
}
525+
526+
mockCacheFetch.mockResolvedValue(mockResponse)
527+
528+
const provider = new GitHubProvider()
529+
const results = await provider.listPrs({
530+
owner: 'owner',
531+
repo: 'repo',
532+
})
533+
534+
expect(results).toHaveLength(1)
535+
expect(results[0]!.author).toBe('<unknown>')
536+
})
537+
538+
it('handles specific states filter', async () => {
539+
const mockResponse = {
540+
repository: {
541+
pullRequests: {
542+
pageInfo: { hasNextPage: false, endCursor: null },
543+
nodes: [
544+
{
545+
number: 1,
546+
title: 'Open PR',
547+
author: { login: 'user' },
548+
headRefName: 'f1',
549+
baseRefName: 'main',
550+
state: 'OPEN',
551+
mergeStateStatus: 'CLEAN',
552+
},
553+
],
554+
},
555+
},
556+
}
557+
558+
mockCacheFetch.mockResolvedValue(mockResponse)
559+
560+
const provider = new GitHubProvider()
561+
const results = await provider.listPrs({
562+
owner: 'owner',
563+
repo: 'repo',
564+
states: 'open',
565+
})
566+
567+
expect(results).toHaveLength(1)
568+
})
569+
570+
it('exits early when ghsaId provided and matches found', async () => {
571+
const mockResponse = {
572+
repository: {
573+
pullRequests: {
574+
pageInfo: { hasNextPage: true, endCursor: 'cursor' },
575+
nodes: [
576+
{
577+
number: 1,
578+
title: 'Fix GHSA-1234',
579+
author: { login: 'user' },
580+
headRefName: 'socket/fix/GHSA-1234',
581+
baseRefName: 'main',
582+
state: 'OPEN',
583+
mergeStateStatus: 'CLEAN',
584+
},
585+
],
586+
},
587+
},
588+
}
589+
590+
mockCacheFetch.mockResolvedValue(mockResponse)
591+
592+
const provider = new GitHubProvider()
593+
const results = await provider.listPrs({
594+
owner: 'owner',
595+
repo: 'repo',
596+
ghsaId: 'GHSA-1234',
597+
})
598+
599+
// Should have exited early after first page due to ghsaId optimization.
600+
expect(mockCacheFetch).toHaveBeenCalledTimes(1)
601+
expect(results).toHaveLength(1)
602+
})
603+
604+
it('handles empty repository response', async () => {
605+
mockCacheFetch.mockResolvedValue({
606+
repository: {
607+
pullRequests: undefined,
608+
},
609+
})
610+
611+
const provider = new GitHubProvider()
612+
const results = await provider.listPrs({
613+
owner: 'owner',
614+
repo: 'repo',
615+
})
616+
617+
expect(results).toHaveLength(0)
618+
})
619+
620+
it('handles GraphQL error gracefully', async () => {
621+
mockCacheFetch.mockRejectedValue(new Error('GraphQL error'))
622+
623+
const provider = new GitHubProvider()
624+
const results = await provider.listPrs({
625+
owner: 'owner',
626+
repo: 'repo',
627+
})
628+
629+
expect(results).toHaveLength(0)
630+
})
300631
})
301632

302633
describe('metadata', () => {

0 commit comments

Comments
 (0)