mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
New test files with expanded coverage: API tests: - ai_handler_test.go: AI handler unit tests with mocking - agent_profiles_tools_test.go: Profile management tests - alerts_endpoints_test.go: Alert API endpoint tests - alerts_test.go: Updated for interface changes - audit_handlers_test.go: Audit handler tests - frontend_embed_test.go: Frontend embedding tests - metadata_handlers_test.go, metadata_provider_test.go: Metadata tests - notifications_test.go: Updated for interface changes - profile_suggestions_test.go: Profile suggestion tests - saml_service_test.go: SAML authentication tests - sensor_proxy_gate_test.go: Sensor proxy tests - updates_test.go: Updated for interface changes Agent tests: - dockeragent/signature_test.go: Docker agent signature tests - hostagent/agent_metrics_test.go: Host agent metrics tests - hostagent/commands_test.go: Command execution tests - hostagent/network_helpers_test.go: Network helper tests - hostagent/proxmox_setup_test.go: Updated setup tests - kubernetesagent/*_test.go: Kubernetes agent tests Core package tests: - monitoring/kubernetes_agents_test.go, reload_test.go - remoteconfig/client_test.go, signature_test.go - sensors/collector_test.go - updates/adapter_installsh_*_test.go: Install adapter tests - updates/manager_*_test.go: Update manager tests - websocket/hub_*_test.go: WebSocket hub tests Library tests: - pkg/audit/export_test.go: Audit export tests - pkg/metrics/store_test.go: Metrics store tests - pkg/proxmox/*_test.go: Proxmox client tests - pkg/reporting/reporting_test.go: Reporting tests - pkg/server/*_test.go: Server tests - pkg/tlsutil/extra_test.go: TLS utility tests Total: ~8000 lines of new test code
231 lines
6.6 KiB
Go
231 lines
6.6 KiB
Go
package metrics
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestStoreWriteBatchAndQuery(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := DefaultConfig(dir)
|
|
cfg.DBPath = filepath.Join(dir, "metrics-test.db")
|
|
cfg.FlushInterval = time.Hour
|
|
cfg.RetentionRaw = 10 * time.Second
|
|
cfg.RetentionMinute = 20 * time.Second
|
|
cfg.RetentionHourly = 30 * time.Second
|
|
cfg.RetentionDaily = 40 * time.Second
|
|
|
|
store, err := NewStore(cfg)
|
|
if err != nil {
|
|
t.Fatalf("NewStore returned error: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
ts := time.Unix(1000, 0)
|
|
store.writeBatch([]bufferedMetric{
|
|
{resourceType: "vm", resourceID: "vm-101", metricType: "cpu", value: 1.5, timestamp: ts},
|
|
{resourceType: "vm", resourceID: "vm-101", metricType: "cpu", value: 2.5, timestamp: ts.Add(1 * time.Second)},
|
|
})
|
|
|
|
points, err := store.Query("vm", "vm-101", "cpu", ts.Add(-1*time.Second), ts.Add(2*time.Second))
|
|
if err != nil {
|
|
t.Fatalf("Query returned error: %v", err)
|
|
}
|
|
if len(points) != 2 {
|
|
t.Fatalf("expected 2 points, got %d", len(points))
|
|
}
|
|
if points[0].Value != 1.5 || points[1].Value != 2.5 {
|
|
t.Fatalf("unexpected query values: %+v", points)
|
|
}
|
|
|
|
all, err := store.QueryAll("vm", "vm-101", ts.Add(-1*time.Second), ts.Add(2*time.Second))
|
|
if err != nil {
|
|
t.Fatalf("QueryAll returned error: %v", err)
|
|
}
|
|
if len(all["cpu"]) != 2 {
|
|
t.Fatalf("expected QueryAll to return 2 cpu points, got %+v", all)
|
|
}
|
|
}
|
|
|
|
func TestStoreSelectTierAndStats(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := DefaultConfig(dir)
|
|
cfg.DBPath = filepath.Join(dir, "metrics-test.db")
|
|
cfg.FlushInterval = time.Hour
|
|
cfg.RetentionRaw = 10 * time.Second
|
|
cfg.RetentionMinute = 20 * time.Second
|
|
cfg.RetentionHourly = 30 * time.Second
|
|
cfg.RetentionDaily = 40 * time.Second
|
|
|
|
store, err := NewStore(cfg)
|
|
if err != nil {
|
|
t.Fatalf("NewStore returned error: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
if store.selectTier(5*time.Second) != TierRaw {
|
|
t.Fatalf("expected raw tier")
|
|
}
|
|
if store.selectTier(15*time.Second) != TierMinute {
|
|
t.Fatalf("expected minute tier")
|
|
}
|
|
if store.selectTier(25*time.Second) != TierHourly {
|
|
t.Fatalf("expected hourly tier")
|
|
}
|
|
if store.selectTier(35*time.Second) != TierDaily {
|
|
t.Fatalf("expected daily tier")
|
|
}
|
|
|
|
// Insert one point for each tier to verify stats aggregation.
|
|
ts := int64(1000)
|
|
_, err = store.db.Exec(
|
|
`INSERT INTO metrics (resource_type, resource_id, metric_type, value, timestamp, tier) VALUES
|
|
('vm','vm-101','cpu',1.0,?, 'raw'),
|
|
('vm','vm-101','cpu',2.0,?, 'minute'),
|
|
('vm','vm-101','cpu',3.0,?, 'hourly'),
|
|
('vm','vm-101','cpu',4.0,?, 'daily')`,
|
|
ts, ts, ts, ts,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("insert metrics returned error: %v", err)
|
|
}
|
|
|
|
stats := store.GetStats()
|
|
if stats.RawCount != 1 || stats.MinuteCount != 1 || stats.HourlyCount != 1 || stats.DailyCount != 1 {
|
|
t.Fatalf("unexpected tier counts: %+v", stats)
|
|
}
|
|
if stats.DBSize <= 0 {
|
|
t.Fatalf("expected stats DB info to be populated: %+v", stats)
|
|
}
|
|
}
|
|
|
|
func TestStoreRollupTier(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := DefaultConfig(dir)
|
|
cfg.DBPath = filepath.Join(dir, "metrics-rollup.db")
|
|
cfg.FlushInterval = time.Hour
|
|
|
|
store, err := NewStore(cfg)
|
|
if err != nil {
|
|
t.Fatalf("NewStore returned error: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
base := time.Now().Add(-2 * time.Minute).Truncate(time.Minute)
|
|
ts := base.Unix()
|
|
|
|
_, err = store.db.Exec(
|
|
`INSERT INTO metrics (resource_type, resource_id, metric_type, value, timestamp, tier) VALUES
|
|
('vm','vm-101','cpu',1.0,?, 'raw'),
|
|
('vm','vm-101','cpu',3.0,?, 'raw')`,
|
|
ts, base.Add(10*time.Second).Unix(),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("insert metrics returned error: %v", err)
|
|
}
|
|
|
|
store.rollupTier(TierRaw, TierMinute, time.Minute, 0)
|
|
|
|
var countRaw int
|
|
if err := store.db.QueryRow(`SELECT COUNT(*) FROM metrics WHERE tier = 'raw'`).Scan(&countRaw); err != nil {
|
|
t.Fatalf("query raw count: %v", err)
|
|
}
|
|
if countRaw != 0 {
|
|
t.Fatalf("expected raw metrics to be rolled up, got %d", countRaw)
|
|
}
|
|
|
|
var value, minValue, maxValue float64
|
|
var bucketTs int64
|
|
if err := store.db.QueryRow(
|
|
`SELECT value, min_value, max_value, timestamp FROM metrics WHERE tier = 'minute'`,
|
|
).Scan(&value, &minValue, &maxValue, &bucketTs); err != nil {
|
|
t.Fatalf("query minute tier: %v", err)
|
|
}
|
|
|
|
expectedBucket := (ts / 60) * 60
|
|
if bucketTs != expectedBucket {
|
|
t.Fatalf("expected bucket %d, got %d", expectedBucket, bucketTs)
|
|
}
|
|
if value != 2.0 || minValue != 1.0 || maxValue != 3.0 {
|
|
t.Fatalf("unexpected rollup values: value=%v min=%v max=%v", value, minValue, maxValue)
|
|
}
|
|
}
|
|
|
|
func TestStoreRetentionPrunesOldData(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := DefaultConfig(dir)
|
|
cfg.DBPath = filepath.Join(dir, "metrics-retention.db")
|
|
cfg.RetentionRaw = time.Minute
|
|
cfg.RetentionMinute = time.Minute
|
|
cfg.RetentionHourly = time.Minute
|
|
cfg.RetentionDaily = time.Minute
|
|
cfg.FlushInterval = time.Hour
|
|
|
|
store, err := NewStore(cfg)
|
|
if err != nil {
|
|
t.Fatalf("NewStore returned error: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
oldTs := time.Now().Add(-2 * time.Hour).Unix()
|
|
newTs := time.Now().Unix()
|
|
|
|
_, err = store.db.Exec(
|
|
`INSERT INTO metrics (resource_type, resource_id, metric_type, value, timestamp, tier) VALUES
|
|
('vm','vm-101','cpu',1.0,?, 'raw'),
|
|
('vm','vm-101','cpu',2.0,?, 'minute'),
|
|
('vm','vm-101','cpu',3.0,?, 'hourly'),
|
|
('vm','vm-101','cpu',4.0,?, 'daily'),
|
|
('vm','vm-101','cpu',5.0,?, 'raw')`,
|
|
oldTs, oldTs, oldTs, oldTs, newTs,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("insert metrics returned error: %v", err)
|
|
}
|
|
|
|
store.runRetention()
|
|
|
|
var rawCount int
|
|
if err := store.db.QueryRow(`SELECT COUNT(*) FROM metrics WHERE tier = 'raw'`).Scan(&rawCount); err != nil {
|
|
t.Fatalf("query raw count: %v", err)
|
|
}
|
|
if rawCount != 1 {
|
|
t.Fatalf("expected 1 raw metric after retention, got %d", rawCount)
|
|
}
|
|
var total int
|
|
if err := store.db.QueryRow(`SELECT COUNT(*) FROM metrics`).Scan(&total); err != nil {
|
|
t.Fatalf("query total count: %v", err)
|
|
}
|
|
if total != 1 {
|
|
t.Fatalf("expected only newest metric to remain, got %d", total)
|
|
}
|
|
}
|
|
|
|
func TestStoreWriteFlushesBuffer(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := DefaultConfig(dir)
|
|
cfg.DBPath = filepath.Join(dir, "metrics-buffer.db")
|
|
cfg.WriteBufferSize = 1
|
|
cfg.FlushInterval = time.Hour
|
|
|
|
store, err := NewStore(cfg)
|
|
if err != nil {
|
|
t.Fatalf("NewStore returned error: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
ts := time.Now().Add(-time.Second)
|
|
store.Write("vm", "vm-101", "cpu", 1.5, ts)
|
|
|
|
deadline := time.Now().Add(500 * time.Millisecond)
|
|
for time.Now().Before(deadline) {
|
|
points, err := store.Query("vm", "vm-101", "cpu", ts.Add(-time.Second), ts.Add(time.Second))
|
|
if err == nil && len(points) == 1 {
|
|
return
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
t.Fatal("expected buffered metric to flush to database")
|
|
}
|