vdi-cloud/platform/internal/server/terminal_metrics_test.go

145 lines
7.0 KiB
Go

package server
import (
"fmt"
"testing"
"time"
"vdi-cloud/platform/internal/store"
)
func TestTerminalMetricsIdentityValidationAndCompatibility(t *testing.T) {
f := newFixtureWorker(t, false)
viewer := f.login("viewer")
for _, id := range []string{"metric-a", "metric-b"} {
_, err := f.db.Exec(`INSERT INTO terminals(id,sn,name,token_hash,bootstrap_hash,last_seen,status) VALUES($1,$1,$1,$2,'',now(),'online')`, id, store.Hash(id+"-token"))
if err != nil {
t.Fatal(err)
}
}
body := map[string]any{"agentVersion": "qt-platform/0.4.0", "terminalId": "metric-b", "systemMetrics": map[string]any{"cpuPercent": 0, "memoryPercent": 30, "socTemperatureC": 44.37, "uptimeSeconds": 120}}
status, out := f.request("POST", "/terminal/heartbeat", "metric-a-token", body, "")
expect(t, 200, status, out)
status, out = f.request("GET", "/terminals/metric-a/metrics", viewer, nil, "")
expect(t, 200, status, out)
latest := out["latest"].(map[string]any)
if latest["supported"] != true || latest["fresh"] != true || latest["cpuPercent"] != float64(0) || latest["socTemperatureC"] != 44.37 {
t.Fatal(latest)
}
status, out = f.request("GET", "/terminals/metric-b/metrics", viewer, nil, "")
expect(t, 200, status, out)
if out["latest"].(map[string]any)["supported"] != false {
t.Fatal("forged terminal identity accepted", out)
}
// Repeated uploads update current state but cannot grow history without limit.
status, out = f.request("POST", "/terminal/heartbeat", "metric-a-token", body, "")
expect(t, 200, status, out)
var count int
f.db.Get(&count, "SELECT count(*) FROM terminal_metric_samples WHERE terminal_id='metric-a'")
if count != 1 {
t.Fatal("unbounded duplicate samples", count)
}
// Session reports also advance last_seen; they must not hide a recent metric.
f.db.Exec("UPDATE terminals SET last_seen=now() WHERE id='metric-a'")
status, out = f.request("GET", "/terminals/metric-a/metrics", viewer, nil, "")
expect(t, 200, status, out)
if out["latest"].(map[string]any)["fresh"] != true {
t.Fatal("session report hid metrics", out)
}
for _, invalid := range []map[string]any{{"cpuPercent": 101}, {"memoryPercent": -1}, {"socTemperatureC": 151}, {"uptimeSeconds": -0.1}, {"cpuPercent": "nan"}} {
status, out = f.request("POST", "/terminal/heartbeat", "metric-a-token", map[string]any{"agentVersion": "test", "systemMetrics": invalid}, "")
expect(t, 400, status, out)
}
f.db.Exec("UPDATE terminals SET last_seen=now()-interval '91 seconds' WHERE id='metric-a'")
status, out = f.request("GET", "/terminals/metric-a/metrics", viewer, nil, "")
expect(t, 200, status, out)
latest = out["latest"].(map[string]any)
if latest["fresh"] != false || latest["online"] != false || latest["cpuPercent"] != nil || latest["socTemperatureC"] != nil {
t.Fatal("stale live value", latest)
}
status, out = f.request("POST", "/terminal/heartbeat", "metric-a-token", map[string]any{"agentVersion": "legacy"}, "")
expect(t, 200, status, out)
status, out = f.request("GET", "/terminals/metric-a/metrics", viewer, nil, "")
expect(t, 200, status, out)
latest = out["latest"].(map[string]any)
if latest["supported"] != false || latest["cpuPercent"] != nil {
t.Fatal("legacy values retained", latest)
}
admin := f.login("admin")
status, out = f.request("POST", "/terminals/metric-a/disable", admin, nil, "")
expect(t, 200, status, out)
status, out = f.request("POST", "/terminal/heartbeat", "metric-a-token", body, "")
expect(t, 401, status, out)
status, out = f.request("POST", "/terminals/metric-a/reactivate", admin, nil, "")
expect(t, 200, status, out)
secret := out["activationSecret"]
status, out = f.request("POST", "/terminal/register", "", map[string]any{"sn": "metric-a", "activationSecret": secret, "agentVersion": "test"}, "")
expect(t, 200, status, out)
status, out = f.request("GET", "/terminals/metric-a/metrics", viewer, nil, "")
expect(t, 200, status, out)
if out["latest"].(map[string]any)["supported"] != false || out["latest"].(map[string]any)["fresh"] != false {
t.Fatal("old identity metrics retained", out)
}
status, out = f.request("GET", "/terminals/missing/metrics", viewer, nil, "")
expect(t, 404, status, out)
status, out = f.request("GET", "/terminals/metric-a/metrics", "metric-b-token", nil, "")
expect(t, 401, status, out)
status, out = f.request("GET", "/terminals/metric-a/metrics", "", nil, "")
expect(t, 401, status, out)
// A never-registered terminal has null last_seen and no current metrics.
f.db.Exec("INSERT INTO terminals(id,sn,name,bootstrap_hash) VALUES('metric-new','metric-new','new','')")
status, out = f.request("GET", "/terminals/metric-new/metrics", viewer, nil, "")
expect(t, 200, status, out)
if out["latest"].(map[string]any)["online"] != false {
t.Fatal(out)
}
}
func TestTerminalMetricsAggregationGapsAndRetention(t *testing.T) {
f := newFixtureWorker(t, false)
viewer := f.login("viewer")
if _, err := f.db.Exec("INSERT INTO terminals(id,sn,name,bootstrap_hash) VALUES('metrics-series','metrics-series','series','')"); err != nil {
t.Fatal(err)
}
start := time.Now().UTC().Add(-time.Hour).Truncate(time.Minute)
for _, p := range []struct{ seconds, cpu int }{{10, 20}, {30, 40}, {130, 60}} {
if _, err := f.db.Exec(`INSERT INTO terminal_metric_samples(terminal_id,sampled_at,cpu_percent) VALUES('metrics-series',$1,$2)`, start.Add(time.Duration(p.seconds)*time.Second), p.cpu); err != nil {
t.Fatal(err)
}
}
path := fmt.Sprintf("/terminals/metrics-series/metrics?from=%s&to=%s", start.Format(time.RFC3339), start.Add(3*time.Minute).Format(time.RFC3339))
status, out := f.request("GET", path, viewer, nil, "")
expect(t, 200, status, out)
points := out["items"].([]any)
if len(points) != 4 {
t.Fatal(out)
}
first, gap, third := points[0].(map[string]any), points[1].(map[string]any), points[2].(map[string]any)
if first["cpuPercent"] != float64(30) || first["samples"] != float64(2) || first["memoryPercent"] != nil || gap["cpuPercent"] != nil || gap["samples"] != float64(0) || third["cpuPercent"] != float64(60) {
t.Fatal(points)
}
status, out = f.request("GET", "/terminals/metrics-series/metrics?from=2026-01-01T00:00:00Z&to=2026-01-09T00:00:00Z", viewer, nil, "")
expect(t, 400, status, out)
status, out = f.request("GET", "/terminals/metrics-series/metrics?from=bad", viewer, nil, "")
expect(t, 400, status, out)
for _, hours := range []int{24, 168} {
status, out = f.request("GET", fmt.Sprintf("/terminals/metrics-series/metrics?from=%s&to=%s", start.Add(-time.Duration(hours)*time.Hour).Format(time.RFC3339), start.Format(time.RFC3339)), viewer, nil, "")
expect(t, 200, status, out)
want := float64(300)
if hours == 168 {
want = 1800
}
if out["bucketSeconds"] != want || len(out["items"].([]any)) > 400 {
t.Fatal(out)
}
}
f.db.Exec("INSERT INTO terminal_metric_samples(terminal_id,sampled_at) VALUES('metrics-series',now()-interval '31 days')")
if err := f.server.observe(t.Context()); err != nil {
t.Fatal(err)
}
var old int
f.db.Get(&old, "SELECT count(*) FROM terminal_metric_samples WHERE sampled_at<now()-interval '30 days'")
if old != 0 {
t.Fatal("metrics retention not applied")
}
}