177 lines
6.0 KiB
Go
177 lines
6.0 KiB
Go
package repository_test
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
managedaccess "billing/internal/access"
|
|
"billing/internal/collection"
|
|
"billing/internal/repository"
|
|
)
|
|
|
|
func TestManagedKeysBootstrapLifecycleAndHistoricalUsage(t *testing.T) {
|
|
store, err := repository.OpenSQLiteUsage(filepath.Join(t.TempDir(), "usage.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
if err := store.Insert(context.Background(), collection.Record{
|
|
APIKey: "000000", Model: "gpt-5", RequestedAt: time.Now(), TotalTokens: 10,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key, err := store.BootstrapManagedKey(context.Background(), "default", "000000")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if key.ID != "key_default" || key.Status != managedaccess.StatusActive || !key.AllModels {
|
|
t.Fatalf("bootstrap key = %+v", key)
|
|
}
|
|
records, err := store.ListRecent(context.Background(), 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(records) != 1 || records[0].ManagedKeyID != key.ID || records[0].KeyAlias != "default" {
|
|
t.Fatalf("historical records = %+v", records)
|
|
}
|
|
key.Status = managedaccess.StatusDisabled
|
|
key.AllModels = false
|
|
key.Models = []string{"gpt-5"}
|
|
if err := store.UpdateManagedKey(context.Background(), key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := store.ArchiveManagedKey(context.Background(), key.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dashboard, err := store.UsageDashboard(context.Background(), time.Now(), 7)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if dashboard.Today.Requests != 1 {
|
|
t.Fatalf("archived key usage should remain in dashboard totals: %+v", dashboard)
|
|
}
|
|
for _, user := range dashboard.Users {
|
|
if user.KeyID == key.ID || user.KeyAlias == key.Name {
|
|
t.Fatalf("archived key should not remain in user dashboard: %+v", dashboard)
|
|
}
|
|
}
|
|
archived, err := store.ManagedKeyByID(context.Background(), key.ID)
|
|
if err != nil || archived.Status != managedaccess.StatusArchived {
|
|
t.Fatalf("archived key = %+v, err=%v", archived, err)
|
|
}
|
|
archived.Status = managedaccess.StatusActive
|
|
if err := store.UpdateManagedKey(context.Background(), archived); err == nil {
|
|
t.Fatal("archived key was restored")
|
|
}
|
|
}
|
|
|
|
func TestManagedKeyStatsUseShanghaiDayBoundary(t *testing.T) {
|
|
store, err := repository.OpenSQLiteUsage(filepath.Join(t.TempDir(), "usage.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
if _, err := store.BootstrapManagedKey(context.Background(), "default", "000000"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cost := int64(25)
|
|
for _, requestedAt := range []time.Time{
|
|
time.Date(2026, 8, 14, 15, 59, 59, 0, time.UTC),
|
|
time.Date(2026, 8, 14, 16, 0, 1, 0, time.UTC),
|
|
} {
|
|
if err := store.Insert(context.Background(), collection.Record{
|
|
ManagedKeyID: "key_default", RequestedAt: requestedAt, TotalTokens: 10, CostMicros: &cost,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
shanghai, _ := time.LoadLocation("Asia/Shanghai")
|
|
today := time.Date(2026, 8, 15, 0, 0, 0, 0, shanghai)
|
|
stats, err := store.KeyStats(context.Background(), "key_default", today)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stats.Total.Requests != 2 || stats.Today.Requests != 1 || stats.Today.TotalTokens != 10 || stats.Total.CostMicros != 50 {
|
|
t.Fatalf("stats = %+v", stats)
|
|
}
|
|
}
|
|
|
|
func TestReconcileUpstreamAccountsListsOnlyCurrentSnapshot(t *testing.T) {
|
|
store, err := repository.OpenSQLiteUsage(filepath.Join(t.TempDir(), "usage.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
ctx := context.Background()
|
|
old := managedaccess.UpstreamAccount{
|
|
CPAAuthID: "codex-user-prolite.json", CPAAuthIndex: "old-index",
|
|
Provider: "codex", DisplayName: "user@example.com", LastSeenAt: time.Now().Add(-time.Hour),
|
|
}
|
|
current := managedaccess.UpstreamAccount{
|
|
CPAAuthID: "codex-user-pro.json", CPAAuthIndex: "current-index",
|
|
Provider: "codex", DisplayName: "user@example.com", LastSeenAt: time.Now(),
|
|
}
|
|
if err := store.ReconcileUpstreamAccounts(ctx, []managedaccess.UpstreamAccount{old, current}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
initial, err := store.ListUpstreamAccounts(ctx)
|
|
if err != nil || len(initial) != 2 {
|
|
t.Fatalf("initial accounts = %+v, err=%v", initial, err)
|
|
}
|
|
var oldID string
|
|
for _, account := range initial {
|
|
if account.CPAAuthID == old.CPAAuthID {
|
|
oldID = account.ID
|
|
}
|
|
}
|
|
if err := store.ReconcileUpstreamAccounts(ctx, []managedaccess.UpstreamAccount{current}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
listed, err := store.ListUpstreamAccounts(ctx)
|
|
if err != nil || len(listed) != 1 || listed[0].CPAAuthID != current.CPAAuthID {
|
|
t.Fatalf("current accounts = %+v, err=%v", listed, err)
|
|
}
|
|
if historical, err := store.UpstreamAccountByID(ctx, oldID); err != nil || historical.CPAAuthID != old.CPAAuthID {
|
|
t.Fatalf("historical account = %+v, err=%v", historical, err)
|
|
}
|
|
}
|
|
|
|
func TestManagedKeyStatsCountsOrphanUsageAndAliasLifecycleOnce(t *testing.T) {
|
|
store, err := repository.OpenSQLiteUsage(filepath.Join(t.TempDir(), "usage.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
if _, err := store.BootstrapManagedKey(context.Background(), "default", "000000"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
startedAt := time.Date(2026, 8, 14, 17, 41, 30, 0, time.UTC)
|
|
cost := int64(25)
|
|
if err := store.Insert(context.Background(), collection.Record{
|
|
ManagedKeyID: "key_default", RequestedAt: startedAt.Add(4 * time.Millisecond),
|
|
APIKey: "000000", Model: "deepseek-v4-flash", InputTokens: 90,
|
|
OutputTokens: 7, TotalTokens: 97, CostMicros: &cost,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := store.UpsertRequest(context.Background(), collection.RequestRecord{
|
|
ManagedKeyID: "key_default", RequestID: "request-alias", RequestedAt: startedAt,
|
|
CompletedAt: startedAt.Add(time.Second), Model: "deepseek-source-a",
|
|
Outcome: "succeeded", StatusCode: 200,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
stats, err := store.KeyStats(context.Background(), "key_default", startedAt.Add(-time.Hour))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stats.Total.Requests != 1 || stats.Total.InputTokens != 90 || stats.Total.OutputTokens != 7 ||
|
|
stats.Total.TotalTokens != 97 || stats.Total.CostMicros != 25 {
|
|
t.Fatalf("stats = %+v", stats)
|
|
}
|
|
}
|