Testing and Quality

This document is the single source of truth for what we test, how tests are classified, which product behaviors they prove, what we assert, how to run the suite, and what is still missing. Update it whenever you add or materially change a test.


1. Objectives

Objective What “good” means in tests
Domain correctness Order lifecycle and cancellation rules hold; illegal transitions throw or are rejected.
Idempotency and concurrency Same idempotency key yields one order; IN_PROGRESS blocks duplicates; recovery works; parallel retries do not fork rows.
Outbox and messaging Claims set lease metadata; publish success marks SENT via markSentIfLeased; failures route to retry policy; Kafka publisher circuit opens after repeated failures.
Consumer semantics Inbound events dedupe without silently changing order status when the scheduler owns promotion.
HTTP and JSON contracts Status codes, ApiError codes, and stable JSON keys for errors and domain DTOs where contract-tested.
Infrastructure behavior Cache hit/miss/degrade; rate limit allow vs 429; regional passive mode blocks writes and surfaces in health.
Backpressure Level (NORMAL / ELEVATED / CRITICAL) and shouldRejectWrites / throttling factor from backlog and pool/lag signals (mocked).
Persistence JPA queries order and filter correctly; processed-event table enforces uniqueness.
Architecture Domain packages do not depend on infrastructure or Spring Framework types.
Real dependencies (IT) App context starts against Postgres + Redis and (separately) Kafka bootstrap from Testcontainers.

2. Visual overview

2.1 Test pyramid (volume vs. scope)

Broader, slower tests are fewer; fast unit tests are the bulk of the suite.

flowchart TB
    subgraph top [Narrow / expensive]
        IT[Testcontainers ITs<br/>Postgres+Redis, Kafka]
    end
    subgraph mid [Slices and contracts]
        SLICE[JPA @DataJpaTest<br/>Web @WebMvcTest]
        CONT[JSON contract tests<br/>ApiError, OrderCreatedEvent]
        ARCH[ArchUnit]
    end
    subgraph base [Broad / cheap]
        UNIT[Unit tests<br/>domain, services, outbox, filters]
    end
    base --> mid
    mid --> top

2.2 Where tests attach to the system (logical map)

This is not a deployment diagram; it shows which layer each family of tests targets.

flowchart LR
    subgraph clients [Clients]
        HTTP[HTTP / JSON]
    end
    subgraph api [API layer]
        C[OrderController]
        GH[GlobalExceptionHandler]
    end
    subgraph app [Application]
        OS[OrderService]
        OQ[OrderQueryService]
    end
    subgraph infra [Infrastructure]
        OBOX[Outbox fetch/process/publish]
        KAFKA[KafkaEventPublisher]
        CONS[OrderCreatedConsumer]
        RL[RateLimitingFilter]
        CACHE[RedisCacheProvider]
        REG[Regional / Backpressure]
    end
    subgraph data [Data]
        JPA[JPA repositories]
        REDIS[(Redis)]
        PG[(PostgreSQL)]
    end
    HTTP --> C
    C --> OS
    C --> OQ
    OS --> JPA
    OBOX --> KAFKA
    CONS --> JPA
    RL --> REDIS
    CACHE --> REDIS

Attachment points (examples):

Attachment Test examples
Raw domain OrderAggregateTest
Service + ports OrderServiceIdempotencyLifecycleTest, OutboxServiceTest
HTTP + security OrderControllerIntegrationTest, OrderControllerWebMvcTest
Error mapping GlobalExceptionHandlerTest, ApiErrorJsonContractTest
Outbox pipeline OutboxFetcherTest, OutboxProcessorTest, OutboxPublisherTest, OutboxRetryHandlerTest
Kafka producer KafkaEventPublisherTest
Consumer OrderCreatedConsumerUnitTest, OrderCreatedEventContractTest
Cross-cutting RateLimitingFilterTest, RedisCacheProviderTest, RegionalFailoverManagerTest, BackpressureManagerResilienceTest
DB mapping SpringOrderJpaRepositoryDataJpaTest, SpringProcessedEventJpaRepositoryDataJpaTest
Full stack + containers PostgresRedisContainersApplicationIT, KafkaContainerApplicationIT

2.3 Request/response path with assertion focus (HTTP slice)

sequenceDiagram
    participant T as Tests
    participant M as MockMvc
    participant S as Security
    participant C as OrderController
    participant SV as OrderService / Query
    T->>M: POST /orders + JWT + Idempotency-Key
    M->>S: authenticate
    S->>C: principal
    C->>SV: createOrder(...)
    SV-->>C: OrderResponse
    C-->>M: 201 + JSON body
    Note over T,M: Integration/WebMvc: assert status, body paths, 401/403/404

