refactor(config): improve sync config with warnings toggle and dedup
Code review improvements to internal/config/sync.go: 1. Warning suppression toggle - Add ConfigWarnings bool to enable/disable warnings - Add ConfigWarningWriter io.Writer for testable output 2. Consolidate sync mode constants - cmd/bd/sync_mode.go now imports from internal/config - Single source of truth for mode values - Uses shared IsValidSyncMode() for validation 3. Fix empty sovereignty semantics - Empty now returns SovereigntyNone (no restriction) - Only non-empty invalid values fall back to T1 with warning 4. Export validation helpers - IsValidSyncMode(), IsValidConflictStrategy(), IsValidSovereignty() - ValidSyncModes(), ValidConflictStrategies(), ValidSovereigntyTiers() - String() methods on all typed values 5. Logger interface - ConfigWarningWriter allows custom logging destinations - Tests can capture warnings without os.Stderr manipulation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -83,18 +82,14 @@ func TestGetSyncMode(t *testing.T) {
|
||||
Set("sync.mode", tt.configValue)
|
||||
}
|
||||
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stderr = w
|
||||
// Capture warnings using ConfigWarningWriter
|
||||
var buf bytes.Buffer
|
||||
oldWriter := ConfigWarningWriter
|
||||
ConfigWarningWriter = &buf
|
||||
defer func() { ConfigWarningWriter = oldWriter }()
|
||||
|
||||
result := GetSyncMode()
|
||||
|
||||
// Restore stderr and get output
|
||||
w.Close()
|
||||
os.Stderr = oldStderr
|
||||
var buf bytes.Buffer
|
||||
buf.ReadFrom(r)
|
||||
stderrOutput := buf.String()
|
||||
|
||||
if result != tt.expectedMode {
|
||||
@@ -103,10 +98,10 @@ func TestGetSyncMode(t *testing.T) {
|
||||
|
||||
hasWarning := strings.Contains(stderrOutput, "Warning:")
|
||||
if tt.expectsWarning && !hasWarning {
|
||||
t.Errorf("Expected warning in stderr, got none. stderr=%q", stderrOutput)
|
||||
t.Errorf("Expected warning in output, got none. output=%q", stderrOutput)
|
||||
}
|
||||
if !tt.expectsWarning && hasWarning {
|
||||
t.Errorf("Unexpected warning in stderr: %q", stderrOutput)
|
||||
t.Errorf("Unexpected warning in output: %q", stderrOutput)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -188,18 +183,14 @@ func TestGetConflictStrategy(t *testing.T) {
|
||||
Set("conflict.strategy", tt.configValue)
|
||||
}
|
||||
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stderr = w
|
||||
// Capture warnings using ConfigWarningWriter
|
||||
var buf bytes.Buffer
|
||||
oldWriter := ConfigWarningWriter
|
||||
ConfigWarningWriter = &buf
|
||||
defer func() { ConfigWarningWriter = oldWriter }()
|
||||
|
||||
result := GetConflictStrategy()
|
||||
|
||||
// Restore stderr and get output
|
||||
w.Close()
|
||||
os.Stderr = oldStderr
|
||||
var buf bytes.Buffer
|
||||
buf.ReadFrom(r)
|
||||
stderrOutput := buf.String()
|
||||
|
||||
if result != tt.expectedStrategy {
|
||||
@@ -208,10 +199,10 @@ func TestGetConflictStrategy(t *testing.T) {
|
||||
|
||||
hasWarning := strings.Contains(stderrOutput, "Warning:")
|
||||
if tt.expectsWarning && !hasWarning {
|
||||
t.Errorf("Expected warning in stderr, got none. stderr=%q", stderrOutput)
|
||||
t.Errorf("Expected warning in output, got none. output=%q", stderrOutput)
|
||||
}
|
||||
if !tt.expectsWarning && hasWarning {
|
||||
t.Errorf("Unexpected warning in stderr: %q", stderrOutput)
|
||||
t.Errorf("Unexpected warning in output: %q", stderrOutput)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -225,9 +216,9 @@ func TestGetSovereignty(t *testing.T) {
|
||||
expectsWarning bool
|
||||
}{
|
||||
{
|
||||
name: "empty returns default",
|
||||
name: "empty returns no restriction",
|
||||
configValue: "",
|
||||
expectedTier: SovereigntyT1,
|
||||
expectedTier: SovereigntyNone,
|
||||
expectsWarning: false,
|
||||
},
|
||||
{
|
||||
@@ -267,19 +258,19 @@ func TestGetSovereignty(t *testing.T) {
|
||||
expectsWarning: false,
|
||||
},
|
||||
{
|
||||
name: "invalid value returns default with warning",
|
||||
name: "invalid value returns T1 with warning",
|
||||
configValue: "T5",
|
||||
expectedTier: SovereigntyT1,
|
||||
expectsWarning: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tier 0 returns default with warning",
|
||||
name: "invalid tier 0 returns T1 with warning",
|
||||
configValue: "T0",
|
||||
expectedTier: SovereigntyT1,
|
||||
expectsWarning: true,
|
||||
},
|
||||
{
|
||||
name: "word tier returns default with warning",
|
||||
name: "word tier returns T1 with warning",
|
||||
configValue: "public",
|
||||
expectedTier: SovereigntyT1,
|
||||
expectsWarning: true,
|
||||
@@ -299,18 +290,14 @@ func TestGetSovereignty(t *testing.T) {
|
||||
Set("federation.sovereignty", tt.configValue)
|
||||
}
|
||||
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stderr = w
|
||||
// Capture warnings using ConfigWarningWriter
|
||||
var buf bytes.Buffer
|
||||
oldWriter := ConfigWarningWriter
|
||||
ConfigWarningWriter = &buf
|
||||
defer func() { ConfigWarningWriter = oldWriter }()
|
||||
|
||||
result := GetSovereignty()
|
||||
|
||||
// Restore stderr and get output
|
||||
w.Close()
|
||||
os.Stderr = oldStderr
|
||||
var buf bytes.Buffer
|
||||
buf.ReadFrom(r)
|
||||
stderrOutput := buf.String()
|
||||
|
||||
if result != tt.expectedTier {
|
||||
@@ -319,11 +306,175 @@ func TestGetSovereignty(t *testing.T) {
|
||||
|
||||
hasWarning := strings.Contains(stderrOutput, "Warning:")
|
||||
if tt.expectsWarning && !hasWarning {
|
||||
t.Errorf("Expected warning in stderr, got none. stderr=%q", stderrOutput)
|
||||
t.Errorf("Expected warning in output, got none. output=%q", stderrOutput)
|
||||
}
|
||||
if !tt.expectsWarning && hasWarning {
|
||||
t.Errorf("Unexpected warning in stderr: %q", stderrOutput)
|
||||
t.Errorf("Unexpected warning in output: %q", stderrOutput)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigWarningsToggle(t *testing.T) {
|
||||
// Reset viper for test
|
||||
ResetForTesting()
|
||||
if err := Initialize(); err != nil {
|
||||
t.Fatalf("Initialize failed: %v", err)
|
||||
}
|
||||
|
||||
// Set an invalid value
|
||||
Set("sync.mode", "invalid-mode")
|
||||
|
||||
// Capture warnings
|
||||
var buf bytes.Buffer
|
||||
oldWriter := ConfigWarningWriter
|
||||
ConfigWarningWriter = &buf
|
||||
|
||||
// With warnings enabled (default)
|
||||
ConfigWarnings = true
|
||||
_ = GetSyncMode()
|
||||
if !strings.Contains(buf.String(), "Warning:") {
|
||||
t.Error("Expected warning with ConfigWarnings=true, got none")
|
||||
}
|
||||
|
||||
// With warnings disabled
|
||||
buf.Reset()
|
||||
ConfigWarnings = false
|
||||
_ = GetSyncMode()
|
||||
if strings.Contains(buf.String(), "Warning:") {
|
||||
t.Error("Expected no warning with ConfigWarnings=false, got one")
|
||||
}
|
||||
|
||||
// Restore defaults
|
||||
ConfigWarnings = true
|
||||
ConfigWarningWriter = oldWriter
|
||||
}
|
||||
|
||||
func TestIsValidSyncMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
mode string
|
||||
valid bool
|
||||
}{
|
||||
{"git-portable", true},
|
||||
{"realtime", true},
|
||||
{"dolt-native", true},
|
||||
{"belt-and-suspenders", true},
|
||||
{"Git-Portable", true}, // case insensitive
|
||||
{" realtime ", true}, // whitespace trimmed
|
||||
{"invalid", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got := IsValidSyncMode(tt.mode); got != tt.valid {
|
||||
t.Errorf("IsValidSyncMode(%q) = %v, want %v", tt.mode, got, tt.valid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidConflictStrategy(t *testing.T) {
|
||||
tests := []struct {
|
||||
strategy string
|
||||
valid bool
|
||||
}{
|
||||
{"newest", true},
|
||||
{"ours", true},
|
||||
{"theirs", true},
|
||||
{"manual", true},
|
||||
{"NEWEST", true}, // case insensitive
|
||||
{" ours ", true}, // whitespace trimmed
|
||||
{"invalid", false},
|
||||
{"lww", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got := IsValidConflictStrategy(tt.strategy); got != tt.valid {
|
||||
t.Errorf("IsValidConflictStrategy(%q) = %v, want %v", tt.strategy, got, tt.valid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidSovereignty(t *testing.T) {
|
||||
tests := []struct {
|
||||
sovereignty string
|
||||
valid bool
|
||||
}{
|
||||
{"T1", true},
|
||||
{"T2", true},
|
||||
{"T3", true},
|
||||
{"T4", true},
|
||||
{"t1", true}, // case insensitive
|
||||
{" T2 ", true}, // whitespace trimmed
|
||||
{"", true}, // empty is valid (no restriction)
|
||||
{"T0", false},
|
||||
{"T5", false},
|
||||
{"public", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got := IsValidSovereignty(tt.sovereignty); got != tt.valid {
|
||||
t.Errorf("IsValidSovereignty(%q) = %v, want %v", tt.sovereignty, got, tt.valid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidSyncModes(t *testing.T) {
|
||||
modes := ValidSyncModes()
|
||||
if len(modes) != 4 {
|
||||
t.Errorf("ValidSyncModes() returned %d modes, want 4", len(modes))
|
||||
}
|
||||
expected := []string{"git-portable", "realtime", "dolt-native", "belt-and-suspenders"}
|
||||
for i, m := range modes {
|
||||
if m != expected[i] {
|
||||
t.Errorf("ValidSyncModes()[%d] = %q, want %q", i, m, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidConflictStrategies(t *testing.T) {
|
||||
strategies := ValidConflictStrategies()
|
||||
if len(strategies) != 4 {
|
||||
t.Errorf("ValidConflictStrategies() returned %d strategies, want 4", len(strategies))
|
||||
}
|
||||
expected := []string{"newest", "ours", "theirs", "manual"}
|
||||
for i, s := range strategies {
|
||||
if s != expected[i] {
|
||||
t.Errorf("ValidConflictStrategies()[%d] = %q, want %q", i, s, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidSovereigntyTiers(t *testing.T) {
|
||||
tiers := ValidSovereigntyTiers()
|
||||
if len(tiers) != 4 {
|
||||
t.Errorf("ValidSovereigntyTiers() returned %d tiers, want 4", len(tiers))
|
||||
}
|
||||
expected := []string{"T1", "T2", "T3", "T4"}
|
||||
for i, tier := range tiers {
|
||||
if tier != expected[i] {
|
||||
t.Errorf("ValidSovereigntyTiers()[%d] = %q, want %q", i, tier, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncModeString(t *testing.T) {
|
||||
if got := SyncModeGitPortable.String(); got != "git-portable" {
|
||||
t.Errorf("SyncModeGitPortable.String() = %q, want %q", got, "git-portable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConflictStrategyString(t *testing.T) {
|
||||
if got := ConflictStrategyNewest.String(); got != "newest" {
|
||||
t.Errorf("ConflictStrategyNewest.String() = %q, want %q", got, "newest")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSovereigntyString(t *testing.T) {
|
||||
if got := SovereigntyT1.String(); got != "T1" {
|
||||
t.Errorf("SovereigntyT1.String() = %q, want %q", got, "T1")
|
||||
}
|
||||
if got := SovereigntyNone.String(); got != "" {
|
||||
t.Errorf("SovereigntyNone.String() = %q, want %q", got, "")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user