mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-03-30 02:27:11 +00:00
151 lines
4.7 KiB
Go
151 lines
4.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type accountUsageCodexProbeRepo struct {
|
|
stubOpenAIAccountRepo
|
|
updateExtraCh chan map[string]any
|
|
rateLimitCh chan time.Time
|
|
}
|
|
|
|
func (r *accountUsageCodexProbeRepo) UpdateExtra(_ context.Context, _ int64, updates map[string]any) error {
|
|
if r.updateExtraCh != nil {
|
|
copied := make(map[string]any, len(updates))
|
|
for k, v := range updates {
|
|
copied[k] = v
|
|
}
|
|
r.updateExtraCh <- copied
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *accountUsageCodexProbeRepo) SetRateLimited(_ context.Context, _ int64, resetAt time.Time) error {
|
|
if r.rateLimitCh != nil {
|
|
r.rateLimitCh <- resetAt
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestShouldRefreshOpenAICodexSnapshot(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rateLimitedUntil := time.Now().Add(5 * time.Minute)
|
|
now := time.Now()
|
|
usage := &UsageInfo{
|
|
FiveHour: &UsageProgress{Utilization: 0},
|
|
SevenDay: &UsageProgress{Utilization: 0},
|
|
}
|
|
|
|
if !shouldRefreshOpenAICodexSnapshot(&Account{RateLimitResetAt: &rateLimitedUntil}, usage, now) {
|
|
t.Fatal("expected rate-limited account to force codex snapshot refresh")
|
|
}
|
|
|
|
if shouldRefreshOpenAICodexSnapshot(&Account{}, usage, now) {
|
|
t.Fatal("expected complete non-rate-limited usage to skip codex snapshot refresh")
|
|
}
|
|
|
|
if !shouldRefreshOpenAICodexSnapshot(&Account{}, &UsageInfo{FiveHour: nil, SevenDay: &UsageProgress{}}, now) {
|
|
t.Fatal("expected missing 5h snapshot to require refresh")
|
|
}
|
|
|
|
staleAt := now.Add(-(openAIProbeCacheTTL + time.Minute)).Format(time.RFC3339)
|
|
if !shouldRefreshOpenAICodexSnapshot(&Account{
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Extra: map[string]any{
|
|
"openai_oauth_responses_websockets_v2_enabled": true,
|
|
"codex_usage_updated_at": staleAt,
|
|
},
|
|
}, usage, now) {
|
|
t.Fatal("expected stale ws snapshot to trigger refresh")
|
|
}
|
|
}
|
|
|
|
func TestExtractOpenAICodexProbeUpdatesAccepts429WithCodexHeaders(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
headers := make(http.Header)
|
|
headers.Set("x-codex-primary-used-percent", "100")
|
|
headers.Set("x-codex-primary-reset-after-seconds", "604800")
|
|
headers.Set("x-codex-primary-window-minutes", "10080")
|
|
headers.Set("x-codex-secondary-used-percent", "100")
|
|
headers.Set("x-codex-secondary-reset-after-seconds", "18000")
|
|
headers.Set("x-codex-secondary-window-minutes", "300")
|
|
|
|
updates, err := extractOpenAICodexProbeUpdates(&http.Response{StatusCode: http.StatusTooManyRequests, Header: headers})
|
|
if err != nil {
|
|
t.Fatalf("extractOpenAICodexProbeUpdates() error = %v", err)
|
|
}
|
|
if len(updates) == 0 {
|
|
t.Fatal("expected codex probe updates from 429 headers")
|
|
}
|
|
if got := updates["codex_5h_used_percent"]; got != 100.0 {
|
|
t.Fatalf("codex_5h_used_percent = %v, want 100", got)
|
|
}
|
|
if got := updates["codex_7d_used_percent"]; got != 100.0 {
|
|
t.Fatalf("codex_7d_used_percent = %v, want 100", got)
|
|
}
|
|
}
|
|
|
|
func TestExtractOpenAICodexProbeSnapshotAccepts429WithResetAt(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
headers := make(http.Header)
|
|
headers.Set("x-codex-primary-used-percent", "100")
|
|
headers.Set("x-codex-primary-reset-after-seconds", "604800")
|
|
headers.Set("x-codex-primary-window-minutes", "10080")
|
|
headers.Set("x-codex-secondary-used-percent", "100")
|
|
headers.Set("x-codex-secondary-reset-after-seconds", "18000")
|
|
headers.Set("x-codex-secondary-window-minutes", "300")
|
|
|
|
updates, resetAt, err := extractOpenAICodexProbeSnapshot(&http.Response{StatusCode: http.StatusTooManyRequests, Header: headers})
|
|
if err != nil {
|
|
t.Fatalf("extractOpenAICodexProbeSnapshot() error = %v", err)
|
|
}
|
|
if len(updates) == 0 {
|
|
t.Fatal("expected codex probe updates from 429 headers")
|
|
}
|
|
if resetAt == nil {
|
|
t.Fatal("expected resetAt from exhausted codex headers")
|
|
}
|
|
}
|
|
|
|
func TestAccountUsageService_PersistOpenAICodexProbeSnapshotSetsRateLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repo := &accountUsageCodexProbeRepo{
|
|
updateExtraCh: make(chan map[string]any, 1),
|
|
rateLimitCh: make(chan time.Time, 1),
|
|
}
|
|
svc := &AccountUsageService{accountRepo: repo}
|
|
resetAt := time.Now().Add(2 * time.Hour).UTC().Truncate(time.Second)
|
|
|
|
svc.persistOpenAICodexProbeSnapshot(321, map[string]any{
|
|
"codex_7d_used_percent": 100.0,
|
|
"codex_7d_reset_at": resetAt.Format(time.RFC3339),
|
|
}, &resetAt)
|
|
|
|
select {
|
|
case updates := <-repo.updateExtraCh:
|
|
if got := updates["codex_7d_used_percent"]; got != 100.0 {
|
|
t.Fatalf("codex_7d_used_percent = %v, want 100", got)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("waiting for codex probe extra persistence timed out")
|
|
}
|
|
|
|
select {
|
|
case got := <-repo.rateLimitCh:
|
|
if got.Before(resetAt.Add(-time.Second)) || got.After(resetAt.Add(time.Second)) {
|
|
t.Fatalf("rate limit resetAt = %v, want around %v", got, resetAt)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("waiting for codex probe rate limit persistence timed out")
|
|
}
|
|
}
|