@@ -2211,3 +2211,187 @@ func TestSetMapValue(t *testing.T) {
22112211 t .Errorf ("Expected no error setting int value, got %v" , err )
22122212 }
22132213}
2214+
2215+ // ─── failingGitClient forces ConfigSetGlobal to return errors for sync tests ───
2216+ // git.ConfigOps requires ConfigGetGlobal, ConfigSetGlobal, GetVersion, GetCommitHash.
2217+
2218+ type failingGitClient struct {
2219+ failKey string // if empty, all ConfigSetGlobal calls fail
2220+ }
2221+
2222+ func newFailingGitClient (failKey string ) * failingGitClient {
2223+ return & failingGitClient {failKey : failKey }
2224+ }
2225+
2226+ func (f * failingGitClient ) ConfigGetGlobal (_ string ) (string , error ) { return "" , nil }
2227+ func (f * failingGitClient ) ConfigSetGlobal (key , _ string ) error {
2228+ if f .failKey == "" || key == f .failKey {
2229+ return fmt .Errorf ("forced git error for %s" , key )
2230+ }
2231+ return nil
2232+ }
2233+ func (f * failingGitClient ) GetVersion () (string , error ) { return "test" , nil }
2234+ func (f * failingGitClient ) GetCommitHash () (string , error ) { return "abc" , nil }
2235+
2236+ func newTestConfigManagerWithFailingGit (failKey string ) * Manager {
2237+ return NewConfigManager (newFailingGitClient (failKey ))
2238+ }
2239+
2240+ // TestSyncDefaultSettings_EditorError covers the error return when setting core.editor fails
2241+ func TestSyncDefaultSettings_EditorError (t * testing.T ) {
2242+ cm := newTestConfigManagerWithFailingGit ("core.editor" )
2243+ cfg := cm .GetConfig ()
2244+ cfg .Default .Editor = "vim"
2245+ err := cm .syncDefaultSettings (cfg )
2246+ if err == nil {
2247+ t .Error ("expected error when core.editor ConfigSetGlobal fails" )
2248+ }
2249+ }
2250+
2251+ // TestSyncDefaultSettings_MergeToolError covers the merge.tool error path
2252+ func TestSyncDefaultSettings_MergeToolError (t * testing.T ) {
2253+ cm := newTestConfigManagerWithFailingGit ("merge.tool" )
2254+ cfg := cm .GetConfig ()
2255+ cfg .Default .Editor = "" // skip editor
2256+ cfg .Default .MergeTool = "vimdiff"
2257+ err := cm .syncDefaultSettings (cfg )
2258+ if err == nil {
2259+ t .Error ("expected error when merge.tool ConfigSetGlobal fails" )
2260+ }
2261+ }
2262+
2263+ // TestSyncDefaultSettings_BranchError covers the init.defaultBranch error path
2264+ func TestSyncDefaultSettings_BranchError (t * testing.T ) {
2265+ cm := newTestConfigManagerWithFailingGit ("init.defaultBranch" )
2266+ cfg := cm .GetConfig ()
2267+ cfg .Default .Editor = ""
2268+ cfg .Default .MergeTool = ""
2269+ cfg .Default .Branch = "main"
2270+ err := cm .syncDefaultSettings (cfg )
2271+ if err == nil {
2272+ t .Error ("expected error when init.defaultBranch ConfigSetGlobal fails" )
2273+ }
2274+ }
2275+
2276+ // TestSyncUISettings_ColorError covers the color.ui error path
2277+ func TestSyncUISettings_ColorError (t * testing.T ) {
2278+ cm := newTestConfigManagerWithFailingGit ("color.ui" )
2279+ cfg := cm .GetConfig ()
2280+ err := cm .syncUISettings (cfg )
2281+ if err == nil {
2282+ t .Error ("expected error when color.ui ConfigSetGlobal fails" )
2283+ }
2284+ }
2285+
2286+ // TestSyncUISettings_PagerError covers the core.pager error path.
2287+ // color.ui always uses ConfigSetGlobal; we use a key-specific failing mock so
2288+ // color.ui succeeds but core.pager fails.
2289+ func TestSyncUISettings_PagerError (t * testing.T ) {
2290+ cm := newTestConfigManagerWithFailingGit ("core.pager" )
2291+ cfg := cm .GetConfig ()
2292+ cfg .UI .Color = true // color.ui will call ConfigSetGlobal("color.ui","true") → passes
2293+ cfg .UI .Pager = false // triggers the pager branch → fails
2294+ err := cm .syncUISettings (cfg )
2295+ if err == nil {
2296+ t .Error ("expected error when core.pager ConfigSetGlobal fails" )
2297+ }
2298+ }
2299+
2300+ // TestSyncBehaviorSettings_AutoFetchError covers the fetch.auto error path
2301+ func TestSyncBehaviorSettings_AutoFetchError (t * testing.T ) {
2302+ cm := newTestConfigManagerWithFailingGit ("fetch.auto" )
2303+ cfg := cm .GetConfig ()
2304+ err := cm .syncBehaviorSettings (cfg )
2305+ if err == nil {
2306+ t .Error ("expected error when fetch.auto ConfigSetGlobal fails" )
2307+ }
2308+ }
2309+
2310+ // TestSyncAliasSettings_Error covers the alias set error path
2311+ func TestSyncAliasSettings_Error (t * testing.T ) {
2312+ cm := newTestConfigManagerWithFailingGit ("alias.co" )
2313+ cfg := cm .GetConfig ()
2314+ cfg .Aliases = map [string ]interface {}{"co" : "checkout" }
2315+ err := cm .syncAliasSettings (cfg )
2316+ if err == nil {
2317+ t .Error ("expected error when alias ConfigSetGlobal fails" )
2318+ }
2319+ }
2320+
2321+ // TestSyncToGitConfig_DefaultSettingsError verifies syncToGitConfig propagates editor error
2322+ func TestSyncToGitConfig_DefaultSettingsError (t * testing.T ) {
2323+ cm := newTestConfigManagerWithFailingGit ("core.editor" )
2324+ cfg := cm .GetConfig ()
2325+ cfg .Default .Editor = "vim"
2326+ err := cm .syncToGitConfig ()
2327+ if err == nil {
2328+ t .Error ("expected syncToGitConfig to return error on editor failure" )
2329+ }
2330+ }
2331+
2332+ // ─── helpers for FileOps error injection ───────────────────────────────────────
2333+
2334+ // mockTempFileWriteError is a TempFile whose Write always fails.
2335+ type mockTempFileWriteError struct {
2336+ name string
2337+ }
2338+
2339+ func (m * mockTempFileWriteError ) Write (_ []byte ) (int , error ) {
2340+ return 0 , fmt .Errorf ("forced write error" )
2341+ }
2342+ func (m * mockTempFileWriteError ) Close () error { return nil }
2343+ func (m * mockTempFileWriteError ) Name () string { return m .name }
2344+
2345+ // MockFileOpsWithWriteError wraps MockFileOps and injects a write error via CreateTemp.
2346+ type MockFileOpsWithWriteError struct {
2347+ * MockFileOps
2348+ }
2349+
2350+ func NewMockFileOpsWithWriteError () * MockFileOpsWithWriteError {
2351+ return & MockFileOpsWithWriteError {MockFileOps : NewMockFileOps ()}
2352+ }
2353+
2354+ func (m * MockFileOpsWithWriteError ) CreateTemp (dir , pattern string ) (TempFile , error ) {
2355+ return & mockTempFileWriteError {name : dir + "/tmp_" + pattern }, nil
2356+ }
2357+
2358+ // MockFileOpsWithRenameError wraps MockFileOps and makes Rename always fail.
2359+ type MockFileOpsWithRenameError struct {
2360+ * MockFileOps
2361+ }
2362+
2363+ func NewMockFileOpsWithRenameError () * MockFileOpsWithRenameError {
2364+ return & MockFileOpsWithRenameError {MockFileOps : NewMockFileOps ()}
2365+ }
2366+
2367+ func (m * MockFileOpsWithRenameError ) Rename (_ , _ string ) error {
2368+ return fmt .Errorf ("forced rename error" )
2369+ }
2370+
2371+ // TestWriteTempConfigWithOps_WriteError verifies that a write error is propagated.
2372+ func TestWriteTempConfigWithOps_WriteError (t * testing.T ) {
2373+ mockFS := NewMockFileOpsWithWriteError ()
2374+ if err := mockFS .MkdirAll ("/test" , 0700 ); err != nil {
2375+ t .Fatal (err )
2376+ }
2377+ cm := newTestConfigManager ()
2378+ cm .configPath = "/test/cfg.yaml"
2379+ _ , err := cm .writeTempConfigWithOps ("/test" , []byte ("data" ), mockFS )
2380+ if err == nil {
2381+ t .Error ("expected error when temp-file Write fails" )
2382+ }
2383+ }
2384+
2385+ // TestReplaceConfigFileWithOps_RenameError simulates a rename failure
2386+ func TestReplaceConfigFileWithOps_RenameError (t * testing.T ) {
2387+ mockFS := NewMockFileOpsWithRenameError ()
2388+ if err := mockFS .MkdirAll ("/test" , 0700 ); err != nil {
2389+ t .Fatal (err )
2390+ }
2391+ cm := newTestConfigManager ()
2392+ cm .configPath = "/test/cfg.yaml"
2393+ err := cm .replaceConfigFileWithOps ("/test/.tmp-file" , mockFS )
2394+ if err == nil {
2395+ t .Error ("expected error when rename fails" )
2396+ }
2397+ }
0 commit comments