ananke/testing/metrics/exporter_http_contract_test.go

107 lines
3.7 KiB
Go
Raw Permalink Normal View History

package metricsquality
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"scm.bstein.dev/bstein/ananke/internal/metrics"
)
// TestExporterEmitsCoreMetrics runs one orchestration or CLI step.
// Signature: TestExporterEmitsCoreMetrics(t *testing.T).
// Why: keeps exporter metric output stable after moving checks into the
// top-level testing module.
func TestExporterEmitsCoreMetrics(t *testing.T) {
exporter := metrics.New()
exporter.UpdateBudget(321)
exporter.UpdateSample(metrics.Sample{
Name: "Pyrphoros",
Target: "pyrphoros@localhost",
OnBattery: true,
LowBattery: false,
RuntimeSecond: 412,
BatteryCharge: 81.5,
LoadPercent: 27.4,
PowerNominalW: 510,
ThresholdSec: 354,
Trigger: true,
BreachCount: 2,
Status: "OB",
UpdatedAt: time.Unix(1710000000, 0).UTC(),
})
exporter.MarkShutdown("ups-threshold")
req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
rr := httptest.NewRecorder()
exporter.Handler("/metrics").ServeHTTP(rr, req)
body := rr.Body.String()
mustContain := []string{
"ananke_shutdown_budget_seconds 321",
"ananke_shutdown_triggers_total 1",
"ananke_ups_on_battery{source=\"Pyrphoros\",target=\"pyrphoros@localhost\"",
"ananke_ups_runtime_seconds{source=\"Pyrphoros\",target=\"pyrphoros@localhost\"",
"ananke_ups_battery_charge_percent{source=\"Pyrphoros\",target=\"pyrphoros@localhost\"",
"ananke_ups_load_percent{source=\"Pyrphoros\",target=\"pyrphoros@localhost\"",
"ananke_ups_power_nominal_watts{source=\"Pyrphoros\",target=\"pyrphoros@localhost\"",
"ananke_ups_threshold_seconds{source=\"Pyrphoros\",target=\"pyrphoros@localhost\"",
}
for _, fragment := range mustContain {
if !strings.Contains(body, fragment) {
t.Fatalf("missing metric fragment %q in output:\n%s", fragment, body)
}
}
}
// TestExporterHealthzAndEscaping runs one orchestration or CLI step.
// Signature: TestExporterHealthzAndEscaping(t *testing.T).
// Why: covers exporter health routing and label escaping from the top-level
// testing module so HTTP behavior stays explicit after migration.
func TestExporterHealthzAndEscaping(t *testing.T) {
exporter := metrics.New()
exporter.UpdateSample(metrics.Sample{
Name: `Sta"tera`,
Target: `statera\host`,
Status: `O"B`,
LastError: "x",
})
handler := exporter.Handler("/custom")
healthReq := httptest.NewRequest(http.MethodGet, "/healthz", nil)
healthRR := httptest.NewRecorder()
handler.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()
handler.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")
}
}
// TestExporterHelperContracts runs one orchestration or CLI step.
// Signature: TestExporterHelperContracts(t *testing.T).
// Why: preserves direct helper coverage for metric formatting utilities after
// removing same-package root tests.
func TestExporterHelperContracts(t *testing.T) {
if metrics.TestHookBoolNum(true) != 1 || metrics.TestHookBoolNum(false) != 0 {
t.Fatalf("unexpected boolNum values")
}
if got := metrics.TestHookSafeLabelValue(`a"b\c`); got != `a\"b\\c` {
t.Fatalf("unexpected escaped string: %q", got)
}
}