mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 11:30:15 +00:00
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
126 lines
3.3 KiB
Go
126 lines
3.3 KiB
Go
package ai
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai/cost"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
)
|
|
|
|
func TestNewCostPersistenceAdapter(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
persistence := config.NewConfigPersistence(tmp)
|
|
|
|
adapter := NewCostPersistenceAdapter(persistence)
|
|
if adapter == nil {
|
|
t.Fatal("expected non-nil adapter")
|
|
}
|
|
if adapter.config != persistence {
|
|
t.Fatal("expected config to match persistence")
|
|
}
|
|
}
|
|
|
|
func TestCostPersistenceAdapter_SaveAndLoad(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
persistence := config.NewConfigPersistence(tmp)
|
|
adapter := NewCostPersistenceAdapter(persistence)
|
|
|
|
events := []cost.UsageEvent{
|
|
{
|
|
Timestamp: time.Now(),
|
|
Provider: "openai",
|
|
RequestModel: "gpt-4",
|
|
ResponseModel: "gpt-4",
|
|
UseCase: "patrol",
|
|
InputTokens: 100,
|
|
OutputTokens: 50,
|
|
TargetType: "vm",
|
|
TargetID: "node1-100",
|
|
FindingID: "finding-123",
|
|
},
|
|
{
|
|
Timestamp: time.Now().Add(-time.Hour),
|
|
Provider: "anthropic",
|
|
RequestModel: "claude-3-sonnet",
|
|
ResponseModel: "claude-3-sonnet",
|
|
UseCase: "chat",
|
|
InputTokens: 200,
|
|
OutputTokens: 100,
|
|
TargetType: "container",
|
|
TargetID: "node1-101",
|
|
FindingID: "",
|
|
},
|
|
}
|
|
|
|
// Save events
|
|
err := adapter.SaveUsageHistory(events)
|
|
if err != nil {
|
|
t.Fatalf("failed to save usage history: %v", err)
|
|
}
|
|
|
|
// Load events back
|
|
loaded, err := adapter.LoadUsageHistory()
|
|
if err != nil {
|
|
t.Fatalf("failed to load usage history: %v", err)
|
|
}
|
|
|
|
if len(loaded) != len(events) {
|
|
t.Fatalf("expected %d events, got %d", len(events), len(loaded))
|
|
}
|
|
|
|
// Verify first event
|
|
if loaded[0].Provider != events[0].Provider {
|
|
t.Errorf("expected provider %q, got %q", events[0].Provider, loaded[0].Provider)
|
|
}
|
|
if loaded[0].InputTokens != events[0].InputTokens {
|
|
t.Errorf("expected input tokens %d, got %d", events[0].InputTokens, loaded[0].InputTokens)
|
|
}
|
|
if loaded[0].UseCase != events[0].UseCase {
|
|
t.Errorf("expected use case %q, got %q", events[0].UseCase, loaded[0].UseCase)
|
|
}
|
|
}
|
|
|
|
func TestCostPersistenceAdapter_LoadEmpty(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
persistence := config.NewConfigPersistence(tmp)
|
|
adapter := NewCostPersistenceAdapter(persistence)
|
|
|
|
// Load from empty persistence should return empty slice, not error
|
|
loaded, err := adapter.LoadUsageHistory()
|
|
if err != nil {
|
|
t.Fatalf("expected no error for empty load, got: %v", err)
|
|
}
|
|
if len(loaded) != 0 {
|
|
t.Fatalf("expected empty slice, got %d events", len(loaded))
|
|
}
|
|
}
|
|
|
|
func TestCostPersistenceAdapter_SaveEmpty(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
persistence := config.NewConfigPersistence(tmp)
|
|
adapter := NewCostPersistenceAdapter(persistence)
|
|
|
|
// Save empty slice should work
|
|
err := adapter.SaveUsageHistory([]cost.UsageEvent{})
|
|
if err != nil {
|
|
t.Fatalf("failed to save empty usage history: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCostPersistenceAdapter_LoadError(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
persistence := config.NewConfigPersistence(tmp)
|
|
adapter := NewCostPersistenceAdapter(persistence)
|
|
|
|
badPath := filepath.Join(tmp, "ai_usage_history.json")
|
|
if err := os.Mkdir(badPath, 0700); err != nil {
|
|
t.Fatalf("failed to create directory at %s: %v", badPath, err)
|
|
}
|
|
|
|
if _, err := adapter.LoadUsageHistory(); err == nil {
|
|
t.Fatal("expected error when usage history path is a directory")
|
|
}
|
|
}
|