feat(infra): funciones impuras Go para carga de env/config (dotenv, env_require, config_from_env, config_from_file)
- dotenv_load: parser .env con no-sobreescritura y soporte de comillas - env_require: os.Getenv con error descriptivo fail-fast - env_require_all: verifica multiples vars y lista todas las faltantes - config_from_env: reflection sobre struct tags env/default/required/secret, 5 tipos soportados - config_from_file: JSON via stdlib, YAML stub con not-implemented - 25 tests unitarios, todos PASS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDotenvLoad(t *testing.T) {
|
||||
// helper to write a temp .env file
|
||||
writeEnv := func(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
p := filepath.Join(dir, ".env")
|
||||
if err := os.WriteFile(p, []byte(content), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
t.Run("parsea KEY=VALUE y setea variable", func(t *testing.T) {
|
||||
p := writeEnv(t, "DOTENV_TEST_FOO=hello\n")
|
||||
t.Setenv("DOTENV_TEST_FOO", "") // ensure unset
|
||||
os.Unsetenv("DOTENV_TEST_FOO")
|
||||
|
||||
if err := DotenvLoad(p); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := os.Getenv("DOTENV_TEST_FOO"); got != "hello" {
|
||||
t.Errorf("expected DOTENV_TEST_FOO=hello, got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ignora lineas comentario y vacias", func(t *testing.T) {
|
||||
content := "# this is a comment\n\nDOTENV_TEST_BAR=world\n"
|
||||
p := writeEnv(t, content)
|
||||
os.Unsetenv("DOTENV_TEST_BAR")
|
||||
|
||||
if err := DotenvLoad(p); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := os.Getenv("DOTENV_TEST_BAR"); got != "world" {
|
||||
t.Errorf("expected world, got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no sobreescribe variables ya seteadas", func(t *testing.T) {
|
||||
p := writeEnv(t, "DOTENV_TEST_EXISTING=new\n")
|
||||
t.Setenv("DOTENV_TEST_EXISTING", "original")
|
||||
|
||||
if err := DotenvLoad(p); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := os.Getenv("DOTENV_TEST_EXISTING"); got != "original" {
|
||||
t.Errorf("expected original (not overwritten), got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valores con comillas dobles se stripean", func(t *testing.T) {
|
||||
p := writeEnv(t, `DOTENV_TEST_QUOTED="quoted value"`)
|
||||
os.Unsetenv("DOTENV_TEST_QUOTED")
|
||||
|
||||
if err := DotenvLoad(p); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := os.Getenv("DOTENV_TEST_QUOTED"); got != "quoted value" {
|
||||
t.Errorf("expected 'quoted value', got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valores con comillas simples se stripean", func(t *testing.T) {
|
||||
p := writeEnv(t, "DOTENV_TEST_SINGLE='single quoted'")
|
||||
os.Unsetenv("DOTENV_TEST_SINGLE")
|
||||
|
||||
if err := DotenvLoad(p); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := os.Getenv("DOTENV_TEST_SINGLE"); got != "single quoted" {
|
||||
t.Errorf("expected 'single quoted', got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("archivo inexistente retorna error", func(t *testing.T) {
|
||||
err := DotenvLoad("/nonexistent/.env.does.not.exist")
|
||||
if err == nil {
|
||||
t.Error("expected error for missing file")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("linea sin signo igual retorna error", func(t *testing.T) {
|
||||
p := writeEnv(t, "INVALID_LINE\n")
|
||||
err := DotenvLoad(p)
|
||||
if err == nil {
|
||||
t.Error("expected error for line without '='")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user