Skip to content

Commit f72eaf5

Browse files
committed
fix: improve coverage for config and git packages
- Add comprehensive tests for GetSection/SetSection with all 20+ config sections - Add edge case tests for mergeMap (nil/empty maps) - Add BackupConfig success and error path tests - Improve determineCurrent test coverage - Create new sections_test.go Signed-off-by: Andre Nogueira <aanogueira@protonmail.com>
1 parent 37ecea9 commit f72eaf5

File tree

9 files changed

+645
-412
lines changed

9 files changed

+645
-412
lines changed

cmd/switch.go

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -88,44 +88,35 @@ func runSwitch(cmd *cobra.Command, args []string) error {
8888
// profileToGitConfig converts a Profile to a git configuration map.
8989
// It maps profile fields to git config keys (user.name, user.email, etc.).
9090
func profileToGitConfig(profile *config.Profile) map[string]any {
91-
config := make(map[string]any)
91+
gitConfig := make(map[string]any)
9292

9393
// User section
9494
if profile.User.Name != "" {
95-
config["user.name"] = profile.User.Name
95+
gitConfig["user.name"] = profile.User.Name
9696
}
9797

9898
if profile.User.Email != "" {
99-
config["user.email"] = profile.User.Email
99+
gitConfig["user.email"] = profile.User.Email
100100
}
101101

102102
if profile.User.SigningKey != "" {
103-
config["user.signingkey"] = profile.User.SigningKey
103+
gitConfig["user.signingkey"] = profile.User.SigningKey
104104
}
105105

106106
// URL rewrites
107107
for _, url := range profile.URL {
108108
key := fmt.Sprintf("url \"%s\".insteadOf", url.Pattern)
109-
config[key] = url.InsteadOf
109+
gitConfig[key] = url.InsteadOf
110110
}
111111

112-
// Add other sections from the profile
113-
addSectionToConfig(config, "http", profile.HTTP)
114-
addSectionToConfig(config, "core", profile.Core)
115-
addSectionToConfig(config, "interactive", profile.Interactive)
116-
addSectionToConfig(config, "add", profile.Add)
117-
addSectionToConfig(config, "delta", profile.Delta)
118-
addSectionToConfig(config, "push", profile.Push)
119-
addSectionToConfig(config, "merge", profile.Merge)
120-
addSectionToConfig(config, "commit", profile.Commit)
121-
addSectionToConfig(config, "gpg", profile.GPG)
122-
addSectionToConfig(config, "pull", profile.Pull)
123-
addSectionToConfig(config, "rerere", profile.Rerere)
124-
addSectionToConfig(config, "column", profile.Column)
125-
addSectionToConfig(config, "branch", profile.Branch)
126-
addSectionToConfig(config, "init", profile.Init)
127-
128-
return config
112+
// Dynamically add all sections from the profile
113+
for _, section := range config.ConfigSections {
114+
if sectionMap := profile.GetSection(section); sectionMap != nil {
115+
addSectionToConfig(gitConfig, section, sectionMap)
116+
}
117+
}
118+
119+
return gitConfig
129120
}
130121

131122
// addSectionToConfig adds a section with values to the git configuration map.

internal/config/config.go

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,30 @@ import (
1414

1515
// Profile represents a git configuration profile.
1616
type Profile struct {
17-
User UserConfig `yaml:"user,omitempty"`
18-
URL []URLConfig `yaml:"url,omitempty"`
19-
HTTP map[string]any `yaml:"http,omitempty"`
20-
Core map[string]any `yaml:"core,omitempty"`
21-
Interactive map[string]any `yaml:"interactive,omitempty"`
2217
Add map[string]any `yaml:"add,omitempty"`
23-
Delta map[string]any `yaml:"delta,omitempty"`
24-
Push map[string]any `yaml:"push,omitempty"`
25-
Merge map[string]any `yaml:"merge,omitempty"`
18+
Alias map[string]any `yaml:"alias,omitempty"`
19+
Branch map[string]any `yaml:"branch,omitempty"`
20+
Column map[string]any `yaml:"column,omitempty"`
2621
Commit map[string]any `yaml:"commit,omitempty"`
22+
Core map[string]any `yaml:"core,omitempty"`
23+
Custom map[string]any `yaml:"custom,omitempty"`
24+
Delta map[string]any `yaml:"delta,omitempty"`
25+
Diff map[string]any `yaml:"diff,omitempty"`
26+
Feature map[string]any `yaml:"feature,omitempty"`
27+
Fetch map[string]any `yaml:"fetch,omitempty"`
2728
GPG map[string]any `yaml:"gpg,omitempty"`
29+
HTTP map[string]any `yaml:"http,omitempty"`
30+
Init map[string]any `yaml:"init,omitempty"`
31+
Interactive map[string]any `yaml:"interactive,omitempty"`
32+
Maintenance map[string]any `yaml:"maintenance,omitempty"`
33+
Merge map[string]any `yaml:"merge,omitempty"`
2834
Pull map[string]any `yaml:"pull,omitempty"`
35+
Push map[string]any `yaml:"push,omitempty"`
36+
Rebase map[string]any `yaml:"rebase,omitempty"`
2937
Rerere map[string]any `yaml:"rerere,omitempty"`
30-
Column map[string]any `yaml:"column,omitempty"`
31-
Branch map[string]any `yaml:"branch,omitempty"`
32-
Init map[string]any `yaml:"init,omitempty"`
33-
Custom map[string]any `yaml:"custom,omitempty"`
38+
Tag map[string]any `yaml:"tag,omitempty"`
39+
URL []URLConfig `yaml:"url,omitempty"`
40+
User UserConfig `yaml:"user,omitempty"`
3441
}
3542

3643
// UserConfig represents git user section.
@@ -180,25 +187,21 @@ func (c *Config) Merge(profileName string) (*Profile, error) {
180187

181188
// Create a new merged profile
182189
merged := &Profile{
183-
User: profile.User,
184-
URL: mergedURLs,
185-
HTTP: mergeMap(getGlobalSection("http"), profile.HTTP),
186-
Core: mergeMap(getGlobalSection("core"), profile.Core),
187-
Interactive: mergeMap(getGlobalSection("interactive"), profile.Interactive),
188-
Add: mergeMap(getGlobalSection("add"), profile.Add),
189-
Delta: mergeMap(getGlobalSection("delta"), profile.Delta),
190-
Push: mergeMap(getGlobalSection("push"), profile.Push),
191-
Merge: mergeMap(getGlobalSection("merge"), profile.Merge),
192-
Commit: mergeMap(getGlobalSection("commit"), profile.Commit),
193-
GPG: mergeMap(getGlobalSection("gpg"), profile.GPG),
194-
Pull: mergeMap(getGlobalSection("pull"), profile.Pull),
195-
Rerere: mergeMap(getGlobalSection("rerere"), profile.Rerere),
196-
Column: mergeMap(getGlobalSection("column"), profile.Column),
197-
Branch: mergeMap(getGlobalSection("branch"), profile.Branch),
198-
Init: mergeMap(getGlobalSection("init"), profile.Init),
199-
Custom: profile.Custom,
190+
User: profile.User,
191+
URL: mergedURLs,
200192
}
201193

194+
// Dynamically merge all sections
195+
for _, section := range ConfigSections {
196+
merged.SetSection(section, mergeMap(
197+
getGlobalSection(section),
198+
profile.GetSection(section),
199+
))
200+
}
201+
202+
// Custom section is not merged with global
203+
merged.Custom = profile.Custom
204+
202205
return merged, nil
203206
}
204207

internal/config/config_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,3 +488,96 @@ func TestDetermineCurrent(t *testing.T) {
488488
t.Errorf("Current should be empty when no matching git config, got: %s", cfg.Current)
489489
}
490490
}
491+
492+
func TestDetermineCurrentDirectly(t *testing.T) {
493+
t.Parallel()
494+
495+
cfg := NewConfig()
496+
497+
// Test with no profiles
498+
cfg.determineCurrent()
499+
500+
if cfg.Current != "" {
501+
t.Errorf("Current should be empty with no profiles, got: %s", cfg.Current)
502+
}
503+
504+
// Add a profile
505+
cfg.Profiles["test"] = &Profile{
506+
User: UserConfig{
507+
Name: "Test User",
508+
Email: "test@example.com",
509+
},
510+
}
511+
512+
// Call determineCurrent - won't match unless git config actually has these values
513+
// This tests the code path for non-matching profiles
514+
cfg.determineCurrent()
515+
// Current will be empty unless the system's actual git config matches
516+
// We're just ensuring no panic/error occurs
517+
518+
// Add multiple profiles to test the matching loop
519+
cfg.Profiles["work"] = &Profile{
520+
User: UserConfig{
521+
Name: "Work User",
522+
Email: "work@example.com",
523+
},
524+
}
525+
cfg.Profiles["personal"] = &Profile{
526+
User: UserConfig{
527+
Name: "Personal User",
528+
Email: "personal@example.com",
529+
},
530+
}
531+
532+
cfg.determineCurrent()
533+
// Again, just ensuring the function completes without error
534+
}
535+
536+
func TestMergeMapEdgeCases(t *testing.T) {
537+
t.Parallel()
538+
539+
// Test merging empty maps
540+
result := mergeMap(nil, nil)
541+
if len(result) != 0 {
542+
t.Errorf("Merging nil maps should produce empty map, got length %d", len(result))
543+
}
544+
545+
// Test merging with nil global
546+
profile := map[string]any{"key1": "value1"}
547+
548+
result = mergeMap(nil, profile)
549+
if len(result) != 1 || result["key1"] != "value1" {
550+
t.Error("Should use profile values when global is nil")
551+
}
552+
553+
// Test merging with nil profile
554+
global := map[string]any{"key2": "value2"}
555+
556+
result = mergeMap(global, nil)
557+
if len(result) != 1 || result["key2"] != "value2" {
558+
t.Error("Should use global values when profile is nil")
559+
}
560+
561+
// Test override behavior
562+
global = map[string]any{
563+
"key1": "global1",
564+
"key2": "global2",
565+
}
566+
profile = map[string]any{
567+
"key1": "profile1",
568+
"key3": "profile3",
569+
}
570+
result = mergeMap(global, profile)
571+
572+
if result["key1"] != "profile1" {
573+
t.Error("Profile value should override global")
574+
}
575+
576+
if result["key2"] != "global2" {
577+
t.Error("Global value should be preserved when not in profile")
578+
}
579+
580+
if result["key3"] != "profile3" {
581+
t.Error("Profile value should be included")
582+
}
583+
}

internal/config/sections.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package config
2+
3+
// ConfigSections defines all supported git config sections.
4+
// This makes it easy to add new sections without modifying multiple places.
5+
var ConfigSections = []string{
6+
"add",
7+
"alias",
8+
"branch",
9+
"column",
10+
"commit",
11+
"core",
12+
"delta",
13+
"diff",
14+
"feature",
15+
"fetch",
16+
"gpg",
17+
"http",
18+
"init",
19+
"interactive",
20+
"maintenance",
21+
"merge",
22+
"pull",
23+
"push",
24+
"rebase",
25+
"rerere",
26+
"tag",
27+
}
28+
29+
// GetSection returns the section map from a profile by name.
30+
// This allows dynamic access to profile sections.
31+
func (p *Profile) GetSection(name string) map[string]any {
32+
switch name {
33+
case "add":
34+
return p.Add
35+
case "alias":
36+
return p.Alias
37+
case "branch":
38+
return p.Branch
39+
case "column":
40+
return p.Column
41+
case "commit":
42+
return p.Commit
43+
case "core":
44+
return p.Core
45+
case "delta":
46+
return p.Delta
47+
case "diff":
48+
return p.Diff
49+
case "feature":
50+
return p.Feature
51+
case "fetch":
52+
return p.Fetch
53+
case "gpg":
54+
return p.GPG
55+
case "http":
56+
return p.HTTP
57+
case "init":
58+
return p.Init
59+
case "interactive":
60+
return p.Interactive
61+
case "maintenance":
62+
return p.Maintenance
63+
case "merge":
64+
return p.Merge
65+
case "pull":
66+
return p.Pull
67+
case "push":
68+
return p.Push
69+
case "rebase":
70+
return p.Rebase
71+
case "rerere":
72+
return p.Rerere
73+
case "tag":
74+
return p.Tag
75+
default:
76+
return nil
77+
}
78+
}
79+
80+
// SetSection sets the section map in a profile by name.
81+
// This allows dynamic modification of profile sections.
82+
func (p *Profile) SetSection(name string, values map[string]any) {
83+
switch name {
84+
case "add":
85+
p.Add = values
86+
case "alias":
87+
p.Alias = values
88+
case "branch":
89+
p.Branch = values
90+
case "column":
91+
p.Column = values
92+
case "commit":
93+
p.Commit = values
94+
case "core":
95+
p.Core = values
96+
case "delta":
97+
p.Delta = values
98+
case "diff":
99+
p.Diff = values
100+
case "feature":
101+
p.Feature = values
102+
case "fetch":
103+
p.Fetch = values
104+
case "gpg":
105+
p.GPG = values
106+
case "http":
107+
p.HTTP = values
108+
case "init":
109+
p.Init = values
110+
case "interactive":
111+
p.Interactive = values
112+
case "maintenance":
113+
p.Maintenance = values
114+
case "merge":
115+
p.Merge = values
116+
case "pull":
117+
p.Pull = values
118+
case "push":
119+
p.Push = values
120+
case "rebase":
121+
p.Rebase = values
122+
case "rerere":
123+
p.Rerere = values
124+
case "tag":
125+
p.Tag = values
126+
}
127+
}

0 commit comments

Comments
 (0)