Public source validation / validate (push) Failing after 3m8s
94 lines
4.8 KiB
Go
94 lines
4.8 KiB
Go
package forecast
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/pool"
|
|
"github.com/itworx/pulse/internal/share"
|
|
)
|
|
|
|
type staticShares struct{ snapshot share.Snapshot }
|
|
|
|
func (s staticShares) Snapshot(context.Context) (share.Snapshot, error) { return s.snapshot, nil }
|
|
|
|
type staticPools struct{ snapshot pool.Snapshot }
|
|
|
|
func (s staticPools) Snapshot(context.Context) (pool.Snapshot, error) { return s.snapshot, nil }
|
|
|
|
type fullHistory struct{ points []Point }
|
|
|
|
func (h fullHistory) Points(_ context.Context, _, _ string, _ time.Time, limit int) ([]Point, error) {
|
|
if limit != len(h.points) {
|
|
return nil, errors.New("history query did not reserve the current-point slot")
|
|
}
|
|
return h.points, nil
|
|
}
|
|
|
|
func TestStorageProviderProjectsQualifiedShareHistory(t *testing.T) {
|
|
now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
|
|
shareSnapshot := share.Snapshot{Source: share.Source{Freshness: share.Fresh}, Shares: []share.Share{{
|
|
ID: "media", Name: "Media", UsedBytes: 400, SizeObservedAt: now, SizeState: share.SizeAvailable,
|
|
Placements: []share.Placement{{PoolID: "cache"}}, GrowthHistory: []share.GrowthPoint{
|
|
{ObservedAt: now.Add(-14 * 24 * time.Hour), UsedBytes: 200},
|
|
{ObservedAt: now.Add(-7 * 24 * time.Hour), UsedBytes: 300},
|
|
{ObservedAt: now, UsedBytes: 400},
|
|
},
|
|
}}}
|
|
poolSnapshot := pool.Snapshot{Pools: []pool.Pool{{ID: " CACHE ", UsableBytes: 1000}}}
|
|
provider := StorageProvider{Shares: staticShares{shareSnapshot}, Pools: staticPools{poolSnapshot}, Policy: Policy{Enabled: true}, Now: func() time.Time { return now }}
|
|
|
|
snapshot, err := provider.Snapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if snapshot.QualifiedCount != 1 || len(snapshot.Items) != 1 || snapshot.Items[0].EntityID != "media" || snapshot.Items[0].ProjectedAt == nil || snapshot.Items[0].CapacityBytes != 1000 {
|
|
t.Fatalf("qualified storage forecast missing: %+v", snapshot)
|
|
}
|
|
}
|
|
|
|
func TestStorageProviderDoesNotCountInsufficientOrStaleHistory(t *testing.T) {
|
|
now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
|
|
base := share.Share{ID: "media", Name: "Media", UsedBytes: 400, SizeObservedAt: now, SizeState: share.SizeAvailable, Placements: []share.Placement{{PoolID: "cache"}}, GrowthHistory: []share.GrowthPoint{{ObservedAt: now, UsedBytes: 400}}}
|
|
provider := StorageProvider{Shares: staticShares{share.Snapshot{Source: share.Source{Freshness: share.Fresh}, Shares: []share.Share{base}}}, Pools: staticPools{pool.Snapshot{Pools: []pool.Pool{{ID: "cache", UsableBytes: 1000}}}}, Policy: Policy{Enabled: true}, Now: func() time.Time { return now }}
|
|
|
|
insufficient, err := provider.Snapshot(context.Background())
|
|
if err != nil || insufficient.QualifiedCount != 0 || insufficient.Items[0].Reason != "insufficient_points" {
|
|
t.Fatalf("insufficient history counted as forecast: %+v, %v", insufficient, err)
|
|
}
|
|
base.SizeState = share.SizeStale
|
|
provider.Shares = staticShares{share.Snapshot{Source: share.Source{Freshness: share.Stale}, Shares: []share.Share{base}}}
|
|
stale, err := provider.Snapshot(context.Background())
|
|
if err != nil || stale.QualifiedCount != 0 || stale.Items[0].Reason != "history_stale" || stale.Items[0].ProjectedAt != nil {
|
|
t.Fatalf("stale history was not explicit: %+v, %v", stale, err)
|
|
}
|
|
}
|
|
|
|
func TestStorageProviderEmptySourceNeverCreatesZeroByteEntity(t *testing.T) {
|
|
now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
|
|
provider := StorageProvider{Shares: staticShares{share.UnknownSnapshot(now, "shares", "unraid", "source_unavailable")}, Pools: staticPools{pool.Snapshot{}}, Policy: Policy{Enabled: true}, Now: func() time.Time { return now }}
|
|
snapshot, err := provider.Snapshot(context.Background())
|
|
if err != nil || len(snapshot.Items) != 0 || snapshot.QualifiedCount != 0 || snapshot.Reason != "source_unavailable" {
|
|
t.Fatalf("empty source created a forecast entity: %+v, %v", snapshot, err)
|
|
}
|
|
}
|
|
|
|
func TestStorageProviderKeepsMatureHistoryInsidePointBound(t *testing.T) {
|
|
now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
|
|
history := make([]Point, 0, 4)
|
|
for day := 4; day > 0; day-- {
|
|
history = append(history, Point{ObservedAt: now.Add(-time.Duration(day) * 24 * time.Hour), UsedBytes: uint64((5 - day) * 100)})
|
|
}
|
|
provider := StorageProvider{
|
|
Shares: staticShares{share.Snapshot{Source: share.Source{Freshness: share.Fresh}, Shares: []share.Share{{ID: "media", Name: "Media", UsedBytes: 500, SizeObservedAt: now, SizeState: share.SizeAvailable, Placements: []share.Placement{{PoolID: "cache"}}}}}},
|
|
Pools: staticPools{pool.Snapshot{Pools: []pool.Pool{{ID: "cache", UsableBytes: 1000}}}},
|
|
History: fullHistory{points: history}, Policy: Policy{Enabled: true, MaxPoints: 5}, Now: func() time.Time { return now },
|
|
}
|
|
snapshot, err := provider.Snapshot(context.Background())
|
|
if err != nil || snapshot.QualifiedCount != 1 || snapshot.Items[0].DataPoints != 5 {
|
|
t.Fatalf("mature history exceeded its bound: %+v, %v", snapshot, err)
|
|
}
|
|
}
|