233 lines
5.4 KiB
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)))
|
|
}
|