Files
Duc Nguyen 747b58d24c fix(usage): repair cost analytics and display precision (#1330)
fix(usage): repair cost analytics and display precision

- Automatic OpenRouter pricing sync with cost backfill for traces/snapshots/events
- Live usage data merging for current-hour dashboard accuracy
- 2-decimal API cost formatting across usage/overview pages
- Comprehensive test coverage (PG, SQLite, HTTP, UI)

Merged by github-maintain automation.
2026-07-03 06:24:21 +07:00

101 lines
2.9 KiB
Go

package pricing
import (
"context"
"io"
"net/http"
"strings"
"testing"
)
func TestFetchOpenRouterCatalogMapsPricingFields(t *testing.T) {
client := &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
if req.Method != http.MethodGet {
t.Fatalf("method = %s, want GET", req.Method)
}
if req.URL.String() != OpenRouterModelsURL {
t.Fatalf("url = %s, want %s", req.URL.String(), OpenRouterModelsURL)
}
body := `{
"data": [
{
"id": "openai/gpt-4o-mini",
"name": "GPT-4o mini",
"pricing": {
"prompt": "0.00000015",
"completion": "0.0000006",
"input_cache_read": "0.000000075",
"input_cache_write": "0.0000003",
"internal_reasoning": "0.0000002",
"request": "0",
"image": "0.001",
"web_search": "0.01"
}
},
{"name": "missing id"}
]
}`
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(body)),
Header: make(http.Header),
}, nil
})}
entries, err := FetchOpenRouterCatalog(context.Background(), client)
if err != nil {
t.Fatalf("FetchOpenRouterCatalog: %v", err)
}
if len(entries) != 1 {
t.Fatalf("entries = %d, want 1", len(entries))
}
got := entries[0]
if got.ModelID != "openai/gpt-4o-mini" || got.CanonicalModelID != got.ModelID {
t.Fatalf("model ids = %q/%q, want openai/gpt-4o-mini", got.ModelID, got.CanonicalModelID)
}
assertStringPtr(t, "input", got.Pricing.Input, "0.00000015")
assertStringPtr(t, "output", got.Pricing.Output, "0.0000006")
assertStringPtr(t, "cache_read", got.Pricing.CacheRead, "0.000000075")
assertStringPtr(t, "cache_write", got.Pricing.CacheWrite, "0.0000003")
assertStringPtr(t, "reasoning", got.Pricing.Reasoning, "0.0000002")
assertStringPtr(t, "request", got.Pricing.Request, "0")
assertStringPtr(t, "image", got.Pricing.Image, "0.001")
assertStringPtr(t, "web_search", got.Pricing.WebSearch, "0.01")
if len(got.RawPricing) == 0 || len(got.RawModel) == 0 {
t.Fatal("expected raw pricing and raw model payloads")
}
if got.SyncedAt.IsZero() {
t.Fatal("SyncedAt is zero")
}
}
func TestFetchOpenRouterCatalogReturnsStatusError(t *testing.T) {
client := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusBadGateway,
Body: io.NopCloser(strings.NewReader(`bad gateway`)),
Header: make(http.Header),
}, nil
})}
if _, err := FetchOpenRouterCatalog(context.Background(), client); err == nil {
t.Fatal("expected non-2xx status error")
}
}
func assertStringPtr(t *testing.T, name string, got *string, want string) {
t.Helper()
if got == nil {
t.Fatalf("%s = nil, want %q", name, want)
}
if *got != want {
t.Fatalf("%s = %q, want %q", name, *got, want)
}
}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}