package api import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "github.com/rcourtman/pulse-go-rewrite/internal/config" ) func TestHandleAddNode(t *testing.T) { tempDir, err := os.MkdirTemp("", "pulse-add-node-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tempDir) // Pre-populate with one node to test duplicate detection dummyCfg := &config.Config{ PVEInstances: []config.PVEInstance{ // Host must be normalized (https + port) for duplicate check to work {Name: "existing", Host: "https://10.0.0.1:8006"}, }, } dummyCfg.DataPath = tempDir // Create handler handler := newTestConfigHandlers(t, dummyCfg) tests := []struct { name string requestBody map[string]interface{} expectedStatus int verifyConfig func(*testing.T, *config.Config) }{ { name: "fail_missing_name", requestBody: map[string]interface{}{ "type": "pve", "host": "10.0.0.2", "user": "root@pam", "password": "password", }, expectedStatus: http.StatusBadRequest, }, { name: "fail_missing_type", requestBody: map[string]interface{}{ "name": "test-newnode", "host": "10.0.0.2", "user": "root@pam", "password": "password", }, expectedStatus: http.StatusBadRequest, }, { name: "fail_missing_host", requestBody: map[string]interface{}{ "name": "test-newnode", "type": "pve", "user": "root@pam", "password": "password", }, expectedStatus: http.StatusBadRequest, }, { name: "fail_invalid_ip", requestBody: map[string]interface{}{ "name": "test-invalidip", "type": "pve", "host": "999.999.999.999", "user": "root@pam", "password": "password", }, expectedStatus: http.StatusBadRequest, }, { name: "fail_duplicate_host", requestBody: map[string]interface{}{ "name": "test-duplicate", "type": "pve", "host": "10.0.0.1", // Will normalize to https://10.0.0.1:8006 and match existing "user": "root@pam", "password": "password", }, expectedStatus: http.StatusConflict, }, { name: "fail_missing_auth", requestBody: map[string]interface{}{ "name": "test-noauth", "type": "pve", "host": "10.0.0.2", // No user/pass }, expectedStatus: http.StatusBadRequest, }, { name: "success_add_pve_password", requestBody: map[string]interface{}{ "name": "test-new-pve", "type": "pve", "host": "10.0.0.2", "user": "root@pam", "password": "secret", }, expectedStatus: http.StatusCreated, verifyConfig: func(t *testing.T, c *config.Config) { found := false for _, node := range c.PVEInstances { if node.Name == "test-new-pve" { found = true if node.Password != "secret" { t.Errorf("expected password 'secret', got '%s'", node.Password) } // Verify host normalization if node.Host != "https://10.0.0.2:8006" { t.Errorf("expected normalized host, got '%s'", node.Host) } break } } if !found { t.Error("new PVE node not found in config") } }, }, { name: "success_add_pve_token", requestBody: map[string]interface{}{ "name": "test-token-pve", "type": "pve", "host": "10.0.0.3", "tokenName": "root@pam!token", "tokenValue": "abcdef", }, expectedStatus: http.StatusCreated, verifyConfig: func(t *testing.T, c *config.Config) { found := false for _, node := range c.PVEInstances { if node.Name == "test-token-pve" { found = true if node.TokenValue != "abcdef" { t.Errorf("expected token 'abcdef', got '%s'", node.TokenValue) } break } } if !found { t.Error("new PVE node (token) not found in config") } }, }, { name: "success_add_pve_token_alias_fields", requestBody: map[string]interface{}{ "name": "test-token-pve-alias", "type": "pve", "host": "10.0.0.4", "tokenId": "root@pam!alias-token", "tokenSecret": "alias-secret", }, expectedStatus: http.StatusCreated, verifyConfig: func(t *testing.T, c *config.Config) { found := false for _, node := range c.PVEInstances { if node.Name == "test-token-pve-alias" { found = true if node.TokenName != "root@pam!alias-token" { t.Errorf("expected token name 'root@pam!alias-token', got '%s'", node.TokenName) } if node.TokenValue != "alias-secret" { t.Errorf("expected token value 'alias-secret', got '%s'", node.TokenValue) } break } } if !found { t.Error("new PVE node (token alias) not found in config") } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { body, _ := json.Marshal(tt.requestBody) req := httptest.NewRequest("POST", "/api/config/nodes", bytes.NewBuffer(body)) w := httptest.NewRecorder() handler.HandleAddNode(w, req) if w.Code != tt.expectedStatus { t.Errorf("handler returned wrong status code: got %v want %v", w.Code, tt.expectedStatus) } if tt.verifyConfig != nil { tt.verifyConfig(t, dummyCfg) } }) } }