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