145 lines
7.0 KiB
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")
|
|
}
|
|
}
|