ops_write_pressure_integration_test.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. //go:build integration
  2. package repository
  3. import (
  4. "context"
  5. "testing"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/service"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestOpsRepositoryBatchInsertErrorLogs(t *testing.T) {
  11. ctx := context.Background()
  12. _, _ = integrationDB.ExecContext(ctx, "TRUNCATE ops_error_logs RESTART IDENTITY")
  13. repo := NewOpsRepository(integrationDB).(*opsRepository)
  14. now := time.Now().UTC()
  15. inserted, err := repo.BatchInsertErrorLogs(ctx, []*service.OpsInsertErrorLogInput{
  16. {
  17. RequestID: "batch-ops-1",
  18. ErrorPhase: "upstream",
  19. ErrorType: "upstream_error",
  20. Severity: "error",
  21. StatusCode: 429,
  22. ErrorMessage: "rate limited",
  23. CreatedAt: now,
  24. },
  25. {
  26. RequestID: "batch-ops-2",
  27. ErrorPhase: "internal",
  28. ErrorType: "api_error",
  29. Severity: "error",
  30. StatusCode: 500,
  31. ErrorMessage: "internal error",
  32. CreatedAt: now.Add(time.Millisecond),
  33. },
  34. })
  35. require.NoError(t, err)
  36. require.EqualValues(t, 2, inserted)
  37. var count int
  38. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM ops_error_logs WHERE request_id IN ('batch-ops-1', 'batch-ops-2')").Scan(&count))
  39. require.Equal(t, 2, count)
  40. }
  41. func TestEnqueueSchedulerOutbox_DeduplicatesIdempotentEvents(t *testing.T) {
  42. ctx := context.Background()
  43. _, _ = integrationDB.ExecContext(ctx, "TRUNCATE scheduler_outbox RESTART IDENTITY")
  44. accountID := int64(12345)
  45. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountChanged, &accountID, nil, nil))
  46. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountChanged, &accountID, nil, nil))
  47. var count int
  48. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM scheduler_outbox WHERE event_type = $1", service.SchedulerOutboxEventAccountChanged).Scan(&count))
  49. require.Equal(t, 1, count)
  50. var firstID int64
  51. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT id FROM scheduler_outbox WHERE event_type = $1", service.SchedulerOutboxEventAccountChanged).Scan(&firstID))
  52. events, err := NewSchedulerOutboxRepository(integrationDB).ListAfterAndReleaseDedup(ctx, 0, 100)
  53. require.NoError(t, err)
  54. require.Len(t, events, 1)
  55. require.Equal(t, firstID, events[0].ID)
  56. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountChanged, &accountID, nil, nil))
  57. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM scheduler_outbox WHERE event_type = $1", service.SchedulerOutboxEventAccountChanged).Scan(&count))
  58. require.Equal(t, 2, count)
  59. }
  60. func TestSchedulerOutbox_ListAfterAndReleaseDedup_AllowsSameKeyWhileEventInFlight(t *testing.T) {
  61. ctx := context.Background()
  62. _, _ = integrationDB.ExecContext(ctx, "TRUNCATE scheduler_outbox RESTART IDENTITY")
  63. accountID := int64(17345)
  64. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountChanged, &accountID, nil, nil))
  65. events, err := NewSchedulerOutboxRepository(integrationDB).ListAfterAndReleaseDedup(ctx, 0, 100)
  66. require.NoError(t, err)
  67. require.Len(t, events, 1)
  68. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountChanged, &accountID, nil, nil))
  69. var count int
  70. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM scheduler_outbox WHERE event_type = $1", service.SchedulerOutboxEventAccountChanged).Scan(&count))
  71. require.Equal(t, 2, count)
  72. var pendingKeys int
  73. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM scheduler_outbox WHERE dedup_key IS NOT NULL").Scan(&pendingKeys))
  74. require.Equal(t, 1, pendingKeys)
  75. }
  76. func TestEnqueueSchedulerOutbox_CoalescesAccountStateBurst(t *testing.T) {
  77. ctx := context.Background()
  78. _, _ = integrationDB.ExecContext(ctx, "TRUNCATE scheduler_outbox RESTART IDENTITY")
  79. accountID := int64(22345)
  80. for range 50 {
  81. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountChanged, &accountID, nil, nil))
  82. }
  83. var count int
  84. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM scheduler_outbox WHERE event_type = $1", service.SchedulerOutboxEventAccountChanged).Scan(&count))
  85. t.Logf("same-account account_changed burst: calls=50 inserted=%d", count)
  86. require.Equal(t, 1, count)
  87. }
  88. func TestEnqueueSchedulerOutbox_DoesNotDeduplicateDifferentPayload(t *testing.T) {
  89. ctx := context.Background()
  90. _, _ = integrationDB.ExecContext(ctx, "TRUNCATE scheduler_outbox RESTART IDENTITY")
  91. accountID := int64(32345)
  92. payload1 := map[string]any{"group_ids": []int64{1}}
  93. payload2 := map[string]any{"group_ids": []int64{2}}
  94. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountChanged, &accountID, nil, payload1))
  95. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountChanged, &accountID, nil, payload2))
  96. var count int
  97. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM scheduler_outbox WHERE event_type = $1", service.SchedulerOutboxEventAccountChanged).Scan(&count))
  98. require.Equal(t, 2, count)
  99. }
  100. func TestEnqueueSchedulerOutbox_DoesNotDeduplicateLastUsed(t *testing.T) {
  101. ctx := context.Background()
  102. _, _ = integrationDB.ExecContext(ctx, "TRUNCATE scheduler_outbox RESTART IDENTITY")
  103. accountID := int64(67890)
  104. payload1 := map[string]any{"last_used": map[string]int64{"67890": 100}}
  105. payload2 := map[string]any{"last_used": map[string]int64{"67890": 200}}
  106. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountLastUsed, &accountID, nil, payload1))
  107. require.NoError(t, enqueueSchedulerOutbox(ctx, integrationDB, service.SchedulerOutboxEventAccountLastUsed, &accountID, nil, payload2))
  108. var count int
  109. require.NoError(t, integrationDB.QueryRowContext(ctx, "SELECT COUNT(*) FROM scheduler_outbox WHERE event_type = $1", service.SchedulerOutboxEventAccountLastUsed).Scan(&count))
  110. require.Equal(t, 2, count)
  111. }