| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- //go:build integration
- package repository
- import (
- "context"
- "strings"
- "testing"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/stretchr/testify/require"
- )
- func TestUsageLog_UpstreamModelMismatchFilterAndPartialIndex(t *testing.T) {
- ctx := context.Background()
- tx := testEntTx(t)
- client := tx.Client()
- repo := newUsageLogRepositoryWithSQL(client, tx)
- user := mustCreateUser(t, client, &service.User{Email: "model-audit@test.com"})
- apiKey := mustCreateApiKey(t, client, &service.APIKey{UserID: user.ID, Key: "sk-model-audit", Name: "model-audit"})
- account := mustCreateAccount(t, client, &service.Account{Name: "model-audit-account"})
- now := time.Now().UTC()
- responseModel := "gpt-5.4"
- for _, mismatch := range []bool{true, false} {
- mismatchValue := mismatch
- _, err := repo.Create(ctx, &service.UsageLog{
- UserID: user.ID, APIKeyID: apiKey.ID, AccountID: account.ID,
- Model: "gpt-5.5", InputTokens: 1, OutputTokens: 1,
- UpstreamResponseModel: &responseModel, UpstreamModelMismatch: &mismatchValue,
- CreatedAt: now,
- })
- require.NoError(t, err)
- }
- start := now.Add(-time.Hour)
- end := now.Add(time.Hour)
- trueValue := true
- stats, err := repo.GetStatsWithFilters(ctx, usagestats.UsageLogFilters{
- UserID: user.ID, StartTime: &start, EndTime: &end, UpstreamModelMismatch: &trueValue,
- })
- require.NoError(t, err)
- require.Equal(t, int64(1), stats.TotalRequests)
- require.Equal(t, []usagestats.EndpointStat{{
- Endpoint: "unknown", Requests: 1, TotalTokens: 2,
- }}, stats.Endpoints)
- require.Equal(t, []usagestats.EndpointStat{{
- Endpoint: "unknown", Requests: 1, TotalTokens: 2,
- }}, stats.UpstreamEndpoints)
- require.Equal(t, []usagestats.EndpointStat{{
- Endpoint: "unknown -> unknown", Requests: 1, TotalTokens: 2,
- }}, stats.EndpointPaths)
- trend, err := repo.GetUsageTrendWithUsageFilters(ctx, start, end, "hour", usagestats.UsageLogFilters{
- UserID: user.ID, UpstreamModelMismatch: &trueValue,
- })
- require.NoError(t, err)
- require.Len(t, trend, 1)
- require.Equal(t, int64(1), trend[0].Requests)
- _, err = tx.ExecContext(ctx, "SET LOCAL enable_seqscan = off")
- require.NoError(t, err)
- assertPlanUsesIndex := func(query, indexName string, args ...any) {
- rows, queryErr := tx.QueryContext(ctx, query, args...)
- require.NoError(t, queryErr)
- var planLines []string
- for rows.Next() {
- var line string
- require.NoError(t, rows.Scan(&line))
- planLines = append(planLines, line)
- }
- require.NoError(t, rows.Err())
- require.NoError(t, rows.Close())
- require.Contains(t, strings.Join(planLines, "\n"), indexName)
- }
- assertPlanUsesIndex(`
- EXPLAIN (COSTS OFF)
- SELECT id
- FROM usage_logs
- WHERE upstream_model_mismatch IS TRUE
- ORDER BY created_at DESC, id DESC
- LIMIT 100
- `, usageLogsUpstreamModelMismatchIndex)
- assertPlanUsesIndex(`
- EXPLAIN (COSTS OFF)
- SELECT id
- FROM usage_logs
- WHERE COALESCE(NULLIF(TRIM(requested_model), ''), model) = $1
- AND created_at >= $2 AND created_at < $3
- ORDER BY created_at DESC, id DESC
- LIMIT 100
- `, usageLogsEffectiveRequestedModelIndex, "gpt-5.5", start, end)
- assertPlanUsesIndex(`
- EXPLAIN (COSTS OFF)
- SELECT id
- FROM usage_logs
- WHERE COALESCE(NULLIF(TRIM(upstream_model), ''), model) = $1
- AND created_at >= $2 AND created_at < $3
- ORDER BY created_at DESC, id DESC
- LIMIT 100
- `, usageLogsEffectiveUpstreamModelIndex, "gpt-5.5", start, end)
- }
- func TestUsageLog_GetStatsWithFilters_AggregatesAndEndpoints(t *testing.T) {
- ctx := context.Background()
- tx := testEntTx(t)
- client := tx.Client()
- repo := newUsageLogRepositoryWithSQL(client, tx)
- user := mustCreateUser(t, client, &service.User{Email: "stats@test.com"})
- apiKey := mustCreateApiKey(t, client, &service.APIKey{UserID: user.ID, Key: "sk-stats-1", Name: "k"})
- account := mustCreateAccount(t, client, &service.Account{Name: "acc-stats"})
- now := time.Now().UTC()
- inboundEndpoint := "/v1/messages"
- upstreamEndpoint := "/v1/responses"
- for i := 0; i < 3; i++ {
- _, err := repo.Create(ctx, &service.UsageLog{
- UserID: user.ID, APIKeyID: apiKey.ID, AccountID: account.ID,
- Model: "claude-3", InputTokens: 2, OutputTokens: 3,
- CacheCreationTokens: 4, CacheReadTokens: 5,
- TotalCost: 0.5, ActualCost: 0.4, CreatedAt: now,
- InboundEndpoint: &inboundEndpoint, UpstreamEndpoint: &upstreamEndpoint,
- })
- require.NoError(t, err)
- }
- start := now.Add(-1 * time.Hour)
- end := now.Add(1 * time.Hour)
- // 按本测试创建的 user 维度过滤:集成库为共享实例,其它用 testEntClient 的兄弟测试会留下
- // 已提交的 usage_log 行(含零 token 的失败请求),不限定 user 会把它们计入 TotalRequests。
- stats, err := repo.GetStatsWithFilters(ctx, usagestats.UsageLogFilters{UserID: user.ID, StartTime: &start, EndTime: &end})
- require.NoError(t, err)
- require.Equal(t, int64(3), stats.TotalRequests)
- require.Equal(t, int64(6), stats.TotalInputTokens)
- require.Equal(t, int64(9), stats.TotalOutputTokens)
- require.Equal(t, int64(27), stats.TotalCacheTokens)
- require.Equal(t, int64(12), stats.TotalCacheCreationTokens)
- require.Equal(t, int64(15), stats.TotalCacheReadTokens)
- require.InDelta(t, 1.2, stats.TotalActualCost, 1e-9)
- require.NotEmpty(t, stats.Endpoints)
- require.NotEmpty(t, stats.UpstreamEndpoints)
- require.NotEmpty(t, stats.EndpointPaths)
- }
|