This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user