package config import ( "os" "path/filepath" "strings" "testing" ) // Test: LoadState loads valid state func TestLoadState(t *testing.T) { tests := []struct { name string stateYAML string verify func(t *testing.T, config *State) wantErr bool }{ { name: "loads complete state", stateYAML: `operator: email: "admin@example.com" cloud: central: domain: "central.example.com" `, verify: func(t *testing.T, config *State) { if config.Operator.Email != "admin@example.com" { t.Error("operator email not loaded correctly") } if config.Cloud.Central.Domain != "central.example.com" { t.Error("central domain not loaded correctly") } }, wantErr: false, }, { name: "loads minimal state", stateYAML: `operator: email: "admin@example.com" `, verify: func(t *testing.T, config *State) { if config.Operator.Email != "admin@example.com" { t.Error("operator email not loaded correctly") } }, wantErr: false, }, { name: "loads empty state", stateYAML: "{}\n", verify: func(t *testing.T, config *State) { if config.Operator.Email != "" { t.Error("expected empty operator email") } }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tempDir := t.TempDir() statePath := filepath.Join(tempDir, "state.yaml") if err := os.WriteFile(statePath, []byte(tt.stateYAML), 0644); err != nil { t.Fatalf("setup failed: %v", err) } config, err := LoadState(statePath) if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Errorf("unexpected error: %v", err) return } if config == nil { t.Fatal("config is nil") } if tt.verify != nil { tt.verify(t, config) } }) } } // Test: LoadState error cases func TestLoadState_Errors(t *testing.T) { tests := []struct { name string setupFunc func(t *testing.T) string errContains string }{ { name: "non-existent file", setupFunc: func(t *testing.T) string { return filepath.Join(t.TempDir(), "nonexistent.yaml") }, errContains: "reading config file", }, { name: "invalid yaml", setupFunc: func(t *testing.T) string { tempDir := t.TempDir() statePath := filepath.Join(tempDir, "state.yaml") content := `invalid: yaml: [[[` if err := os.WriteFile(statePath, []byte(content), 0644); err != nil { t.Fatalf("setup failed: %v", err) } return statePath }, errContains: "parsing config file", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { statePath := tt.setupFunc(t) _, err := LoadState(statePath) if err == nil { t.Error("expected error, got nil") } else if !strings.Contains(err.Error(), tt.errContains) { t.Errorf("error %q does not contain %q", err.Error(), tt.errContains) } }) } } // Test: SaveState saves state correctly func TestSaveState(t *testing.T) { tests := []struct { name string config *State verify func(t *testing.T, statePath string) }{ { name: "saves complete state", config: func() *State { cfg := &State{} cfg.Operator.Email = "admin@example.com" cfg.Cloud.Central.Domain = "central.example.com" return cfg }(), verify: func(t *testing.T, statePath string) { content, err := os.ReadFile(statePath) if err != nil { t.Fatalf("failed to read saved state: %v", err) } contentStr := string(content) if !strings.Contains(contentStr, "admin@example.com") { t.Error("saved state missing operator email") } if !strings.Contains(contentStr, "central.example.com") { t.Error("saved state missing central domain") } }, }, { name: "saves empty state", config: &State{}, verify: func(t *testing.T, statePath string) { if _, err := os.Stat(statePath); os.IsNotExist(err) { t.Error("state file not created") } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tempDir := t.TempDir() statePath := filepath.Join(tempDir, "subdir", "state.yaml") err := SaveState(tt.config, statePath) if err != nil { t.Errorf("SaveState failed: %v", err) return } // Verify file exists if _, err := os.Stat(statePath); err != nil { t.Errorf("state file not created: %v", err) return } // Verify file permissions info, err := os.Stat(statePath) if err != nil { t.Fatalf("failed to stat state file: %v", err) } if info.Mode().Perm() != 0644 { t.Errorf("expected permissions 0644, got %v", info.Mode().Perm()) } // Verify content can be loaded back loadedConfig, err := LoadState(statePath) if err != nil { t.Errorf("failed to reload saved state: %v", err) } else if loadedConfig == nil { t.Error("loaded config is nil") } if tt.verify != nil { tt.verify(t, statePath) } }) } } // Test: SaveState creates directory func TestSaveState_CreatesDirectory(t *testing.T) { tempDir := t.TempDir() statePath := filepath.Join(tempDir, "nested", "dirs", "state.yaml") config := &State{} err := SaveState(config, statePath) if err != nil { t.Fatalf("SaveState failed: %v", err) } // Verify nested directories were created if _, err := os.Stat(filepath.Dir(statePath)); err != nil { t.Errorf("directory not created: %v", err) } // Verify file exists if _, err := os.Stat(statePath); err != nil { t.Errorf("state file not created: %v", err) } } // Test: State.IsEmpty checks if config is empty func TestState_IsEmpty(t *testing.T) { tests := []struct { name string config *State want bool }{ { name: "nil config is empty", config: nil, want: true, }, { name: "default config is empty", config: &State{}, want: true, }, { name: "config with only central domain is not empty", config: func() *State { cfg := &State{} cfg.Cloud.Central.Domain = "central.example.com" return cfg }(), want: false, }, { name: "config with only operator email is not empty", config: func() *State { cfg := &State{} cfg.Operator.Email = "admin@example.com" return cfg }(), want: false, }, { name: "config with all fields is not empty", config: func() *State { cfg := &State{} cfg.Cloud.Central.Domain = "central.example.com" cfg.Operator.Email = "admin@example.com" return cfg }(), want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.config.IsEmpty() if got != tt.want { t.Errorf("IsEmpty() = %v, want %v", got, tt.want) } }) } } // Test: Round-trip save and load preserves data func TestState_RoundTrip(t *testing.T) { tempDir := t.TempDir() statePath := filepath.Join(tempDir, "state.yaml") // Create config with all fields original := &State{} original.Operator.Email = "admin@example.com" original.Cloud.Central.Domain = "central.example.com" // Save if err := SaveState(original, statePath); err != nil { t.Fatalf("SaveState failed: %v", err) } // Load loaded, err := LoadState(statePath) if err != nil { t.Fatalf("LoadState failed: %v", err) } // Verify all fields match if loaded.Operator.Email != original.Operator.Email { t.Errorf("email mismatch: got %q, want %q", loaded.Operator.Email, original.Operator.Email) } if loaded.Cloud.Central.Domain != original.Cloud.Central.Domain { t.Errorf("central domain mismatch: got %q, want %q", loaded.Cloud.Central.Domain, original.Cloud.Central.Domain) } }