obm/internal/config/config_test.go

233 lines
5.4 KiB
Go

package config
import (
"os"
"path/filepath"
"testing"
)
func TestLoad(t *testing.T) {
// Create a temp config file
tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "config.json")
configContent := `{
"project": "test-project",
"provider": {
"name": "hcloud",
"region": "nyc1"
},
"variables": {
"TF_VAR_count": "3"
}
}`
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
t.Fatalf("failed to write test config: %v", err)
}
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load failed: %v", err)
}
if cfg.Project != "test-project" {
t.Errorf("expected project 'test-project', got %q", cfg.Project)
}
if cfg.Provider.Name != "hcloud" {
t.Errorf("expected provider name 'hcloud', got %q", cfg.Provider.Name)
}
if cfg.Provider.Region != "nyc1" {
t.Errorf("expected provider region 'nyc1', got %q", cfg.Provider.Region)
}
if cfg.Variables["TF_VAR_count"] != "3" {
t.Errorf("expected TF_VAR_count=3, got %q", cfg.Variables["TF_VAR_count"])
}
}
func TestWriteEnv(t *testing.T) {
tmpDir := t.TempDir()
envPath := filepath.Join(tmpDir, ".env")
cfg := &Config{
Project: "test-project",
Provider: ProviderConfig{
Name: "hcloud",
Region: "nyc1",
},
Variables: map[string]string{
"TF_VAR_count": "3",
"API_KEY": "secret123",
"DATABASE_URL": "postgres://user:pass@localhost:5432/db",
"PUBLIC_VAR": "hello world",
},
Env: map[string]string{
"EXTRA_VAR": "extra",
},
}
if err := cfg.WriteEnv(envPath); err != nil {
t.Fatalf("WriteEnv failed: %v", err)
}
// Read back and verify
data, err := os.ReadFile(envPath)
if err != nil {
t.Fatalf("failed to read .env: %v", err)
}
content := string(data)
// Check header
if !contains(content, "# Generated by obm") {
t.Error("missing generated header")
}
if !contains(content, "# Project: test-project") {
t.Error("missing project name in header")
}
// Check variables are present
if !contains(content, "TF_VAR_count=3") {
t.Error("missing TF_VAR_count")
}
if !contains(content, "EXTRA_VAR=extra") {
t.Error("missing EXTRA_VAR")
}
}
func TestReadEnvFile(t *testing.T) {
tmpDir := t.TempDir()
envPath := filepath.Join(tmpDir, ".env")
envContent := `# Comment line
VAR1=value1
VAR2="quoted value"
VAR3='single quoted'
# Another comment
EMPTY_VAR=""
`
if err := os.WriteFile(envPath, []byte(envContent), 0644); err != nil {
t.Fatalf("failed to write test .env: %v", err)
}
vars, err := ReadEnvFile(envPath)
if err != nil {
t.Fatalf("ReadEnvFile failed: %v", err)
}
if vars["VAR1"] != "value1" {
t.Errorf("expected VAR1='value1', got %q", vars["VAR1"])
}
if vars["VAR2"] != "quoted value" {
t.Errorf("expected VAR2='quoted value', got %q", vars["VAR2"])
}
if vars["VAR3"] != "single quoted" {
t.Errorf("expected VAR3='single quoted', got %q", vars["VAR3"])
}
if vars["EMPTY_VAR"] != "" {
t.Errorf("expected EMPTY_VAR='', got %q", vars["EMPTY_VAR"])
}
// Comments should not be parsed as variables
if _, exists := vars["# Comment line"]; exists {
t.Error("comment line was parsed as variable")
}
}
func TestMergeEnvFiles(t *testing.T) {
tmpDir := t.TempDir()
// Create two env files
env1 := filepath.Join(tmpDir, "env1")
env2 := filepath.Join(tmpDir, "env2")
os.WriteFile(env1, []byte("VAR1=value1\nVAR2=original"), 0644)
os.WriteFile(env2, []byte("VAR2=overridden\nVAR3=value3"), 0644)
cfg := &Config{
Variables: map[string]string{},
Env: map[string]string{},
}
if err := cfg.MergeEnvFiles(env1, env2); err != nil {
t.Fatalf("MergeEnvFiles failed: %v", err)
}
if cfg.Variables["VAR1"] != "value1" {
t.Errorf("expected VAR1='value1', got %q", cfg.Variables["VAR1"])
}
// env2 should override env1 for VAR2
if cfg.Variables["VAR2"] != "overridden" {
t.Errorf("expected VAR2='overridden', got %q", cfg.Variables["VAR2"])
}
if cfg.Variables["VAR3"] != "value3" {
t.Errorf("expected VAR3='value3', got %q", cfg.Variables["VAR3"])
}
}
func TestIsSensitive(t *testing.T) {
tests := []struct {
key string
expected bool
}{
{"password", true},
{"api_key", true},
{"secret", true},
{"token", true},
{"auth", true},
{"credential", true},
{"DATABASE_URL", false},
{"port", false},
{"count", false},
{"HOST_KEY", true},
{"my_password_here", true},
}
for _, tt := range tests {
result := isSensitive(tt.key)
if result != tt.expected {
t.Errorf("isSensitive(%q) = %v, expected %v", tt.key, result, tt.expected)
}
}
}
func TestMaskValue(t *testing.T) {
tests := []struct {
value string
expected string
}{
{"short", "****"},
{"abc", "****"},
{"secret123", "se****23"},
{"verylongsecretvalue", "ve****ue"},
}
for _, tt := range tests {
result := maskValue(tt.value)
if result != tt.expected {
t.Errorf("maskValue(%q) = %q, expected %q", tt.value, result, tt.expected)
}
}
}
func TestNeedsQuoting(t *testing.T) {
tests := []struct {
value string
expected bool
}{
{"simple", false},
{"", true},
{"has space", true},
{"has'quote", true},
{"has\"quote", true},
{"has$var", true},
{"normalvalue", false},
}
for _, tt := range tests {
result := needsQuoting(tt.value)
if result != tt.expected {
t.Errorf("needsQuoting(%q) = %v, expected %v", tt.value, result, tt.expected)
}
}
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && (s[:len(substr)] == substr || contains(s[1:], substr)))
}