Pulse/internal/ai/cost_persistence_test.go
2026-03-18 16:06:30 +00:00

142 lines
3.8 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")
}
}
func TestCostPersistenceAdapter_NilConfigFailsClosed(t *testing.T) {
adapter := NewCostPersistenceAdapter(nil)
if err := adapter.SaveUsageHistory([]cost.UsageEvent{}); err == nil {
t.Fatal("expected save to fail when config persistence is nil")
}
loaded, err := adapter.LoadUsageHistory()
if err == nil {
t.Fatal("expected load to fail when config persistence is nil")
}
if loaded == nil {
t.Fatal("expected load to return normalized empty slice on failure")
}
}