fromozuzhouzzz
Deploy grok2api v3.0.11 to HF Spaces
f1dd159
Raw
History Blame Contribute Delete
31.2 kB
package settings
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"time"
settingsdomain "github.com/chenyme/grok2api/backend/internal/domain/settings"
"github.com/chenyme/grok2api/backend/internal/infra/config"
"github.com/chenyme/grok2api/backend/internal/repository"
)
type runtimeSettingsRepositoryStub struct {
value settingsdomain.Config
updatedAt time.Time
revision uint64
found bool
getCount int
}
func (r *runtimeSettingsRepositoryStub) Get(context.Context) (settingsdomain.Config, time.Time, uint64, bool, error) {
r.getCount++
return r.value, r.updatedAt, r.revision, r.found, nil
}
func (r *runtimeSettingsRepositoryStub) Save(_ context.Context, value settingsdomain.Config, expectedRevision uint64) (time.Time, uint64, error) {
if expectedRevision != r.revision {
return time.Time{}, 0, repository.ErrConflict
}
r.value = value
r.updatedAt = time.Now().UTC()
r.revision++
r.found = true
return r.updatedAt, r.revision, nil
}
func TestUpdatePersistsAppliesAndReportsRestart(t *testing.T) {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
var applied config.Config
service := NewService(cfg, time.Time{}, 0, repository, nil, func(next config.Config) { applied = next })
input := service.Get().Config
input.Server.MaxConcurrentRequests = 2048
input.ProviderBuild.ResponseHeaderTimeout = "7m"
input.Routing.MaxAttempts = 5
input.Routing.PreferFreeBuild = true
input.Routing.SegmentedSelector = SegmentedSelectorConfig{Enabled: true, MinCandidates: 5000, WindowSize: 96}
input.Audit.BufferSize = cfg.Audit.BufferSize + 1
input.Media.MaxTotalBytes = 2 << 30
input.Media.CleanupThresholdPercent = 75
input.Media.CleanupInterval = "5m"
input.Frontend.PublicAPIBaseURL = "https://public.example.com"
input.ProviderConsole.BaseURL = "https://console.example.com"
input.ProviderConsole.ChatTimeout = "6m"
input.Batch = BatchConfig{ImportConcurrency: 26, ConversionConcurrency: 27, SyncConcurrency: 28, RefreshConcurrency: 29, RandomDelay: "750ms"}
snapshot, err := service.Update(context.Background(), service.Get().Revision, input)
if err != nil {
t.Fatal(err)
}
if applied.Routing.MaxAttempts != 5 || !applied.Routing.PreferFreeBuild || !applied.Routing.SegmentedSelectorEnabled || applied.Routing.SegmentedMinCandidates != 5000 || applied.Routing.SegmentedWindowSize != 96 {
t.Fatalf("runtime configuration was not applied: %#v", applied.Routing)
}
if applied.Server.MaxConcurrentRequests != 2048 {
t.Fatalf("server configuration was not applied: %#v", applied.Server)
}
if applied.Provider.Build.ResponseHeaderTimeout.Value() != 7*time.Minute {
t.Fatalf("Build response header timeout was not applied: %s", applied.Provider.Build.ResponseHeaderTimeout.Value())
}
if applied.Media.MaxTotalBytes != 2<<30 || applied.Media.CleanupThresholdPercent != 75 || applied.Media.CleanupInterval.Value() != 5*time.Minute {
t.Fatalf("media configuration was not applied: %#v", applied.Media)
}
if applied.Frontend.PublicAPIBaseURLOverride != "https://public.example.com" || applied.Frontend.EffectivePublicAPIBaseURL() != "https://public.example.com" {
t.Fatalf("frontend configuration was not applied: %#v", applied.Frontend)
}
if applied.Batch.ImportConcurrency != 26 || applied.Batch.ConversionConcurrency != 27 || applied.Batch.SyncConcurrency != 28 || applied.Batch.RefreshConcurrency != 29 || applied.Batch.RandomDelay.Value() != 750*time.Millisecond {
t.Fatalf("batch configuration was not applied: %#v", applied.Batch)
}
if applied.Provider.Console.BaseURL != "https://console.example.com" || applied.Provider.Console.ChatTimeout.Value() != 6*time.Minute {
t.Fatalf("console configuration was not applied: %#v", applied.Provider.Console)
}
if len(snapshot.RestartRequired) != 1 || snapshot.RestartRequired[0] != "audit.bufferSize" {
t.Fatalf("restartRequired = %#v", snapshot.RestartRequired)
}
reloaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if reloaded.Server.MaxConcurrentRequests != 2048 || reloaded.Provider.Build.ResponseHeaderTimeout.Value() != 7*time.Minute || reloaded.Routing.MaxAttempts != 5 || !reloaded.Routing.PreferFreeBuild || !reloaded.Routing.SegmentedSelectorEnabled || reloaded.Routing.SegmentedMinCandidates != 5000 || reloaded.Routing.SegmentedWindowSize != 96 || reloaded.Audit.BufferSize != input.Audit.BufferSize || reloaded.Media.MaxTotalBytes != 2<<30 || reloaded.Media.CleanupThresholdPercent != 75 || reloaded.Batch.SyncConcurrency != 28 || reloaded.Batch.RandomDelay.Value() != 750*time.Millisecond || reloaded.Provider.Console.BaseURL != "https://console.example.com" {
t.Fatalf("configuration was not persisted")
}
}
func TestUpdateRejectsBuildResponseHeaderTimeoutOutsideSafeRange(t *testing.T) {
for _, value := range []string{"29s", "31m"} {
t.Run(value, func(t *testing.T) {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
service := NewService(cfg, time.Time{}, 0, repository, nil, nil)
input := service.Get().Config
input.ProviderBuild.ResponseHeaderTimeout = value
if _, err := service.Update(context.Background(), 0, input); !errors.Is(err, ErrInvalidInput) {
t.Fatalf("error = %v", err)
}
if repository.found {
t.Fatal("invalid Build response header timeout was persisted")
}
})
}
}
func TestUpdateValidatesMaxAttemptsRange(t *testing.T) {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
var applied config.Config
service := NewService(cfg, time.Time{}, 0, repository, nil, func(next config.Config) { applied = next })
input := service.Get().Config
input.Routing.MaxAttempts = 65535
snapshot, err := service.Update(context.Background(), 0, input)
if err != nil {
t.Fatalf("maximum maxAttempts was rejected: %v", err)
}
if applied.Routing.MaxAttempts != 65535 || snapshot.Config.Routing.MaxAttempts != 65535 {
t.Fatalf("maximum maxAttempts was not applied: applied=%d snapshot=%d", applied.Routing.MaxAttempts, snapshot.Config.Routing.MaxAttempts)
}
input = snapshot.Config
input.Routing.MaxAttempts = -1
snapshot, err = service.Update(context.Background(), snapshot.Revision, input)
if err != nil {
t.Fatalf("unlimited maxAttempts was rejected: %v", err)
}
if applied.Routing.MaxAttempts != -1 || snapshot.Config.Routing.MaxAttempts != -1 {
t.Fatalf("unlimited maxAttempts was not applied: applied=%d snapshot=%d", applied.Routing.MaxAttempts, snapshot.Config.Routing.MaxAttempts)
}
input = snapshot.Config
input.Routing.MaxAttempts = 65536
if _, err := service.Update(context.Background(), snapshot.Revision, input); !errors.Is(err, ErrInvalidInput) {
t.Fatalf("maxAttempts above maximum error = %v", err)
}
if repository.value.Routing.MaxAttempts != -1 {
t.Fatalf("invalid maxAttempts was persisted: %d", repository.value.Routing.MaxAttempts)
}
}
func TestUpdatePreservesBuildChatDeniedPolicyWhenFieldIsOmitted(t *testing.T) {
cfg := testConfig(t)
cfg.Routing.MarkBuildChatDeniedAsReauth = true
repository := &runtimeSettingsRepositoryStub{}
var applied config.Config
service := NewService(cfg, time.Time{}, 0, repository, nil, func(next config.Config) { applied = next })
input := service.Get().Config
input.Routing.MarkBuildChatDeniedAsReauth = false
input.Routing.MarkBuildChatDeniedAsReauthProvided = false
if _, err := service.Update(context.Background(), 0, input); err != nil {
t.Fatal(err)
}
if !applied.Routing.MarkBuildChatDeniedAsReauth || !repository.value.Routing.MarkBuildChatDeniedAsReauth {
t.Fatalf("omitted Build chat denied policy was overwritten: applied=%t persisted=%t", applied.Routing.MarkBuildChatDeniedAsReauth, repository.value.Routing.MarkBuildChatDeniedAsReauth)
}
}
func TestLoadPersistedKeepsSegmentedSelectorDefaultsForOlderPayload(t *testing.T) {
cfg := testConfig(t)
cfg.Routing.SegmentedSelectorEnabled = true
cfg.Routing.SegmentedMinCandidates = 4321
cfg.Routing.SegmentedWindowSize = 72
value := toDomainConfig(cfg)
value.Routing.SegmentedSelector = nil
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if !loaded.Routing.SegmentedSelectorEnabled || loaded.Routing.SegmentedMinCandidates != 4321 || loaded.Routing.SegmentedWindowSize != 72 {
t.Fatalf("segmented selector defaults were not preserved: %#v", loaded.Routing)
}
}
func TestLoadPersistedPreservesExplicitlyDisabledSegmentedSelector(t *testing.T) {
cfg := testConfig(t)
cfg.Routing.SegmentedSelectorEnabled = true
value := toDomainConfig(cfg)
value.Routing.SegmentedSelector = &settingsdomain.SegmentedSelectorConfig{
ActiveEnabled: false, MinCandidates: 6000, WindowSize: 128,
}
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if loaded.Routing.SegmentedSelectorEnabled || loaded.Routing.SegmentedMinCandidates != 6000 || loaded.Routing.SegmentedWindowSize != 128 {
t.Fatalf("explicit segmented selector settings were not preserved: %#v", loaded.Routing)
}
}
func TestLegacyShadowSettingCannotEnableSegmentedSelector(t *testing.T) {
var persisted settingsdomain.Config
if err := json.Unmarshal([]byte(`{"Routing":{"SegmentedSelector":{"ActiveEnabled":false,"Enabled":true,"MinCandidates":3000,"WindowSize":64,"SamplePercent":100}}}`), &persisted); err != nil {
t.Fatal(err)
}
loaded := applyDomainConfig(testConfig(t), persisted)
if loaded.Routing.SegmentedSelectorEnabled {
t.Fatal("legacy shadow-only setting enabled the authoritative segmented selector")
}
}
func TestLoadPersistedKeepsConsoleDefaultsWhenFieldIsMissing(t *testing.T) {
cfg := testConfig(t)
value := toDomainConfig(cfg)
value.ProviderConsole = settingsdomain.ProviderConsoleConfig{}
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if loaded.Provider.Console != cfg.Provider.Console {
t.Fatalf("console config = %#v, want %#v", loaded.Provider.Console, cfg.Provider.Console)
}
}
func TestLoadPersistedKeepsClearanceDefaultsForOlderPayload(t *testing.T) {
cfg := testConfig(t)
value := toDomainConfig(cfg)
value.ProviderWeb.ClearanceMode = ""
value.ProviderWeb.FlareSolverrURL = ""
value.ProviderWeb.ClearanceTimeout = 0
value.ProviderWeb.ClearanceRefresh = 0
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if loaded.Provider.Web.ClearanceMode != cfg.Provider.Web.ClearanceMode || loaded.Provider.Web.FlareSolverrURL != cfg.Provider.Web.FlareSolverrURL || loaded.Provider.Web.ClearanceTimeout != cfg.Provider.Web.ClearanceTimeout || loaded.Provider.Web.ClearanceRefresh != cfg.Provider.Web.ClearanceRefresh {
t.Fatalf("clearance config = %#v, want %#v", loaded.Provider.Web, cfg.Provider.Web)
}
}
func TestSnapshotIncludesRecommendedBuildBaseline(t *testing.T) {
service := NewService(testConfig(t), time.Time{}, 0, &runtimeSettingsRepositoryStub{}, nil, nil)
recommended := service.Get().RecommendedProviderBuild
if recommended.ClientVersion != config.RecommendedBuildClientVersion || recommended.UserAgent != config.RecommendedBuildUserAgent {
t.Fatalf("recommended build = %#v", recommended)
}
}
func TestUpdateRejectsBatchConcurrencyOutsideSafeRange(t *testing.T) {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
service := NewService(cfg, time.Time{}, 0, repository, nil, nil)
input := service.Get().Config
input.Batch.ConversionConcurrency = 51
if _, err := service.Update(context.Background(), 0, input); !errors.Is(err, ErrInvalidInput) {
t.Fatalf("error = %v", err)
}
if repository.found {
t.Fatal("invalid batch settings were persisted")
}
}
func TestBatchRandomDelayCanBeDisabledAndPersisted(t *testing.T) {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
service := NewService(cfg, time.Time{}, 0, repository, nil, nil)
input := service.Get().Config
input.Batch.RandomDelay = "0s"
if _, err := service.Update(context.Background(), 0, input); err != nil {
t.Fatal(err)
}
if repository.value.Batch.RandomDelay == nil || *repository.value.Batch.RandomDelay != 0 {
t.Fatalf("persisted random delay = %#v", repository.value.Batch.RandomDelay)
}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if loaded.Batch.RandomDelay.Value() != 0 {
t.Fatalf("loaded random delay = %s", loaded.Batch.RandomDelay.Value())
}
}
func TestUpdateRejectsInvalidDurationWithoutChangingConfig(t *testing.T) {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
service := NewService(cfg, time.Time{}, 0, repository, nil, nil)
input := service.Get().Config
input.Routing.StickyTTL = "tomorrow"
if _, err := service.Update(context.Background(), service.Get().Revision, input); err == nil {
t.Fatal("expected invalid duration error")
}
if service.Get().Config.Routing.StickyTTL != cfg.Routing.StickyTTL.String() || repository.found {
t.Fatal("invalid update changed or persisted runtime configuration")
}
}
func TestStatsigManualValueIsWriteOnlyAndClearedByURLMode(t *testing.T) {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
service := NewService(cfg, time.Time{}, 0, repository, nil, nil)
manual := base64.RawStdEncoding.EncodeToString(make([]byte, 70))
input := service.Get().Config
input.ProviderWeb.StatsigMode = config.StatsigModeManual
input.ProviderWeb.StatsigManualValue = manual
snapshot, err := service.Update(context.Background(), service.Get().Revision, input)
if err != nil {
t.Fatal(err)
}
if snapshot.Config.ProviderWeb.StatsigManualValue != "" || !snapshot.Config.ProviderWeb.StatsigManualConfigured {
t.Fatalf("manual value leaked in snapshot: %#v", snapshot.Config.ProviderWeb)
}
if repository.value.ProviderWeb.StatsigManualValue != manual {
t.Fatal("manual value was not persisted")
}
keep := service.Get().Config
if _, err := service.Update(context.Background(), service.Get().Revision, keep); err != nil {
t.Fatalf("blank write-only value did not preserve existing value: %v", err)
}
if repository.value.ProviderWeb.StatsigManualValue != manual {
t.Fatal("blank write-only value cleared the existing manual value")
}
urlMode := service.Get().Config
urlMode.ProviderWeb.StatsigMode = config.StatsigModeURL
if _, err := service.Update(context.Background(), service.Get().Revision, urlMode); err != nil {
t.Fatal(err)
}
if repository.value.ProviderWeb.StatsigManualValue != "" {
t.Fatal("URL mode retained the manual x-statsig-id")
}
}
func TestLoadPersistedRejectsIncompleteStatsigPayload(t *testing.T) {
cfg := testConfig(t)
value := toDomainConfig(cfg)
value.ProviderWeb.StatsigMode = ""
value.ProviderWeb.StatsigSignerURL = ""
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
if _, _, _, err := LoadPersisted(context.Background(), cfg, repository); err == nil {
t.Fatal("incomplete Statsig settings were accepted")
}
}
func TestLoadPersistedRejectsIncompleteBatchPayload(t *testing.T) {
cfg := testConfig(t)
value := toDomainConfig(cfg)
value.Batch = settingsdomain.BatchConfig{}
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
if _, _, _, err := LoadPersisted(context.Background(), cfg, repository); err == nil {
t.Fatal("incomplete batch settings were accepted")
}
}
func TestLoadPersistedBackfillsMissingServerConcurrency(t *testing.T) {
cfg := testConfig(t)
value := toDomainConfig(cfg)
value.Server = settingsdomain.ServerConfig{}
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if loaded.Server.MaxConcurrentRequests != cfg.Server.MaxConcurrentRequests {
t.Fatalf("maxConcurrentRequests = %d, want %d", loaded.Server.MaxConcurrentRequests, cfg.Server.MaxConcurrentRequests)
}
}
func TestApplyDomainConfigPreservesExplicitCapacitySettings(t *testing.T) {
base := testConfig(t)
value := toDomainConfig(base)
value.Server.MaxConcurrentRequests = 1024
value.Routing.CapacityWait = 500 * time.Millisecond
value.ProviderWeb.ChatTimeout = 2 * time.Minute
value.ClientKeyDefaults.RPMLimit = 120
value.ClientKeyDefaults.MaxConcurrent = 8
applied := applyDomainConfig(base, value)
if applied.Server.MaxConcurrentRequests != 1024 {
t.Fatalf("maxConcurrentRequests = %d, want 1024", applied.Server.MaxConcurrentRequests)
}
if applied.Routing.CapacityWait.Value() != 500*time.Millisecond {
t.Fatalf("capacityWait = %s, want 500ms", applied.Routing.CapacityWait.Value())
}
if applied.Provider.Web.ChatTimeout.Value() != 2*time.Minute {
t.Fatalf("chatTimeout = %s, want 2m", applied.Provider.Web.ChatTimeout.Value())
}
if applied.ClientKeyDefaults.RPMLimit != 120 || applied.ClientKeyDefaults.MaxConcurrent != 8 {
t.Fatalf("client key defaults = %+v, want 120/8", applied.ClientKeyDefaults)
}
}
func TestLoadPersistedBackfillsMissingConsoleSection(t *testing.T) {
cfg := testConfig(t)
value := toDomainConfig(cfg)
value.ProviderConsole = settingsdomain.ProviderConsoleConfig{}
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if loaded.Provider.Console != cfg.Provider.Console {
t.Fatalf("console = %#v, want %#v", loaded.Provider.Console, cfg.Provider.Console)
}
}
func TestLoadPersistedRejectsPartiallyInvalidConsoleSection(t *testing.T) {
cfg := testConfig(t)
value := toDomainConfig(cfg)
value.ProviderConsole.BaseURL = ""
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
if _, _, _, err := LoadPersisted(context.Background(), cfg, repository); err == nil {
t.Fatal("partially invalid Console settings were accepted")
}
}
func TestReloadPersistedAppliesOnlyNewerVersion(t *testing.T) {
cfg := testConfig(t)
updatedAt := time.Now().UTC()
repository := &runtimeSettingsRepositoryStub{value: toDomainConfig(cfg), updatedAt: updatedAt, revision: 1, found: true}
applyCount := 0
service := NewService(cfg, updatedAt, 1, repository, nil, func(config.Config) { applyCount++ })
if err := service.ReloadPersisted(context.Background()); err != nil {
t.Fatal(err)
}
if applyCount != 0 {
t.Fatalf("unchanged settings applied %d times", applyCount)
}
repository.value.Routing.MaxAttempts = cfg.Routing.MaxAttempts + 1
repository.updatedAt = updatedAt.Add(time.Second)
repository.revision = 2
if err := service.ReloadPersisted(context.Background()); err != nil {
t.Fatal(err)
}
if applyCount != 1 || service.Get().Config.Routing.MaxAttempts != cfg.Routing.MaxAttempts+1 {
t.Fatalf("newer settings were not applied")
}
}
func TestUpdateRejectsStaleRevision(t *testing.T) {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
service := NewService(cfg, time.Time{}, 0, repository, nil, nil)
input := service.Get().Config
input.Routing.MaxAttempts++
if _, err := service.Update(context.Background(), 1, input); !errors.Is(err, ErrConflict) {
t.Fatalf("stale revision error = %v", err)
}
}
func testConfig(t *testing.T) config.Config {
t.Helper()
path := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(path, []byte(`secrets:
jwtSecret: "12345678901234567890123456789012"
credentialEncryptionKey: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
`), 0o600); err != nil {
t.Fatal(err)
}
cfg, err := config.Load(path)
if err != nil {
t.Fatal(err)
}
return cfg
}
func TestLoadPersistedKeepsYAMLFrontendWhenUnset(t *testing.T) {
cfg := testConfig(t)
cfg.Frontend.PublicAPIBaseURL = "http://yaml.example.com"
value := toDomainConfig(cfg)
value.Frontend = settingsdomain.FrontendConfig{}
repository := &runtimeSettingsRepositoryStub{
value: value,
found: true, revision: 1, updatedAt: time.Now().UTC(),
}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if loaded.Frontend.PublicAPIBaseURL != "http://yaml.example.com" || loaded.Frontend.PublicAPIBaseURLOverride != "" || loaded.Frontend.EffectivePublicAPIBaseURL() != "http://yaml.example.com" {
t.Fatalf("frontend = %#v", loaded.Frontend)
}
}
func TestUpdateEmptyFrontendOverrideFallsBackToYAML(t *testing.T) {
cfg := testConfig(t)
cfg.Frontend.PublicAPIBaseURL = "http://yaml.example.com"
cfg.Frontend.PublicAPIBaseURLOverride = "http://runtime.example.com"
repository := &runtimeSettingsRepositoryStub{}
var applied config.Config
service := NewService(cfg, time.Time{}, 0, repository, nil, func(next config.Config) { applied = next })
input := service.Get().Config
input.Frontend.PublicAPIBaseURL = ""
if _, err := service.Update(context.Background(), 0, input); err != nil {
t.Fatal(err)
}
if applied.Frontend.PublicAPIBaseURLOverride != "" || applied.Frontend.EffectivePublicAPIBaseURL() != "http://yaml.example.com" {
t.Fatalf("frontend fallback = %#v", applied.Frontend)
}
if repository.value.Frontend.PublicAPIBaseURL != "" {
t.Fatalf("persisted override = %q", repository.value.Frontend.PublicAPIBaseURL)
}
}
func TestApplyDomainConfigAccountsDefaults(t *testing.T) {
base := testConfig(t)
// 旧持久化 JSON 无 Accounts 段时,应保持代码默认:关闭 + 10m + 1h。
loaded := applyDomainConfig(base, settingsdomain.Config{
Server: settingsdomain.ServerConfig{MaxConcurrentRequests: base.Server.MaxConcurrentRequests},
ProviderBuild: settingsdomain.ProviderBuildConfig{
BaseURL: base.Provider.Build.BaseURL, FallbackBaseURL: base.Provider.Build.FallbackBaseURL,
ClientVersion: base.Provider.Build.ClientVersion, ClientIdentifier: base.Provider.Build.ClientIdentifier,
TokenAuth: base.Provider.Build.TokenAuth, UserAgent: base.Provider.Build.UserAgent,
},
ProviderWeb: settingsdomain.ProviderWebConfig{
BaseURL: base.Provider.Web.BaseURL, QuotaTimeout: base.Provider.Web.QuotaTimeout.Value(),
StatsigMode: base.Provider.Web.StatsigMode, StatsigManualValue: base.Provider.Web.StatsigManualValue,
StatsigSignerURL: base.Provider.Web.StatsigSignerURL,
ChatTimeout: base.Provider.Web.ChatTimeout.Value(), ImageTimeout: base.Provider.Web.ImageTimeout.Value(),
VideoTimeout: base.Provider.Web.VideoTimeout.Value(), MediaConcurrency: base.Provider.Web.MediaConcurrency,
AllowNSFW: base.Provider.Web.AllowNSFW,
RecoveryBackoffBase: base.Provider.Web.RecoveryBackoffBase.Value(), RecoveryBackoffMax: base.Provider.Web.RecoveryBackoffMax.Value(),
},
Batch: settingsdomain.BatchConfig{
ImportConcurrency: base.Batch.ImportConcurrency, ConversionConcurrency: base.Batch.ConversionConcurrency,
SyncConcurrency: base.Batch.SyncConcurrency, RefreshConcurrency: base.Batch.RefreshConcurrency,
RandomDelay: func() *time.Duration { d := base.Batch.RandomDelay.Value(); return &d }(),
},
Media: settingsdomain.MediaConfig{
MaxImageBytes: base.Media.MaxImageBytes, MaxTotalBytes: base.Media.MaxTotalBytes,
CleanupThresholdPercent: base.Media.CleanupThresholdPercent, CleanupInterval: base.Media.CleanupInterval.Value(),
},
Routing: settingsdomain.RoutingConfig{
StickyTTL: base.Routing.StickyTTL.Value(), CooldownBase: base.Routing.CooldownBase.Value(),
CooldownMax: base.Routing.CooldownMax.Value(), CapacityWait: base.Routing.CapacityWait.Value(),
MaxAttempts: base.Routing.MaxAttempts, PreferFreeBuild: base.Routing.PreferFreeBuild,
MarkBuildChatDeniedAsReauth: base.Routing.MarkBuildChatDeniedAsReauth,
},
Audit: settingsdomain.AuditConfig{
BufferSize: base.Audit.BufferSize, BatchSize: base.Audit.BatchSize, FlushInterval: base.Audit.FlushInterval.Value(),
},
ClientKeyDefaults: settingsdomain.ClientKeyDefaultsConfig{
RPMLimit: base.ClientKeyDefaults.RPMLimit, MaxConcurrent: base.ClientKeyDefaults.MaxConcurrent,
},
})
if loaded.Accounts.AutoCleanReauthEnabled || loaded.Accounts.AutoCleanIncludeDisabled {
t.Fatalf("accounts flags should stay false: %#v", loaded.Accounts)
}
if loaded.Accounts.AutoCleanReauthInterval.Value() != 10*time.Minute || loaded.Accounts.AutoCleanReauthMinAge.Value() != time.Hour {
t.Fatalf("accounts defaults = %#v", loaded.Accounts)
}
if loaded.Audit.CommitDelay.Value() != base.Audit.CommitDelay.Value() {
t.Fatalf("audit commit delay = %s", loaded.Audit.CommitDelay.Value())
}
}
func TestUpdateAuditCommitDelayRoundTrip(t *testing.T) {
cfg := testConfig(t)
repo := &runtimeSettingsRepositoryStub{}
var applied config.Config
service := NewService(cfg, time.Time{}, 0, repo, nil, func(next config.Config) { applied = next })
input := service.Get().Config
input.Audit.CommitDelayMS = 12
snapshot, err := service.Update(context.Background(), service.Get().Revision, input)
if err != nil {
t.Fatal(err)
}
if applied.Audit.CommitDelay.Value() != 12*time.Millisecond || snapshot.Config.Audit.CommitDelayMS != 12 {
t.Fatalf("applied=%s snapshot=%d", applied.Audit.CommitDelay.Value(), snapshot.Config.Audit.CommitDelayMS)
}
}
func TestUpdateRejectsNegativeAuditCommitDelay(t *testing.T) {
cfg := testConfig(t)
service := NewService(cfg, time.Time{}, 0, &runtimeSettingsRepositoryStub{}, nil, nil)
input := service.Get().Config
input.Audit.CommitDelayMS = -1
if _, err := service.Update(context.Background(), service.Get().Revision, input); err == nil {
t.Fatal("negative audit commit delay was accepted")
}
}
func TestUpdateAccountsAutoCleanRoundTrip(t *testing.T) {
cfg := testConfig(t)
repo := &runtimeSettingsRepositoryStub{}
var applied config.Config
service := NewService(cfg, time.Time{}, 0, repo, nil, func(next config.Config) { applied = next })
input := service.Get().Config
input.Accounts = AccountsConfig{
MarkBuildForbiddenReauth: true, MarkBuildForbiddenReauthProvided: true,
BuildForbiddenReauthCodes: []string{" Permission-Denied ", "permission-denied", "team-access-denied"}, BuildForbiddenReauthCodesProvided: true,
AutoCleanReauthEnabled: true, AutoCleanReauthInterval: "5m",
AutoCleanReauthMinAge: "2h", AutoCleanIncludeDisabled: true,
}
snapshot, err := service.Update(context.Background(), service.Get().Revision, input)
if err != nil {
t.Fatal(err)
}
if !applied.Accounts.MarkBuildForbiddenReauth || !applied.Accounts.AutoCleanReauthEnabled || !applied.Accounts.AutoCleanIncludeDisabled {
t.Fatalf("applied accounts = %#v", applied.Accounts)
}
if len(applied.Accounts.BuildForbiddenReauthCodes) != 2 || applied.Accounts.BuildForbiddenReauthCodes[0] != "permission-denied" || applied.Accounts.BuildForbiddenReauthCodes[1] != "team-access-denied" {
t.Fatalf("normalized Build forbidden codes = %#v", applied.Accounts.BuildForbiddenReauthCodes)
}
if applied.Accounts.AutoCleanReauthInterval.Value() != 5*time.Minute || applied.Accounts.AutoCleanReauthMinAge.Value() != 2*time.Hour {
t.Fatalf("applied accounts durations = %#v", applied.Accounts)
}
if !snapshot.Config.Accounts.MarkBuildForbiddenReauth || !snapshot.Config.Accounts.AutoCleanReauthEnabled || snapshot.Config.Accounts.AutoCleanReauthInterval != "5m" {
t.Fatalf("snapshot accounts = %#v", snapshot.Config.Accounts)
}
}
func TestUpdateWithoutAccountsPreservesCurrentAutoCleanConfig(t *testing.T) {
cfg := testConfig(t)
cfg.Accounts.AutoCleanReauthEnabled = true
cfg.Accounts.MarkBuildForbiddenReauth = true
cfg.Accounts.BuildForbiddenReauthCodes = []string{"custom-denial"}
cfg.Accounts.AutoCleanReauthInterval = config.Duration(7 * time.Minute)
cfg.Accounts.AutoCleanReauthMinAge = config.Duration(3 * time.Hour)
cfg.Accounts.AutoCleanIncludeDisabled = true
repo := &runtimeSettingsRepositoryStub{}
var applied config.Config
service := NewService(cfg, time.Time{}, 0, repo, nil, func(next config.Config) { applied = next })
input := service.Get().Config
input.Accounts = AccountsConfig{}
input.AccountsProvided = false
input.Server.MaxConcurrentRequests++
if _, err := service.Update(context.Background(), 0, input); err != nil {
t.Fatal(err)
}
if !applied.Accounts.MarkBuildForbiddenReauth || !applied.Accounts.AutoCleanReauthEnabled || !applied.Accounts.AutoCleanIncludeDisabled || applied.Accounts.AutoCleanReauthInterval.Value() != 7*time.Minute || applied.Accounts.AutoCleanReauthMinAge.Value() != 3*time.Hour {
t.Fatalf("accounts changed by legacy update: %#v", applied.Accounts)
}
}
func TestUpdateWithoutBuildForbiddenFieldPreservesCurrentPolicy(t *testing.T) {
cfg := testConfig(t)
cfg.Accounts.MarkBuildForbiddenReauth = true
cfg.Accounts.BuildForbiddenReauthCodes = []string{"custom-denial"}
var applied config.Config
service := NewService(cfg, time.Time{}, 0, &runtimeSettingsRepositoryStub{}, nil, func(next config.Config) { applied = next })
input := service.Get().Config
input.Accounts.MarkBuildForbiddenReauth = false
input.Accounts.MarkBuildForbiddenReauthProvided = false
input.Accounts.BuildForbiddenReauthCodes = nil
input.Accounts.BuildForbiddenReauthCodesProvided = false
input.Server.MaxConcurrentRequests++
if _, err := service.Update(context.Background(), 0, input); err != nil {
t.Fatal(err)
}
if !applied.Accounts.MarkBuildForbiddenReauth || len(applied.Accounts.BuildForbiddenReauthCodes) != 1 || applied.Accounts.BuildForbiddenReauthCodes[0] != "custom-denial" {
t.Fatalf("legacy update changed the Build forbidden-account policy: %#v", applied.Accounts)
}
}
func TestLoadPersistedKeepsDefaultBuildForbiddenCodesForOlderPayload(t *testing.T) {
cfg := testConfig(t)
value := toDomainConfig(cfg)
value.Accounts.BuildForbiddenReauthCodes = nil
repository := &runtimeSettingsRepositoryStub{value: value, found: true}
loaded, _, _, err := LoadPersisted(context.Background(), cfg, repository)
if err != nil {
t.Fatal(err)
}
if len(loaded.Accounts.BuildForbiddenReauthCodes) != 1 || loaded.Accounts.BuildForbiddenReauthCodes[0] != "permission-denied" {
t.Fatalf("Build forbidden code defaults were not preserved: %#v", loaded.Accounts.BuildForbiddenReauthCodes)
}
}
func TestUpdateRejectsInvalidBuildForbiddenCodes(t *testing.T) {
for _, codes := range [][]string{{}, {"contains spaces"}, func() []string {
values := make([]string, 33)
for index := range values {
values[index] = fmt.Sprintf("denial-%d", index)
}
return values
}()} {
cfg := testConfig(t)
repository := &runtimeSettingsRepositoryStub{}
service := NewService(cfg, time.Time{}, 0, repository, nil, nil)
input := service.Get().Config
input.Accounts.BuildForbiddenReauthCodes = codes
input.Accounts.BuildForbiddenReauthCodesProvided = true
if _, err := service.Update(context.Background(), 0, input); !errors.Is(err, ErrInvalidInput) {
t.Fatalf("codes %#v: error = %v", codes, err)
}
if repository.found {
t.Fatalf("invalid codes were persisted: %#v", codes)
}
}
}