3. Test types (reference)

Each row is a category used in this repo. A single class stays in one primary category; OrderControllerIntegrationTest is API integration (MockMvc + full security stack + @MockBean repositories), not Testcontainers.

Type Definition Typical annotations / tools What we usually assert
Unit Isolated class under test; collaborators mocked or in-memory fakes. JUnit 5, Mockito State changes, return values, exceptions, verify(...) on mocks.
JPA slice Spring Data + Hibernate only; embedded DB. @DataJpaTest, @AutoConfigureTestDatabase, H2 Entity counts, page content order, existsBy..., DB constraint exceptions.
Web slice Dispatcher, controller, validation, security filter chain; no real DB. @WebMvcTest, MockMvc, jwt() HTTP status, jsonPath, validation 400 without invoking services for real.
API integration (in-process) Same as web slice breadth but often more scenarios; may use @SpringBootTest + MockMvc + mocks—this project uses a dedicated integration test class. MockMvc, @MockBean End-to-end HTTP rules: auth, idempotency headers, role behavior.
Contract Serialized shape stability for integrations (BFFs, consumers). Jackson, fixed JSON strings Presence of JSON keys; values for canonical payloads; backward compatibility.
Architecture Dependency rules between packages. ArchUnit @AnalyzeClasses No forbidden edges between package sets.
Resilience (simulated) Threshold and policy behavior with mocks—not production fault injection. Mockito, HikariPoolMXBean mock BackpressureManager level, shouldRejectWrites, throttling factor.
Integration (Testcontainers) Real processes: Postgres, Redis, Kafka broker. @SpringBootTest, Testcontainers Context starts; JDBC / Redis / KafkaTemplate wiring smoke checks.

4. How to run

Command When to use
mvn clean test Full suite: unit, slices, contracts, ArchUnit, resilience mocks, and Testcontainers ITs if Docker is available.
mvn -Dtest='!*IT' test Skip classes whose names end with IT (no Docker / faster feedback).

Docker: PostgresRedisContainersApplicationIT and KafkaContainerApplicationIT use @Testcontainers(disabledWithoutDocker = true). No Docker → those tests are skipped, not failed.

Coverage report: target/site/jacoco/index.html after mvn clean test.


5. Detailed test catalog (features and assertions)

Convention: Feature = product or technical behavior under test. Assertions = observable outcomes checked in that test (not every assert line, but the intent).

5.1 Domain (unit)

Class Test / method Feature under test Assertions (summary)
OrderAggregateTest shouldAllowValidLifecycleTransitions Valid status chain Final status DELIVERED.
  shouldRejectInvalidTransitionFromDelivered Terminal state immutability ConflictException when moving from DELIVERED.
  shouldAllowCancelOnlyWhenPending Cancel rules PENDINGCANCELLED; non-pending cancel throws.
  shouldPromotePendingOnlyOnce Idempotent promotion First promotePendingToProcessing true; second false; stays PROCESSING.

5.2 Application services (unit)

Class Test / method Feature under test Assertions (summary)
OrderServiceIdempotencyLifecycleTest sameRequestSameKeyReturnsSameOrder Idempotent create Two creates same UUID; one persisted row.
  inProgressStatePreventsDuplicateThenRetrySucceeds Crash window Error while IN_PROGRESS; after clear, create succeeds once.
  completedStateAlwaysReusesSameOrder Completed idempotency All calls return same id; one row.
  concurrentRetriesDoNotCreateDuplicateOrders Concurrent idempotency One row in repo; one success UUID; one failure whose message matches in-progress/ conflict pattern.
OrderServiceScheduledPromotionTest promotePendingOrdersScheduled_movesAllPendingToProcessing Scheduler sweep Both pending orders become PROCESSING.
OutboxServiceTest enqueueOrderCreatedPersistsPendingOutboxWithPartitionAndPayload Enqueue outbox row Captured entity: aggregate type/id, event type, payload, PENDING, retry 0, partition from hash, nextAttemptAt set.
OrderMapperTest toDomainItems_mapsAllFields Request → domain items Product, quantity, price per line.
  toDomain_roundTripsRecord OrderRecordOrder Id, status, owner, version, items.
  toResponse_mapsDomainToDto OrderOrderResponse Id, status, item list fields.
  toRecord_includesRegionAndItems OrderOrderRecord regionId, lastUpdatedTimestamp, items.
  toEmbeddablesFromRecord_mapsItemRecords JPA embeddables Product, quantity, price on embeddables.
