account_repo_codex_fingerprint_seed_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. package repository
  2. import (
  3. "context"
  4. "errors"
  5. "regexp"
  6. "testing"
  7. "github.com/DATA-DOG/go-sqlmock"
  8. dbent "github.com/Wei-Shaw/sub2api/ent"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/stretchr/testify/require"
  11. "entgo.io/ent/dialect"
  12. entsql "entgo.io/ent/dialect/sql"
  13. )
  14. func TestBulkUpdateEnsuresCodexFingerprintSeedWithPerRowSQL(t *testing.T) {
  15. exec := &recordingSQLExecutor{result: rowsAffectedResult(0)}
  16. repo := newAccountRepositoryWithSQL(nil, exec, nil)
  17. _, err := repo.BulkUpdate(context.Background(), []int64{27, 28}, service.AccountBulkUpdate{
  18. Extra: map[string]any{
  19. "codex_fingerprint_mode": "session",
  20. "codex_fingerprint_seed": "22222222-2222-4222-8222-222222222222",
  21. },
  22. EnsureCodexFingerprintSeed: true,
  23. })
  24. require.NoError(t, err)
  25. require.NotEmpty(t, exec.execQueries)
  26. query := normalizeSQLWhitespace(exec.execQueries[0])
  27. require.Contains(t, query, "jsonb_set")
  28. require.Contains(t, query, "gen_random_uuid()::text")
  29. require.Contains(t, query, "platform = 'openai' AND type = 'oauth'")
  30. require.Contains(t, query, "to_jsonb(extra ->> 'codex_fingerprint_seed')")
  31. require.Contains(t, query, codexFingerprintSeedCanonicalPattern)
  32. require.NotContains(t, query, "22222222-2222-4222-8222-222222222222")
  33. require.NotEmpty(t, exec.execArgs)
  34. payload, ok := exec.execArgs[0][0].([]byte)
  35. require.True(t, ok)
  36. require.Equal(t, `{"codex_fingerprint_mode":"session"}`, string(payload))
  37. }
  38. func TestUpdateExtraEnsuresCodexFingerprintSeedAtomicallyWhenEnabling(t *testing.T) {
  39. db, mock, err := sqlmock.New()
  40. require.NoError(t, err)
  41. t.Cleanup(func() { _ = db.Close() })
  42. client := dbent.NewClient(dbent.Driver(entsql.OpenDB(dialect.Postgres, db)))
  43. t.Cleanup(func() { _ = client.Close() })
  44. mock.ExpectBegin()
  45. mock.ExpectExec(`(?s)UPDATE accounts SET extra = .*jsonb_set.*gen_random_uuid\(\)::text.*WHERE id = \$2 AND deleted_at IS NULL`).
  46. WithArgs(`{"codex_fingerprint_mode":"device"}`, int64(27)).
  47. WillReturnResult(sqlmock.NewResult(0, 1))
  48. mock.ExpectExec(regexp.QuoteMeta("INSERT INTO scheduler_outbox")).
  49. WithArgs(service.SchedulerOutboxEventAccountChanged, int64(27), nil, nil, sqlmock.AnyArg()).
  50. WillReturnResult(sqlmock.NewResult(1, 1))
  51. mock.ExpectCommit()
  52. repo := newAccountRepositoryWithSQL(client, db, nil)
  53. err = repo.UpdateExtra(context.Background(), 27, map[string]any{
  54. "codex_fingerprint_mode": "device",
  55. "codex_fingerprint_seed": "22222222-2222-4222-8222-222222222222",
  56. })
  57. require.NoError(t, err)
  58. require.NoError(t, mock.ExpectationsWereMet())
  59. }
  60. func TestBulkUpdateCodexFingerprintSeedRollsBackWhenUpdateFails(t *testing.T) {
  61. db, mock, err := sqlmock.New()
  62. require.NoError(t, err)
  63. t.Cleanup(func() { _ = db.Close() })
  64. client := dbent.NewClient(dbent.Driver(entsql.OpenDB(dialect.Postgres, db)))
  65. t.Cleanup(func() { _ = client.Close() })
  66. mock.ExpectBegin()
  67. mock.ExpectExec(`(?s)UPDATE accounts SET extra = .*gen_random_uuid\(\)::text.*WHERE id = ANY\(\$2\)`).
  68. WithArgs(sqlmock.AnyArg(), `{27,28}`).
  69. WillReturnError(errors.New("update failed"))
  70. mock.ExpectRollback()
  71. repo := newAccountRepositoryWithSQL(client, db, nil)
  72. rows, err := repo.BulkUpdate(context.Background(), []int64{27, 28}, service.AccountBulkUpdate{
  73. Extra: map[string]any{
  74. "codex_fingerprint_mode": "session",
  75. },
  76. EnsureCodexFingerprintSeed: true,
  77. })
  78. require.EqualError(t, err, "update failed")
  79. require.Zero(t, rows)
  80. require.NoError(t, mock.ExpectationsWereMet())
  81. }
  82. func TestBulkUpdateCodexFingerprintSeedRollsBackWhenOutboxFails(t *testing.T) {
  83. db, mock, err := sqlmock.New()
  84. require.NoError(t, err)
  85. t.Cleanup(func() { _ = db.Close() })
  86. client := dbent.NewClient(dbent.Driver(entsql.OpenDB(dialect.Postgres, db)))
  87. t.Cleanup(func() { _ = client.Close() })
  88. mock.ExpectBegin()
  89. mock.ExpectExec(`(?s)UPDATE accounts SET extra = .*gen_random_uuid\(\)::text.*WHERE id = ANY\(\$2\)`).
  90. WithArgs(sqlmock.AnyArg(), `{27,28}`).
  91. WillReturnResult(sqlmock.NewResult(0, 2))
  92. mock.ExpectExec(regexp.QuoteMeta("INSERT INTO scheduler_outbox")).
  93. WillReturnError(errors.New("outbox failed"))
  94. mock.ExpectRollback()
  95. repo := newAccountRepositoryWithSQL(client, db, nil)
  96. rows, err := repo.BulkUpdate(context.Background(), []int64{27, 28}, service.AccountBulkUpdate{
  97. Extra: map[string]any{
  98. "codex_fingerprint_mode": "full",
  99. },
  100. EnsureCodexFingerprintSeed: true,
  101. })
  102. require.EqualError(t, err, "outbox failed")
  103. require.Zero(t, rows)
  104. require.NoError(t, mock.ExpectationsWereMet())
  105. }