Pulse/internal/ai/cost/store_extended_test.go
rcourtman cb960a04e3 Add comprehensive AI test coverage
- Add integration tests for Ollama provider (17 tests against real API)
- Add unit tests for baseline, correlation, patterns, memory, knowledge, cost packages
- Add context formatter and builder tests
- Add factory tests for provider initialization
- Add Makefile targets: test-integration, test-all
- Clean up test theatre (removed struct field tests)

Integration tests require Ollama at OLLAMA_URL (default: 192.168.0.124:11434)
Run with: make test-integration
2025-12-16 12:33:06 +00:00

267 lines
6.2 KiB
Go

package cost
import (
"testing"
"time"
)
// Additional tests to improve coverage
func TestNewStore_DefaultMaxDays(t *testing.T) {
// Test with default
store := NewStore(0)
if store == nil {
t.Fatal("Expected non-nil store")
}
// Should use default max days (365)
if store.maxDays != DefaultMaxDays {
t.Errorf("Expected default max days %d, got %d", DefaultMaxDays, store.maxDays)
}
}
func TestNewStore_CustomMaxDays(t *testing.T) {
store := NewStore(30)
if store.maxDays != 30 {
t.Errorf("Expected 30 max days, got %d", store.maxDays)
}
}
func TestListEvents_Empty(t *testing.T) {
store := NewStore(30)
events := store.ListEvents(30)
if len(events) != 0 {
t.Errorf("Expected empty events, got %d", len(events))
}
}
func TestListEvents_WithData(t *testing.T) {
store := NewStore(30)
now := time.Now()
store.Record(UsageEvent{
Timestamp: now,
Provider: "openai",
RequestModel: "openai:gpt-4o",
InputTokens: 100,
OutputTokens: 50,
UseCase: "chat",
})
events := store.ListEvents(30)
if len(events) != 1 {
t.Errorf("Expected 1 event, got %d", len(events))
}
}
func TestListEvents_FiltersByDays(t *testing.T) {
store := NewStore(30)
now := time.Now()
// Event from 10 days ago
store.Record(UsageEvent{
Timestamp: now.Add(-10 * 24 * time.Hour),
Provider: "openai",
RequestModel: "openai:gpt-4o",
InputTokens: 100,
OutputTokens: 50,
})
// Request only last 5 days - should not include the event
events := store.ListEvents(5)
if len(events) != 0 {
t.Errorf("Expected 0 events for 5 day window, got %d", len(events))
}
// Request last 15 days - should include
events = store.ListEvents(15)
if len(events) != 1 {
t.Errorf("Expected 1 event for 15 day window, got %d", len(events))
}
}
func TestGetSummary_Empty(t *testing.T) {
store := NewStore(30)
summary := store.GetSummary(30)
if len(summary.ProviderModels) != 0 {
t.Errorf("Expected empty provider models, got %d", len(summary.ProviderModels))
}
if summary.Totals.TotalTokens != 0 {
t.Errorf("Expected 0 total tokens, got %d", summary.Totals.TotalTokens)
}
}
func TestFlush_NoPersistence(t *testing.T) {
store := NewStore(30)
// Record something
store.Record(UsageEvent{
Timestamp: time.Now(),
Provider: "openai",
RequestModel: "openai:gpt-4o",
InputTokens: 100,
OutputTokens: 50,
})
// Flush without persistence should not error
err := store.Flush()
if err != nil {
t.Errorf("Flush without persistence should not error: %v", err)
}
}
func TestEstimateUSD_UnknownProvider(t *testing.T) {
usd, ok, _ := EstimateUSD("unknown_provider", "unknown_model", 1000, 1000)
if ok {
t.Error("Should not find pricing for unknown provider")
}
if usd != 0 {
t.Errorf("Expected 0 USD for unknown provider, got %f", usd)
}
}
func TestEstimateUSD_OllamaFree(t *testing.T) {
usd, ok, _ := EstimateUSD("ollama", "llama2", 1_000_000, 1_000_000)
if !ok {
t.Error("Ollama pricing should be known (free)")
}
if usd != 0 {
t.Errorf("Ollama should be free, got $%f", usd)
}
}
func TestEstimateUSD_AnthropicModels(t *testing.T) {
tests := []struct {
model string
known bool
}{
{"claude-sonnet-4-20250514", true}, // matches claude-sonnet*
{"claude-opus-4-20250514", true}, // matches claude-opus*
{"claude-haiku-something", true}, // matches claude-haiku*
{"unknown-claude", false}, // no match
}
for _, tt := range tests {
_, ok, _ := EstimateUSD("anthropic", tt.model, 1000, 1000)
if ok != tt.known {
t.Errorf("EstimateUSD(anthropic, %s): expected known=%v, got %v", tt.model, tt.known, ok)
}
}
}
func TestEstimateUSD_OpenAIModels(t *testing.T) {
tests := []struct {
model string
known bool
}{
{"gpt-4o", true}, // matches gpt-4o*
{"gpt-4o-mini", true}, // matches gpt-4o-mini*
{"gpt-4o-mini-2024", true}, // matches gpt-4o-mini*
{"unknown-gpt", false}, // no match
}
for _, tt := range tests {
_, ok, _ := EstimateUSD("openai", tt.model, 1000, 1000)
if ok != tt.known {
t.Errorf("EstimateUSD(openai, %s): expected known=%v, got %v", tt.model, tt.known, ok)
}
}
}
func TestEstimateUSD_DeepSeekModels(t *testing.T) {
tests := []struct {
model string
known bool
}{
{"deepseek-chat", true},
{"deepseek-coder", true},
{"deepseek-reasoner", true},
{"unknown-deepseek", false},
}
for _, tt := range tests {
_, ok, _ := EstimateUSD("deepseek", tt.model, 1000, 1000)
if ok != tt.known {
t.Errorf("EstimateUSD(deepseek, %s): expected known=%v, got %v", tt.model, tt.known, ok)
}
}
}
func TestSummary_Totals(t *testing.T) {
store := NewStore(30)
now := time.Now()
store.Record(UsageEvent{
Timestamp: now,
Provider: "openai",
RequestModel: "openai:gpt-4o",
InputTokens: 100,
OutputTokens: 50,
})
store.Record(UsageEvent{
Timestamp: now,
Provider: "anthropic",
RequestModel: "anthropic:claude-sonnet-4-20250514",
InputTokens: 200,
OutputTokens: 100,
})
summary := store.GetSummary(30)
// Check totals
if summary.Totals.InputTokens != 300 {
t.Errorf("Expected 300 input tokens, got %d", summary.Totals.InputTokens)
}
if summary.Totals.OutputTokens != 150 {
t.Errorf("Expected 150 output tokens, got %d", summary.Totals.OutputTokens)
}
if summary.Totals.TotalTokens != 450 {
t.Errorf("Expected 450 total tokens, got %d", summary.Totals.TotalTokens)
}
}
func TestSummary_RetentionInfo(t *testing.T) {
store := NewStore(30)
summary := store.GetSummary(7)
if summary.RetentionDays != 30 {
t.Errorf("Expected retention days 30, got %d", summary.RetentionDays)
}
if summary.EffectiveDays != 7 {
t.Errorf("Expected effective days 7, got %d", summary.EffectiveDays)
}
}
func TestClear_MultipleTimes(t *testing.T) {
store := NewStore(30)
// Record, clear, record, clear
store.Record(UsageEvent{
Timestamp: time.Now(),
Provider: "openai",
RequestModel: "openai:gpt-4o",
InputTokens: 100,
OutputTokens: 50,
})
store.Clear()
store.Record(UsageEvent{
Timestamp: time.Now(),
Provider: "anthropic",
RequestModel: "anthropic:claude-sonnet",
InputTokens: 100,
OutputTokens: 50,
})
store.Clear()
summary := store.GetSummary(30)
if summary.Totals.TotalTokens != 0 {
t.Errorf("Expected 0 tokens after clear, got %d", summary.Totals.TotalTokens)
}
}