PendingToProcessingSchedulerTest promotePendingOrders_delegatesToOrderService Cron → service promotePendingOrdersScheduled() called.
  promotePendingOrders_swallowsRuntimeExceptionFromService Resilience Exception from service does not propagate; delegate still verified.
RegionalConsistencyManagerTest allowsWrites_delegatesToFailoverManager Write gating Mirrors RegionalFailoverManager.allowsWrites().
  shouldApplyIncomingUpdate_delegatesToStrategy Conflict strategy Returns strategy decision.
  shouldApplyIncomingUpdate_incrementsCounterWhenRejected Metrics on reject region.conflict.rejected.count incremented when strategy rejects.

5.3 HTTP — exception mapping and error envelope (unit)

Class Test / method Feature under test Assertions (summary)
GlobalExceptionHandlerTest handleNotFound_* NotFoundException 404, body code NOT_FOUND.
  handleConflict_* ConflictException 409, CONFLICT.
  handleForbidden_* ForbiddenException 403.
  handleInfrastructure_* InfrastructureException 503, code INFRASTRUCTURE_ERROR, safe user message.
  handleValidation_* MethodArgumentNotValidException 400, VALIDATION_FAILED, message mentions fields.
  handleMalformedJson_* Bad JSON 400, MALFORMED_REQUEST.
  handleConstraintViolation_* Constraint violations 400.
  handleBadRequest_* IllegalArgumentException 400, BAD_REQUEST.
  handleUnexpected_* Unknown errors 500; Micrometer counter api.errors.unexpected incremented.

5.4 HTTP — contract (JSON keys)

Class Test / method Feature under test Assertions (summary)
ApiErrorJsonContractTest serializesExpectedTopLevelKeys Stable error JSON JSON has code, message, requestId, timestamp; values match inputs.

5.5 HTTP — web slice (@WebMvcTest)

Class Test / method Feature under test Assertions (summary)
OrderControllerWebMvcTest postOrders_returns201WithBody Create order mapping 201; $.id, $.status match mocked service; JWT + idempotency header path.
  postOrders_validationError_returns400 Bean validation 400 for {}.
  getOrder_returns200 Get by id 200; $.status matches mock.

Note: RateLimitingFilter is mocked to pass-through; rate-limit behavior is in RateLimitingFilterTest and richer scenarios in OrderControllerIntegrationTest where applicable.

5.6 HTTP — API integration (OrderControllerIntegrationTest)

Test / method Feature under test Assertions (summary)
shouldRequireAuthentication Unauthenticated access 401 without JWT.
shouldCreateAndFetchOrderWhenAuthorized Happy path create + get 201 then 200; body consistency.
shouldRejectStatusUpdateForNonAdminRole Role guard on admin operation Non-admin rejected for status update.
shouldValidateHeaderAndPayload Validation / headers 400 for invalid input or missing headers per scenario.
shouldReturnSameOrderForSameIdempotencyKey HTTP idempotency Same key → same order id / no duplicate resource.
shouldCancelOwnPendingOrder Owner cancel Success for owner.
shouldRejectCancelWhenDifferentUser Cancel scope 403 for other user.
shouldAllowAdminToCancelAnotherUsersOrder Admin override Admin can cancel.
shouldReturnNotFoundWhenFetchingAnotherUsersOrder Read isolation 404 for non-owner non-admin.
shouldAllowAdminToFetchAnyOrderById Admin read Admin 200 for any id.
shouldListOnlyOwnOrdersForNonAdmin List scope Non-admin sees only own orders.
shouldListOrdersFilteredByPendingStatus Query filter status=PENDING filters results.
shouldAllowAdminToAdvanceStatusToShipped Admin PATCH PENDINGSHIPPED with version.

5.7 Messaging — outbox and Kafka producer (unit)

Class Test / method Feature under test Assertions (summary)
OutboxFetcherTest claimPartitionBatchMarksRowsInFlightWithinLease Claim pipeline Rows IN_FLIGHT; nextAttemptAt, leaseOwner, leaseVersion set; saveAll once.
OutboxProcessorTest processBatchMarksEventSentOnAsyncSuccess Success path Status SENT; markSentIfLeased called; retry handler not used.
  processBatchDelegatesToRetryHandlerOnAsyncFailure Publish failure retryHandler.handleFailure invoked.
OutboxPublisherTest pollAndPublishProcessesOwnedPartitionBatchesOnly Partition ownership Only owned partition claimed; processor invoked.
  cleanupSentEventsArchivesAndDeletesBatch Sent cleanup Archive + delete batch APIs called.
