package clusteradmin import ( "bytes" "context" "encoding/json" "strings" "testing" "time" "net/http" "net/http/httptest" "forgejo.digital-droplets.de/philschlo/proxui/platform/cluster" ) func TestCreateClusterDoesNotReturnTokenSecret(t *testing.T) { repository := newStubRepository() handler := NewHandler(repository) req := httptest.NewRequest(http.MethodPost, "/internal/clusters", strings.NewReader(`{ "name": "Lab", "api_endpoint": "https://pve.example.test:8006/api2/json", "tls_fingerprint": "AA:BB", "token_id": "root@pam!proxui", "token_secret": "secret-token" }`)) rec := httptest.NewRecorder() handler.CreateCluster(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("status = %d, want %d", rec.Code, http.StatusCreated) } if repository.saved.TokenSecret != "secret-token" { t.Fatalf("saved token secret = %q", repository.saved.TokenSecret) } if bytes.Contains(rec.Body.Bytes(), []byte("secret-token")) { t.Fatal("response contains token secret") } var response clusterResponse if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil { t.Fatalf("unmarshal response: %v", err) } if response.TokenID != "root@pam!proxui" { t.Fatalf("TokenID = %q, want root@pam!proxui", response.TokenID) } } func TestUpdateClusterUsesPathIDAndRotatesToken(t *testing.T) { repository := newStubRepository() handler := NewHandler(repository) req := httptest.NewRequest(http.MethodPut, "/internal/clusters/cluster-1", strings.NewReader(`{ "name": "Lab", "api_endpoint": "https://pve.example.test:8006/api2/json", "tls_fingerprint": "AA:BB", "token_id": "root@pam!proxui", "token_secret": "rotated-token", "status": "active" }`)) req.SetPathValue("clusterID", "cluster-1") rec := httptest.NewRecorder() handler.UpdateCluster(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) } if repository.saved.ID != "cluster-1" { t.Fatalf("saved ID = %q, want cluster-1", repository.saved.ID) } if repository.saved.TokenSecret != "rotated-token" { t.Fatalf("saved token secret = %q", repository.saved.TokenSecret) } if bytes.Contains(rec.Body.Bytes(), []byte("rotated-token")) { t.Fatal("response contains rotated token secret") } } func TestSetClusterStatus(t *testing.T) { repository := newStubRepository() handler := NewHandler(repository) req := httptest.NewRequest(http.MethodPatch, "/internal/clusters/cluster-1/status", strings.NewReader(`{"status":"disabled"}`)) req.SetPathValue("clusterID", "cluster-1") rec := httptest.NewRecorder() handler.SetClusterStatus(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) } if repository.statusID != "cluster-1" { t.Fatalf("status ID = %q, want cluster-1", repository.statusID) } if repository.status != "disabled" { t.Fatalf("status = %q, want disabled", repository.status) } } func TestSetClusterStatusReturnsNotFound(t *testing.T) { repository := newStubRepository() repository.statusFound = false handler := NewHandler(repository) req := httptest.NewRequest(http.MethodPatch, "/internal/clusters/missing/status", strings.NewReader(`{"status":"disabled"}`)) req.SetPathValue("clusterID", "missing") rec := httptest.NewRecorder() handler.SetClusterStatus(rec, req) if rec.Code != http.StatusNotFound { t.Fatalf("status = %d, want %d", rec.Code, http.StatusNotFound) } } type stubRepository struct { saved cluster.Cluster statusID string status string statusFound bool } func newStubRepository() *stubRepository { return &stubRepository{statusFound: true} } func (s *stubRepository) UpsertCluster(_ context.Context, cluster cluster.Cluster) (cluster.Cluster, error) { s.saved = cluster if cluster.ID == "" { cluster.ID = "cluster-1" } if cluster.Status == "" { cluster.Status = "active" } cluster.CreatedAt = time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC) return cluster, nil } func (s *stubRepository) SetClusterStatus(_ context.Context, id string, status string) (cluster.Cluster, bool, error) { s.statusID = id s.status = status if !s.statusFound { return cluster.Cluster{}, false, nil } return cluster.Cluster{ ID: id, Name: "Lab", APIEndpoint: "https://pve.example.test:8006/api2/json", TLSFingerprint: "AA:BB", TokenID: "root@pam!proxui", Status: status, CreatedAt: time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC), }, true, nil }