package api import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/rcourtman/pulse-go-rewrite/internal/ai/knowledge" "github.com/rcourtman/pulse-go-rewrite/internal/config" "github.com/rcourtman/pulse-go-rewrite/internal/models" "github.com/rcourtman/pulse-go-rewrite/internal/monitoring" "github.com/rcourtman/pulse-go-rewrite/pkg/metrics" ) func TestHandleMetricsStoreStats_MethodNotAllowed(t *testing.T) { router := &Router{} req := httptest.NewRequest(http.MethodPost, "/api/metrics/store/stats", nil) rec := httptest.NewRecorder() router.handleMetricsStoreStats(rec, req) if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed) } } func TestHandleMetricsStoreStats_NoMonitor(t *testing.T) { router := &Router{} req := httptest.NewRequest(http.MethodGet, "/api/metrics/store/stats", nil) rec := httptest.NewRecorder() router.handleMetricsStoreStats(rec, req) if rec.Code != http.StatusInternalServerError { t.Fatalf("status = %d, want %d", rec.Code, http.StatusInternalServerError) } } func TestHandleMetricsStoreStats_NoStore(t *testing.T) { monitor, _, _ := newTestMonitor(t) router := &Router{monitor: monitor} req := httptest.NewRequest(http.MethodGet, "/api/metrics/store/stats", nil) rec := httptest.NewRecorder() router.handleMetricsStoreStats(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) } var payload map[string]interface{} if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } if payload["enabled"] != false { t.Fatalf("expected enabled=false, got %#v", payload["enabled"]) } } func TestHandleMetricsStoreStats_WithStore(t *testing.T) { monitor, _, _ := newTestMonitor(t) store, err := metrics.NewStore(metrics.DefaultConfig(t.TempDir())) if err != nil { t.Fatalf("metrics.NewStore error: %v", err) } defer store.Close() setUnexportedField(t, monitor, "metricsStore", store) router := &Router{monitor: monitor} req := httptest.NewRequest(http.MethodGet, "/api/metrics/store/stats", nil) rec := httptest.NewRecorder() router.handleMetricsStoreStats(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) } var payload map[string]interface{} if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } if payload["enabled"] != true { t.Fatalf("expected enabled=true, got %#v", payload["enabled"]) } } func TestHandleDiagnosticsDockerPrepareToken_MethodNotAllowed(t *testing.T) { router := &Router{} req := httptest.NewRequest(http.MethodGet, "/api/diagnostics/docker/prepare-token", nil) rec := httptest.NewRecorder() router.handleDiagnosticsDockerPrepareToken(rec, req) if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed) } } func TestHandleDiagnosticsDockerPrepareToken_InvalidJSON(t *testing.T) { router := &Router{monitor: &monitoring.Monitor{}, config: &config.Config{}} req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", strings.NewReader("{")) rec := httptest.NewRecorder() router.handleDiagnosticsDockerPrepareToken(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest) } } func TestHandleDiagnosticsDockerPrepareToken_MissingAgentID(t *testing.T) { router := &Router{monitor: &monitoring.Monitor{}, config: &config.Config{}} body := bytes.NewBufferString(`{"agentId":""}`) req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", body) rec := httptest.NewRecorder() router.handleDiagnosticsDockerPrepareToken(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest) } } func TestHandleDiagnosticsDockerPrepareToken_AgentNotFound(t *testing.T) { monitor, _, _ := newTestMonitor(t) router := &Router{monitor: monitor, config: &config.Config{}} body := bytes.NewBufferString(`{"agentId":"missing"}`) req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", body) rec := httptest.NewRecorder() router.handleDiagnosticsDockerPrepareToken(rec, req) if rec.Code != http.StatusNotFound { t.Fatalf("status = %d, want %d", rec.Code, http.StatusNotFound) } } func TestHandleDiagnosticsDockerPrepareToken_Success(t *testing.T) { monitor, state, _ := newTestMonitor(t) state.DockerHosts = []models.DockerHost{{ID: "host-1", DisplayName: "Docker Host"}} router := &Router{monitor: monitor, config: &config.Config{PublicURL: "https://pulse.example.com"}} body := bytes.NewBufferString(`{"agentId":"host-1","tokenName":""}`) req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", body) rec := httptest.NewRecorder() router.handleDiagnosticsDockerPrepareToken(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) } var payload map[string]interface{} if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } if ok, _ := payload["success"].(bool); !ok { t.Fatalf("expected success true, got %#v", payload["success"]) } if payload["token"] == "" { t.Fatalf("expected token in response") } agent, _ := payload["agent"].(map[string]interface{}) if agent["id"] != "host-1" { t.Fatalf("unexpected agent id: %#v", agent["id"]) } if !strings.Contains(payload["installCommand"].(string), "https://pulse.example.com") { t.Fatalf("expected install command to include base URL") } if !strings.Contains(payload["installCommand"].(string), "--enable-host=false") { t.Fatalf("expected install command to disable host metrics with the canonical lifecycle flag: %q", payload["installCommand"]) } if !strings.Contains(payload["installCommand"].(string), "--enable-docker") { t.Fatalf("expected install command to enable docker metrics: %q", payload["installCommand"]) } if !strings.Contains(payload["installCommand"].(string), `| { if [ "$(id -u)" -eq 0 ]; then bash -s --`) { t.Fatalf("expected install command to use lifecycle privilege wrapper: %q", payload["installCommand"]) } if strings.Contains(payload["installCommand"].(string), "| sudo bash -s -- --url") { t.Fatalf("expected install command to preserve the governed root-or-sudo wrapper instead of a raw sudo pipe: %q", payload["installCommand"]) } if !strings.Contains(payload["systemdServiceSnippet"].(string), "--enable-host=false") { t.Fatalf("expected systemd snippet to disable host metrics: %q", payload["systemdServiceSnippet"]) } if len(router.config.APITokens) == 0 { t.Fatalf("expected API token to be recorded") } } func TestHandleDiagnosticsDockerPrepareToken_NormalizesTrailingSlashPublicURL(t *testing.T) { monitor, state, _ := newTestMonitor(t) state.DockerHosts = []models.DockerHost{{ID: "host-1", DisplayName: "Docker Host"}} router := &Router{monitor: monitor, config: &config.Config{PublicURL: "https://pulse.example.com/base///"}} body := bytes.NewBufferString(`{"agentId":"host-1","tokenName":""}`) req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", body) rec := httptest.NewRecorder() router.handleDiagnosticsDockerPrepareToken(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) } var payload map[string]interface{} if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil { t.Fatalf("decode response: %v", err) } installCommand, _ := payload["installCommand"].(string) if !strings.Contains(installCommand, posixShellQuote("https://pulse.example.com/base/install.sh")) { t.Fatalf("expected normalized install script URL, got %q", installCommand) } if strings.Contains(installCommand, "//install.sh") { t.Fatalf("expected normalized install script URL without double slash, got %q", installCommand) } systemdSnippet, _ := payload["systemdServiceSnippet"].(string) if !strings.Contains(systemdSnippet, `Environment="PULSE_URL=https://pulse.example.com/base"`) { t.Fatalf("expected normalized Pulse URL in systemd snippet, got %q", systemdSnippet) } if strings.Contains(systemdSnippet, `Environment="PULSE_URL=https://pulse.example.com/base/"`) { t.Fatalf("expected trailing slash to be trimmed from systemd snippet, got %q", systemdSnippet) } if pulseURL, _ := payload["pulseURL"].(string); pulseURL != "https://pulse.example.com/base" { t.Fatalf("expected normalized pulseURL, got %q", pulseURL) } } func TestKnowledgeStoreProviderWrapper(t *testing.T) { wrapper := &knowledgeStoreProviderWrapper{} if err := wrapper.SaveNote("res-1", "note", "service"); err == nil { t.Fatalf("expected error when store is nil") } if got := wrapper.GetKnowledge("res-1", ""); got != nil { t.Fatalf("expected nil knowledge when store is nil") } store, err := knowledge.NewStore(t.TempDir()) if err != nil { t.Fatalf("knowledge.NewStore error: %v", err) } wrapper.store = store if err := wrapper.SaveNote("res-1", "hello", "service"); err != nil { t.Fatalf("SaveNote error: %v", err) } entries := wrapper.GetKnowledge("res-1", "service") if len(entries) != 1 { t.Fatalf("expected 1 entry, got %d", len(entries)) } if entries[0].Category != "service" || entries[0].Note != "hello" { t.Fatalf("unexpected entry: %#v", entries[0]) } all := wrapper.GetKnowledge("res-1", "") if len(all) != 1 { t.Fatalf("expected 1 entry from full query, got %d", len(all)) } }