package main import ( "os" "path/filepath" "testing" ) // TestConfigLoadFromJSONFile tests loading config from a valid JSON file func TestConfigLoadFromJSONFile(t *testing.T) { // Save original environment originalConfigFile := os.Getenv("CONFIG_FILE") defer func() { os.Setenv("CONFIG_FILE", originalConfigFile) }() // Set CONFIG_FILE to our test data testConfigPath := filepath.Join("testdata", "test_config.json") absPath, err := filepath.Abs(testConfigPath) if err != nil { t.Fatalf("Failed to get absolute path: %v", err) } os.Setenv("CONFIG_FILE", absPath) // Load config from JSON file cfg, err := loadConfig() if err != nil { t.Fatalf("loadConfig() failed: %v", err) } // Verify all fields loaded correctly from JSON if cfg.DB != "postgres://user:password@localhost:5432/opendtu" { t.Errorf("Expected DB from JSON, got %v", cfg.DB) } if cfg.OpenDTUAddress != "192.168.1.100" { t.Errorf("Expected OpenDTUAddress from JSON, got %v", cfg.OpenDTUAddress) } if !cfg.OpenDTUAuth { t.Errorf("Expected OpenDTUAuth=true from JSON") } if cfg.OpenDTUUser != "admin" { t.Errorf("Expected OpenDTUUser from JSON, got %v", cfg.OpenDTUUser) } if cfg.OpenDTUPassword != "secret123" { t.Errorf("Expected OpenDTUPassword from JSON, got %v", cfg.OpenDTUPassword) } if !cfg.TimescaleDB { t.Errorf("Expected TimescaleDB=true from JSON") } if cfg.TZ != "Europe/Amsterdam" { t.Errorf("Expected TZ from JSON, got %v", cfg.TZ) } if cfg.LogLevel != "INFO" { t.Errorf("Expected LogLevel from JSON, got %v", cfg.LogLevel) } } // TestConfigLoadFromJSONFile_NoAuth tests loading config from JSON without auth func TestConfigLoadFromJSONFile_NoAuth(t *testing.T) { // Create a temporary config file without auth tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "config_noauth.json") configContent := `{ "db": "postgres://localhost/testdb", "opendtu_address": "192.168.1.200", "opendtu_auth": false, "timescaledb": false, "tz": "UTC", "log_level": "DEBUG" }` err := os.WriteFile(tmpFile, []byte(configContent), 0644) if err != nil { t.Fatalf("Failed to write temp config: %v", err) } // Save original environment originalConfigFile := os.Getenv("CONFIG_FILE") defer func() { os.Setenv("CONFIG_FILE", originalConfigFile) }() os.Setenv("CONFIG_FILE", tmpFile) // Load config cfg, err := loadConfig() if err != nil { t.Fatalf("loadConfig() failed: %v", err) } // Verify fields if cfg.DB != "postgres://localhost/testdb" { t.Errorf("Expected DB from JSON, got %v", cfg.DB) } if cfg.OpenDTUAddress != "192.168.1.200" { t.Errorf("Expected OpenDTUAddress from JSON, got %v", cfg.OpenDTUAddress) } if cfg.OpenDTUAuth { t.Errorf("Expected OpenDTUAuth=false from JSON") } if cfg.TimescaleDB { t.Errorf("Expected TimescaleDB=false from JSON") } } // TestConfigLoadFromJSONFile_InvalidTimezone tests the timezone validation func TestConfigLoadFromJSONFile_InvalidTimezone(t *testing.T) { // Create a temporary config file with invalid timezone tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "config_badtz.json") configContent := `{ "db": "postgres://localhost/testdb", "opendtu_address": "192.168.1.200", "opendtu_auth": false, "tz": "Invalid/Timezone" }` err := os.WriteFile(tmpFile, []byte(configContent), 0644) if err != nil { t.Fatalf("Failed to write temp config: %v", err) } // Save original environment originalConfigFile := os.Getenv("CONFIG_FILE") defer func() { os.Setenv("CONFIG_FILE", originalConfigFile) }() os.Setenv("CONFIG_FILE", tmpFile) // Load config - should not fatal, just log warning cfg, err := loadConfig() if err != nil { t.Fatalf("loadConfig() failed: %v", err) } // Should still load other fields successfully if cfg.DB != "postgres://localhost/testdb" { t.Errorf("Expected DB to load despite invalid timezone") } } // TestConfigLoadFromEnv_WithTimescaleDB tests env var path with TimescaleDB enabled func TestConfigLoadFromEnv_WithTimescaleDB(t *testing.T) { // Save original environment originalVars := map[string]string{ "CONFIG_FILE": os.Getenv("CONFIG_FILE"), "DB_URL": os.Getenv("DB_URL"), "OPENDTU_ADDRESS": os.Getenv("OPENDTU_ADDRESS"), "OPENDTU_AUTH": os.Getenv("OPENDTU_AUTH"), "TIMESCALEDB_ENABLED": os.Getenv("TIMESCALEDB_ENABLED"), "TZ": os.Getenv("TZ"), "LOG_LEVEL": os.Getenv("LOG_LEVEL"), } defer func() { for k, v := range originalVars { os.Setenv(k, v) } }() // Set environment for non-existent config file os.Setenv("CONFIG_FILE", "/nonexistent/config.json") os.Setenv("DB_URL", "postgres://testhost/testdb") os.Setenv("OPENDTU_ADDRESS", "10.0.0.1") os.Setenv("OPENDTU_AUTH", "false") os.Setenv("TIMESCALEDB_ENABLED", "true") os.Setenv("TZ", "America/New_York") os.Setenv("LOG_LEVEL", "WARN") cfg, err := loadConfig() if err != nil { t.Fatalf("loadConfig() failed: %v", err) } if cfg.DB != "postgres://testhost/testdb" { t.Errorf("Expected DB from env, got %v", cfg.DB) } if !cfg.TimescaleDB { t.Errorf("Expected TimescaleDB=true from env") } if cfg.TZ != "America/New_York" { t.Errorf("Expected TZ from env, got %v", cfg.TZ) } if cfg.LogLevel != "WARN" { t.Errorf("Expected LogLevel from env, got %v", cfg.LogLevel) } } // TestConfigLoadFromEnv_WithAuth tests env var path with auth enabled func TestConfigLoadFromEnv_WithAuth(t *testing.T) { // Save original environment originalVars := map[string]string{ "CONFIG_FILE": os.Getenv("CONFIG_FILE"), "DB_URL": os.Getenv("DB_URL"), "OPENDTU_ADDRESS": os.Getenv("OPENDTU_ADDRESS"), "OPENDTU_AUTH": os.Getenv("OPENDTU_AUTH"), "OPENDTU_USERNAME": os.Getenv("OPENDTU_USERNAME"), "OPENDTU_PASSWORD": os.Getenv("OPENDTU_PASSWORD"), } defer func() { for k, v := range originalVars { os.Setenv(k, v) } }() // Set environment with auth enabled os.Setenv("CONFIG_FILE", "/nonexistent/config.json") os.Setenv("DB_URL", "postgres://testhost/testdb") os.Setenv("OPENDTU_ADDRESS", "10.0.0.1") os.Setenv("OPENDTU_AUTH", "true") os.Setenv("OPENDTU_USERNAME", "testuser") os.Setenv("OPENDTU_PASSWORD", "testpass") cfg, err := loadConfig() if err != nil { t.Fatalf("loadConfig() failed: %v", err) } if !cfg.OpenDTUAuth { t.Errorf("Expected OpenDTUAuth=true from env") } if cfg.OpenDTUUser != "testuser" { t.Errorf("Expected OpenDTUUser from env, got %v", cfg.OpenDTUUser) } if cfg.OpenDTUPassword != "testpass" { t.Errorf("Expected OpenDTUPassword from env, got %v", cfg.OpenDTUPassword) } } // TestConfigLoadFromJSONFile_InvalidJSON tests error when JSON is malformed func TestConfigLoadFromJSONFile_InvalidJSON(t *testing.T) { tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "invalid.json") // Write invalid JSON err := os.WriteFile(tmpFile, []byte("{invalid json}"), 0644) if err != nil { t.Fatalf("Failed to write temp config: %v", err) } originalConfigFile := os.Getenv("CONFIG_FILE") defer func() { os.Setenv("CONFIG_FILE", originalConfigFile) }() os.Setenv("CONFIG_FILE", tmpFile) _, err = loadConfig() if err == nil { t.Error("Expected error for invalid JSON, got nil") } if err != nil && !contains(err.Error(), "error parsing config file") { t.Errorf("Expected 'error parsing config file' error, got: %v", err) } } // TestConfigLoadFromJSONFile_MissingDB tests error when DB is not set in JSON func TestConfigLoadFromJSONFile_MissingDB(t *testing.T) { // Reset global config to avoid test pollution config = Config{} tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "no_db.json") configContent := `{ "opendtu_address": "192.168.1.100" }` err := os.WriteFile(tmpFile, []byte(configContent), 0644) if err != nil { t.Fatalf("Failed to write temp config: %v", err) } originalConfigFile := os.Getenv("CONFIG_FILE") defer func() { os.Setenv("CONFIG_FILE", originalConfigFile) }() os.Setenv("CONFIG_FILE", tmpFile) _, err = loadConfig() if err == nil { t.Error("Expected error for missing DB, got nil") } if err != nil && !contains(err.Error(), "db connection settings are not set") { t.Errorf("Expected 'db connection settings' error, got: %v", err) } } // TestConfigLoadFromJSONFile_MissingOpenDTUAddress tests error when opendtu_address is not set func TestConfigLoadFromJSONFile_MissingOpenDTUAddress(t *testing.T) { // Reset global config to avoid test pollution config = Config{} tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "no_address.json") configContent := `{ "db": "postgres://localhost/testdb" }` err := os.WriteFile(tmpFile, []byte(configContent), 0644) if err != nil { t.Fatalf("Failed to write temp config: %v", err) } originalConfigFile := os.Getenv("CONFIG_FILE") defer func() { os.Setenv("CONFIG_FILE", originalConfigFile) }() os.Setenv("CONFIG_FILE", tmpFile) _, err = loadConfig() if err == nil { t.Error("Expected error for missing OpenDTU address, got nil") } if err != nil && !contains(err.Error(), "opendtu_address is not set") { t.Errorf("Expected 'opendtu_address is not set' error, got: %v", err) } } // TestConfigLoadFromJSONFile_MissingUsername tests error when username is missing with auth enabled func TestConfigLoadFromJSONFile_MissingUsername(t *testing.T) { // Reset global config to avoid test pollution config = Config{} tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "no_username.json") configContent := `{ "db": "postgres://localhost/testdb", "opendtu_address": "192.168.1.100", "opendtu_auth": true, "opendtu_password": "secret" }` err := os.WriteFile(tmpFile, []byte(configContent), 0644) if err != nil { t.Fatalf("Failed to write temp config: %v", err) } originalConfigFile := os.Getenv("CONFIG_FILE") defer func() { os.Setenv("CONFIG_FILE", originalConfigFile) }() os.Setenv("CONFIG_FILE", tmpFile) _, err = loadConfig() if err == nil { t.Error("Expected error for missing username, got nil") } if err != nil && !contains(err.Error(), "opendtu_username is not set") { t.Errorf("Expected 'opendtu_username is not set' error, got: %v", err) } } // TestConfigLoadFromJSONFile_MissingPassword tests error when password is missing with auth enabled func TestConfigLoadFromJSONFile_MissingPassword(t *testing.T) { // Reset global config to avoid test pollution config = Config{} tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "no_password.json") configContent := `{ "db": "postgres://localhost/testdb", "opendtu_address": "192.168.1.100", "opendtu_auth": true, "opendtu_username": "admin" }` err := os.WriteFile(tmpFile, []byte(configContent), 0644) if err != nil { t.Fatalf("Failed to write temp config: %v", err) } originalConfigFile := os.Getenv("CONFIG_FILE") defer func() { os.Setenv("CONFIG_FILE", originalConfigFile) }() os.Setenv("CONFIG_FILE", tmpFile) _, err = loadConfig() if err == nil { t.Error("Expected error for missing password, got nil") } if err != nil && !contains(err.Error(), "opendtu_password is not set") { t.Errorf("Expected 'opendtu_password is not set' error, got: %v", err) } } // TestConfigLoadFromEnv_MissingDBURL tests error when DB_URL env var is missing func TestConfigLoadFromEnv_MissingDBURL(t *testing.T) { originalVars := map[string]string{ "CONFIG_FILE": os.Getenv("CONFIG_FILE"), "DB_URL": os.Getenv("DB_URL"), "OPENDTU_ADDRESS": os.Getenv("OPENDTU_ADDRESS"), } defer func() { for k, v := range originalVars { os.Setenv(k, v) } }() os.Setenv("CONFIG_FILE", "/nonexistent/config.json") os.Unsetenv("DB_URL") os.Setenv("OPENDTU_ADDRESS", "192.168.1.100") _, err := loadConfig() if err == nil { t.Error("Expected error for missing DB_URL, got nil") } if err != nil && !contains(err.Error(), "DB_URL environment variable is not set") { t.Errorf("Expected 'DB_URL environment variable is not set' error, got: %v", err) } } // TestConfigLoadFromEnv_MissingOpenDTUAddress tests error when OPENDTU_ADDRESS is missing func TestConfigLoadFromEnv_MissingOpenDTUAddress(t *testing.T) { originalVars := map[string]string{ "CONFIG_FILE": os.Getenv("CONFIG_FILE"), "DB_URL": os.Getenv("DB_URL"), "OPENDTU_ADDRESS": os.Getenv("OPENDTU_ADDRESS"), } defer func() { for k, v := range originalVars { os.Setenv(k, v) } }() os.Setenv("CONFIG_FILE", "/nonexistent/config.json") os.Setenv("DB_URL", "postgres://localhost/testdb") os.Unsetenv("OPENDTU_ADDRESS") _, err := loadConfig() if err == nil { t.Error("Expected error for missing OPENDTU_ADDRESS, got nil") } if err != nil && !contains(err.Error(), "OPENDTU_ADDRESS environment variable is not set") { t.Errorf("Expected 'OPENDTU_ADDRESS environment variable is not set' error, got: %v", err) } } // TestConfigLoadFromEnv_InvalidAuthBool tests error when OPENDTU_AUTH has invalid boolean func TestConfigLoadFromEnv_InvalidAuthBool(t *testing.T) { originalVars := map[string]string{ "CONFIG_FILE": os.Getenv("CONFIG_FILE"), "DB_URL": os.Getenv("DB_URL"), "OPENDTU_ADDRESS": os.Getenv("OPENDTU_ADDRESS"), "OPENDTU_AUTH": os.Getenv("OPENDTU_AUTH"), } defer func() { for k, v := range originalVars { os.Setenv(k, v) } }() os.Setenv("CONFIG_FILE", "/nonexistent/config.json") os.Setenv("DB_URL", "postgres://localhost/testdb") os.Setenv("OPENDTU_ADDRESS", "192.168.1.100") os.Setenv("OPENDTU_AUTH", "invalid") _, err := loadConfig() if err == nil { t.Error("Expected error for invalid OPENDTU_AUTH, got nil") } if err != nil && !contains(err.Error(), "error parsing OPENDTU_AUTH") { t.Errorf("Expected 'error parsing OPENDTU_AUTH' error, got: %v", err) } } // TestConfigLoadFromEnv_MissingUsername tests error when OPENDTU_USERNAME is missing with auth func TestConfigLoadFromEnv_MissingUsername(t *testing.T) { originalVars := map[string]string{ "CONFIG_FILE": os.Getenv("CONFIG_FILE"), "DB_URL": os.Getenv("DB_URL"), "OPENDTU_ADDRESS": os.Getenv("OPENDTU_ADDRESS"), "OPENDTU_AUTH": os.Getenv("OPENDTU_AUTH"), "OPENDTU_USERNAME": os.Getenv("OPENDTU_USERNAME"), } defer func() { for k, v := range originalVars { os.Setenv(k, v) } }() os.Setenv("CONFIG_FILE", "/nonexistent/config.json") os.Setenv("DB_URL", "postgres://localhost/testdb") os.Setenv("OPENDTU_ADDRESS", "192.168.1.100") os.Setenv("OPENDTU_AUTH", "true") os.Unsetenv("OPENDTU_USERNAME") os.Setenv("OPENDTU_PASSWORD", "secret") _, err := loadConfig() if err == nil { t.Error("Expected error for missing OPENDTU_USERNAME, got nil") } if err != nil && !contains(err.Error(), "OPENDTU_USERNAME environment variable is not set") { t.Errorf("Expected 'OPENDTU_USERNAME environment variable is not set' error, got: %v", err) } } // TestConfigLoadFromEnv_MissingPassword tests error when OPENDTU_PASSWORD is missing with auth func TestConfigLoadFromEnv_MissingPassword(t *testing.T) { originalVars := map[string]string{ "CONFIG_FILE": os.Getenv("CONFIG_FILE"), "DB_URL": os.Getenv("DB_URL"), "OPENDTU_ADDRESS": os.Getenv("OPENDTU_ADDRESS"), "OPENDTU_AUTH": os.Getenv("OPENDTU_AUTH"), "OPENDTU_USERNAME": os.Getenv("OPENDTU_USERNAME"), "OPENDTU_PASSWORD": os.Getenv("OPENDTU_PASSWORD"), } defer func() { for k, v := range originalVars { os.Setenv(k, v) } }() os.Setenv("CONFIG_FILE", "/nonexistent/config.json") os.Setenv("DB_URL", "postgres://localhost/testdb") os.Setenv("OPENDTU_ADDRESS", "192.168.1.100") os.Setenv("OPENDTU_AUTH", "true") os.Setenv("OPENDTU_USERNAME", "admin") os.Unsetenv("OPENDTU_PASSWORD") _, err := loadConfig() if err == nil { t.Error("Expected error for missing OPENDTU_PASSWORD, got nil") } if err != nil && !contains(err.Error(), "OPENDTU_PASSWORD environment variable is not set") { t.Errorf("Expected 'OPENDTU_PASSWORD environment variable is not set' error, got: %v", err) } } // TestConfigLoadFromEnv_InvalidTimescaleDBBool tests error when TIMESCALEDB_ENABLED has invalid boolean func TestConfigLoadFromEnv_InvalidTimescaleDBBool(t *testing.T) { originalVars := map[string]string{ "CONFIG_FILE": os.Getenv("CONFIG_FILE"), "DB_URL": os.Getenv("DB_URL"), "OPENDTU_ADDRESS": os.Getenv("OPENDTU_ADDRESS"), "OPENDTU_AUTH": os.Getenv("OPENDTU_AUTH"), "TIMESCALEDB_ENABLED": os.Getenv("TIMESCALEDB_ENABLED"), } defer func() { for k, v := range originalVars { os.Setenv(k, v) } }() os.Setenv("CONFIG_FILE", "/nonexistent/config.json") os.Setenv("DB_URL", "postgres://localhost/testdb") os.Setenv("OPENDTU_ADDRESS", "192.168.1.100") os.Setenv("OPENDTU_AUTH", "false") // Set auth to false so we don't need username/password os.Setenv("TIMESCALEDB_ENABLED", "not-a-bool") _, err := loadConfig() if err == nil { t.Error("Expected error for invalid TIMESCALEDB_ENABLED, got nil") } if err != nil && !contains(err.Error(), "error parsing TIMESCALEDB_ENABLED") { t.Errorf("Expected 'error parsing TIMESCALEDB_ENABLED' error, got: %v", err) } } // Helper function to check if a string contains a substring func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || func() bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }())) }