OutboxRetryHandlerTest handleFailurePersistsRetryMetadataAndSchedulesNextAttempt Transient failure Retry count, failure metadata, nextAttemptAt; markFailedIfLeased with expected args.
  handleFailureTerminalizesPermanentFailureImmediately Permanent failure Status FAILED; lease-aware update.
KafkaEventPublisherTest publishOrderCreatedCompletesOnAsyncSuccess Transactional send Future completes successfully.
  publishOrderCreatedOpensCircuitAfterConsecutiveFailures Circuit breaker Repeated failures → exceptional completion; InfrastructureException in chain.

5.8 Messaging — consumer and event contract

Class Test / method Feature under test Assertions (summary)
OrderCreatedConsumerUnitTest shouldAcknowledgeOrderCreatedWithoutPromotingStatusAndPersistProcessedMarker Dedupe without promotion Order stays PENDING; processed-event persisted.
OrderCreatedEventContractTest shouldSerializeWithStableContractFields Producer JSON v2 Keys: schemaVersion, eventId, eventType, orderId, occurredAt.
  shouldDeserializeCanonicalProducerPayloadV2 Round-trip v2 Field equality after deserialize.
  shouldDeserializeBackwardCompatibleV1PayloadWithoutSchemaVersion v1 compatibility Default schemaVersion = 1.
  shouldSupportForwardCompatiblePayloadWithFutureVersionAndOptionalFields Forward compat Deserializes with extra fields.
  shouldFailValidationWhenRequiredFieldMissing Invalid payloads Validation error path.
  shouldFailOnNonJsonPayload Garbage input Failure for non-JSON.

5.9 Infrastructure — cache, rate limit, regional, health

Class Test / method Feature under test Assertions (summary)
RedisCacheProviderTest getReturnsParsedValueOnCacheHit Cache hit Optional present; parsed field.
  getReturnsEmptyOnRedisFailureFallback Degraded read Empty optional on Redis error.
  putWithTtlWritesToRedis Write path set with TTL verified.
RateLimitingFilterTest allowsRequestWhenTokenBucketAllows Allow path Filter chain proceeds.
  blocksRequestWith429WhenTokenBucketRejects Block path Status 429; body contains RATE_LIMITED.
RegionalFailoverManagerTest switchesToPassiveWhenDependenciesUnhealthy Failover allowsWrites() false when unhealthy (multi-region on).
  remainsWritableWhenMultiRegionDisabled Feature flag Writes allowed when multi-region disabled.
MultiRegionHealthIndicatorTest healthIsUpWhenRegionActive Active region Health UP.
  healthIsOutOfServiceWhenRegionPassive Passive region OUT_OF_SERVICE.

5.10 Resilience — backpressure (mocked signals)

Class Test / method Feature under test Assertions (summary)
BackpressureManagerResilienceTest elevatedWhenOutboxBacklogCrossesElevatedOnly Backlog threshold Level ELEVATED; writes not rejected; throttling factor 0.70.
  criticalWhenBacklogHitsCriticalThreshold Critical backlog Level CRITICAL; shouldRejectWrites; throttling 0.35.
  kafkaLagAloneCanElevate Lag signal recordKafkaLagMsELEVATED without backlog.

5.11 Cross-region conflict strategies (unit)

Class Test / method Feature under test Assertions (summary)
HybridTimestampTest from_nullPhysicalUsesZeroMillis Factory defaults Physical millis 0; logical counter preserved.
  from_nullLogicalUsesZero Factory defaults Logical counter 0 when null.
  compareTo_ordersByPhysicalThenLogical Total order Earlier physical or lower logical sorts first.
  compareTo_nullOtherIsGreater Null-safe compare compareTo(null) returns 1.
VersionBasedConflictResolutionStrategyTest nullCurrent_alwaysApplies First write Incoming update applies when no current record.
  nullTimestamp_defaultsToApply Missing timestamp Applies when incoming timestamp null.
  newerHybridTimestamp_applies Last-write-wins Newer instant wins.
  olderHybridTimestamp_rejects Stale reject Older instant rejected.
  tieBreak_usesRegionIdWhenBothPresent Lexicographic tie-break Same timestamp: region id ordering decides apply/reject.

5.12 Persistence — JPA slice

Class Test / method Feature under test Assertions (summary)
SpringOrderJpaRepositoryDataJpaTest findByStatusOrderByCreatedAtAsc_returnsOldestFirstPage Scheduler ordering query Total count; first page oldest by createdAt.
  findByOwnerSubjectAndStatus_scopesToOwner Owner scoping Only matching owner rows.
