@@ -4,6 +4,7 @@ import { GitHubService } from '../lib/GitHubService.js'
44import { GitWorktreeManager } from '../lib/GitWorktreeManager.js'
55import { ValidationRunner } from '../lib/ValidationRunner.js'
66import { CommitManager } from '../lib/CommitManager.js'
7+ import { MergeManager } from '../lib/MergeManager.js'
78import type { Issue , PullRequest } from '../types/index.js'
89import type { GitWorktree } from '../types/worktree.js'
910
@@ -12,6 +13,7 @@ vi.mock('../lib/GitHubService.js')
1213vi . mock ( '../lib/GitWorktreeManager.js' )
1314vi . mock ( '../lib/ValidationRunner.js' )
1415vi . mock ( '../lib/CommitManager.js' )
16+ vi . mock ( '../lib/MergeManager.js' )
1517
1618// Mock the logger to prevent console output during tests
1719vi . mock ( '../utils/logger.js' , ( ) => ( {
@@ -30,12 +32,14 @@ describe('FinishCommand', () => {
3032 let mockGitWorktreeManager : GitWorktreeManager
3133 let mockValidationRunner : ValidationRunner
3234 let mockCommitManager : CommitManager
35+ let mockMergeManager : MergeManager
3336
3437 beforeEach ( ( ) => {
3538 mockGitHubService = new GitHubService ( )
3639 mockGitWorktreeManager = new GitWorktreeManager ( )
3740 mockValidationRunner = new ValidationRunner ( )
3841 mockCommitManager = new CommitManager ( )
42+ mockMergeManager = new MergeManager ( )
3943
4044 // Mock ValidationRunner.runValidations to always succeed by default
4145 vi . mocked ( mockValidationRunner . runValidations ) . mockResolvedValue ( {
@@ -57,11 +61,16 @@ describe('FinishCommand', () => {
5761 // Mock CommitManager.commitChanges to succeed by default
5862 vi . mocked ( mockCommitManager . commitChanges ) . mockResolvedValue ( undefined )
5963
64+ // Mock MergeManager methods to succeed by default
65+ vi . mocked ( mockMergeManager . rebaseOnMain ) . mockResolvedValue ( undefined )
66+ vi . mocked ( mockMergeManager . performFastForwardMerge ) . mockResolvedValue ( undefined )
67+
6068 command = new FinishCommand (
6169 mockGitHubService ,
6270 mockGitWorktreeManager ,
6371 mockValidationRunner ,
64- mockCommitManager
72+ mockCommitManager ,
73+ mockMergeManager
6574 )
6675 } )
6776
@@ -1296,6 +1305,82 @@ describe('FinishCommand', () => {
12961305 } )
12971306
12981307 describe ( 'workflow execution order' , ( ) => {
1308+ it ( 'should execute complete workflow including merge steps' , async ( ) => {
1309+ // Test the complete workflow order: validate → detect → commit → rebase → merge
1310+ const executionOrder : string [ ] = [ ]
1311+
1312+ // Mock ValidationRunner to track execution order
1313+ vi . mocked ( mockValidationRunner . runValidations ) . mockImplementation ( async ( ) => {
1314+ executionOrder . push ( 'validation' )
1315+ return {
1316+ success : true ,
1317+ steps : [ ] ,
1318+ totalDuration : 0 ,
1319+ }
1320+ } )
1321+
1322+ // Mock CommitManager to track execution order
1323+ vi . mocked ( mockCommitManager . detectUncommittedChanges ) . mockImplementation ( async ( ) => {
1324+ executionOrder . push ( 'commit-detect' )
1325+ return {
1326+ hasUncommittedChanges : true ,
1327+ unstagedFiles : [ 'src/test.ts' ] ,
1328+ stagedFiles : [ ] ,
1329+ currentBranch : 'feat/issue-123' ,
1330+ isAheadOfRemote : false ,
1331+ isBehindRemote : false ,
1332+ }
1333+ } )
1334+
1335+ vi . mocked ( mockCommitManager . commitChanges ) . mockImplementation ( async ( ) => {
1336+ executionOrder . push ( 'commit-execute' )
1337+ } )
1338+
1339+ // Mock MergeManager to track execution order
1340+ vi . mocked ( mockMergeManager . rebaseOnMain ) . mockImplementation ( async ( ) => {
1341+ executionOrder . push ( 'rebase' )
1342+ } )
1343+
1344+ vi . mocked ( mockMergeManager . performFastForwardMerge ) . mockImplementation ( async ( ) => {
1345+ executionOrder . push ( 'merge' )
1346+ } )
1347+
1348+ vi . mocked ( mockGitHubService . detectInputType ) . mockResolvedValue ( {
1349+ type : 'issue' ,
1350+ number : 123 ,
1351+ rawInput : '123' ,
1352+ } )
1353+ vi . mocked ( mockGitHubService . fetchIssue ) . mockResolvedValue ( {
1354+ number : 123 ,
1355+ title : 'Test Issue' ,
1356+ state : 'open' ,
1357+ body : '' ,
1358+ labels : [ ] ,
1359+ assignees : [ ] ,
1360+ url : 'https://github.com/test/repo/issues/123' ,
1361+ } as Issue )
1362+ vi . mocked ( mockGitWorktreeManager . findWorktreesByIdentifier ) . mockResolvedValue ( [
1363+ { path : '/test/issue-123' , branch : 'feat/issue-123' , commit : 'abc123' , bare : false } ,
1364+ ] as GitWorktree [ ] )
1365+
1366+ // This should succeed with the correct order
1367+ await expect (
1368+ command . execute ( {
1369+ identifier : '123' ,
1370+ options : { } ,
1371+ } )
1372+ ) . resolves . not . toThrow ( )
1373+
1374+ // ✅ CORRECT: The implementation should follow this order
1375+ expect ( executionOrder ) . toEqual ( [
1376+ 'validation' , // ✅ First: Ensure code quality
1377+ 'commit-detect' , // ✅ Second: Check if there are changes to commit
1378+ 'commit-execute' , // ✅ Third: Only commit if validation passed
1379+ 'rebase' , // ✅ Fourth: Rebase branch on main
1380+ 'merge' // ✅ Fifth: Perform fast-forward merge
1381+ ] )
1382+ } )
1383+
12991384 it ( 'should run validation BEFORE detecting and committing changes' , async ( ) => {
13001385 // Test the correct workflow order: validate → detect → commit
13011386 const executionOrder : string [ ] = [ ]
@@ -1423,6 +1508,77 @@ describe('FinishCommand', () => {
14231508 expect ( mockCommitManager . detectUncommittedChanges ) . not . toHaveBeenCalled ( )
14241509 expect ( mockCommitManager . commitChanges ) . not . toHaveBeenCalled ( )
14251510 } )
1511+
1512+ it ( 'should pass correct options to MergeManager' , async ( ) => {
1513+ vi . mocked ( mockGitHubService . detectInputType ) . mockResolvedValue ( {
1514+ type : 'issue' ,
1515+ number : 123 ,
1516+ rawInput : '123' ,
1517+ } )
1518+ vi . mocked ( mockGitHubService . fetchIssue ) . mockResolvedValue ( {
1519+ number : 123 ,
1520+ title : 'Test Issue' ,
1521+ state : 'open' ,
1522+ body : '' ,
1523+ labels : [ ] ,
1524+ assignees : [ ] ,
1525+ url : 'https://github.com/test/repo/issues/123' ,
1526+ } as Issue )
1527+ vi . mocked ( mockGitWorktreeManager . findWorktreesByIdentifier ) . mockResolvedValue ( [
1528+ { path : '/test/issue-123' , branch : 'feat/issue-123' , commit : 'abc123' , bare : false } ,
1529+ ] as GitWorktree [ ] )
1530+
1531+ await command . execute ( {
1532+ identifier : '123' ,
1533+ options : { dryRun : true , force : true } ,
1534+ } )
1535+
1536+ // Verify MergeManager was called with correct options
1537+ expect ( mockMergeManager . rebaseOnMain ) . toHaveBeenCalledWith ( '/test/issue-123' , {
1538+ dryRun : true ,
1539+ force : true ,
1540+ } )
1541+ expect ( mockMergeManager . performFastForwardMerge ) . toHaveBeenCalledWith ( 'feat/issue-123' , '/test/issue-123' , {
1542+ dryRun : true ,
1543+ force : true ,
1544+ } )
1545+ } )
1546+
1547+ it ( 'should handle rebase conflicts and stop workflow' , async ( ) => {
1548+ vi . mocked ( mockGitHubService . detectInputType ) . mockResolvedValue ( {
1549+ type : 'issue' ,
1550+ number : 123 ,
1551+ rawInput : '123' ,
1552+ } )
1553+ vi . mocked ( mockGitHubService . fetchIssue ) . mockResolvedValue ( {
1554+ number : 123 ,
1555+ title : 'Test Issue' ,
1556+ state : 'open' ,
1557+ body : '' ,
1558+ labels : [ ] ,
1559+ assignees : [ ] ,
1560+ url : 'https://github.com/test/repo/issues/123' ,
1561+ } as Issue )
1562+ vi . mocked ( mockGitWorktreeManager . findWorktreesByIdentifier ) . mockResolvedValue ( [
1563+ { path : '/test/issue-123' , branch : 'feat/issue-123' , commit : 'abc123' , bare : false } ,
1564+ ] as GitWorktree [ ] )
1565+
1566+ // Mock rebase to fail with conflicts
1567+ vi . mocked ( mockMergeManager . rebaseOnMain ) . mockRejectedValue (
1568+ new Error ( 'Rebase failed: conflicts detected in src/test.ts' )
1569+ )
1570+
1571+ await expect (
1572+ command . execute ( {
1573+ identifier : '123' ,
1574+ options : { } ,
1575+ } )
1576+ ) . rejects . toThrow ( 'Rebase failed: conflicts detected in src/test.ts' )
1577+
1578+ // Verify merge was never called after rebase failure
1579+ expect ( mockMergeManager . performFastForwardMerge ) . not . toHaveBeenCalled ( )
1580+ } )
1581+
14261582 } )
14271583
14281584 describe ( 'dependency injection' , ( ) => {
@@ -1450,6 +1606,12 @@ describe('FinishCommand', () => {
14501606 expect ( cmd ) . toBeDefined ( )
14511607 } )
14521608
1609+ it ( 'should accept MergeManager via constructor' , ( ) => {
1610+ const customMergeManager = new MergeManager ( )
1611+ const cmd = new FinishCommand ( undefined , undefined , undefined , undefined , customMergeManager )
1612+ expect ( cmd ) . toBeDefined ( )
1613+ } )
1614+
14531615 it ( 'should create default instances when not provided' , ( ) => {
14541616 const cmd = new FinishCommand ( )
14551617 expect ( cmd ) . toBeDefined ( )
0 commit comments