- TopK initial filter now drops quota-paused accounts: fold the quota check into isAccountRequestCompatible so session-hash, TopK pool, and per-candidate rechecks all skip paused accounts. Previously the candidate pool was built without the quota check, so paused accounts could fill TopK and leave the scheduler returning "no available accounts" even with healthy ones available. - Add per-account explicit disable flags auto_pause_5h_disabled / auto_pause_7d_disabled with toggles in EditAccountModal. Without these, leaving the account threshold blank silently falls back to the global default, so admins could not exempt a single account once a global default existed. Disable is per-window: an account can opt out of 5h auto-pause while still honoring 7d. Schedule snapshot whitelist includes the new fields, i18n EN/ZH updated, threshold-hint text revised to explain "blank = global default". - Move quota auto-pause settings off the request hot path: replace the per-repo TTL+singleflight sync DB read with a per-SettingService stale-while-revalidate in-memory snapshot. Get is non-blocking (atomic.Pointer load + async refresh on staleness); writes via UpdateOpsAdvancedSettings push directly into the cache through an injected sink; wire warms the cache at startup. Adds Warm (sync) for tests/init and SetOpenAIQuotaAutoPauseSettings (sink target). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
111 lines
3.9 KiB
Go
111 lines
3.9 KiB
Go
//go:build unit
|
|
|
|
package repository
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestBuildSchedulerMetadataAccount_KeepsOpenAIWSFlags(t *testing.T) {
|
|
account := service.Account{
|
|
ID: 42,
|
|
Platform: service.PlatformOpenAI,
|
|
Type: service.AccountTypeOAuth,
|
|
Extra: map[string]any{
|
|
"openai_oauth_responses_websockets_v2_enabled": true,
|
|
"openai_oauth_responses_websockets_v2_mode": service.OpenAIWSIngressModePassthrough,
|
|
"openai_ws_force_http": true,
|
|
"openai_responses_mode": "force_chat_completions",
|
|
"openai_responses_supported": false,
|
|
"mixed_scheduling": true,
|
|
"unused_large_field": "drop-me",
|
|
},
|
|
}
|
|
|
|
got := buildSchedulerMetadataAccount(account)
|
|
|
|
require.Equal(t, true, got.Extra["openai_oauth_responses_websockets_v2_enabled"])
|
|
require.Equal(t, service.OpenAIWSIngressModePassthrough, got.Extra["openai_oauth_responses_websockets_v2_mode"])
|
|
require.Equal(t, true, got.Extra["openai_ws_force_http"])
|
|
require.Equal(t, "force_chat_completions", got.Extra["openai_responses_mode"])
|
|
require.Equal(t, false, got.Extra["openai_responses_supported"])
|
|
require.Equal(t, true, got.Extra["mixed_scheduling"])
|
|
require.Nil(t, got.Extra["unused_large_field"])
|
|
}
|
|
|
|
func TestBuildSchedulerMetadataAccount_KeepsSlimGroupMembership(t *testing.T) {
|
|
account := service.Account{
|
|
ID: 42,
|
|
Platform: service.PlatformAnthropic,
|
|
GroupIDs: []int64{7, 9, 7, 0},
|
|
AccountGroups: []service.AccountGroup{
|
|
{
|
|
AccountID: 42,
|
|
GroupID: 7,
|
|
Priority: 2,
|
|
Account: &service.Account{ID: 42, Name: "drop-from-metadata"},
|
|
Group: &service.Group{ID: 7, Name: "drop-from-metadata"},
|
|
},
|
|
{
|
|
AccountID: 42,
|
|
GroupID: 11,
|
|
Priority: 3,
|
|
Group: &service.Group{ID: 11, Name: "drop-from-metadata"},
|
|
},
|
|
{
|
|
AccountID: 42,
|
|
GroupID: 0,
|
|
Priority: 4,
|
|
},
|
|
},
|
|
}
|
|
|
|
got := buildSchedulerMetadataAccount(account)
|
|
|
|
require.Equal(t, []int64{7, 9, 11}, got.GroupIDs)
|
|
require.Len(t, got.AccountGroups, 2)
|
|
require.Equal(t, int64(42), got.AccountGroups[0].AccountID)
|
|
require.Equal(t, int64(7), got.AccountGroups[0].GroupID)
|
|
require.Equal(t, 2, got.AccountGroups[0].Priority)
|
|
require.Nil(t, got.AccountGroups[0].Account)
|
|
require.Nil(t, got.AccountGroups[0].Group)
|
|
require.Equal(t, int64(11), got.AccountGroups[1].GroupID)
|
|
require.Nil(t, got.Groups)
|
|
}
|
|
|
|
func TestBuildSchedulerMetadataAccount_KeepsQuotaAutoPauseFields(t *testing.T) {
|
|
account := service.Account{
|
|
ID: 88,
|
|
Extra: map[string]any{
|
|
"codex_5h_used_percent": 12.34,
|
|
"codex_7d_used_percent": 56.78,
|
|
"codex_5h_reset_at": "2026-05-29T10:00:00Z",
|
|
"codex_7d_reset_at": "2026-06-01T10:00:00Z",
|
|
"codex_5h_reset_after_seconds": 300,
|
|
"codex_7d_reset_after_seconds": 600,
|
|
"codex_usage_updated_at": "2026-05-29T09:00:00Z",
|
|
"auto_pause_5h_threshold": 0.95,
|
|
"auto_pause_7d_threshold": 0.96,
|
|
"auto_pause_5h_disabled": true,
|
|
"auto_pause_7d_disabled": false,
|
|
},
|
|
}
|
|
|
|
got := buildSchedulerMetadataAccount(account)
|
|
|
|
require.Equal(t, 12.34, got.Extra["codex_5h_used_percent"])
|
|
require.Equal(t, 56.78, got.Extra["codex_7d_used_percent"])
|
|
require.Equal(t, "2026-05-29T10:00:00Z", got.Extra["codex_5h_reset_at"])
|
|
require.Equal(t, "2026-06-01T10:00:00Z", got.Extra["codex_7d_reset_at"])
|
|
require.Equal(t, 300, got.Extra["codex_5h_reset_after_seconds"])
|
|
require.Equal(t, 600, got.Extra["codex_7d_reset_after_seconds"])
|
|
require.Equal(t, "2026-05-29T09:00:00Z", got.Extra["codex_usage_updated_at"])
|
|
require.Equal(t, 0.95, got.Extra["auto_pause_5h_threshold"])
|
|
require.Equal(t, 0.96, got.Extra["auto_pause_7d_threshold"])
|
|
require.Equal(t, true, got.Extra["auto_pause_5h_disabled"])
|
|
require.Equal(t, false, got.Extra["auto_pause_7d_disabled"])
|
|
}
|