package forecast import ( "context" "encoding/json" "fmt" "os" "testing" "time" "github.com/itworx/pulse/internal/agentstore" "github.com/itworx/pulse/internal/database" "github.com/itworx/pulse/internal/pool" "github.com/itworx/pulse/internal/share" ) func TestStorageForecastPostgreSQLHistory(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() db, err := database.NewPool(ctx, database.Config{URL: dsn}) if err != nil { t.Fatal(err) } defer db.Close() if err := database.Migrate(ctx, db); err != nil { t.Fatal(err) } run := fmt.Sprintf("forecast-%x", time.Now().UnixNano()) now := time.Now().UTC().Truncate(time.Second) for _, point := range []struct { at time.Time used int64 }{{now.Add(-14 * 24 * time.Hour), 200}, {now.Add(-7 * 24 * time.Hour), 300}, {now, 400}} { _, err := db.Exec(ctx, `INSERT INTO capacity_samples (entity_kind,entity_id,entity_name,source_id,sampled_at,observed_at,used_bytes,capacity_bytes) VALUES ('share',$1,'Media',$2,$3,$3,$4,0)`, run, run, point.at, point.used) if err != nil { t.Fatal(err) } } store := agentstore.PostgresStore{Pool: db, Clock: func() time.Time { return now }} sharePayload, err := json.Marshal(share.RawSnapshot{Source: share.Source{ID: "forecast-test", Type: "test"}, ObservedAt: now, ReceivedAt: now, Shares: []share.RawShare{{ID: run, Name: "Media forecast", UsedBytes: 400, SizeObservedAt: now, SizeState: share.SizeAvailable, Placements: []share.RawPlacement{{PoolID: "cache"}}}}}) if err != nil { t.Fatal(err) } poolPayload, err := json.Marshal(pool.RawSnapshot{Source: pool.Source{ID: "forecast-test", Type: "test"}, ObservedAt: now, ReceivedAt: now, Pools: []pool.RawPool{{ID: "cache", Name: "Cache", State: pool.StateHealthy, UsableBytes: 1000, UsedBytes: 400, Capabilities: pool.Capabilities{Capacity: pool.CapabilityAvailable}}}}) if err != nil { t.Fatal(err) } if err := store.Put(ctx, agentstore.Snapshot{AgentID: run, Capability: agentstore.CapabilityShares, ObservedAt: now, Payload: sharePayload}); err != nil { t.Fatal(err) } if err := store.Put(ctx, agentstore.Snapshot{AgentID: run, Capability: agentstore.CapabilityPools, ObservedAt: now, Payload: poolPayload}); err != nil { t.Fatal(err) } provider := StorageProvider{ Shares: staticShares{share.Snapshot{Source: share.Source{Freshness: share.Fresh}, Shares: []share.Share{{ID: run, Name: "Media", UsedBytes: 400, SizeObservedAt: now, SizeState: share.SizeAvailable, Placements: []share.Placement{{PoolID: "cache"}}}}}}, Pools: staticPools{pool.Snapshot{Pools: []pool.Pool{{ID: "cache", UsableBytes: 1000}}}}, History: PostgresHistory{Pool: db}, Policy: Policy{Enabled: true}, Now: func() time.Time { return now }, } snapshot, err := provider.Snapshot(ctx) if err != nil { t.Fatal(err) } if snapshot.QualifiedCount != 1 || len(snapshot.Items) != 1 || snapshot.Items[0].DataPoints != 3 || snapshot.Items[0].ProjectedAt == nil { t.Fatalf("persisted history did not produce a qualified forecast: %+v", snapshot) } }