Skip to content

Cross-service contract tests for the AS↔DC back-channel (lightweight shared-fixture, no Spring Cloud) #150

Closed

Description

@dfcoffin

Summary

Filed per the senior-architect review during PR D scoping (issue #148): the proper validation pattern for "do AS and DC actually agree on the back-channel JSON wire format at runtime" is a consumer-driven contract test (Spring Cloud Contract or Pact), NOT "two Spring contexts in one JVM" and NOT an in-process mock of either side.

Already-shipped coverage establishes the foundation:

  • DataCustodianBackchannelClientTest (PR C4) — AS as consumer, validates the wire shape against a MockRestServiceServer stubbed JSON contract
  • SubscriptionProvisioningControllerTest (PR B2) — DC as provider, validates the controller's request/response handling

What's missing is a shared contract artifact that both sides bind to, with a CI-enforced compatibility check.

Why contract testing is the right pattern

For independently-deployed Spring Boot services that communicate over HTTP, contract tests are the industry-standard pattern because they:

  1. Catch breaking changes when either side updates their DTO without the other knowing
  2. Run fast (no Spring context startup, no Docker, no real network)
  3. Are honest about what they cover (the contract; not customer UX, not production wiring, not network layers)
  4. Scale (adding a new endpoint requires one contract, not a cross-service integration test setup)

The "two Spring contexts in one JVM" alternative considered in #148 was rejected as non-idiomatic and brittle — it pretends to validate the cross-service flow but doesn't catch the failures that actually happen in production (proxy timeouts, CORS, TLS, ingress config).

Scope

Two interacting wire contracts to formalize:

Contract 1: AS → DC /internal/backchannel/v1/subscriptions

  • Request: BackchannelRequest (correlation_id, client_id, granted_scope, retail_customer_id, selected_usage_point_ids, customer_resource_uri)
  • Response 201: BackchannelResponse (authorization_id, resource_subscription_id, customer_subscription_id, resource_uri, authorization_uri, customer_resource_uri)
  • Response 400/500: error envelope shape

Contract 2: DC → AS /oauth2/introspect

  • Request: standard RFC 7662 (token + Basic auth)
  • Response 200: standard RFC 7662 + ESPI-specific claims (resourceURI, authorizationURI, customerResourceURI, authorization_id, resource_subscription_id, customer_subscription_id) per PR C4's customizer

Recommended approach

Spring Cloud Contract is the natural fit for a Spring Boot 4 monorepo:

Pact is a viable alternative but adds a Pact Broker deployment burden the project doesn't currently need.

Sub-tasks

  1. Add spring-cloud-contract-verifier to AS and DC parent POM
  2. Author Contract 1 in DC's src/test/resources/contracts/ — Groovy DSL files for the happy path + 400 + 500 cases
  3. Author Contract 2 in AS's src/test/resources/contracts/ — happy path + non-existent-token (active=false) cases
  4. AS consumer-side stubs — replace MockRestServiceServer in DataCustodianBackchannelClientTest with @AutoConfigureStubRunner (cleaner; the stub is generated from DC's contract)
  5. DC consumer-side stubs — wire AS's introspection contract into DC's resource-server tests
  6. CI enforcement — both modules' mvn verify fails if their contracts diverge from the published wire format
  7. Documentation — README section explaining the contract test pattern for future endpoints

Acceptance

  • Both wire contracts are formalized as machine-checkable artifacts
  • AS and DC builds verify their respective contracts on every CI run
  • Adding a new field to either DTO requires updating the contract (which fails CI until both producer + consumer agree)
  • PR D's DataCustodianBackchannelClientTest is migrated to use the generated stubs
  • Documentation exists for adding new cross-service contracts

Out of scope

Effort estimate

2-3 working days for someone familiar with Spring Cloud Contract. First-timers should expect 4-5 days including learning curve.

Related

Activity

  1. added
    enhancementNew feature or request
    taskA general task
    ESPI 4.0Touches the NAESB ESPI 4.0 implementation
    infrastructureCI, build, deployment, or developer tooling
    on Jun 3, 2026
  2. added 3 commits that reference this issue on Jun 4, 2026
  3. changed the title [-]Cross-service contract test between AS and DC's back-channel (Spring Cloud Contract / Pact)[/-] [+]Cross-service contract tests for the AS↔DC back-channel (lightweight shared-fixture, no Spring Cloud)[/+] on Jun 5, 2026
  4. dfcoffin commented on Jun 5, 2026

    @dfcoffin
    ContributorAuthor

    Scope/approach correction (supersedes the original "Spring Cloud Contract / Pact" framing).

    This issue predates two later decisions, so the title and parts of the body are stale:

    1. No Spring Cloud Contract / Pact. Per the standing architecture constraint (the AS, DC, and TP are deployed standalone on EC2 — not a Spring Cloud mesh), we use the repo's lightweight, framework-free consumer-driven contract pattern instead: a shared fixture under repo-root contracts/ that both sides bind to in their own fast unit tests (a producer test asserts the service emits the shape; a consumer test asserts it parses the shape). This is the same pattern already used by DC scope enforcement vocabulary doesn't match AS-issued ESPI FB scopes — customer tokens 403 on all resources #157, DC→TP notification contract test + TP-receiver stub #158, and AS↔DC token/introspection wire format diverges from ESPI standard (Batch URI forms + non-standard *_id claims) #160. Retitled accordingly.

    2. Contract 2 (introspection / token-response) is already delivered by AS↔DC token/introspection wire format diverges from ESPI standard (Batch URI forms + non-standard *_id claims) #160 — IntrospectionWireContractTest pins it against contracts/token-response-*.json. Note the authorization_id / resource_subscription_id / customer_subscription_id claims this issue listed for the introspection body were removed in AS↔DC token/introspection wire format diverges from ESPI standard (Batch URI forms + non-standard *_id claims) #160 (they were non-standard); the ESPI introspection/token body carries only the three *URI fields. Those *_id fields legitimately remain on the internal back-channel response (DC→AS), which is what Contract 1 covers.

    Remaining work = Contract 1 only: the AS→DC back-channel POST /internal/backchannel/v1/subscriptions request + 201 response. Delivered as:

    • contracts/backchannel-subscription-request.json and contracts/backchannel-subscription-response.json
    • DC provider test (consumes request, produces response; URIs bound to EspiBatchUri, scope to EspiScope)
    • AS consumer binding (the existing DataCustodianBackchannelClientTest happy-path migrated off its inline JSON onto the shared fixtures)
    • README section documenting the contract

    Out: the Spring Cloud Contract sub-tasks (verifier dependency, Groovy DSL, stub-runner) — not applicable.

  5. added 2 commits that reference this issue on Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ESPI 4.0Touches the NAESB ESPI 4.0 implementationenhancementNew feature or requestinfrastructureCI, build, deployment, or developer toolingtaskA general task

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions