Skip to content

Add integration tests module with contract, concurrency, and E2E tests (KOJAK-20)#12

Merged
endrju19 merged 15 commits intomainfrom
feat103
Apr 1, 2026
Merged

Add integration tests module with contract, concurrency, and E2E tests (KOJAK-20)#12
endrju19 merged 15 commits intomainfrom
feat103

Conversation

@endrju19
Copy link
Copy Markdown
Collaborator

Summary

  • New okapi-integration-tests module (non-published) with comprehensive test suite
  • Refactored existing E2E tests from BehaviorSpec to FunSpec (fixing fragile isolation)
  • Refactored PostgresOutboxStore.claimPending() from raw SQL to type-safe Exposed DSL with ForUpdateOption.SKIP_LOCKED
  • Added MySQL composite index + FORCE INDEX hint to fix InnoDB locking behavior with SKIP LOCKED

What's included

Test support infrastructure

  • PostgresTestSupport, MysqlTestSupport, KafkaTestSupport — Testcontainers wrappers
  • RecordingMessageDeliverer — thread-safe test spy with assertNoAmplification()

Store contract tests (16 tests)

Shared via FunSpec extension function, run on both Postgres and MySQL:

  • persist/read roundtrip, ordering, limit, status filtering, update, purge, count, empty edge case

Concurrency tests (4 tests)

  • Deterministic (CountDownLatch + virtual threads): Thread A holds lock → Thread B gets disjoint entries via SKIP LOCKED
  • Realistic (CyclicBarrier + virtual thread executor): 5 concurrent processors, verify zero delivery amplification

Kafka transport integration (5 tests)

  • Real Kafka broker via Testcontainers: topic delivery, headers, partition key, null key, success result

E2E tests (11 tests)

  • HttpEndToEndTest (Postgres + HTTP): 6 tests including transaction rollback and retry exhaustion (new scenarios)
  • MysqlHttpEndToEndTest (MySQL + HTTP): 4 tests
  • KafkaEndToEndTest (Postgres + Kafka): full pipeline

Cleanup

  • Removed old OutboxEndToEndTest and MysqlOutboxEndToEndTest (BehaviorSpec, fragile isolation)
  • Cleaned up test dependencies from okapi-spring-boot and okapi-mysql

Key discovery: MySQL FORCE INDEX

Concurrency tests revealed that without FORCE INDEX(idx_outbox_status_created_at), InnoDB locks ALL examined rows during SELECT ... FOR UPDATE SKIP LOCKED (full table scan), not just the LIMIT'd ones. Added composite index migration + FORCE INDEX hint.

Test plan

  • ./gradlew test -x :okapi-integration-tests:test — unit tests pass
  • ./gradlew :okapi-integration-tests:test — all 36 integration tests pass (requires Docker)
  • ./gradlew ktlintCheck — formatting clean

Base automatically changed from feat102 to main April 1, 2026 08:09
endrju19 added 15 commits April 1, 2026 10:12
Shared ConcurrentClaimTests extension function with two tests:
- Deterministic: verifies disjoint claim sets when one processor
  holds locks while another claims concurrently
- Realistic: 5 virtual threads process 50 entries with no
  delivery amplification

Also adds (status, created_at) index migration for Postgres
to improve claimPending query performance.
Adds MysqlConcurrentClaimTest reusing the shared test suite.

Fixes MySQL claimPending to use FORCE INDEX on (status, created_at)
so that FOR UPDATE SKIP LOCKED only row-locks the rows returned by
LIMIT. Without the index hint, InnoDB locks all rows matching the
WHERE clause during a full table scan, preventing concurrent
processors from claiming disjoint sets.

Changes:
- Add (status, created_at) index to OutboxTable and Liquibase migration
- Add FORCE INDEX hint to MysqlOutboxStore.claimPending()
Migrated to okapi-integration-tests module:
- OutboxEndToEndTest (Postgres+HTTP) → HttpEndToEndTest
- MysqlOutboxEndToEndTest (MySQL+HTTP) → MysqlHttpEndToEndTest

Removed Testcontainers/WireMock/Liquibase test deps from
okapi-spring-boot and okapi-mysql modules.
…e(SKIP_LOCKED)

Replace raw SQL string + manual ResultSet mapping with type-safe
Exposed DSL query and ForUpdateOption.PostgreSQL.SKIP_LOCKED.
Eliminates mapFromResultSet() in favor of ResultRow.toOutboxEntry().

MySQL keeps raw SQL because InnoDB requires FORCE INDEX hint for
correct SKIP LOCKED behavior, which Exposed DSL cannot express.
- Add missing `eq` import in PostgresOutboxStore (lost during merge)
- Update removeDeliveredBefore call to match new signature (time, limit)
Changeset IDs were not updated when migration files were renumbered
during rebase (003→004 for Postgres, 002→003 for MySQL).
@endrju19 endrju19 merged commit 539c951 into main Apr 1, 2026
7 checks passed
@endrju19 endrju19 deleted the feat103 branch April 1, 2026 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants