1+ import { describe , it , expect , vi , beforeEach , afterEach } from "vitest" ;
2+ import { resolveConflicts } from "./index" ;
3+ import { execSync } from "node:child_process" ;
4+ import fs from "node:fs/promises" ;
5+
6+ // Mock all dependencies
7+ vi . mock ( "node:child_process" ) ;
8+ vi . mock ( "node:fs/promises" ) ;
9+ vi . mock ( "./file-parser" ) ;
10+ vi . mock ( "./file-serializer" ) ;
11+ vi . mock ( "./merger" ) ;
12+ vi . mock ( "./normalizer" ) ;
13+ vi . mock ( "./utils" ) ;
14+ vi . mock ( "./conflict-helper" ) ;
15+ vi . mock ( "./logger" ) ;
16+
17+ import { parseConflictContent } from "./file-parser" ;
18+ import { serialize } from "./file-serializer" ;
19+ import { mergeObject } from "./merger" ;
20+ import { normalizeConfig } from "./normalizer" ;
21+ import { backupFile , listMatchingFiles } from "./utils" ;
22+ import { reconstructConflict } from "./conflict-helper" ;
23+ import { createLogger } from "./logger" ;
24+
25+ const mockExecSync = vi . mocked ( execSync ) ;
26+ const mockFs = vi . mocked ( fs ) ;
27+ const mockParseConflictContent = vi . mocked ( parseConflictContent ) ;
28+ const mockSerialize = vi . mocked ( serialize ) ;
29+ const mockMergeObject = vi . mocked ( mergeObject ) ;
30+ const mockNormalizeConfig = vi . mocked ( normalizeConfig ) ;
31+ const mockBackupFile = vi . mocked ( backupFile ) ;
32+ const mockListMatchingFiles = vi . mocked ( listMatchingFiles ) ;
33+ const mockReconstructConflict = vi . mocked ( reconstructConflict ) ;
34+ const mockCreateLogger = vi . mocked ( createLogger ) ;
35+
36+ describe ( "resolveConflicts" , ( ) => {
37+ const mockLogger = {
38+ info : vi . fn ( ) ,
39+ debug : vi . fn ( ) ,
40+ warn : vi . fn ( ) ,
41+ flush : vi . fn ( ) ,
42+ } ;
43+
44+ beforeEach ( ( ) => {
45+ vi . clearAllMocks ( ) ;
46+ mockCreateLogger . mockReturnValue ( mockLogger ) ;
47+ mockNormalizeConfig . mockResolvedValue ( {
48+ debug : false ,
49+ customStrategies : { } ,
50+ } as any ) ;
51+ mockListMatchingFiles . mockResolvedValue ( [ ] ) ;
52+ } ) ;
53+
54+ afterEach ( ( ) => {
55+ vi . clearAllMocks ( ) ;
56+ } ) ;
57+
58+ it ( "processes files without conflicts successfully" , async ( ) => {
59+ const config = { defaultStrategy : "ours" as const } ;
60+ const fileEntry = { filePath : "test.json" , content : "content" } ;
61+ const parsedContent = { theirs : { a : 1 } , ours : { a : 2 } , format : "json" } ;
62+ const mergedResult = { a : 2 } ;
63+
64+ mockListMatchingFiles . mockResolvedValue ( [ fileEntry ] ) ;
65+ mockParseConflictContent . mockResolvedValue ( parsedContent ) ;
66+ mockMergeObject . mockResolvedValue ( mergedResult ) ;
67+ mockSerialize . mockResolvedValue ( '{"a":2}' ) ;
68+ mockBackupFile . mockResolvedValue ( "backup/test.json" ) ;
69+
70+ await resolveConflicts ( config ) ;
71+
72+ expect ( mockNormalizeConfig ) . toHaveBeenCalledWith ( config ) ;
73+ expect ( mockListMatchingFiles ) . toHaveBeenCalled ( ) ;
74+ expect ( mockParseConflictContent ) . toHaveBeenCalledWith ( "content" , { filename : "test.json" } ) ;
75+ expect ( mockMergeObject ) . toHaveBeenCalledWith ( {
76+ ours : { a : 2 } ,
77+ theirs : { a : 1 } ,
78+ base : undefined ,
79+ filePath : "test.json" ,
80+ conflicts : [ ] ,
81+ path : "" ,
82+ ctx : expect . objectContaining ( {
83+ config : expect . any ( Object ) ,
84+ strategies : { } ,
85+ _strategyCache : expect . any ( Map ) ,
86+ } ) ,
87+ } ) ;
88+ expect ( mockBackupFile ) . toHaveBeenCalledWith ( "test.json" , undefined ) ;
89+ expect ( mockSerialize ) . toHaveBeenCalledWith ( "json" , mergedResult ) ;
90+ expect ( mockFs . writeFile ) . toHaveBeenCalledWith ( "test.json" , '{"a":2}' , "utf8" ) ;
91+ expect ( mockExecSync ) . toHaveBeenCalledWith ( "git add test.json" ) ;
92+ expect ( mockLogger . flush ) . toHaveBeenCalled ( ) ;
93+ } ) ;
94+
95+ it ( "handles files with conflicts" , async ( ) => {
96+ const config = { writeConflictSidecar : true } ;
97+ const conflicts = [ { path : "a" , reason : "test conflict" } ] ;
98+ const reconstructedContent = "conflict content" ;
99+
100+ mockListMatchingFiles . mockResolvedValue ( [ { filePath : "test.json" , content : "content" } ] ) ;
101+ mockParseConflictContent . mockResolvedValue ( { theirs : { } , ours : { } , format : "json" } ) ;
102+ mockMergeObject . mockImplementation ( ( { conflicts : conflictsArray } ) => {
103+ conflictsArray . push ( ...conflicts ) ;
104+ return Promise . resolve ( { } ) ;
105+ } ) ;
106+ mockReconstructConflict . mockResolvedValue ( reconstructedContent ) ;
107+
108+ await resolveConflicts ( config ) ;
109+
110+ expect ( mockReconstructConflict ) . toHaveBeenCalledWith ( { } , { } , { } , "json" ) ;
111+ expect ( mockFs . writeFile ) . toHaveBeenCalledWith ( "test.json" , reconstructedContent , "utf8" ) ;
112+ expect ( mockFs . writeFile ) . toHaveBeenCalledWith (
113+ "test.json.conflict.json" ,
114+ JSON . stringify ( conflicts , null , 2 )
115+ ) ;
116+ expect ( mockExecSync ) . not . toHaveBeenCalled ( ) ;
117+ } ) ;
118+
119+ it ( "handles git staging errors gracefully" , async ( ) => {
120+ mockListMatchingFiles . mockResolvedValue ( [ { filePath : "test.json" , content : "content" } ] ) ;
121+ mockParseConflictContent . mockResolvedValue ( { theirs : { } , ours : { } , format : "json" } ) ;
122+ mockMergeObject . mockResolvedValue ( { } ) ;
123+ mockSerialize . mockResolvedValue ( "{}" ) ;
124+ mockExecSync . mockImplementation ( ( ) => {
125+ throw new Error ( "git error" ) ;
126+ } ) ;
127+
128+ await resolveConflicts ( { } ) ;
129+
130+ expect ( mockLogger . warn ) . toHaveBeenCalledWith ( "test.json" , "Failed to stage file: Error: git error" ) ;
131+ } ) ;
132+
133+ it ( "enables debug logging when configured" , async ( ) => {
134+ const config = { debug : true } ;
135+ mockNormalizeConfig . mockResolvedValue ( { debug : true , customStrategies : { } } as any ) ;
136+ mockListMatchingFiles . mockResolvedValue ( [ { filePath : "test.json" , content : "content" } ] ) ;
137+ mockParseConflictContent . mockResolvedValue ( { theirs : { } , ours : { } , format : "json" } ) ;
138+ mockMergeObject . mockResolvedValue ( { } ) ;
139+
140+ await resolveConflicts ( config ) ;
141+
142+ expect ( mockLogger . info ) . toHaveBeenCalledWith (
143+ "all" ,
144+ expect . stringContaining ( "normalizedConfig" )
145+ ) ;
146+ expect ( mockLogger . debug ) . toHaveBeenCalledWith (
147+ "test.json" ,
148+ expect . stringContaining ( "merged" )
149+ ) ;
150+ } ) ;
151+
152+ it ( "processes multiple files concurrently" , async ( ) => {
153+ const files = [
154+ { filePath : "file1.json" , content : "content1" } ,
155+ { filePath : "file2.json" , content : "content2" } ,
156+ ] ;
157+
158+ mockListMatchingFiles . mockResolvedValue ( files ) ;
159+ mockParseConflictContent . mockResolvedValue ( { theirs : { } , ours : { } , format : "json" } ) ;
160+ mockMergeObject . mockResolvedValue ( { } ) ;
161+ mockSerialize . mockResolvedValue ( "{}" ) ;
162+
163+ await resolveConflicts ( { } ) ;
164+
165+ expect ( mockParseConflictContent ) . toHaveBeenCalledTimes ( 2 ) ;
166+ expect ( mockMergeObject ) . toHaveBeenCalledTimes ( 2 ) ;
167+ expect ( mockBackupFile ) . toHaveBeenCalledTimes ( 2 ) ;
168+ } ) ;
169+
170+ it ( "uses custom backup directory" , async ( ) => {
171+ const config = { backupDir : "custom-backup" } ;
172+ mockListMatchingFiles . mockResolvedValue ( [ { filePath : "test.json" , content : "content" } ] ) ;
173+ mockParseConflictContent . mockResolvedValue ( { theirs : { } , ours : { } , format : "json" } ) ;
174+ mockMergeObject . mockResolvedValue ( { } ) ;
175+
176+ await resolveConflicts ( config ) ;
177+
178+ expect ( mockBackupFile ) . toHaveBeenCalledWith ( "test.json" , "custom-backup" ) ;
179+ } ) ;
180+ } ) ;
0 commit comments