Pulse/internal/ai/baseline_adapter_test.go
rcourtman 053a40d7df fix: Docker container update detection showing false positives
Fixed an issue where all Docker containers were showing 'click to update'
even when they were up to date. The root cause was comparing the wrong
digest types:

- Previously: Compared ImageID (local config hash) vs registry manifest digest
- Now: Uses RepoDigests from image inspect, which is the actual manifest
  digest that Docker received from the registry when pulling the image

For multi-arch images, the registry returns a manifest list digest, while
Docker stores the platform-specific image config digest locally. These
will never match, causing false positives for all multi-arch images.

Changes:
- Added ImageInspectWithRaw to dockerClient interface
- Added getImageRepoDigest method to extract RepoDigest from image
- Added matchesImageReference helper for Docker Hub naming conventions
- Added tests for matchesImageReference

Fixes #955
2025-12-29 13:49:04 +00:00

77 lines
2.1 KiB
Go

package ai
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/baseline"
)
func TestBaselineStoreAdapter(t *testing.T) {
store := baseline.NewStore(baseline.StoreConfig{
MinSamples: 1,
})
err := store.Learn("node:pve1", "node", "cpu", []baseline.MetricPoint{
{Value: 10, Timestamp: time.Now().Add(-time.Minute)},
{Value: 10, Timestamp: time.Now()},
})
if err != nil {
t.Fatalf("Learn: %v", err)
}
adapter := NewBaselineStoreAdapter(store)
if adapter == nil {
t.Fatalf("expected adapter")
}
mean, stddev, samples, ok := adapter.GetBaseline("node:pve1", "cpu")
if !ok {
t.Fatalf("expected baseline to exist")
}
if mean != 10 || stddev != 0 || samples != 2 {
t.Fatalf("unexpected baseline: mean=%v stddev=%v samples=%d", mean, stddev, samples)
}
severity, z, gotMean, gotStd, ok := adapter.CheckAnomaly("node:pve1", "cpu", 16)
if !ok {
t.Fatalf("expected anomaly check ok")
}
// With new practical thresholds: 6 point difference from stable baseline (stddev=0)
// should be flagged as medium severity (not critical, since we don't have variance data)
if severity != "medium" || gotMean != 10 || gotStd != 0 {
t.Fatalf("unexpected anomaly: severity=%q z=%v mean=%v stddev=%v", severity, z, gotMean, gotStd)
}
}
func TestBaselineStoreAdapter_NilStore(t *testing.T) {
adapter := &BaselineStoreAdapter{}
if _, _, _, ok := adapter.GetBaseline("r", "m"); ok {
t.Fatalf("expected ok=false")
}
if _, _, _, _, ok := adapter.CheckAnomaly("r", "m", 1); ok {
t.Fatalf("expected ok=false")
}
}
func TestBaselineStoreAdapter_NilFactory(t *testing.T) {
if NewBaselineStoreAdapter(nil) != nil {
t.Fatalf("expected nil adapter for nil store")
}
}
func TestBaselineStoreAdapter_MissingBaseline(t *testing.T) {
store := baseline.NewStore(baseline.StoreConfig{MinSamples: 1})
adapter := NewBaselineStoreAdapter(store)
if adapter == nil {
t.Fatalf("expected adapter")
}
if _, _, _, ok := adapter.GetBaseline("missing", "cpu"); ok {
t.Fatalf("expected ok=false for missing baseline")
}
if _, _, _, _, ok := adapter.CheckAnomaly("missing", "cpu", 42); ok {
t.Fatalf("expected ok=false for missing anomaly data")
}
}