@@ -31,7 +31,7 @@ vi.mock('../../../src/services/metrics', () => ({
3131} ) ) ;
3232
3333describe ( 'GitService Optimized Unit Tests' , ( ) => {
34- const mockGit = { clone : vi . fn ( ) , raw : vi . fn ( ) } ;
34+ const mockGit = { clone : vi . fn ( ) , raw : vi . fn ( ) , log : vi . fn ( ) } ;
3535 const mockMemoryStats = {
3636 system : {
3737 free : 1024 * 1024 * 1024 ,
@@ -50,6 +50,7 @@ describe('GitService Optimized Unit Tests', () => {
5050 const createMockContext = ( ) => {
5151 vi . clearAllMocks ( ) ;
5252 mockGit . raw . mockReset ( ) ;
53+ mockGit . log . mockReset ( ) ;
5354 ( simpleGit as any ) . mockImplementation ( ( ) => mockGit ) ;
5455 ( mkdtemp as any ) . mockResolvedValue ( '/tmp/test-repo' ) ;
5556 ( rm as any ) . mockResolvedValue ( undefined ) ;
@@ -1307,125 +1308,139 @@ def456|2023-01-02T12:00:00Z|Bob|bob@example.com|chore: merge commit
13071308 } ) ;
13081309 } ) ;
13091310
1310- describe ( 'getTopContributors ' , ( ) => {
1311- test ( 'should aggregate and return top 5 contributors sorted by commit count ' , async ( ) => {
1311+ describe ( 'getContributors ' , ( ) => {
1312+ test ( 'should return all unique contributors sorted alphabetically ' , async ( ) => {
13121313 // Arrange
1313- const commitsWithStats = `abc123|2023-01-01T12:00:00Z|Alice|alice@example.com|commit 1
1314- 10\t5\tfile1.ts
1315-
1316- def456|2023-01-02T12:00:00Z|Bob|bob@example.com|commit 2
1317- 15\t3\tfile2.ts
1318-
1319- ghi789|2023-01-03T12:00:00Z|Alice|alice@example.com|commit 3
1320- 20\t10\tfile3.ts
1321-
1322- jkl012|2023-01-04T12:00:00Z|Charlie|charlie@example.com|commit 4
1323- 5\t2\tfile4.ts
1324-
1325- mno345|2023-01-05T12:00:00Z|Alice|alice@example.com|commit 5
1326- 8\t4\tfile5.ts
1327-
1328- pqr678|2023-01-06T12:00:00Z|Bob|bob@example.com|commit 6
1329- 12\t6\tfile6.ts` ;
1330- mockGit . raw . mockResolvedValue ( commitsWithStats ) ;
1314+ const rawOutput = 'Alice\nBob\nAlice\nCharlie\nBob' ;
1315+ mockGit . raw . mockResolvedValue ( rawOutput ) ;
13311316
13321317 // Act
1333- const result = await gitService . getTopContributors ( '/test/repo' ) ;
1318+ const result = await gitService . getContributors ( '/test/repo' ) ;
13341319
13351320 // Assert
1336- expect ( result ) . toHaveLength ( 3 ) ; // Alice, Bob, Charlie
1337- expect ( result [ 0 ] ) . toEqual ( {
1338- login : 'Alice' ,
1339- commitCount : 3 ,
1340- linesAdded : 38 ,
1341- linesDeleted : 19 ,
1342- contributionPercentage : 0.5 , // 3 out of 6 commits
1343- } ) ;
1344- expect ( result [ 1 ] ) . toEqual ( {
1345- login : 'Bob' ,
1346- commitCount : 2 ,
1347- linesAdded : 27 ,
1348- linesDeleted : 9 ,
1349- contributionPercentage : 2 / 6 ,
1350- } ) ;
1351- expect ( result [ 2 ] ) . toEqual ( {
1352- login : 'Charlie' ,
1353- commitCount : 1 ,
1354- linesAdded : 5 ,
1355- linesDeleted : 2 ,
1356- contributionPercentage : 1 / 6 ,
1357- } ) ;
1321+ expect ( result ) . toHaveLength ( 3 ) ;
1322+ expect ( result ) . toEqual ( [
1323+ { login : 'Alice' } ,
1324+ { login : 'Bob' } ,
1325+ { login : 'Charlie' } ,
1326+ ] ) ;
1327+ expect ( mockGit . raw ) . toHaveBeenCalledWith ( [ 'log' , '--format=%aN' ] ) ;
13581328 } ) ;
13591329
13601330 test ( 'should return empty array when no commits exist' , async ( ) => {
13611331 // Arrange
13621332 mockGit . raw . mockResolvedValue ( '' ) ;
13631333
13641334 // Act
1365- const result = await gitService . getTopContributors ( '/test/repo' ) ;
1335+ const result = await gitService . getContributors ( '/test/repo' ) ;
13661336
13671337 // Assert
13681338 expect ( result ) . toEqual ( [ ] ) ;
13691339 } ) ;
13701340
1371- test ( 'should limit results to top 5 contributors ' , async ( ) => {
1341+ test ( 'should apply date filter options ' , async ( ) => {
13721342 // Arrange
1373- const manyCommits = Array . from (
1374- { length : 10 } ,
1375- ( _ , i ) =>
1376- `commit${ i } |2023-01-0${ i + 1 } T12:00:00Z|User${ i } |user${ i } @example.com|commit ${ i } \n10\t5\tfile${ i } .ts`
1377- ) . join ( '\n\n' ) ;
1378- mockGit . raw . mockResolvedValue ( manyCommits ) ;
1343+ mockGit . raw . mockResolvedValue ( 'Alice' ) ;
13791344
13801345 // Act
1381- const result = await gitService . getTopContributors ( '/test/repo' ) ;
1346+ await gitService . getContributors ( '/test/repo' , {
1347+ fromDate : '2023-01-01' ,
1348+ toDate : '2023-12-31' ,
1349+ } ) ;
13821350
13831351 // Assert
1384- expect ( result . length ) . toBeLessThanOrEqual ( 5 ) ;
1352+ expect ( mockGit . raw ) . toHaveBeenCalledWith (
1353+ expect . arrayContaining ( [
1354+ 'log' ,
1355+ '--format=%aN' ,
1356+ '--since=2023-01-01' ,
1357+ '--until=2023-12-31' ,
1358+ ] )
1359+ ) ;
13851360 } ) ;
13861361
1387- test ( 'should apply filter options to underlying commits ' , async ( ) => {
1362+ test ( 'should apply single author filter ' , async ( ) => {
13881363 // Arrange
1389- const commitData = `abc123|2023-01-01T12:00:00Z|Alice|alice@example.com|commit 1
1390- 10\t5\tfile1.ts` ;
1391- mockGit . raw . mockResolvedValue ( commitData ) ;
1364+ mockGit . raw . mockResolvedValue ( 'Alice' ) ;
13921365
13931366 // Act
1394- await gitService . getTopContributors ( '/test/repo' , {
1395- fromDate : '2023-01-01' ,
1396- toDate : '2023-12-31' ,
1367+ await gitService . getContributors ( '/test/repo' , {
1368+ author : 'Alice' ,
13971369 } ) ;
13981370
13991371 // Assert
14001372 expect ( mockGit . raw ) . toHaveBeenCalledWith (
1401- expect . arrayContaining ( [ '--since=2023-01-01 ' , '--until=2023-12-31 ' ] )
1373+ expect . arrayContaining ( [ 'log' , '--format=%aN ', '--author=Alice ' ] )
14021374 ) ;
14031375 } ) ;
14041376
1405- test ( 'should calculate contribution percentage correctly ' , async ( ) => {
1377+ test ( 'should apply multiple authors filter using regex pattern ' , async ( ) => {
14061378 // Arrange
1407- const twoCommits = `abc123|2023-01-01T12:00:00Z|Alice|alice@example.com|commit 1
1408- 10\t5\tfile1.ts
1379+ mockGit . raw . mockResolvedValue ( 'Alice\nBob' ) ;
14091380
1410- def456|2023-01-02T12:00:00Z|Alice|alice@example.com|commit 2
1411- 20\t10\tfile2.ts` ;
1412- mockGit . raw . mockResolvedValue ( twoCommits ) ;
1381+ // Act
1382+ await gitService . getContributors ( '/test/repo' , {
1383+ authors : [ 'Alice' , 'Bob' ] ,
1384+ } ) ;
1385+
1386+ // Assert
1387+ expect ( mockGit . raw ) . toHaveBeenCalledWith (
1388+ expect . arrayContaining ( [ 'log' , '--format=%aN' , '--author=Alice|Bob' ] )
1389+ ) ;
1390+ } ) ;
1391+
1392+ test ( 'should deduplicate contributor names correctly' , async ( ) => {
1393+ // Arrange
1394+ const rawOutput = 'Alice\nalice\nAlice\nAlice ' ;
1395+ mockGit . raw . mockResolvedValue ( rawOutput ) ;
14131396
14141397 // Act
1415- const result = await gitService . getTopContributors ( '/test/repo' ) ;
1398+ const result = await gitService . getContributors ( '/test/repo' ) ;
14161399
14171400 // Assert
1418- expect ( result [ 0 ] . contributionPercentage ) . toBe ( 1.0 ) ; // 100% when only one contributor
1401+ expect ( result ) . toHaveLength ( 2 ) ; // "Alice" and "alice" (case-sensitive)
1402+ // localeCompare sorts lowercase before uppercase, so "alice" comes before "Alice"
1403+ expect ( result ) . toEqual ( [ { login : 'alice' } , { login : 'Alice' } ] ) ;
1404+ } ) ;
1405+
1406+ test ( 'should handle empty or whitespace-only author names' , async ( ) => {
1407+ // Arrange
1408+ const rawOutput = 'Alice\n\n \nBob' ;
1409+ mockGit . raw . mockResolvedValue ( rawOutput ) ;
1410+
1411+ // Act
1412+ const result = await gitService . getContributors ( '/test/repo' ) ;
1413+
1414+ // Assert
1415+ expect ( result ) . toHaveLength ( 2 ) ;
1416+ expect ( result ) . toEqual ( [ { login : 'Alice' } , { login : 'Bob' } ] ) ;
14191417 } ) ;
14201418
14211419 test ( 'should handle git errors gracefully' , async ( ) => {
14221420 // Arrange
14231421 mockGit . raw . mockRejectedValue ( new Error ( 'Git error' ) ) ;
14241422
14251423 // Act & Assert
1426- await expect ( gitService . getTopContributors ( '/test/repo' ) ) . rejects . toThrow (
1427- 'Failed to get top contributors'
1424+ await expect ( gitService . getContributors ( '/test/repo' ) ) . rejects . toThrow (
1425+ 'Failed to get contributors'
1426+ ) ;
1427+ } ) ;
1428+
1429+ test ( 'should return all contributors without limiting to top 5' , async ( ) => {
1430+ // Arrange
1431+ const rawOutput = Array . from ( { length : 10 } , ( _ , i ) => `User${ i } ` ) . join (
1432+ '\n'
14281433 ) ;
1434+ mockGit . raw . mockResolvedValue ( rawOutput ) ;
1435+
1436+ // Act
1437+ const result = await gitService . getContributors ( '/test/repo' ) ;
1438+
1439+ // Assert
1440+ expect ( result ) . toHaveLength ( 10 ) ; // All 10 contributors returned
1441+ expect (
1442+ result . every ( ( c ) => 'login' in c && Object . keys ( c ) . length === 1 )
1443+ ) . toBe ( true ) ;
14291444 } ) ;
14301445 } ) ;
14311446
0 commit comments