account_protection_test.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package service
  2. import (
  3. "context"
  4. "github.com/stretchr/testify/require"
  5. "testing"
  6. )
  7. func TestAccountProtectionNewDefaultsCoverProvidersAndImports(t *testing.T) {
  8. for _, tt := range []struct {
  9. name, platform, kind, scope string
  10. random, shadow bool
  11. }{
  12. {"oauth", PlatformOpenAI, AccountTypeOAuth, "legacy", false, false},
  13. {"setup", PlatformOpenAI, AccountTypeSetupToken, "legacy", false, false},
  14. {"api_key", PlatformOpenAI, AccountTypeAPIKey, "generic_v1", false, false},
  15. {"anthropic", PlatformAnthropic, AccountTypeOAuth, "legacy", false, false},
  16. {"anthropic_setup", PlatformAnthropic, AccountTypeSetupToken, "legacy", false, false},
  17. {"gemini", PlatformGemini, AccountTypeAPIKey, "generic_v1", false, false},
  18. {"random_proxy", PlatformOpenAI, AccountTypeOAuth, "generic_v1", true, false},
  19. {"shadow", PlatformOpenAI, AccountTypeOAuth, "generic_v1", false, true},
  20. } {
  21. t.Run(tt.name, func(t *testing.T) {
  22. extra := map[string]any{AntiDegradationExtraKey: false, "codex_fingerprint_seed": "22222222-2222-4222-8222-222222222222", "untouched": "value"}
  23. if tt.random {
  24. extra["proxy_mode"] = "random"
  25. }
  26. a := &Account{Platform: tt.platform, Type: tt.kind, Concurrency: 90, Extra: extra}
  27. if tt.shadow {
  28. id := int64(1)
  29. a.ParentAccountID = &id
  30. a.QuotaDimension = "spark"
  31. }
  32. PrepareNewAccountProtection(a)
  33. require.True(t, a.AntiDegradationEnabled())
  34. require.Equal(t, tt.scope, a.ProtectionScope())
  35. require.Equal(t, 90, a.Concurrency)
  36. require.Equal(t, false, extra[AntiDegradationExtraKey], "import input must not be mutated")
  37. require.Equal(t, "value", a.Extra["untouched"])
  38. require.NoError(t, ValidateAccountProtectionConfiguration(a))
  39. if tt.scope == "legacy" {
  40. require.Equal(t, "legacy", a.ProtectionMode())
  41. require.NotContains(t, mode1Marker(a), "policy_version")
  42. require.True(t, a.IsTLSFingerprintEnabled())
  43. require.Equal(t, "nodejs24", a.Extra["tls_fingerprint_builtin"])
  44. if tt.platform == PlatformOpenAI {
  45. require.Equal(t, codexFingerprintSession, a.GetCodexFingerprintMode())
  46. require.NotEqual(t, extra["codex_fingerprint_seed"], requireValidCodexFingerprintSeed(t, a.Extra))
  47. } else {
  48. require.NotContains(t, a.Extra, "codex_fingerprint_mode")
  49. }
  50. } else {
  51. require.False(t, a.IsMode1ProtectionEnabled())
  52. require.NotContains(t, a.Extra, "tls_fingerprint_builtin")
  53. }
  54. })
  55. }
  56. }
  57. func TestAccountProtectionOldAccountsAreNotSilentlyChanged(t *testing.T) {
  58. for _, tt := range []struct {
  59. extra map[string]any
  60. want bool
  61. }{
  62. {nil, false},
  63. {map[string]any{AntiDegradeMarkerExtraKey: map[string]any{"enabled": false}}, false},
  64. {map[string]any{AntiDegradeMarkerExtraKey: map[string]any{"enabled": true}}, true},
  65. {map[string]any{AntiDegradationExtraKey: false, AntiDegradeMarkerExtraKey: map[string]any{"enabled": true}}, false},
  66. } {
  67. a := &Account{Extra: tt.extra}
  68. require.Equal(t, tt.want, a.AntiDegradationEnabled())
  69. require.Equal(t, tt.extra, a.Extra)
  70. }
  71. }
  72. func TestAccountProtectionOrdinaryUpdateCannotDisableOrErasePolicy(t *testing.T) {
  73. ctx := context.Background()
  74. for _, extra := range []map[string]any{{}, {AntiDegradationExtraKey: false}, {AntiDegradeMarkerExtraKey: nil}, {"codex_fingerprint_mode": "off", "proxy_mode": "random"}} {
  75. a := &Account{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Concurrency: 4}
  76. PrepareNewAccountProtection(a)
  77. repo := &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{1: a}}
  78. svc := &adminServiceImpl{accountRepo: repo}
  79. uncapped := 99
  80. updated, err := svc.UpdateAccount(ctx, 1, &UpdateAccountInput{Extra: extra, Concurrency: &uncapped})
  81. if extra["proxy_mode"] == "random" {
  82. require.ErrorIs(t, err, ErrProtectedProxyModeChange)
  83. continue
  84. }
  85. require.NoError(t, err)
  86. require.True(t, updated.AntiDegradationEnabled())
  87. require.Equal(t, uncapped, updated.Concurrency)
  88. require.NoError(t, ValidateAccountProtectionConfiguration(updated))
  89. }
  90. }
  91. func TestAccountProtectionDisableRequiresConfirmationAndKeepsIdentity(t *testing.T) {
  92. ctx := context.Background()
  93. a := &Account{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Concurrency: 32}
  94. PrepareNewAccountProtection(a)
  95. seed := a.Extra[codexFingerprintSeedExtraKey]
  96. repo := &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{1: a}}
  97. svc := NewAntiDegradeService(&adminServiceImpl{accountRepo: repo})
  98. _, err := svc.SetProtection(ctx, 1, false, false)
  99. require.ErrorContains(t, err, "明确确认")
  100. require.True(t, a.AntiDegradationEnabled())
  101. disabled, err := svc.SetProtection(ctx, 1, false, true)
  102. require.NoError(t, err)
  103. require.False(t, disabled.AntiDegradationEnabled())
  104. require.Equal(t, 32, disabled.Concurrency)
  105. updated, err := svc.SetProtection(ctx, 1, true, false)
  106. require.NoError(t, err)
  107. require.True(t, updated.AntiDegradationEnabled())
  108. require.Equal(t, "legacy", updated.ProtectionMode())
  109. require.Equal(t, codexFingerprintSession, updated.GetCodexFingerprintMode())
  110. require.Equal(t, "nodejs24", updated.Extra["tls_fingerprint_builtin"])
  111. require.Equal(t, seed, updated.Extra[codexFingerprintSeedExtraKey])
  112. }
  113. func TestDefaultProtectionKeepsEnabledExistingModes(t *testing.T) {
  114. ctx := context.Background()
  115. for _, mode := range []AntiDegradeMode{AntiDegradeMode1, AntiDegradeMode2, AntiDegradeModeMinimal} {
  116. t.Run(string(mode), func(t *testing.T) {
  117. a := &Account{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Concurrency: 3}
  118. admin := &adminServiceImpl{accountRepo: &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{1: a}}}
  119. svc := NewAntiDegradeService(admin)
  120. applied, err := svc.ApplyAntiDegradeMode(ctx, 1, mode)
  121. require.NoError(t, err)
  122. original := snapshotAntiDegradeConfig(applied)
  123. updated, err := svc.SetProtection(ctx, 1, true, false)
  124. require.NoError(t, err)
  125. require.Equal(t, string(mode), updated.ProtectionMode())
  126. require.Equal(t, original, snapshotAntiDegradeConfig(updated))
  127. require.Equal(t, 3, updated.Concurrency)
  128. })
  129. }
  130. }
  131. func TestProtectionApplyRevertRestoresTLSConfiguration(t *testing.T) {
  132. ctx := context.Background()
  133. for _, mode := range []AntiDegradeMode{AntiDegradeModeLegacy, AntiDegradeModeMinimal, AntiDegradeModeSession, AntiDegradeModeTLSNode24, AntiDegradeModeLowConcurrency, AntiDegradeMode2} {
  134. t.Run(string(mode), func(t *testing.T) {
  135. a := &Account{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Concurrency: 32,
  136. Extra: map[string]any{"enable_tls_fingerprint": true, "tls_fingerprint_profile_id": 9, "codex_fingerprint_mode": "device"}}
  137. original := snapshotAntiDegradeConfig(a)
  138. admin := &adminServiceImpl{accountRepo: &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{1: a}}}
  139. svc := NewAntiDegradeService(admin)
  140. _, err := svc.ApplyAntiDegradeMode(ctx, 1, mode)
  141. require.NoError(t, err)
  142. restored, err := svc.Revert(ctx, 1)
  143. require.NoError(t, err)
  144. require.Equal(t, original, snapshotAntiDegradeConfig(restored))
  145. require.True(t, restored.IsTLSFingerprintEnabled())
  146. require.EqualValues(t, 9, restored.GetTLSFingerprintProfileID())
  147. })
  148. }
  149. }
  150. func TestGenericProtectionRevertPreservesUnmanagedTLSAndCanSelectMode1(t *testing.T) {
  151. ctx := context.Background()
  152. a := &Account{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Concurrency: 32,
  153. Extra: map[string]any{"proxy_mode": "random", "enable_tls_fingerprint": true, "tls_fingerprint_profile_id": 9}}
  154. PrepareNewAccountProtection(a)
  155. require.Equal(t, AntiDegradeMode("generic"), antiDegradeMode(a))
  156. admin := &adminServiceImpl{accountRepo: &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{1: a}}}
  157. svc := NewAntiDegradeService(admin)
  158. _, err := admin.UpdateAccount(ctx, 1, &UpdateAccountInput{Extra: map[string]any{"enable_tls_fingerprint": true, "tls_fingerprint_profile_id": 9}})
  159. require.NoError(t, err)
  160. mode1, err := svc.ApplyAntiDegradeMode(ctx, 1, AntiDegradeMode1)
  161. require.NoError(t, err)
  162. require.True(t, mode1.IsMode1ProtectionEnabled())
  163. restored, err := svc.Revert(ctx, 1)
  164. require.NoError(t, err)
  165. require.True(t, restored.IsTLSFingerprintEnabled())
  166. require.EqualValues(t, 9, restored.GetTLSFingerprintProfileID())
  167. reenabled, err := svc.SetProtection(ctx, 1, true, false)
  168. require.NoError(t, err)
  169. require.Equal(t, "legacy", reenabled.ProtectionMode())
  170. }