Files
sub2api/backend/internal/service/api_key_auth_cache_profit_test.go
T
IanShaw027 7eb1310701 fix(grok): close free-by-default billing and related review blockers
H1/H2: bill search and voice with code defaults when group prices are
nil (explicit 0 remains free); bump API key auth snapshot to v19 and
refresh incomplete media/search/audio projections.

M1–M6: free-quota soft gate fails open on cache miss with background
refresh and 60s default TTL; correct password_auth config docs; default
cross-client model map to true (→ grok-4.5); audit /tts and /web_search;
exclude composite from migration 220 video-price clears; never let a
search surcharge mask token pricing failures.
2026-08-08 14:39:22 +08:00

94 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
// 投影漏列回归(service 半程):认证快照 build → L2 JSON 序列化
// → 反序列化 → 还原 apiKey.Group → 请求 ctx → 利润门解析,全链路保真。
// repository 半程(真实 GetByKeyForAuth 投影)见
// internal/repository/api_key_repo_profit_projection_integration_test.go。
import (
"context"
"encoding/json"
"testing"
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/stretchr/testify/require"
)
func profitAuthTestAPIKey() *APIKey {
groupID := int64(50)
return &APIKey{
ID: 82,
UserID: 40,
GroupID: &groupID,
Name: "profit-auth-roundtrip",
Status: StatusActive,
User: &User{
ID: 40,
Email: "profit@test.local",
Status: StatusActive,
Concurrency: 5,
},
Group: &Group{
ID: groupID,
Name: "VIP-roundtrip",
Platform: PlatformOpenAI,
Status: StatusActive,
Hydrated: true,
RateMultiplier: 0.06,
SubscriptionType: SubscriptionTypeStandard,
PeakRateEnabled: false,
ProfitControlEnabled: true,
ProfitMinMargin: 0.2,
ProfitSafetyBuffer: 0.05,
},
}
}
// 快照构建 → L2 JSON 往返 → 还原 → 装门:利润字段必须全程保真,阈值与
// 计费同源(0.06 × (1−0.25) = 0.045)。
func TestAPIKeyAuthSnapshotProfitControlRoundtrip(t *testing.T) {
svc := &APIKeyService{}
apiKey := profitAuthTestAPIKey()
snapshot := svc.snapshotFromAPIKey(context.Background(), apiKey)
require.NotNil(t, snapshot)
require.Equal(t, apiKeyAuthSnapshotVersion, snapshot.Version)
require.Equal(t, 19, snapshot.Version, "v19 起认证快照携带 search/audio/video_model_prices 计费字段")
// 模拟 L2 缓存的完整 JSON 往返(与 apiKeyCache.SetAuthCache/GetAuthCache 同构)。
payload, err := json.Marshal(&APIKeyAuthCacheEntry{Snapshot: snapshot})
require.NoError(t, err)
var restored APIKeyAuthCacheEntry
require.NoError(t, json.Unmarshal(payload, &restored))
materialized, used, err := svc.applyAuthCacheEntry(apiKey.Key, &restored)
require.NoError(t, err)
require.True(t, used)
require.NotNil(t, materialized.Group)
require.True(t, materialized.Group.Hydrated)
require.True(t, materialized.Group.ProfitControlEnabled)
require.InDelta(t, 0.2, materialized.Group.ProfitMinMargin, 1e-12)
require.InDelta(t, 0.05, materialized.Group.ProfitSafetyBuffer, 1e-12)
require.InDelta(t, 0.06, materialized.Group.RateMultiplier, 1e-12)
// 中间件语义:materialized.Group 进请求 ctx → 门必须按快照配置装上。
ctx := context.WithValue(context.Background(), ctxkey.Group, materialized.Group)
gwSvc := &OpenAIGatewayService{}
gate := gwSvc.resolveOpenAIProfitControlGate(ctx, materialized.GroupID)
require.NotNil(t, gate, "还原后的认证分组必须能装门(投影漏列时本断言最先失败)")
require.InDelta(t, 0.06*(1-0.25), gate.threshold, 1e-12)
}
// 旧版本快照(v16 及更早,无利润字段保真保证)必须被淘汰回源,不得复用。
func TestAPIKeyAuthSnapshotOldVersionEvicted(t *testing.T) {
svc := &APIKeyService{}
snapshot := svc.snapshotFromAPIKey(context.Background(), profitAuthTestAPIKey())
require.NotNil(t, snapshot)
snapshot.Version = 16
materialized, used, err := svc.applyAuthCacheEntry("sk-old", &APIKeyAuthCacheEntry{Snapshot: snapshot})
require.NoError(t, err)
require.False(t, used, "版本不匹配的缓存条目必须淘汰并回源重建")
require.Nil(t, materialized)
}