account_codex_fingerprint_seed_lifecycle_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package service
  2. import (
  3. "context"
  4. "testing"
  5. "github.com/stretchr/testify/require"
  6. )
  7. const userSuppliedCodexFingerprintSeed = "22222222-2222-4222-8222-222222222222"
  8. func requireValidCodexFingerprintSeed(t *testing.T, extra map[string]any) string {
  9. t.Helper()
  10. seed, ok := codexFingerprintSeed(extra)
  11. require.True(t, ok, "expected valid canonical Codex fingerprint seed")
  12. return seed
  13. }
  14. func TestAdminCreateAccountStripsUserSeedAndCreatesFreshSeedWhenEnabled(t *testing.T) {
  15. repo := &upstreamBillingProbeAccountRepo{}
  16. svc := &adminServiceImpl{accountRepo: repo}
  17. created, err := svc.CreateAccount(context.Background(), &CreateAccountInput{
  18. Name: "codex-oauth",
  19. Platform: PlatformOpenAI,
  20. Type: AccountTypeOAuth,
  21. SkipDefaultGroupBind: true,
  22. Extra: map[string]any{
  23. codexFingerprintModeExtraKey: "session",
  24. codexFingerprintSeedExtraKey: userSuppliedCodexFingerprintSeed,
  25. },
  26. })
  27. require.NoError(t, err)
  28. seed := requireValidCodexFingerprintSeed(t, created.Extra)
  29. require.NotEqual(t, userSuppliedCodexFingerprintSeed, seed)
  30. require.Equal(t, "session", created.Extra[codexFingerprintModeExtraKey])
  31. }
  32. func TestAdminUpdateAccountPreservesExistingSeedAndStripsUserSeed(t *testing.T) {
  33. accountID := int64(201)
  34. repo := &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{
  35. accountID: {
  36. ID: accountID,
  37. Name: "before",
  38. Platform: PlatformOpenAI,
  39. Type: AccountTypeOAuth,
  40. Status: StatusActive,
  41. Extra: map[string]any{
  42. codexFingerprintModeExtraKey: "session",
  43. codexFingerprintSeedExtraKey: testCodexFingerprintSeed,
  44. },
  45. },
  46. }}
  47. svc := &adminServiceImpl{accountRepo: repo}
  48. updated, err := svc.UpdateAccount(context.Background(), accountID, &UpdateAccountInput{
  49. Extra: map[string]any{
  50. codexFingerprintModeExtraKey: "full",
  51. codexFingerprintSeedExtraKey: userSuppliedCodexFingerprintSeed,
  52. "custom": "value",
  53. },
  54. })
  55. require.NoError(t, err)
  56. require.Equal(t, testCodexFingerprintSeed, requireValidCodexFingerprintSeed(t, updated.Extra))
  57. require.Equal(t, "full", updated.Extra[codexFingerprintModeExtraKey])
  58. require.Equal(t, "value", updated.Extra["custom"])
  59. }
  60. func TestAdminUpdateAccountInitializesSeedWhenFullEditEnables(t *testing.T) {
  61. accountID := int64(202)
  62. repo := &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{
  63. accountID: {
  64. ID: accountID,
  65. Name: "before",
  66. Platform: PlatformOpenAI,
  67. Type: AccountTypeOAuth,
  68. Status: StatusActive,
  69. Extra: map[string]any{
  70. codexFingerprintModeExtraKey: "off",
  71. codexFingerprintSeedExtraKey: "not-a-seed",
  72. },
  73. },
  74. }}
  75. updated, err := (&adminServiceImpl{accountRepo: repo}).UpdateAccount(context.Background(), accountID, &UpdateAccountInput{
  76. Extra: map[string]any{codexFingerprintModeExtraKey: "device"},
  77. })
  78. require.NoError(t, err)
  79. require.NotEqual(t, "not-a-seed", requireValidCodexFingerprintSeed(t, updated.Extra))
  80. require.Equal(t, "device", updated.Extra[codexFingerprintModeExtraKey])
  81. }
  82. func TestAdminUpdateAccountDisableReenablePreservesValidSeed(t *testing.T) {
  83. accountID := int64(203)
  84. repo := &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{
  85. accountID: {
  86. ID: accountID,
  87. Platform: PlatformOpenAI,
  88. Type: AccountTypeOAuth,
  89. Status: StatusActive,
  90. Extra: map[string]any{
  91. codexFingerprintModeExtraKey: "session",
  92. codexFingerprintSeedExtraKey: testCodexFingerprintSeed,
  93. },
  94. },
  95. }}
  96. svc := &adminServiceImpl{accountRepo: repo}
  97. disabled, err := svc.UpdateAccount(context.Background(), accountID, &UpdateAccountInput{
  98. Extra: map[string]any{codexFingerprintModeExtraKey: "off"},
  99. })
  100. require.NoError(t, err)
  101. require.Equal(t, testCodexFingerprintSeed, requireValidCodexFingerprintSeed(t, disabled.Extra))
  102. reenabled, err := svc.UpdateAccount(context.Background(), accountID, &UpdateAccountInput{
  103. Extra: map[string]any{codexFingerprintModeExtraKey: "session"},
  104. })
  105. require.NoError(t, err)
  106. require.Equal(t, testCodexFingerprintSeed, requireValidCodexFingerprintSeed(t, reenabled.Extra))
  107. }
  108. func TestAdminUpdateAccountExtraStripsSeedAndLeavesAtomicEnsureToRepository(t *testing.T) {
  109. accountID := int64(204)
  110. repo := &upstreamBillingProbeAccountRepo{accounts: map[int64]*Account{
  111. accountID: {
  112. ID: accountID,
  113. Platform: PlatformOpenAI,
  114. Type: AccountTypeOAuth,
  115. Extra: map[string]any{},
  116. },
  117. }}
  118. err := (&adminServiceImpl{accountRepo: repo}).UpdateAccountExtra(context.Background(), accountID, map[string]any{
  119. codexFingerprintModeExtraKey: "device",
  120. codexFingerprintSeedExtraKey: userSuppliedCodexFingerprintSeed,
  121. })
  122. require.NoError(t, err)
  123. require.Len(t, repo.updates[accountID], 1)
  124. require.Equal(t, "device", repo.updates[accountID][0][codexFingerprintModeExtraKey])
  125. require.NotContains(t, repo.updates[accountID][0], codexFingerprintSeedExtraKey)
  126. }
  127. func TestBulkUpdateAccountsDoesNotPrewriteCodexSeed(t *testing.T) {
  128. repo := &upstreamBillingProbeAccountRepo{}
  129. result, err := (&adminServiceImpl{accountRepo: repo}).BulkUpdateAccounts(context.Background(), &BulkUpdateAccountsInput{
  130. AccountIDs: []int64{301, 302},
  131. Extra: map[string]any{
  132. codexFingerprintModeExtraKey: "session",
  133. codexFingerprintSeedExtraKey: userSuppliedCodexFingerprintSeed,
  134. },
  135. })
  136. require.NoError(t, err)
  137. require.Equal(t, 2, result.Success)
  138. require.Empty(t, repo.updates, "bulk enable must not loop through UpdateExtra before BulkUpdate")
  139. require.Len(t, repo.bulkUpdates, 1)
  140. require.True(t, repo.bulkUpdates[0].EnsureCodexFingerprintSeed)
  141. require.Equal(t, "session", repo.bulkUpdates[0].Extra[codexFingerprintModeExtraKey])
  142. require.NotContains(t, repo.bulkUpdates[0].Extra, codexFingerprintSeedExtraKey)
  143. }
  144. type codexSeedDuplicateRepo struct {
  145. *upstreamBillingProbeAccountRepo
  146. }
  147. func (r *codexSeedDuplicateRepo) CreateWithAccountGroups(ctx context.Context, account *Account, _ []AccountGroup) error {
  148. return r.Create(ctx, account)
  149. }
  150. func TestDuplicateAccountDoesNotCopyCodexFingerprintSeed(t *testing.T) {
  151. ctx := context.Background()
  152. repo := &codexSeedDuplicateRepo{upstreamBillingProbeAccountRepo: &upstreamBillingProbeAccountRepo{accounts: make(map[int64]*Account)}}
  153. svc := &adminServiceImpl{accountRepo: repo, accountDuplicateRepo: repo}
  154. source := &Account{
  155. Name: "source",
  156. Platform: PlatformOpenAI,
  157. Type: AccountTypeAPIKey,
  158. Extra: map[string]any{
  159. codexFingerprintModeExtraKey: "session",
  160. codexFingerprintSeedExtraKey: testCodexFingerprintSeed,
  161. },
  162. }
  163. require.NoError(t, repo.Create(ctx, source))
  164. duplicate, err := svc.DuplicateAccount(ctx, source.ID, "admin:1", "")
  165. require.NoError(t, err)
  166. require.NotEqual(t, source.ID, duplicate.ID)
  167. require.NotContains(t, duplicate.Extra, codexFingerprintSeedExtraKey)
  168. require.Equal(t, "session", duplicate.Extra[codexFingerprintModeExtraKey])
  169. }
  170. func TestDuplicateCreatePathMintsFreshSeedWhenEligible(t *testing.T) {
  171. extra, err := duplicateAccountExtra(map[string]any{
  172. codexFingerprintModeExtraKey: "session",
  173. codexFingerprintSeedExtraKey: testCodexFingerprintSeed,
  174. })
  175. require.NoError(t, err)
  176. account, err := buildAccountForCreate(&CreateAccountInput{
  177. Name: "eligible-copy",
  178. Platform: PlatformOpenAI,
  179. Type: AccountTypeOAuth,
  180. Extra: extra,
  181. }, extra)
  182. require.NoError(t, err)
  183. require.NotEqual(t, testCodexFingerprintSeed, requireValidCodexFingerprintSeed(t, account.Extra))
  184. require.Equal(t, "session", account.Extra[codexFingerprintModeExtraKey])
  185. }
  186. func TestAccountServiceCreateAndUpdateCodexSeedLifecycle(t *testing.T) {
  187. ctx := context.Background()
  188. repo := &upstreamBillingProbeAccountRepo{accounts: make(map[int64]*Account)}
  189. svc := NewAccountService(repo, nil)
  190. created, err := svc.Create(ctx, CreateAccountRequest{
  191. Name: "legacy-create",
  192. Platform: PlatformOpenAI,
  193. Type: AccountTypeOAuth,
  194. Extra: map[string]any{
  195. codexFingerprintModeExtraKey: "session",
  196. codexFingerprintSeedExtraKey: userSuppliedCodexFingerprintSeed,
  197. },
  198. })
  199. require.NoError(t, err)
  200. createdSeed := requireValidCodexFingerprintSeed(t, created.Extra)
  201. require.NotEqual(t, userSuppliedCodexFingerprintSeed, createdSeed)
  202. updateSeed := userSuppliedCodexFingerprintSeed
  203. updated, err := svc.Update(ctx, created.ID, UpdateAccountRequest{
  204. Extra: &map[string]any{
  205. codexFingerprintModeExtraKey: "full",
  206. codexFingerprintSeedExtraKey: updateSeed,
  207. },
  208. })
  209. require.NoError(t, err)
  210. require.Equal(t, createdSeed, requireValidCodexFingerprintSeed(t, updated.Extra))
  211. require.Equal(t, "full", updated.Extra[codexFingerprintModeExtraKey])
  212. }