package metrics import ( "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) // TestExporterHealthzAndEscaping runs one orchestration or CLI step. // Signature: TestExporterHealthzAndEscaping(t *testing.T). // Why: covers health endpoint and label escaping branches in metrics renderer. func TestExporterHealthzAndEscaping(t *testing.T) { e := New() e.UpdateSample(Sample{ Name: `Sta"tera`, Target: `statera\host`, Status: `O"B`, LastError: "x", }) h := e.Handler("/custom") healthReq := httptest.NewRequest(http.MethodGet, "/healthz", nil) healthRR := httptest.NewRecorder() h.ServeHTTP(healthRR, healthReq) if healthRR.Code != http.StatusOK || strings.TrimSpace(healthRR.Body.String()) != "ok" { t.Fatalf("unexpected health response: code=%d body=%q", healthRR.Code, healthRR.Body.String()) } metricsReq := httptest.NewRequest(http.MethodGet, "/custom", nil) metricsRR := httptest.NewRecorder() h.ServeHTTP(metricsRR, metricsReq) body := metricsRR.Body.String() if !strings.Contains(body, `source="Sta\\\"tera"`) { t.Fatalf("expected escaped source label, got:\n%s", body) } if !strings.Contains(body, `target="statera\\\\host"`) { t.Fatalf("expected escaped target label, got:\n%s", body) } if !strings.Contains(body, "ananke_ups_error") { t.Fatalf("expected error metric line in output") } } // TestBoolNumAndSafeHelpers runs one orchestration or CLI step. // Signature: TestBoolNumAndSafeHelpers(t *testing.T). // Why: directly covers remaining helper branches. func TestBoolNumAndSafeHelpers(t *testing.T) { if boolNum(true) != 1 || boolNum(false) != 0 { t.Fatalf("unexpected boolNum values") } if got := safe(`a"b\c`); got != `a\"b\\c` { t.Fatalf("unexpected escaped string: %q", got) } } // TestExporterAppendsQualityGateMetrics runs one orchestration or CLI step. // Signature: TestExporterAppendsQualityGateMetrics(t *testing.T). // Why: verifies quality-gate metrics are surfaced on /metrics for Grafana suite // pass-rate tracking. func TestExporterAppendsQualityGateMetrics(t *testing.T) { tmp := t.TempDir() metricsPath := filepath.Join(tmp, "quality-gate.prom") content := strings.Join([]string{ `# HELP ananke_quality_gate_runs_total Total quality gate runs by status.`, `# TYPE ananke_quality_gate_runs_total counter`, `ananke_quality_gate_runs_total{suite="ananke",status="ok"} 10`, `ananke_quality_gate_runs_total{suite="ananke",status="failed"} 2`, "", }, "\n") if err := os.WriteFile(metricsPath, []byte(content), 0o600); err != nil { t.Fatalf("write quality metrics file: %v", err) } t.Setenv("ANANKE_QUALITY_METRICS_FILE", metricsPath) e := New() req := httptest.NewRequest(http.MethodGet, "/metrics", nil) rr := httptest.NewRecorder() e.Handler("/metrics").ServeHTTP(rr, req) body := rr.Body.String() if !strings.Contains(body, `ananke_quality_gate_runs_total{suite="ananke",status="ok"} 10`) { t.Fatalf("expected quality gate metrics appended to exporter output, got:\n%s", body) } } // TestExporterDefaultsQualityGateMetrics runs one orchestration or CLI step. // Signature: TestExporterDefaultsQualityGateMetrics(t *testing.T). // Why: ensures Grafana panels see zero-value quality-gate series instead of // "no data" before host-side quality-gate files exist. func TestExporterDefaultsQualityGateMetrics(t *testing.T) { t.Setenv("ANANKE_QUALITY_METRICS_FILE", filepath.Join(t.TempDir(), "missing.prom")) e := New() req := httptest.NewRequest(http.MethodGet, "/metrics", nil) rr := httptest.NewRecorder() e.Handler("/metrics").ServeHTTP(rr, req) body := rr.Body.String() if !strings.Contains(body, `ananke_quality_gate_runs_total{suite="ananke",status="ok"} 0`) { t.Fatalf("expected default ok=0 quality gate metric, got:\n%s", body) } if !strings.Contains(body, `ananke_quality_gate_runs_total{suite="ananke",status="failed"} 0`) { t.Fatalf("expected default failed=0 quality gate metric, got:\n%s", body) } }