Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/server/internal/observability/metrics_test.go
Fimeg f7882241c7 give the system a window you can scrape, and patch a migration that couldn't run twice
OBS-001A: an authenticated /metrics so something other than a human reading
logs can watch the box. no new dep — emits prometheus text straight from the
counters we already keep:

- /metrics behind a dedicated bearer token (sha-256 hash stored, plaintext only
  from REDFLAG_METRICS_TOKEN for bootstrap). constant-time compare, rotates
  without a restart, disabled by default, never an open route
- exports db pool, taskrunner snapshot, scheduler + queue, breaker state, and
  the deferred-advisory count — read live on each scrape, bounded labels only
- settings + migration 054 for observability.metrics_enabled / _token_hash

also fixed migration 046 — it added a column and an index without IF NOT EXISTS
and backfilled off a column it then drops, so it couldn't survive a second run.
guarded every step; the idempotency lint is green again. only the migrations
the runner hasn't recorded see the change, so live dbs don't care.

builds clean, vet quiet, new tests pass.
2026-06-07 20:59:29 -04:00

112 lines
3.1 KiB
Go

package observability
import (
"database/sql"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/Fimeg/RedFlag/server/internal/circuitbreaker"
"github.com/Fimeg/RedFlag/server/internal/scheduler"
"github.com/Fimeg/RedFlag/server/internal/taskrunner"
)
type fakeScheduler struct {
stats scheduler.Stats
queue scheduler.QueueStats
}
func (f fakeScheduler) GetStats() scheduler.Stats {
return f.stats
}
func (f fakeScheduler) GetQueueStats() scheduler.QueueStats {
return f.queue
}
func TestExporterExposesLivePointInTimeSources(t *testing.T) {
nextRun := time.Unix(2000, 0).UTC()
exporter := NewExporter(
func() sql.DBStats {
return sql.DBStats{
MaxOpenConnections: 10,
OpenConnections: 3,
InUse: 2,
Idle: 1,
WaitCount: 4,
WaitDuration: 5 * time.Second,
}
},
func() taskrunner.Snapshot {
return taskrunner.Snapshot{
Workers: 8,
QueueLen: 2,
QueueCap: 256,
Inflight: 1,
Submitted: 12,
Completed: 10,
Overflow: 1,
Panicked: 0,
Periodic: []taskrunner.PeriodicSnapshot{
{Name: "rate_limit_cleanup", Runs: 3, LastRun: time.Unix(1000, 0).UTC().Format(time.RFC3339)},
},
}
},
fakeScheduler{
stats: scheduler.Stats{
JobsProcessed: 5,
JobsSkipped: 1,
CommandsCreated: 4,
CommandsFailed: 2,
BackpressureSkips: 1,
QueueSize: 7,
WorkerPoolUtilized: 3,
AverageProcessingMS: 20,
LastProcessedAt: time.Unix(1500, 0).UTC(),
},
queue: scheduler.QueueStats{
Size: 9,
NextRunAt: &nextRun,
JobsBySubsystem: map[string]int{"updates": 6},
},
},
map[string]func() circuitbreaker.Stats{
"osv": func() circuitbreaker.Stats {
return circuitbreaker.Stats{Name: "osv", State: "open", RecentFailures: 5}
},
},
func() (int, error) { return 2, nil },
)
rec := httptest.NewRecorder()
exporter.ServeHTTP(rec, httptest.NewRequest("GET", "/metrics", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d", rec.Code, http.StatusOK)
}
body := rec.Body.String()
assertContains(t, body, "# TYPE redflag_db_connections gauge")
assertContains(t, body, `redflag_db_connections{state="in_use"} 2`)
assertContains(t, body, `redflag_taskrunner_total{field="overflow"} 1`)
assertContains(t, body, `redflag_taskrunner_periodic_runs_total{task="rate_limit_cleanup"} 3`)
assertContains(t, body, `redflag_scheduler_total{field="jobs_processed"} 5`)
assertContains(t, body, `redflag_scheduler_queue_jobs{subsystem="updates"} 6`)
assertContains(t, body, `redflag_circuit_breaker_state{breaker="osv",state="open"} 1`)
assertContains(t, body, "redflag_advisory_deferred_packages 2")
}
func TestEscapeLabel(t *testing.T) {
got := escapeLabel("line\nquote\"slash\\")
want := `line\nquote\"slash\\`
if got != want {
t.Fatalf("escapeLabel()=%q, want %q", got, want)
}
}
func assertContains(t *testing.T, haystack, needle string) {
t.Helper()
if !strings.Contains(haystack, needle) {
t.Fatalf("expected metrics output to contain %q\n%s", needle, haystack)
}
}