Search code examples
openapispring-cloud-contract

Is it possible to have one OpenApi3 definition for contract and for contract test specification which will be parsable by spring-cloud-contract?


The idea is to have one file for both - contract definition and contract test specification.

I found the plugin 'guru.springframework:spring-cloud-contract-oa3:2.1.2.0' which should make what I want but it fails for me. I based my config on the examples from this repo https://github.com/springframeworkguru/sccoa3-fraud-example

Here is the error message after executing

gradle build clean

Error Processing yaml file. Skipping Contract Generation 
java.lang.IllegalStateException: Exception occurred while processing the file [.../build/stubs/META-INF/com.bla.bla/api-mlpe/0.0.1-SNAPSHOT/contracts/v1/0/openapi.yml]
...
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "openapi" (class org.springframework.cloud.contract.verifier.converter.YamlContract), not marked as ignorable (10 known properties: "response", "ignored", "label", "outputMessage", "input", "name", "description", "request", "inProgress", "priority"])
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: org.springframework.cloud.contract.verifier.converter.YamlContract["openapi"])

here is my build.gradle config

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.springframework.cloud:spring-cloud-contract-gradle-plugin:2.2.4.RELEASE'
    }
}

plugins {
    id 'org.springframework.boot' version '2.3.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

apply plugin: 'spring-cloud-contract'

group = 'com.bla.bla'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "Hoxton.SR8")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework:spring-web:5.2.9.RELEASE'
    implementation 'guru.springframework:spring-cloud-contract-oa3:2.1.2.0'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

contracts {
    testFramework = org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT5
}

test {
    useJUnitPlatform()
    testLogging {
        exceptionFormat = 'full'
    }
}

and here is openapi.yml (based on the repo mentioned earlier and placed under test/resources/contracts)

openapi: 3.0.3
info:
  description: Spring Cloud Contract Verifier Http Server OA3 Sample
  version: "1.0.0"
  title: Fraud Service API
paths:
  /v1/consumers/global-consumers:
    put:
      summary: Perform Fraud Check
      x-contracts:
        - contractId: 1
          name: Should Mark Client as Fraud
          priority: 1
        - contractId: 2
          name: Should Not Mark Client as Fraud
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                "client.id":
                  type: integer
                loanAmount:
                  type: integer
#START REQUEST - part with SPEC definitions
        x-contracts:
          - contractId: 1
            body:
              "client.id": 1234567890
              loanAmount: 99999
            matchers:
              body:
                - path: $.['client.id']
                  type: by_regex
                  value: "[0-9]{10}"
          - contractId: 2
            body:
              "client.id": 1234567890
              loanAmount: 123.123
            matchers:
              body:
                - path: $.['client.id']
                  type: by_regex
                  value: "[0-9]{10}"
#END REQUEST - part with SPEC definitions
      responses:
        '200':
          description: created ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  fraudCheckStatus:
                    type: string
                  "rejection.reason":
                    type: string
#START RESPONSE - part with SPEC definitions
          x-contracts:
            - contractId: 1
              body:
                fraudCheckStatus: "FRAUD"
                "rejection.reason": "Amount too high"
              headers:
                Content-Type: application/json;charset=UTF-8
            - contractId: 2
              body:
                fraudCheckStatus: "OK"
                "rejection.reason": null
              headers:
                Content-Type: application/json;charset=UTF-8
              matchers:
                body:
                  - path: $.['rejection.reason']
                    type: by_command
                    value: assertThatRejectionReasonIsNull($it)
#END RESPONSE - part with SPEC definitions

Apart from the fact that it fails I am not sure If my configuration is proper to achieve my goal :-) My goal is to have one file describing contract (in OpenApi3 standard) including contract test specification (in the same file which should not break the OpenAPI3 spec standard) and basing on this file I want to generate:

  • api model classes
  • java feign classes
  • node stub modules
  • wiremock stubs
  • api contract tests (jUnit5 or Spock)

Is it possible to have all of them in the way that I specified ? If yes - how to achieve that ? The mentioned plugin is not working for me in the provided configuration.


Solution

  • You need to add the guru.springframework:spring-cloud-contract-oa3:2.1.2.0 dependency to the plugin's classpath not to the project's classpath.

    Not like this:

    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath 'org.springframework.cloud:spring-cloud-contract-gradle-plugin:2.2.4.RELEASE'
        }
    }
    
    plugins {
        id 'org.springframework.boot' version '2.3.4.RELEASE'
        id 'io.spring.dependency-management' version '1.0.10.RELEASE'
        id 'java'
    }
    

    Like this:

    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath 'org.springframework.cloud:spring-cloud-contract-gradle-plugin:2.2.4.RELEASE'
            classpath 'guru.springframework:spring-cloud-contract-oa3:2.1.2.0'
        }
    }
    
    plugins {
        id 'org.springframework.boot' version '2.3.4.RELEASE'
        id 'io.spring.dependency-management' version '1.0.10.RELEASE'
        id 'java'
    }
    

    We describe that also in the docs (on another example) https://docs.spring.io/spring-cloud-contract/docs/2.2.4.RELEASE/reference/html/advanced.html#customization-plugin-dep


    Oh such a lame mistake. Thanks @marcin-grzejszczak After applying changes it looks better. Parsing yaml spec works.

    Now I have issue in the generateClientStubs

    after running gradle clean build -x test --stacktrace

    I’m getting

    Execution failed for task ':generateClientStubs'.
    13:43:33.759 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] > Class org.springframework.cloud.contract.verifier.converter.YamlContract does not implement the requested interface groovy.lang.GroovyObject
    ….
    Caused by: java.lang.IncompatibleClassChangeError: Class org.springframework.cloud.contract.verifier.converter.YamlContract does not implement the requested interface groovy.lang.GroovyObject
            at org.springframework.cloud.contract.verifier.converter.OpenApiContractConverter$_convertFrom_closure2$_closure5$_closure6.doCall(OpenApiContractConverter.groovy:84)
            at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    

    I found the issue reported some time ago which looks pretty similar [https://github.com/spring-cloud/spring-cloud-contract/issues/1289] Now the versions defined in my build gradle are the ones generated via spring initializr two days ago so I am assuming that they should be compatible. Is it possible that this issue comes from the added ‘guru.springframework:spring-cloud-contract-oa3’ plugin ? At first glance it rather looks like the mentioned similar reported issue.