proxy_repeated_expiry_integration_test.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. //go:build integration
  2. package repository
  3. import (
  4. "encoding/json"
  5. "time"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. )
  8. func (s *ProxyExpirySuite) TestSweep_RepeatedExpiryPreservesOriginalProxy() {
  9. for _, direct := range []bool{false, true} {
  10. s.Run(map[bool]string{false: "next backup", true: "direct"}[direct], func() {
  11. now := time.Now()
  12. past := now.Add(-time.Hour)
  13. soon := now.Add(time.Hour)
  14. later := now.Add(48 * time.Hour)
  15. originalID := s.mkProxy("repeated-original", service.FallbackModeProxy, &past, nil)
  16. middleID := s.mkProxy("repeated-middle", service.FallbackModeDirect, &soon, nil)
  17. lastID := s.mkProxy("repeated-last", service.FallbackModeNone, &later, nil)
  18. // Populate directed foreign keys directly so this regression does not depend
  19. // on the separate ORM backup-edge fix.
  20. _, err := s.tx.ExecContext(s.ctx, `UPDATE proxies SET backup_proxy_id=$1 WHERE id=$2`, middleID, originalID)
  21. s.Require().NoError(err)
  22. if !direct {
  23. _, err = s.tx.ExecContext(s.ctx, `UPDATE proxies SET fallback_mode='proxy',backup_proxy_id=$1 WHERE id=$2`, lastID, middleID)
  24. s.Require().NoError(err)
  25. }
  26. accountID := s.mkAccountWithProxy(originalID)
  27. changed, err := s.repo.SweepExpiredProxies(s.ctx, now)
  28. s.Require().NoError(err)
  29. s.Require().EqualValues(1, changed)
  30. s.Require().Equal(&middleID, s.accountProxyID(accountID))
  31. changed, err = s.repo.SweepExpiredProxies(s.ctx, now.Add(2*time.Hour))
  32. s.Require().NoError(err)
  33. s.Require().EqualValues(1, changed, "accounts already in fallback must still be rerouted")
  34. if direct {
  35. s.Require().Nil(s.accountProxyID(accountID))
  36. } else {
  37. s.Require().Equal(&lastID, s.accountProxyID(accountID))
  38. }
  39. var origin *int64
  40. err = scanSingleRow(s.ctx, s.tx, `SELECT proxy_fallback_origin_id FROM accounts WHERE id=$1`, []any{accountID}, &origin)
  41. s.Require().NoError(err)
  42. s.Require().Equal(&originalID, origin, "manual revert must retain the first original proxy")
  43. var payloadRaw []byte
  44. err = scanSingleRow(s.ctx, s.tx, `SELECT payload FROM scheduler_outbox WHERE event_type=$1 ORDER BY id DESC LIMIT 1`, []any{service.SchedulerOutboxEventAccountBulkChanged}, &payloadRaw)
  45. s.Require().NoError(err)
  46. var payload struct {
  47. AccountIDs []int64 `json:"account_ids"`
  48. }
  49. s.Require().NoError(json.Unmarshal(payloadRaw, &payload))
  50. s.Require().Equal([]int64{accountID}, payload.AccountIDs)
  51. changed, err = s.repo.SweepExpiredProxies(s.ctx, now.Add(3*time.Hour))
  52. s.Require().NoError(err)
  53. s.Require().Zero(changed, "repeating the scan must be idempotent")
  54. accounts := newAccountRepositoryWithSQL(s.tx.Client(), s.tx, nil)
  55. s.Require().NoError(accounts.RevertProxyFallback(s.ctx, accountID))
  56. s.Require().Equal(&originalID, s.accountProxyID(accountID))
  57. err = scanSingleRow(s.ctx, s.tx, `SELECT proxy_fallback_origin_id FROM accounts WHERE id=$1`, []any{accountID}, &origin)
  58. s.Require().NoError(err)
  59. s.Require().Nil(origin)
  60. })
  61. }
  62. }