Files
uptop/internal/metrics/prometheus_test.go
T
lerko 52ccd7ad91
CI / test (pull_request) Successful in 1m58s
CI / lint (pull_request) Successful in 1m21s
CI / vulncheck (pull_request) Successful in 1m2s
refactor(models): split Site into SiteConfig + SiteState
Site now embeds SiteConfig (22 persistent fields) and SiteState
(11 ephemeral runtime fields). Field access unchanged via promotion
— site.Name and site.Status still work.

Store layer deals exclusively in SiteConfig — the DB never sees
runtime state. Engine's liveState keeps full Site composites.
UpdateSiteConfig reduced from 11-line field-by-field copy to
`existing.SiteConfig = cfg`.

RunCheck takes SiteConfig (only needs config fields). Checker is
now statically prevented from reading/writing runtime state.

Backup.Sites changed to []SiteConfig — exports no longer carry
zero-valued runtime fields. Import backward-compatible (json
ignores unknown fields).
2026-06-11 17:13:09 -04:00

82 lines
2.0 KiB
Go

package metrics
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
"gitea.lerkolabs.com/lerkolabs/uptop/internal/monitor"
"gitea.lerkolabs.com/lerkolabs/uptop/internal/store/storetest"
)
type mockStore struct {
storetest.BaseMock
sites []models.SiteConfig
}
func (m *mockStore) GetSites(_ context.Context) ([]models.SiteConfig, error) {
return m.sites, nil
}
func TestMetricsHandler(t *testing.T) {
ms := &mockStore{
sites: []models.SiteConfig{
{ID: 1, Name: "Example", URL: "https://example.com", Type: "http", Interval: 30},
{ID: 2, Name: "DNS Check", Type: "dns", Interval: 60},
},
}
eng := monitor.NewEngine(ms)
ctx, cancel := context.WithCancel(context.Background())
eng.Start(ctx)
time.Sleep(100 * time.Millisecond)
rec := httptest.NewRecorder()
Handler(eng)(rec, httptest.NewRequest("GET", "/metrics", nil))
cancel()
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", rec.Code)
}
body := rec.Body.String()
ct := rec.Header().Get("Content-Type")
if !strings.Contains(ct, "text/plain") {
t.Errorf("expected text/plain content type, got %q", ct)
}
expected := []string{
"# HELP uptop_monitor_up",
"# TYPE uptop_monitor_up gauge",
`uptop_monitor_up{id="1",name="Example",type="http"}`,
`uptop_monitor_up{id="2",name="DNS Check",type="dns"}`,
"# HELP uptop_monitor_latency_seconds",
"# HELP uptop_monitor_paused",
"# HELP uptop_monitor_checks_total",
}
for _, s := range expected {
if !strings.Contains(body, s) {
t.Errorf("missing expected line: %s", s)
}
}
}
func TestEscapeLabelValue(t *testing.T) {
cases := []struct{ in, want string }{
{`simple`, `simple`},
{`has "quotes"`, `has \"quotes\"`},
{"has\nnewline", `has\nnewline`},
{`back\slash`, `back\\slash`},
}
for _, tc := range cases {
got := escapeLabelValue(tc.in)
if got != tc.want {
t.Errorf("escapeLabelValue(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}