SpringProcessedEventJpaRepositoryDataJpaTest existsByEventId_reflectsPersistedMarker Dedupe lookup existsByEventId true after save.
  duplicateEventIdViolatesUniqueConstraint DB uniqueness Second insert throws DataIntegrityViolationException.

5.13 Architecture

Class Rule Assertions (summary)
HexagonalArchitectureTest domainMustNotDependOnInfrastructure No ..domain....infrastructure.. imports.
  domainMustNotDependOnSpringFramework No ..domain..org.springframework.. imports.

5.14 Integration — Testcontainers

Class Test / method Feature under test Assertions (summary)
PostgresRedisContainersApplicationIT contextStartsAndJdbcAndRedisRoundTrip Real Postgres + Redis SELECT 1 = 1; Redis set/get round-trip.
KafkaContainerApplicationIT kafkaTemplateIsWired Real Kafka bootstrap KafkaTemplate non-null (context loads against broker).

6. Assertion categories (how to read failures)

Category Examples in this codebase
Equality Same UUID for idempotent creates; status enums; JSON field values.
Presence / structure jsonPath; contract tests JsonNode.has("code").
Exception type ConflictException, DataIntegrityViolationException.
HTTP status 401, 400, 403, 404, 409, 429, 503.
Mock verification verify(repository).markSentIfLeased(...); verify(chain).doFilter for allow path.
State on entities Outbox IN_FLIGHT, lease fields, SENT, FAILED.
Side-effect counters Micrometer api.errors.unexpected; regional conflict counter.

7. Critical messaging flows (compact diagram)

flowchart TD
    F[OutboxFetcherTest] --> F1[IN_FLIGHT + lease fields]
    P[OutboxProcessorTest] --> P1[markSentIfLeased on success]
    P --> P2[RetryHandler on async failure]
    R[OutboxRetryHandlerTest] --> R1[Transient → schedule]
    R --> R2[Permanent → FAILED]
    K[KafkaEventPublisherTest] --> K1[Circuit after failures]
    D[OrderCreatedConsumerUnitTest] --> D1[Dedupe; no silent promote]

8. Design standards

  • Deterministic: No reliance on wall-clock for correctness unless abstracted (schedulers often disabled in IT).
  • Observable outcomes first: Prefer status codes, JSON, and entity state over internal private methods.
  • Contract tests are strict: Renaming ApiError fields or event JSON keys without a version story should fail CI.
  • Integration tests are smoke + wiring: They do not replace domain or outbox unit coverage.

9. Continuous integration

  • Workflow: .github/workflows/ci.yml
  • Trigger: push to main / master / docs; pull requests to main / master
  • Command: mvn -B clean test on ubuntu-latest, JDK 17 (Temurin), Maven cache
  • Docker: Runners provide Docker; Testcontainers ITs should execute in CI.

10. Quality gates

  • mvn clean compile and mvn clean test must pass on mainline workflows.
  • JaCoCo: minimum line coverage ratio 0.65 on the bundle (jacoco:check); use HTML report for per-package gaps.
  • Changes to OrderService, OrderQueryService, or security should extend or preserve OrderControllerIntegrationTest and idempotency coverage where behavior changes.

11. Residual gaps (honest backlog)

  • Full Kafka E2E: outbox → publish → consume → dedupe row in one Testcontainers scenario (current ITs: wiring smoke only).
  • Lease expiry / reclaim: Worker crash simulation and second worker reclaim (unit tests cover logic; not full IT).
  • Active-active multi-region: Integration beyond RegionalFailoverManager mocks.
  • Chaos / toxics: No Toxiproxy or network partition automation; BackpressureManagerResilienceTest is mocked thresholds only.
  • GET /orders/page: Dedicated contract for totals and owner scoping if required by product.
  • Kafka log noise: Admin client may log connection attempts during some context loads; green tests are required; silencing logs is cleanup.

12. Principal reliability matrix

P0 (must not regress silently)

  • Lease-fenced outbox updates (markSentIfLeased / markFailedIfLeased) and consumer dedupe semantics.
  • Kafka publisher circuit and transactional send behavior under failure.
  • Consumer does not promote order status on ingest (scheduler owns PENDINGPROCESSING).

P1 / P2 (operational)

  • List and page performance at scale (not load-tested here).
  • Backpressure levels in production telemetry vs. BackpressureManagerResilienceTest doubles.
  • DLQ and replay procedures (not automated).

Maintenance: Add a row to section 5 (and adjust section 2.2 if the attachment map changes) when you introduce a test class. Update section 11 when you close a gap. Update sections 4, 9, 10 when run instructions, CI, or JaCoCo rules change.