feat(infra): tipos ConfigError y ConfigValidation + funciones puras Go (validate, merge, dump)
- ConfigError y ConfigValidation como tipos producto con sus .md en types/infra/ - config_validate: validacion con tags required/format/min/max/oneof via reflection - config_merge: merge no-mutante de map[string]string con precedencia de override - config_dump: serializacion de structs a map con mascara *** para campos secret - 17 tests unitarios, todos PASS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
t.Run("struct valido retorna IsValid true", func(t *testing.T) {
|
||||
type Cfg struct {
|
||||
Host string `required:"true"`
|
||||
Port int `required:"true"`
|
||||
}
|
||||
got := ConfigValidate(Cfg{Host: "localhost", Port: 8080})
|
||||
if !got.IsValid {
|
||||
t.Errorf("expected IsValid=true, errors: %v", got.Errors)
|
||||
}
|
||||
if len(got.Errors) != 0 {
|
||||
t.Errorf("expected no errors, got %v", got.Errors)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("campo required vacio acumula error", func(t *testing.T) {
|
||||
type Cfg struct {
|
||||
Host string `required:"true"`
|
||||
Key string `required:"true"`
|
||||
}
|
||||
got := ConfigValidate(Cfg{})
|
||||
if got.IsValid {
|
||||
t.Error("expected IsValid=false")
|
||||
}
|
||||
if len(got.Errors) != 2 {
|
||||
t.Errorf("expected 2 errors, got %d: %v", len(got.Errors), got.Errors)
|
||||
}
|
||||
for _, e := range got.Errors {
|
||||
if e.Tag != "required" {
|
||||
t.Errorf("expected tag 'required', got %q", e.Tag)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("format email invalido produce error", func(t *testing.T) {
|
||||
type Cfg struct {
|
||||
Email string `format:"email"`
|
||||
}
|
||||
got := ConfigValidate(Cfg{Email: "not-an-email"})
|
||||
if got.IsValid {
|
||||
t.Error("expected IsValid=false")
|
||||
}
|
||||
if len(got.Errors) == 0 || got.Errors[0].Tag != "format" {
|
||||
t.Errorf("expected format error, got %v", got.Errors)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("format email valido pasa", func(t *testing.T) {
|
||||
type Cfg struct {
|
||||
Email string `format:"email"`
|
||||
}
|
||||
got := ConfigValidate(Cfg{Email: "user@example.com"})
|
||||
if !got.IsValid {
|
||||
t.Errorf("expected IsValid=true, errors: %v", got.Errors)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("format url invalido produce error", func(t *testing.T) {
|
||||
type Cfg struct {
|
||||
BaseURL string `format:"url"`
|
||||
}
|
||||
got := ConfigValidate(Cfg{BaseURL: "ftp://bad.url"})
|
||||
if got.IsValid {
|
||||
t.Error("expected IsValid=false")
|
||||
}
|
||||
if len(got.Errors) == 0 || got.Errors[0].Tag != "format" {
|
||||
t.Errorf("expected format error, got %v", got.Errors)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("puntero a struct funciona igual", func(t *testing.T) {
|
||||
type Cfg struct {
|
||||
Name string `required:"true"`
|
||||
}
|
||||
got := ConfigValidate(&Cfg{Name: "test"})
|
||||
if !got.IsValid {
|
||||
t.Errorf("expected IsValid=true, errors: %v", got.Errors)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("todos los errores acumulados aunque falle el primero", func(t *testing.T) {
|
||||
type Cfg struct {
|
||||
A string `required:"true"`
|
||||
B string `required:"true"`
|
||||
C string `required:"true"`
|
||||
}
|
||||
got := ConfigValidate(Cfg{})
|
||||
if len(got.Errors) != 3 {
|
||||
t.Errorf("expected 3 errors, got %d", len(got.Errors))
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user