fix(test): WebhookE2ETest — pool=4 + tighter timeouts + truncate outbox

Предыдущий cleanup-fix не помог CI: ConditionTimeout 60s × 2 теста.
Trace показал WebhookDispatcher initialized + ZERO sendTick/matchTick
лог lines после этого. Spring @Scheduled default pool size = 1; если
sendTick блокируется на HTTP timeout (5s) к dead leftover URL —
matchTick тоже не запускается до конца HTTP send.

CI Postgres testcontainer withReuse(true) тащит leftover deliveries
из предыдущих runs. Их слишком много чтобы fail'ить за 60s с pool=1.

Фикс комбо:
- spring.task.scheduling.pool.size=4 — matchTick/sendTick параллельно,
  один blocked на dead URL не стопает другие
- ordinis.webhook.send-timeout-ms=1000 — leftover dead URLs fail'ят
  за 1с вместо 5с (5x faster cleanup)
- truncate outbox_events дополнительно — matchTick не порождает
  delivery rows на старые события

Локально 2/2 passed за 9.9s. Race-warnings про FK violation
(matchTick видит cached subscription но row deleted в @BeforeEach) —
noise, тест проходит.
This commit is contained in:
Zimin A.N.
2026-05-06 21:27:07 +03:00
parent 8fd0da957a
commit fbb0aa121a
@@ -60,6 +60,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
"ordinis.webhook.match-interval-ms=200", "ordinis.webhook.match-interval-ms=200",
"ordinis.webhook.send-interval-ms=200", "ordinis.webhook.send-interval-ms=200",
"ordinis.webhook.allowed-hosts=localhost,127.0.0.1", "ordinis.webhook.allowed-hosts=localhost,127.0.0.1",
// CI: postgres testcontainer withReuse(true) тащит leftover
// deliveries с dead URLs из предыдущих runs. Default send-timeout
// 5000ms × N leftovers + scheduling pool=1 → блокирует matchTick
// на минуты, наш event не доходит до receiver. Жмём timeout до
// 1s + thread pool 4 чтобы matchTick/sendTick не сериализовались.
"ordinis.webhook.send-timeout-ms=1000",
"spring.task.scheduling.pool.size=4",
// Spring auto-config Kafka не нужно — нет KafkaTemplate потребителей // Spring auto-config Kafka не нужно — нет KafkaTemplate потребителей
// без OutboxPoller. Иначе при старте Boot попытается подключиться // без OutboxPoller. Иначе при старте Boot попытается подключиться
// к localhost:9092 и завесит контекст. // к localhost:9092 и завесит контекст.
@@ -93,10 +100,14 @@ class WebhookE2ETest {
// Cleanup leftover state из предыдущего CI run. // Cleanup leftover state из предыдущего CI run.
// Postgres testcontainer withReuse(true) сохраняет данные между // Postgres testcontainer withReuse(true) сохраняет данные между
// запусками — старые subscriptions с dead URLs дают `findDue()` // запусками — старые subscriptions с dead URLs дают `findDue()`
// нагрузку (5s timeout × N = слишком долго до нашего нового event'а). // нагрузку (timeout × N = слишком долго до нашего нового event'а).
// Очищаем deliveries сначала (FK на subscriptions), потом subscriptions. // Также очищаем outbox_events чтобы matchTick не порождал deliveries
// на старые события (которые соответствуют scope=PUBLIC subscription'у
// нашего теста).
// Order: deliveries (FK), subscriptions, outbox events.
deliveryRepo.deleteAllInBatch(); deliveryRepo.deleteAllInBatch();
subRepo.deleteAllInBatch(); subRepo.deleteAllInBatch();
outboxRepo.deleteAllInBatch();
receiver = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); receiver = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
receiver.createContext("/hook", exchange -> { receiver.createContext("/hook", exchange -> {