Public source validation / validate (push) Failing after 3m8s
111 lines
4.1 KiB
Go
111 lines
4.1 KiB
Go
package agentstore
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/database"
|
|
)
|
|
|
|
// TestPostgreSQLSnapshotRoundTrip exercises the real table created by migration 0016. It
|
|
// skips unless PULSE_TEST_DATABASE_URL points at a disposable database.
|
|
func TestPostgreSQLSnapshotRoundTrip(t *testing.T) {
|
|
dsn := os.Getenv("PULSE_TEST_DATABASE_URL")
|
|
if dsn == "" {
|
|
t.Skip("PULSE_TEST_DATABASE_URL is not set")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
pool, err := database.NewPool(ctx, database.Config{URL: dsn})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
if err := database.Migrate(ctx, pool); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `DELETE FROM agent_snapshots WHERE agent_id = 'integration-agent'`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `DELETE FROM capacity_samples WHERE source_id = 'integration-agent'`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
cleanup, cancelCleanup := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancelCleanup()
|
|
_, _ = pool.Exec(cleanup, `DELETE FROM agent_snapshots WHERE agent_id = 'integration-agent'`)
|
|
_, _ = pool.Exec(cleanup, `DELETE FROM capacity_samples WHERE source_id = 'integration-agent'`)
|
|
})
|
|
|
|
now := time.Now().UTC().Truncate(time.Microsecond)
|
|
store := PostgresStore{Pool: pool, Clock: func() time.Time { return now }}
|
|
readOwn := func() Snapshot {
|
|
t.Helper()
|
|
stored := Snapshot{AgentID: "integration-agent", Capability: CapabilityPools}
|
|
var payload []byte
|
|
if err := pool.QueryRow(ctx, `SELECT observed_at,received_at,payload FROM agent_snapshots WHERE agent_id=$1 AND capability=$2`, stored.AgentID, stored.Capability).Scan(&stored.ObservedAt, &stored.ReceivedAt, &payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stored.Payload = payload
|
|
return stored
|
|
}
|
|
if _, err := store.Latest(ctx, CapabilityPools); err != nil && !errors.Is(err, ErrNoSnapshot) {
|
|
t.Fatalf("unexpected error reading an empty capability: %v", err)
|
|
}
|
|
|
|
first := Snapshot{AgentID: "integration-agent", Capability: CapabilityPools, ObservedAt: now.Add(-30 * time.Second), ReceivedAt: now.Add(72 * time.Hour), Payload: json.RawMessage(`{"pools":[{"id":"cache","name":"Cache","usedBytes":100,"usableBytes":1000}],"generation":1}`)}
|
|
if err := store.Put(ctx, first); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stored := readOwn()
|
|
if !stored.ReceivedAt.Equal(now) {
|
|
t.Fatalf("received at = %s, want the store clock %s", stored.ReceivedAt, now)
|
|
}
|
|
if !stored.ObservedAt.Equal(first.ObservedAt) {
|
|
t.Fatalf("observed at = %s, want %s", stored.ObservedAt, first.ObservedAt)
|
|
}
|
|
|
|
newer := first
|
|
newer.ObservedAt = now.Add(-5 * time.Second)
|
|
newer.Payload = json.RawMessage(`{"pools":[{"id":"cache","name":"Cache","usedBytes":200,"usableBytes":1000}],"generation":2}`)
|
|
if err := store.Put(ctx, newer); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
older := first
|
|
older.ObservedAt = now.Add(-120 * time.Second)
|
|
older.Payload = json.RawMessage(`{"pools":[{"id":"cache","name":"Cache","usedBytes":50,"usableBytes":1000}],"generation":3}`)
|
|
if err := store.Put(ctx, older); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stored = readOwn()
|
|
var decoded struct {
|
|
Generation int `json:"generation"`
|
|
}
|
|
if err := json.Unmarshal(stored.Payload, &decoded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if decoded.Generation != 2 {
|
|
t.Fatalf("a delayed retry must not resurrect superseded telemetry, got generation %d", decoded.Generation)
|
|
}
|
|
|
|
var rows int
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM agent_snapshots WHERE agent_id = 'integration-agent'`).Scan(&rows); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rows != 1 {
|
|
t.Fatalf("agent snapshot rows = %d, want exactly one per (agent, capability)", rows)
|
|
}
|
|
var sampleRows int
|
|
var usedBytes int64
|
|
if err := pool.QueryRow(ctx, `SELECT count(*),max(used_bytes) FROM capacity_samples WHERE source_id='integration-agent' AND entity_kind='pool' AND entity_id='cache'`).Scan(&sampleRows, &usedBytes); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sampleRows != 1 || usedBytes != 200 {
|
|
t.Fatalf("six-hour bucket was not idempotent or accepted an older retry: rows=%d used=%d", sampleRows, usedBytes)
|
|
}
|
|
}
|