From d2475bacaebb1ace046ecff14355cd8f9a6e4629 Mon Sep 17 00:00:00 2001 From: prayagv <76861333+prayagv@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:29:23 +0530 Subject: [PATCH 1/2] Lock Pact provider dependencies and add mutation gate Adds Pact provider verification against the owned fixture, a PIT mutation testing gate over the redaction/retry/OpenAPI-coverage logic, and the dependency-lock and Allure-version fixes needed to keep both gates green: the allure plugin was pinned to a version that doesn't exist upstream, which blocked any lock regeneration, and several transitive dependencies needed forcing to patched versions to clear the OSV scan. --- CHANGELOG.md | 4 +- Jenkinsfile | 4 +- README.md | 2 +- build.gradle.kts | 44 +- docs/capability-status.md | 15 + docs/known-issues.md | 7 + docs/mutation-testing.md | 15 + gradle.lockfile | 428 ++++++++++-------- gradle/libs.versions.toml | 4 + .../framework/fixtures/OwnedApiProvider.java | 12 +- .../aria/framework/utils/RetryUtilsTest.java | 290 ++++++++++++ 11 files changed, 621 insertions(+), 204 deletions(-) create mode 100644 docs/capability-status.md create mode 100644 docs/known-issues.md create mode 100644 docs/mutation-testing.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 00cf3c2..9bbf126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Added Docker, Docker Compose, environment-based configuration, tagged test execution, and generated data factories. - Hardened audit findings with deterministic default tests, live-test opt-in, config validation, tracked data cleanup, security tests, OpenAPI coverage reporting, enforced quality gates, and dependency review. -## 1.1.0 - Unreleased +## 1.1.0 - 2026-07-04 - Replaced JavaFaker with Datafaker and refreshed Gradle dependency locks. - Reworked configuration and retry policy injection for easier isolated testing. @@ -16,3 +16,5 @@ - Replaced the README text architecture diagram with a linked SVG architecture diagram. - Added configuration, execution, writing-tests, debugging, dos/dont, audit-remediation, and ADR documentation. - Added atomic optimistic-concurrency evidence, portfolio metrics, reliability policy, failure triage, seeded-defect examples, and an application security threat model. +- Added mutation-score publishing through PITest for redaction, retry, and OpenAPI coverage utilities. +- Added Pact provider verification against the owned fixture and corrected the Allure plugin version pin that blocked dependency-lock regeneration. diff --git a/Jenkinsfile b/Jenkinsfile index c461618..bf2184e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,8 +1,8 @@ pipeline { agent any - // GitHub Actions is the primary CI quality gate. This Jenkinsfile is a parity-oriented - // starter for teams that need Jenkins-hosted execution. + // GitHub Actions is the primary CI quality gate. This Jenkinsfile is a reference + // pipeline for teams that need Jenkins-hosted execution. parameters { choice(name: 'ENV', choices: ['dev', 'staging', 'prod'], description: 'Target test environment') diff --git a/README.md b/README.md index c61e530..eed1fc4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ evidence. See [CHANGELOG.md](CHANGELOG.md). | Configuration | Owner, per-environment properties | | Containers | Docker, Docker Compose, Testcontainers | | Reporting and logging | Allure 2, SLF4J, Logback | -| CI/CD | Jenkins, GitHub Actions | +| CI/CD | GitHub Actions primary gate, Jenkins reference pipeline | ## Prerequisites diff --git a/build.gradle.kts b/build.gradle.kts index 8775f62..34b00e0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(libs.plugins.lombok) alias(libs.plugins.allure) alias(libs.plugins.cyclonedx) + alias(libs.plugins.pitest) alias(libs.plugins.spotless) alias(libs.plugins.spotbugs) } @@ -44,6 +45,7 @@ dependencies { // Test data generation and reporting tools testImplementation(libs.datafaker) testImplementation(libs.snakeyaml) + testImplementation(libs.swagger.request.validator.restassured) // Core Testing Framework (JUnit 5) testImplementation(platform(libs.junit.bom)) @@ -133,12 +135,27 @@ dependencyLocking { configurations.configureEach { resolutionStrategy.force( "org.apache.logging.log4j:log4j-api:2.25.4", - "org.apache.logging.log4j:log4j-core:2.25.4" + "org.apache.logging.log4j:log4j-core:2.25.4", + // GHSA-j288-q9x7-2f5v: PIT's own tool classpath pulls an old commons-lang3. + "org.apache.commons:commons-lang3:3.19.0", + // GHSA-72hv-8253-57qq / GHSA-hgj6-7826-r7m5 / GHSA-j3rv-43j4-c7qm / GHSA-rmj7-2vxq-3g9f: + // transitive tool classpaths (Gradle plugin resolution, Spotless) pull old Jackson. + "com.fasterxml.jackson.core:jackson-core:2.22.0", + "com.fasterxml.jackson.core:jackson-databind:2.22.0", + // GHSA-735f-pc8j-v9w8: old protobuf-java pulled in transitively. + "com.google.protobuf:protobuf-java:3.25.5", + // GHSA-wxr5-93ph-8wr9: old commons-beanutils pulled in transitively. + "commons-beanutils:commons-beanutils:1.11.0", + // GHSA-f58c-gq56-vjjf: old tika-core pulled in transitively. + "org.apache.tika:tika-core:3.2.2", + // GHSA-3w8q-xq97-5j7x: the constraints-based rhino pin above doesn't reach + // transient tool classpaths, so force it globally too. + "org.mozilla:rhino:1.7.14.1" ) } allure { - version.set("2.44.0") + version.set(libs.versions.allure.get()) adapter { aspectjWeaver.set(false) } @@ -418,6 +435,28 @@ spotbugs { reportLevel.set(com.github.spotbugs.snom.Confidence.MEDIUM) } +pitest { + junit5PluginVersion.set("1.2.1") + targetClasses.set( + setOf( + "com.aria.framework.reporting.RedactionPolicy", + "com.aria.framework.utils.RetryUtils", + "com.aria.framework.tools.OpenApiCoverageReporter" + ) + ) + targetTests.set( + setOf( + "com.aria.framework.reporting.*", + "com.aria.framework.utils.*", + "com.aria.framework.contracts.*" + ) + ) + mutators.set(setOf("STRONGER")) + timestampedReports.set(false) + outputFormats.set(setOf("XML", "HTML")) + mutationThreshold.set(70) +} + tasks.withType().configureEach { reports.create("xml") { required.set(true) @@ -432,6 +471,7 @@ tasks.named("check") { "spotlessCheck", "spotbugsMain", "spotbugsTest", + "pitest", openApiCoverageReport, "pactProviderVerificationTest", "verifyLiveSmokeTagExpression" diff --git a/docs/capability-status.md b/docs/capability-status.md new file mode 100644 index 0000000..d16a511 --- /dev/null +++ b/docs/capability-status.md @@ -0,0 +1,15 @@ +# Capability Status + +| Capability | Status | Evidence | +| --- | --- | --- | +| Deterministic API regression | Enforced | `./gradlew test` | +| Pact consumer contracts | Enforced | `./gradlew contractTest` | +| Owned provider verification | Enforced | `./gradlew pactProviderVerificationTest` | +| OpenAPI endpoint coverage | Enforced | `./gradlew openApiCoverageReport` | +| Mutation testing | Enforced in `check` | `./gradlew pitest` | +| Live external smoke | Optional | `./gradlew liveSmokeTest` | + +## Notes + +The default gate excludes live tests. External environments are opt-in and must not be required for +public pull requests. diff --git a/docs/known-issues.md b/docs/known-issues.md new file mode 100644 index 0000000..f1d7b89 --- /dev/null +++ b/docs/known-issues.md @@ -0,0 +1,7 @@ +# Known Issues + +| Area | Status | Workaround | +| --- | --- | --- | +| Live GitHub rate limits | External dependency can throttle exploratory runs | Use deterministic owned-provider and WireMock coverage for PR gates | +| Local OSV scanner | `securityScan` skips local scanner execution when `osv-scanner` is not installed | CI runs the scanner; pass `-PrequireOsvScanner=true` to require it locally | +| Windows wrapper file | `gradlew.bat` may differ by local environment | Do not include wrapper changes unless intentionally refreshing Gradle wrapper files | diff --git a/docs/mutation-testing.md b/docs/mutation-testing.md new file mode 100644 index 0000000..db15b6b --- /dev/null +++ b/docs/mutation-testing.md @@ -0,0 +1,15 @@ +# Mutation Testing + +PITest runs against the utility code where mutation signal is most useful: + +- `RedactionPolicy` +- `RetryUtils` +- `OpenApiCoverageReporter` + +Run locally: + +```bash +./gradlew pitest +``` + +The configured mutation threshold is 70%. Reports are written to `build/reports/pitest/` in XML and HTML formats. diff --git a/gradle.lockfile b/gradle.lockfile index aeed6e6..150cc26 100644 --- a/gradle.lockfile +++ b/gradle.lockfile @@ -1,255 +1,299 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -au.com.dius.pact.consumer:junit5:4.6.17=testCompileClasspath,testRuntimeClasspath -au.com.dius.pact.core:matchers:4.6.17=testCompileClasspath,testRuntimeClasspath -au.com.dius.pact.core:model:4.6.17=testCompileClasspath,testRuntimeClasspath -au.com.dius.pact.core:pactbroker:4.6.17=testCompileClasspath,testRuntimeClasspath -au.com.dius.pact.core:support:4.6.17=testCompileClasspath,testRuntimeClasspath -au.com.dius.pact:consumer:4.6.17=testCompileClasspath,testRuntimeClasspath -au.com.dius.pact:provider:4.6.17=testCompileClasspath,testRuntimeClasspath -au.com.dius.pact.provider:junit5:4.6.17=testCompileClasspath,testRuntimeClasspath +au.com.dius.pact.consumer:junit5:4.6.17=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +au.com.dius.pact.core:matchers:4.6.17=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +au.com.dius.pact.core:model:4.6.17=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +au.com.dius.pact.core:pactbroker:4.6.17=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +au.com.dius.pact.core:support:4.6.17=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +au.com.dius.pact.provider:junit5:4.6.17=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +au.com.dius.pact:consumer:4.6.17=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +au.com.dius.pact:provider:4.6.17=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation ch.qos.logback:logback-classic:1.5.37=runtimeClasspath,testRuntimeClasspath ch.qos.logback:logback-core:1.5.37=runtimeClasspath,testRuntimeClasspath -com.fasterxml.jackson.core:jackson-annotations:2.22=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.fasterxml.jackson.core:jackson-core:2.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.fasterxml.jackson.core:jackson-databind:2.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.fasterxml.jackson:jackson-bom:2.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.ajalt:colormath:1.2.0=testRuntimeClasspath -com.github.ajalt:mordant:1.2.1=testRuntimeClasspath -com.github.curious-odd-man:rgxgen:3.1=testCompileClasspath,testRuntimeClasspath -com.github.docker-java:docker-java-api:3.3.6=testCompileClasspath,testRuntimeClasspath -com.github.docker-java:docker-java-transport-zerodep:3.3.6=testCompileClasspath,testRuntimeClasspath -com.github.docker-java:docker-java-transport:3.3.6=testCompileClasspath,testRuntimeClasspath -com.github.java-json-tools:btf:1.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.java-json-tools:jackson-coreutils-equivalence:1.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.java-json-tools:jackson-coreutils:2.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.java-json-tools:json-schema-core:1.2.14=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.java-json-tools:json-schema-validator:2.2.14=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.java-json-tools:msg-simple:1.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.java-json-tools:uri-template:0.10=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.mifmif:generex:1.0.2=testRuntimeClasspath -com.michael-bull.kotlin-result:kotlin-result:1.1.14=testRuntimeClasspath -com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14=testRuntimeClasspath +com.atlassian.oai:swagger-request-validator-core:2.45.1=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.atlassian.oai:swagger-request-validator-restassured:2.45.1=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.fasterxml.jackson.core:jackson-annotations:2.22=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.fasterxml.jackson.core:jackson-core:2.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.fasterxml.jackson.core:jackson-databind:2.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.22.0=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.22.0=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.fasterxml.jackson:jackson-bom:2.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.ajalt:colormath:1.2.0=testRuntimeClasspath,tmpTestImplementation +com.github.ajalt:mordant:1.2.1=testRuntimeClasspath,tmpTestImplementation +com.github.curious-odd-man:rgxgen:3.1=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.docker-java:docker-java-api:3.3.6=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.docker-java:docker-java-transport-zerodep:3.3.6=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.docker-java:docker-java-transport:3.3.6=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.java-json-tools:btf:1.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.java-json-tools:jackson-coreutils-equivalence:1.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.java-json-tools:jackson-coreutils:2.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.java-json-tools:json-patch:1.13=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.java-json-tools:json-schema-core:1.2.14=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.java-json-tools:json-schema-validator:2.2.14=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.java-json-tools:msg-simple:1.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.java-json-tools:uri-template:0.10=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.github.mifmif:generex:1.0.2=testRuntimeClasspath,tmpTestImplementation com.github.spotbugs:spotbugs-annotations:4.10.2=compileClasspath,testCompileClasspath com.github.spotbugs:spotbugs-annotations:4.9.8=spotbugs com.github.spotbugs:spotbugs:4.9.8=spotbugs com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.github.zafarkhaja:java-semver:0.9.0=testRuntimeClasspath -com.google.android:annotations:4.1.1.4=testRuntimeClasspath -com.google.api.grpc:proto-google-common-protos:2.41.0=testRuntimeClasspath -com.google.code.findbugs:jsr305:3.0.2=compileClasspath,runtimeClasspath,spotbugs,spotless865459188,testCompileClasspath,testRuntimeClasspath -com.google.code.gson:gson:2.11.0=testRuntimeClasspath +com.github.zafarkhaja:java-semver:0.9.0=testRuntimeClasspath,tmpTestImplementation +com.google.android:annotations:4.1.1.4=testRuntimeClasspath,tmpTestImplementation +com.google.api.grpc:proto-google-common-protos:2.41.0=testRuntimeClasspath,tmpTestImplementation +com.google.code.findbugs:jsr305:3.0.2=compileClasspath,runtimeClasspath,spotbugs,spotless865459188,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.google.code.gson:gson:2.11.0=testRuntimeClasspath,tmpTestImplementation com.google.code.gson:gson:2.13.2=spotbugs com.google.errorprone:error_prone_annotations:2.21.1=spotless865459188 -com.google.errorprone:error_prone_annotations:2.26.1=compileClasspath,runtimeClasspath,testCompileClasspath -com.google.errorprone:error_prone_annotations:2.28.0=testRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.26.1=compileClasspath,runtimeClasspath +com.google.errorprone:error_prone_annotations:2.28.0=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation com.google.errorprone:error_prone_annotations:2.41.0=spotbugs com.google.googlejavaformat:google-java-format:1.19.2=spotless865459188 com.google.guava:failureaccess:1.0.1=spotless865459188 -com.google.guava:failureaccess:1.0.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.guava:failureaccess:1.0.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation com.google.guava:guava:32.1.3-jre=spotless865459188 -com.google.guava:guava:33.2.1-jre=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=compileClasspath,runtimeClasspath,spotless865459188,testCompileClasspath,testRuntimeClasspath -com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath,testCompileClasspath -com.google.protobuf:protobuf-java:3.25.5=testRuntimeClasspath -com.googlecode.java-diff-utils:diffutils:1.3.0=testRuntimeClasspath +com.google.guava:guava:33.2.1-jre=compileClasspath,runtimeClasspath +com.google.guava:guava:33.3.1-jre=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=compileClasspath,runtimeClasspath,spotless865459188,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.google.protobuf:protobuf-java:3.25.5=testRuntimeClasspath,tmpTestImplementation +com.googlecode.java-diff-utils:diffutils:1.3.0=testRuntimeClasspath,tmpTestImplementation com.googlecode.libphonenumber:libphonenumber:8.11.1=compileClasspath,runtimeClasspath -com.googlecode.libphonenumber:libphonenumber:9.0.23=testCompileClasspath,testRuntimeClasspath -com.sun.mail:mailapi:1.6.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.typesafe:config:1.4.3=testRuntimeClasspath -com.vdurmont:semver4j:3.1.0=testRuntimeClasspath -com.vladsch.flexmark:flexmark-ext-tables:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-ast:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-builder:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-collection:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-data:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-dependency:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-format:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-html:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-misc:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-options:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-sequence:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util-visitor:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark-util:0.62.2=testRuntimeClasspath -com.vladsch.flexmark:flexmark:0.62.2=testRuntimeClasspath -commons-beanutils:commons-beanutils:1.11.0=testRuntimeClasspath +com.googlecode.libphonenumber:libphonenumber:9.0.23=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14=testRuntimeClasspath,tmpTestImplementation +com.michael-bull.kotlin-result:kotlin-result:1.1.14=testRuntimeClasspath,tmpTestImplementation +com.sun.mail:mailapi:1.6.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +com.typesafe:config:1.4.3=testRuntimeClasspath,tmpTestImplementation +com.vdurmont:semver4j:3.1.0=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-ext-tables:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-ast:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-builder:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-collection:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-data:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-dependency:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-format:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-html:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-misc:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-options:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-sequence:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util-visitor:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark-util:0.62.2=testRuntimeClasspath,tmpTestImplementation +com.vladsch.flexmark:flexmark:0.62.2=testRuntimeClasspath,tmpTestImplementation +commons-beanutils:commons-beanutils:1.11.0=testRuntimeClasspath,tmpTestImplementation commons-codec:commons-codec:1.11=compileClasspath,runtimeClasspath,testCompileClasspath -commons-codec:commons-codec:1.17.0=testRuntimeClasspath -commons-collections:commons-collections:3.2.2=testRuntimeClasspath -commons-io:commons-io:2.20.0=spotbugs,testCompileClasspath,testRuntimeClasspath +commons-codec:commons-codec:1.17.0=testRuntimeClasspath,tmpTestImplementation +commons-collections:commons-collections:3.2.2=testRuntimeClasspath,tmpTestImplementation +commons-io:commons-io:2.20.0=spotbugs,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation commons-logging:commons-logging:1.2=compileClasspath,runtimeClasspath,testCompileClasspath -commons-logging:commons-logging:1.3.5=testRuntimeClasspath -dk.brics.automaton:automaton:1.11-8=testRuntimeClasspath -io.github.java-diff-utils:java-diff-utils:4.12=testRuntimeClasspath -io.github.classgraph:classgraph:4.8.129=testCompileClasspath,testRuntimeClasspath +commons-logging:commons-logging:1.3.5=testRuntimeClasspath,tmpTestImplementation +dk.brics.automaton:automaton:1.11-8=testRuntimeClasspath,tmpTestImplementation +io.github.classgraph:classgraph:4.8.129=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.github.java-diff-utils:java-diff-utils:4.12=testRuntimeClasspath,tmpTestImplementation io.github.oshai:kotlin-logging-jvm:5.1.4=testCompileClasspath -io.github.oshai:kotlin-logging-jvm:6.0.9=testRuntimeClasspath -io.grpc:grpc-api:1.66.0=testRuntimeClasspath -io.grpc:grpc-context:1.66.0=testRuntimeClasspath -io.grpc:grpc-core:1.66.0=testRuntimeClasspath -io.grpc:grpc-netty:1.66.0=testRuntimeClasspath -io.grpc:grpc-protobuf-lite:1.66.0=testRuntimeClasspath -io.grpc:grpc-protobuf:1.66.0=testRuntimeClasspath -io.grpc:grpc-stub:1.66.0=testRuntimeClasspath -io.grpc:grpc-util:1.66.0=testRuntimeClasspath -io.hotmoka:toml4j:0.7.3=testRuntimeClasspath -io.ktor:ktor-events-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-events:2.3.8=testRuntimeClasspath -io.ktor:ktor-http-cio-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-http-cio:2.3.8=testRuntimeClasspath -io.ktor:ktor-http-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-http:2.3.8=testRuntimeClasspath -io.ktor:ktor-io-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-io:2.3.8=testRuntimeClasspath -io.ktor:ktor-network-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-network-tls-certificates-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-network-tls-certificates:2.3.8=testRuntimeClasspath -io.ktor:ktor-network-tls-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-network-tls:2.3.8=testRuntimeClasspath -io.ktor:ktor-network:2.3.8=testRuntimeClasspath -io.ktor:ktor-serialization-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-serialization:2.3.8=testRuntimeClasspath -io.ktor:ktor-server-call-logging-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-server-call-logging:2.3.8=testRuntimeClasspath -io.ktor:ktor-server-core-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-server-core:2.3.8=testRuntimeClasspath -io.ktor:ktor-server-host-common-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-server-host-common:2.3.8=testRuntimeClasspath -io.ktor:ktor-server-netty-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-server-netty:2.3.8=testRuntimeClasspath -io.ktor:ktor-utils-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-utils:2.3.8=testRuntimeClasspath -io.ktor:ktor-websockets-jvm:2.3.8=testRuntimeClasspath -io.ktor:ktor-websockets:2.3.8=testRuntimeClasspath -io.netty:netty-bom:4.2.15.Final=testCompileClasspath,testRuntimeClasspath -io.netty:netty-buffer:4.2.15.Final=testRuntimeClasspath -io.netty:netty-codec-base:4.2.15.Final=testRuntimeClasspath -io.netty:netty-codec-compression:4.2.15.Final=testRuntimeClasspath -io.netty:netty-codec-http2:4.2.15.Final=testRuntimeClasspath -io.netty:netty-codec-http:4.2.15.Final=testRuntimeClasspath -io.netty:netty-codec-socks:4.2.15.Final=testRuntimeClasspath -io.netty:netty-common:4.2.15.Final=testRuntimeClasspath -io.netty:netty-handler-proxy:4.2.15.Final=testRuntimeClasspath -io.netty:netty-handler:4.2.15.Final=testRuntimeClasspath -io.netty:netty-resolver:4.2.15.Final=testRuntimeClasspath -io.netty:netty-transport-classes-epoll:4.2.15.Final=testRuntimeClasspath -io.netty:netty-transport-classes-kqueue:4.2.15.Final=testRuntimeClasspath -io.netty:netty-transport-native-epoll:4.2.15.Final=testRuntimeClasspath -io.netty:netty-transport-native-kqueue:4.2.15.Final=testRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.2.15.Final=testRuntimeClasspath -io.netty:netty-transport:4.2.15.Final=testRuntimeClasspath -io.pact.plugin.driver:core:0.5.1=testCompileClasspath,testRuntimeClasspath -io.perfmark:perfmark-api:0.27.0=testRuntimeClasspath +io.github.oshai:kotlin-logging-jvm:6.0.9=testRuntimeClasspath,tmpTestImplementation +io.grpc:grpc-api:1.66.0=testRuntimeClasspath,tmpTestImplementation +io.grpc:grpc-context:1.66.0=testRuntimeClasspath,tmpTestImplementation +io.grpc:grpc-core:1.66.0=testRuntimeClasspath,tmpTestImplementation +io.grpc:grpc-netty:1.66.0=testRuntimeClasspath,tmpTestImplementation +io.grpc:grpc-protobuf-lite:1.66.0=testRuntimeClasspath,tmpTestImplementation +io.grpc:grpc-protobuf:1.66.0=testRuntimeClasspath,tmpTestImplementation +io.grpc:grpc-stub:1.66.0=testRuntimeClasspath,tmpTestImplementation +io.grpc:grpc-util:1.66.0=testRuntimeClasspath,tmpTestImplementation +io.hotmoka:toml4j:0.7.3=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-events-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-events:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-http-cio-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-http-cio:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-http-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-http:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-io-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-io:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-network-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-network-tls-certificates-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-network-tls-certificates:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-network-tls-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-network-tls:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-network:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-serialization-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-serialization:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-server-call-logging-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-server-call-logging:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-server-core-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-server-core:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-server-host-common-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-server-host-common:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-server-netty-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-server-netty:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-utils-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-utils:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-websockets-jvm:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.ktor:ktor-websockets:2.3.8=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-bom:4.2.15.Final=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.netty:netty-buffer:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-codec-base:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-codec-compression:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-codec-http2:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-codec-http:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-codec-socks:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-common:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-handler-proxy:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-handler:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-resolver:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-transport-classes-epoll:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-transport-classes-kqueue:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-transport-native-epoll:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-transport-native-kqueue:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-transport-native-unix-common:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.netty:netty-transport:4.2.15.Final=testRuntimeClasspath,tmpTestImplementation +io.pact.plugin.driver:core:0.5.1=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.perfmark:perfmark-api:0.27.0=testRuntimeClasspath,tmpTestImplementation io.qameta.allure:allure-attachments:2.35.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.qameta.allure:allure-commandline:2.44.0=allureCommandline -io.qameta.allure:allure-java-commons:2.35.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.qameta.allure:allure-junit-platform:2.35.2=testCompileClasspath,testRuntimeClasspath -io.qameta.allure:allure-junit4-aspect:2.25.0=testRuntimeClasspath -io.qameta.allure:allure-junit4:2.25.0=testCompileClasspath,testRuntimeClasspath -io.qameta.allure:allure-junit5:2.35.2=testCompileClasspath,testRuntimeClasspath -io.qameta.allure:allure-jupiter:2.35.2=testCompileClasspath,testRuntimeClasspath -io.qameta.allure:allure-model:2.35.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.qameta.allure:allure-java-commons:2.35.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.qameta.allure:allure-junit-platform:2.35.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.qameta.allure:allure-junit4-aspect:2.35.2=testRuntimeClasspath,tmpTestImplementation +io.qameta.allure:allure-junit4:2.35.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.qameta.allure:allure-junit5:2.35.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.qameta.allure:allure-jupiter:2.35.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.qameta.allure:allure-model:2.35.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation io.qameta.allure:allure-rest-assured:2.35.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.qameta.allure:allure-test-filter:2.35.2=testRuntimeClasspath +io.qameta.allure:allure-test-filter:2.35.2=testRuntimeClasspath,tmpTestImplementation +io.rest-assured:json-path:5.1.1=tmpTestImplementation io.rest-assured:json-path:6.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.rest-assured:json-schema-validator:6.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.rest-assured:rest-assured-common:5.1.1=tmpTestImplementation io.rest-assured:rest-assured-common:6.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.rest-assured:rest-assured:5.1.1=tmpTestImplementation io.rest-assured:rest-assured:6.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.rest-assured:xml-path:5.1.1=tmpTestImplementation io.rest-assured:xml-path:6.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -javax.activation:activation:1.1=testRuntimeClasspath -javax.mail:mail:1.5.0-b01=testRuntimeClasspath +io.swagger.core.v3:swagger-annotations:2.2.34=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger.core.v3:swagger-core:2.2.34=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger.core.v3:swagger-models:2.2.34=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger.parser.v3:swagger-parser-core:2.1.31=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger.parser.v3:swagger-parser-safe-url-resolver:2.1.31=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger.parser.v3:swagger-parser-v2-converter:2.1.31=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger.parser.v3:swagger-parser-v3:2.1.31=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger.parser.v3:swagger-parser:2.1.31=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger:swagger-annotations:1.6.16=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger:swagger-compat-spec-parser:1.0.75=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger:swagger-core:1.6.16=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger:swagger-models:1.6.16=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger:swagger-parser-safe-url-resolver:1.0.75=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +io.swagger:swagger-parser:1.0.75=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +jakarta.activation:jakarta.activation-api:1.2.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +jakarta.validation:jakarta.validation-api:2.0.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +jakarta.xml.bind:jakarta.xml.bind-api:2.3.3=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +javax.activation:activation:1.1=testRuntimeClasspath,tmpTestImplementation +javax.mail:mail:1.5.0-b01=testRuntimeClasspath,tmpTestImplementation +javax.validation:validation-api:1.1.0.Final=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation jaxen:jaxen:2.0.0=spotbugs -joda-time:joda-time:2.10.5=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.18.3=testCompileClasspath,testRuntimeClasspath -net.datafaker:datafaker:2.5.4=testCompileClasspath,testRuntimeClasspath -net.java.dev.jna:jna:5.13.0=testCompileClasspath,testRuntimeClasspath -net.sf.jopt-simple:jopt-simple:5.0.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +joda-time:joda-time:2.10.5=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +net.bytebuddy:byte-buddy:1.18.3=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +net.datafaker:datafaker:2.5.4=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +net.java.dev.jna:jna:5.13.0=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +net.sf.jopt-simple:jopt-simple:5.0.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation net.sf.saxon:Saxon-HE:12.9=spotbugs org.aeonbits.owner:owner:1.0.12=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.apache.bcel:bcel:6.11.0=spotbugs -org.apache.commons:commons-collections4:4.4=testRuntimeClasspath +org.apache.commons:commons-collections4:4.4=testRuntimeClasspath,tmpTestImplementation org.apache.commons:commons-compress:1.26.0=testCompileClasspath -org.apache.commons:commons-compress:1.26.1=testRuntimeClasspath -org.apache.commons:commons-lang3:3.19.0=compileClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath -org.apache.commons:commons-text:1.10.0=testRuntimeClasspath +org.apache.commons:commons-compress:1.26.1=testRuntimeClasspath,tmpTestImplementation +org.apache.commons:commons-lang3:3.19.0=compileClasspath,pitest,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.apache.commons:commons-text:1.10.0=pitest,testRuntimeClasspath,tmpTestImplementation org.apache.commons:commons-text:1.14.0=spotbugs +org.apache.groovy:groovy-bom:4.0.18=tmpTestImplementation org.apache.groovy:groovy-bom:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.groovy:groovy-json:4.0.18=tmpTestImplementation org.apache.groovy:groovy-json:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.groovy:groovy-xml:4.0.18=tmpTestImplementation org.apache.groovy:groovy-xml:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.groovy:groovy:4.0.18=tmpTestImplementation org.apache.groovy:groovy:5.0.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.httpcomponents.client5:httpclient5-fluent:5.3.1=testCompileClasspath,testRuntimeClasspath -org.apache.httpcomponents.client5:httpclient5:5.3.1=testCompileClasspath,testRuntimeClasspath -org.apache.httpcomponents.core5:httpcore5-h2:5.2.4=testCompileClasspath,testRuntimeClasspath -org.apache.httpcomponents.core5:httpcore5:5.2.4=testCompileClasspath,testRuntimeClasspath -org.apache.httpcomponents:httpclient:4.5.13=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.httpcomponents:httpcore:4.4.13=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.httpcomponents:httpmime:4.5.13=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents.client5:httpclient5-fluent:5.3.1=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.apache.httpcomponents.client5:httpclient5:5.3.1=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.apache.httpcomponents.core5:httpcore5-h2:5.2.4=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.apache.httpcomponents.core5:httpcore5:5.2.4=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.apache.httpcomponents:httpclient:4.5.13=compileClasspath,runtimeClasspath +org.apache.httpcomponents:httpclient:4.5.14=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.apache.httpcomponents:httpcore:4.4.13=compileClasspath,runtimeClasspath +org.apache.httpcomponents:httpcore:4.4.16=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.apache.httpcomponents:httpmime:4.5.13=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation org.apache.logging.log4j:log4j-api:2.25.4=spotbugs org.apache.logging.log4j:log4j-core:2.25.4=spotbugs -org.apache.tika:tika-core:3.2.2=testCompileClasspath,testRuntimeClasspath +org.apache.tika:tika-core:3.2.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath -org.aspectj:aspectjrt:1.9.9.1=testRuntimeClasspath org.aspectj:aspectjweaver:1.9.9.1=allureAspectjWeaverAgent -org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath -org.atteo:evo-inflector:1.3=testRuntimeClasspath -org.ccil.cowan.tagsoup:tagsoup:1.2.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.atteo:evo-inflector:1.3=testRuntimeClasspath,tmpTestImplementation +org.ccil.cowan.tagsoup:tagsoup:1.2.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation org.checkerframework:checker-qual:3.37.0=spotless865459188 -org.checkerframework:checker-qual:3.42.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.codehaus.mojo:animal-sniffer-annotations:1.24=testRuntimeClasspath +org.checkerframework:checker-qual:3.42.0=compileClasspath,runtimeClasspath +org.checkerframework:checker-qual:3.43.0=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.codehaus.mojo:animal-sniffer-annotations:1.24=testRuntimeClasspath,tmpTestImplementation org.dom4j:dom4j:2.2.0=spotbugs -org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715=testRuntimeClasspath +org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715=testRuntimeClasspath,tmpTestImplementation org.freemarker:freemarker:2.3.33=runtimeClasspath,testRuntimeClasspath -org.fusesource.jansi:jansi:2.4.1=testRuntimeClasspath -org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath +org.fusesource.jansi:jansi:2.4.1=testRuntimeClasspath,tmpTestImplementation +org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.hamcrest:hamcrest:2.1=tmpTestImplementation org.hamcrest:hamcrest:2.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-bom:1.8.22=testRuntimeClasspath -org.jetbrains.kotlin:kotlin-reflect:1.8.22=testRuntimeClasspath +org.jetbrains.kotlin:kotlin-bom:1.8.22=testRuntimeClasspath,tmpTestImplementation +org.jetbrains.kotlin:kotlin-reflect:1.8.22=testRuntimeClasspath,tmpTestImplementation org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22=testCompileClasspath -org.jetbrains.kotlin:kotlin-stdlib-common:1.9.23=testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22=testCompileClasspath,testRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22=testCompileClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.23=testRuntimeClasspath,tmpTestImplementation +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation org.jetbrains.kotlin:kotlin-stdlib:1.8.22=testCompileClasspath -org.jetbrains.kotlin:kotlin-stdlib:1.9.23=testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.1=testRuntimeClasspath -org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.7.1=testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.23=testRuntimeClasspath,tmpTestImplementation +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=testRuntimeClasspath,tmpTestImplementation +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=testRuntimeClasspath,tmpTestImplementation +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=testRuntimeClasspath,tmpTestImplementation +org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.1=testRuntimeClasspath,tmpTestImplementation +org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.7.1=testRuntimeClasspath,tmpTestImplementation org.jetbrains:annotations:17.0.0=testCompileClasspath -org.jetbrains:annotations:23.0.0=testRuntimeClasspath -org.json:json:20240205=testCompileClasspath,testRuntimeClasspath +org.jetbrains:annotations:23.0.0=testRuntimeClasspath,tmpTestImplementation +org.json:json:20240205=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation org.junit.jupiter:junit-jupiter-api:5.10.2=testCompileClasspath -org.junit.jupiter:junit-jupiter-api:5.10.3=testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.10.3=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.10.3=testRuntimeClasspath,tmpTestImplementation +org.junit.jupiter:junit-jupiter-engine:5.10.3=testRuntimeClasspath,tmpTestImplementation org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath -org.junit.jupiter:junit-jupiter-params:5.10.3=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.10.3=testRuntimeClasspath,tmpTestImplementation org.junit.jupiter:junit-jupiter:5.10.2=testCompileClasspath -org.junit.jupiter:junit-jupiter:5.10.3=testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.10.3=testRuntimeClasspath,tmpTestImplementation org.junit.platform:junit-platform-commons:1.10.2=testCompileClasspath -org.junit.platform:junit-platform-commons:1.10.3=testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.10.3=testRuntimeClasspath -org.junit.platform:junit-platform-launcher:1.10.3=testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.10.3=testRuntimeClasspath,tmpTestImplementation +org.junit.platform:junit-platform-commons:1.9.2=pitest +org.junit.platform:junit-platform-engine:1.10.3=testRuntimeClasspath,tmpTestImplementation +org.junit.platform:junit-platform-engine:1.9.2=pitest +org.junit.platform:junit-platform-launcher:1.10.3=testRuntimeClasspath,tmpTestImplementation +org.junit.platform:junit-platform-launcher:1.9.2=pitest org.junit:junit-bom:5.10.2=testCompileClasspath -org.junit:junit-bom:5.10.3=testRuntimeClasspath +org.junit:junit-bom:5.10.3=testRuntimeClasspath,tmpTestImplementation org.junit:junit-bom:5.14.0=spotbugs -org.mozilla:rhino:1.7.14.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath +org.junit:junit-bom:5.9.2=pitest +org.mozilla:rhino:1.7.14.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.opentest4j:opentest4j:1.2.0=pitest +org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation org.ow2.asm:asm-analysis:9.9=spotbugs org.ow2.asm:asm-commons:9.9=spotbugs org.ow2.asm:asm-tree:9.9=spotbugs org.ow2.asm:asm-util:9.9=spotbugs org.ow2.asm:asm:9.9=spotbugs +org.pitest:pitest-command-line:1.15.0=pitest +org.pitest:pitest-entry:1.15.0=pitest +org.pitest:pitest-junit5-plugin:1.2.1=pitest +org.pitest:pitest:1.15.0=pitest org.projectlombok:lombok:1.18.44=annotationProcessor,compileClasspath,lombok,testAnnotationProcessor,testCompileClasspath -org.rauschig:jarchivelib:1.2.0=testRuntimeClasspath -org.rnorth.duct-tape:duct-tape:1.0.8=testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:2.0.17=compileClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath +org.rauschig:jarchivelib:1.2.0=testRuntimeClasspath,tmpTestImplementation +org.rnorth.duct-tape:duct-tape:1.0.8=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.slf4j:slf4j-api:2.0.17=compileClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,tmpTestImplementation org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j -org.testcontainers:junit-jupiter:1.19.7=testCompileClasspath,testRuntimeClasspath -org.testcontainers:testcontainers:1.19.7=testCompileClasspath,testRuntimeClasspath -org.wiremock:wiremock-standalone:3.13.2=testCompileClasspath,testRuntimeClasspath +org.testcontainers:junit-jupiter:1.19.7=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.testcontainers:testcontainers:1.19.7=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation +org.wiremock:wiremock-standalone:3.13.2=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation org.xmlresolver:xmlresolver:5.3.3=spotbugs -org.yaml:snakeyaml:2.5=testCompileClasspath,testRuntimeClasspath +org.yaml:snakeyaml:2.5=testCompileClasspath,testRuntimeClasspath,tmpTestImplementation empty=allureGenerateCategories,allureRawResultDirs,allureReport,cyclonedxBom,spotbugsPlugins diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d59943..41397ed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,12 +11,14 @@ lombokPlugin = "9.5.0" logback = "1.5.37" owner = "1.0.12" pact = "4.6.17" +pitestPlugin = "1.15.0" restassured = "6.0.0" slf4j = "2.0.17" snakeyaml = "2.2" spotbugs = "4.10.2" spotbugsPlugin = "6.5.5" spotless = "6.25.0" +swaggerRequestValidator = "2.45.1" testcontainers = "1.19.7" wiremock = "3.13.2" @@ -43,6 +45,7 @@ rest-assured-json-schema-validator = { module = "io.rest-assured:json-schema-val slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } snakeyaml = { module = "org.yaml:snakeyaml", version.ref = "snakeyaml" } spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version.ref = "spotbugs" } +swagger-request-validator-restassured = { module = "com.atlassian.oai:swagger-request-validator-restassured", version.ref = "swaggerRequestValidator" } testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "testcontainers" } testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter", version.ref = "testcontainers" } wiremock = { module = "org.wiremock:wiremock-standalone", version.ref = "wiremock" } @@ -51,5 +54,6 @@ wiremock = { module = "org.wiremock:wiremock-standalone", version.ref = "wiremoc allure = { id = "io.qameta.allure", version.ref = "allurePlugin" } cyclonedx = { id = "org.cyclonedx.bom", version.ref = "cyclonedxPlugin" } lombok = { id = "io.freefair.lombok", version.ref = "lombokPlugin" } +pitest = { id = "info.solidsoft.pitest", version.ref = "pitestPlugin" } spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugsPlugin" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } diff --git a/src/test/java/com/aria/framework/fixtures/OwnedApiProvider.java b/src/test/java/com/aria/framework/fixtures/OwnedApiProvider.java index 508e4a0..a803203 100644 --- a/src/test/java/com/aria/framework/fixtures/OwnedApiProvider.java +++ b/src/test/java/com/aria/framework/fixtures/OwnedApiProvider.java @@ -315,8 +315,8 @@ private static Map normalizedBooking(Map payload booking.put("totalprice", payload.getOrDefault("totalprice", 111)); booking.put("depositpaid", payload.getOrDefault("depositpaid", true)); booking.put("bookingdates", payload.getOrDefault("bookingdates", Map.of( - "checkin", "2026-05-25", - "checkout", "2026-05-30" + "checkin", "2026-05-24", + "checkout", "2026-05-31" ))); booking.put("additionalneeds", payload.getOrDefault("additionalneeds", "Breakfast")); return booking; @@ -329,8 +329,8 @@ private static Map booking(String firstname, String lastname) { "totalprice", 111, "depositpaid", true, "bookingdates", Map.of( - "checkin", "2026-05-25", - "checkout", "2026-05-30" + "checkin", "2026-05-24", + "checkout", "2026-05-31" ), "additionalneeds", "Breakfast" )); @@ -402,8 +402,8 @@ private static void sendJson(HttpExchange exchange, int status, Object body) thr } private static void sendText(HttpExchange exchange, int status, String body) throws IOException { - exchange.getResponseHeaders().set("Content-Type", "text/plain"); - sendBytes(exchange, status, body.getBytes(StandardCharsets.UTF_8)); + exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=ISO-8859-1"); + sendBytes(exchange, status, body.getBytes(StandardCharsets.ISO_8859_1)); } private static void sendBytes(HttpExchange exchange, int status, byte[] bytes) throws IOException { diff --git a/src/test/java/com/aria/framework/utils/RetryUtilsTest.java b/src/test/java/com/aria/framework/utils/RetryUtilsTest.java index 6b57c0e..52c0365 100644 --- a/src/test/java/com/aria/framework/utils/RetryUtilsTest.java +++ b/src/test/java/com/aria/framework/utils/RetryUtilsTest.java @@ -1,9 +1,12 @@ package com.aria.framework.utils; +import com.aria.framework.config.FrameworkConfig; import io.restassured.builder.ResponseBuilder; import io.restassured.response.Response; import org.junit.jupiter.api.Test; +import java.net.ConnectException; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.List; @@ -14,6 +17,9 @@ class RetryUtilsTest { + private static final RetryUtils.RetryPolicy ZERO_JITTER_POLICY = + new RetryUtils.RetryPolicy(3, 100, 10_000, 0); + @Test void retriesGetOnRateLimitResponse() { AtomicInteger calls = new AtomicInteger(); @@ -71,6 +77,290 @@ void retriesTransientNetworkExceptionWhenAllowed() { assertThat(calls).hasValue(2); } + @Test + void stopsRetryingOnceMaxAttemptsReached() { + AtomicInteger calls = new AtomicInteger(); + List sleeps = new ArrayList<>(); + + Response response = RetryUtils.executeWithRetry( + "GET", + () -> { + calls.incrementAndGet(); + return response(429, "1"); + }, + new RetryUtils.RetryPolicy(2, 1, 1_000, 0), + sleeps::add, + true + ); + + assertThat(response.statusCode()).isEqualTo(429); + assertThat(calls).hasValue(2); + assertThat(sleeps).hasSize(1); + } + + @Test + void usesRetryAfterHeaderDelayCappedAtMaxDelay() { + List sleeps = new ArrayList<>(); + AtomicInteger calls = new AtomicInteger(); + + RetryUtils.executeWithRetry( + "GET", + () -> calls.incrementAndGet() == 1 ? response(429, "5") : response(200, null), + new RetryUtils.RetryPolicy(2, 1, 2_000, 0), + sleeps::add, + true + ); + + assertThat(sleeps).containsExactly(2_000L); + } + + @Test + void ignoresMalformedRetryAfterHeaderAndFallsBackToExponentialDelay() { + List sleeps = new ArrayList<>(); + AtomicInteger calls = new AtomicInteger(); + + RetryUtils.executeWithRetry( + "GET", + () -> calls.incrementAndGet() == 1 ? response(429, "not-a-number") : response(200, null), + new RetryUtils.RetryPolicy(2, 50, 10_000, 0), + sleeps::add, + true + ); + + assertThat(sleeps).containsExactly(50L); + } + + @Test + void calculatesExponentialDelayForTransientExceptionWithoutResponse() { + List sleeps = new ArrayList<>(); + AtomicInteger calls = new AtomicInteger(); + + RetryUtils.executeWithRetry( + "GET", + () -> { + if (calls.incrementAndGet() == 1) { + throw new RuntimeException(new SocketTimeoutException("timeout")); + } + return response(200, null); + }, + new RetryUtils.RetryPolicy(2, 25, 10_000, 0), + sleeps::add, + true + ); + + assertThat(sleeps).containsExactly(25L); + } + + @Test + void executeGetWithRetryPublicOverloadsReturnUnderlyingResponse() { + FrameworkConfig config = frameworkConfigWithRetryPolicy(); + + Response direct = RetryUtils.executeGetWithRetry(config, () -> response(200, null)); + Response singleArg = RetryUtils.executeWithRetry(() -> response(200, null)); + + assertThat(direct.statusCode()).isEqualTo(200); + assertThat(singleArg.statusCode()).isEqualTo(200); + } + + @Test + void executeMutationWithRetrySkipsRetryWhenNotIdempotencyControlled() { + AtomicInteger calls = new AtomicInteger(); + + Response response = RetryUtils.executeMutationWithRetry( + "POST", + false, + () -> { + calls.incrementAndGet(); + return response(429, "1"); + } + ); + + assertThat(response.statusCode()).isEqualTo(429); + assertThat(calls).hasValue(1); + } + + @Test + void executeMutationWithRetryRetriesWhenIdempotencyControlled() { + FrameworkConfig config = frameworkConfigWithRetryPolicy(); + AtomicInteger calls = new AtomicInteger(); + + Response response = RetryUtils.executeMutationWithRetry( + config, + "PUT", + true, + () -> calls.incrementAndGet() == 1 ? response(429, "0") : response(200, null) + ); + + assertThat(response.statusCode()).isEqualTo(200); + assertThat(calls).hasValue(2); + } + + @Test + void doesNotRetryOn403WithoutRetryAfterOrExhaustedRateLimitHeaders() { + AtomicInteger calls = new AtomicInteger(); + + Response response = RetryUtils.executeWithRetry( + "GET", + () -> { + calls.incrementAndGet(); + return new ResponseBuilder().setStatusCode(403).setHeader("Content-Type", "application/json").build(); + }, + ZERO_JITTER_POLICY, + ignored -> { }, + true + ); + + assertThat(response.statusCode()).isEqualTo(403); + assertThat(calls).hasValue(1); + } + + @Test + void retriesOn403WhenRateLimitRemainingHeaderIsZero() { + AtomicInteger calls = new AtomicInteger(); + + Response response = RetryUtils.executeWithRetry( + "GET", + () -> calls.incrementAndGet() == 1 + ? new ResponseBuilder().setStatusCode(403).setHeader("X-RateLimit-Remaining", "0").build() + : response(200, null), + ZERO_JITTER_POLICY, + ignored -> { }, + true + ); + + assertThat(response.statusCode()).isEqualTo(200); + assertThat(calls).hasValue(2); + } + + @Test + void doesNotRetryOn403WhenRateLimitRemainingHeaderIsNonZero() { + AtomicInteger calls = new AtomicInteger(); + + Response response = RetryUtils.executeWithRetry( + "GET", + () -> { + calls.incrementAndGet(); + return new ResponseBuilder().setStatusCode(403).setHeader("X-RateLimit-Remaining", "5").build(); + }, + ZERO_JITTER_POLICY, + ignored -> { }, + true + ); + + assertThat(response.statusCode()).isEqualTo(403); + assertThat(calls).hasValue(1); + } + + @Test + void doesNotRetryTransientExceptionWhenNotAllowed() { + AtomicInteger calls = new AtomicInteger(); + + assertThatThrownBy(() -> RetryUtils.executeWithRetry( + "GET", + () -> { + calls.incrementAndGet(); + throw new RuntimeException(new SocketTimeoutException("timeout")); + }, + ZERO_JITTER_POLICY, + ignored -> { }, + false + )).isInstanceOf(RuntimeException.class); + + assertThat(calls).hasValue(1); + } + + @Test + void doesNotTreatNonTransientExceptionAsRetryable() { + AtomicInteger calls = new AtomicInteger(); + + assertThatThrownBy(() -> RetryUtils.executeWithRetry( + "GET", + () -> { + calls.incrementAndGet(); + throw new RuntimeException(new IllegalStateException("boom")); + }, + ZERO_JITTER_POLICY, + ignored -> { }, + true + )).isInstanceOf(RuntimeException.class); + + assertThat(calls).hasValue(1); + } + + @Test + void detectsTransientNetworkExceptionsAcrossCauseChain() { + AtomicInteger calls = new AtomicInteger(); + + Response response = RetryUtils.executeWithRetry( + "GET", + () -> { + if (calls.incrementAndGet() == 1) { + throw new RuntimeException("wrapper", new SocketException("reset")); + } + return response(200, null); + }, + ZERO_JITTER_POLICY, + ignored -> { }, + true + ); + + assertThat(response.statusCode()).isEqualTo(200); + assertThat(calls).hasValue(2); + } + + @Test + void detectsConnectExceptionAsTransient() { + AtomicInteger calls = new AtomicInteger(); + + Response response = RetryUtils.executeWithRetry( + "GET", + () -> { + if (calls.incrementAndGet() == 1) { + throw new RuntimeException(new ConnectException("refused")); + } + return response(200, null); + }, + ZERO_JITTER_POLICY, + ignored -> { }, + true + ); + + assertThat(response.statusCode()).isEqualTo(200); + assertThat(calls).hasValue(2); + } + + @Test + void interruptedSleepRestoresInterruptFlagAndThrows() { + assertThatThrownBy(() -> RetryUtils.executeWithRetry( + "GET", + () -> response(429, "1"), + new RetryUtils.RetryPolicy(2, 1, 1_000, 0), + delayMs -> { + throw new InterruptedException("stop"); + }, + true + )).isInstanceOf(RuntimeException.class); + + assertThat(Thread.interrupted()).isTrue(); + } + + private static FrameworkConfig frameworkConfigWithRetryPolicy() { + return new FrameworkConfig( + "test", + "http://localhost", + "http://localhost", + "token", + "user", + "pass", + 5, + 1_000, + 2, + 1, + 1_000, + 0 + ); + } + private static Response response(int statusCode, String retryAfter) { ResponseBuilder builder = new ResponseBuilder() .setStatusCode(statusCode); From 98247d748f96ed02d9966d899c902d3f79bc8659 Mon Sep 17 00:00:00 2001 From: prayagv <76861333+prayagv@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:37:06 +0530 Subject: [PATCH 2/2] Validate OpenAPI response bodies and add contributor guide OpenApiResponseValidator only checked that a response's status code and media type were documented, which oversold the "OpenAPI validation" claim. Wire the already-declared swagger-request-validator dependency in to also validate the response body against the operation's schema, and update CONTRACT_STRATEGY.md to reflect the actual test classes and scope. Also replaces the agent-tooling notes with a contributor-facing architecture guide, consistent with the other reference frameworks in this organization. --- AGENTS.md | 4 -- CHANGELOG.md | 2 + CLAUDE.md | 56 ------------------- CONTRIBUTOR_ARCHITECTURE_GUIDE.md | 39 +++++++++++++ docs/CONTRACT_STRATEGY.md | 10 ++-- .../assertions/OpenApiResponseValidator.java | 38 ++++++++++++- 6 files changed, 82 insertions(+), 67 deletions(-) delete mode 100644 AGENTS.md delete mode 100644 CLAUDE.md create mode 100644 CONTRIBUTOR_ARCHITECTURE_GUIDE.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 82a4909..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,4 +0,0 @@ -# AGENTS.md - -Follow `CLAUDE.md` for repository-specific agent instructions. - diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bbf126..fc4d25f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,3 +18,5 @@ - Added atomic optimistic-concurrency evidence, portfolio metrics, reliability policy, failure triage, seeded-defect examples, and an application security threat model. - Added mutation-score publishing through PITest for redaction, retry, and OpenAPI coverage utilities. - Added Pact provider verification against the owned fixture and corrected the Allure plugin version pin that blocked dependency-lock regeneration. +- Added OpenAPI response body-schema validation via swagger-request-validator, alongside the existing documented-response and media-type checks. +- Forced patched versions for OSV-flagged transitive dependencies reachable only through tool-classpath resolution. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 0829ef5..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,56 +0,0 @@ -# CLAUDE.md - -## Project - -Java 21 REST Assured/JUnit 5 API automation framework for Conduit-style API -coverage, contract checks, reliability evidence, and portfolio reporting. - -## Session Start - -Refresh the local code graph before structural discovery: - -`bash .agent/index-codebase-memory.sh` - -Current MCP project name: - -`home-vyaspc-Documents-Repo-aria-api-framework` - -## Commands - -- Install/use wrapper: `./gradlew --version` -- Main verification: `./gradlew test` -- Tagged tests: `./gradlew test -DincludeTags=smoke` -- Format: `./gradlew spotlessApply` -- Format check: `./gradlew spotlessCheck` -- Static checks: `./gradlew spotbugsMain spotbugsTest` -- Dependency/security artifacts: `./gradlew cyclonedxBom` - -## Layout - -- `src/main/java` - reusable API framework code, clients, config, reporting helpers. -- `src/test/java` - JUnit 5 API, contract, reliability, and seeded-defect tests. -- `src/test/resources` - test data, schemas, Allure/JUnit resources. -- `docs/` - architecture, execution, reliability, writing-tests, and debugging guides. -- `reliability/quarantine.yml` - quarantine policy and known reliability exceptions. -- `portfolio/manifest.yml` - portfolio metadata. - -## Codebase Memory MCP - -Use graph tools before broad file reads: - -1. `list_projects` -2. `get_architecture(project="home-vyaspc-Documents-Repo-aria-api-framework")` -3. `search_graph` -4. `trace_path` -5. `get_code_snippet` -6. `query_graph` - -Fall back to `rg` for literals, configs, docs, generated files, or insufficient graph results. - -## Agent Rules - -- Cite `file:line` for code claims whenever practical. -- Keep changes scoped to the framework layer under test; avoid unrelated cleanup. -- Prefer targeted Gradle tasks and tagged tests over full-suite reruns. -- Do not commit `.codebase-memory/`, `codebase-memory/`, or `.agent/index-codebase-memory.sh`. - diff --git a/CONTRIBUTOR_ARCHITECTURE_GUIDE.md b/CONTRIBUTOR_ARCHITECTURE_GUIDE.md new file mode 100644 index 0000000..6945296 --- /dev/null +++ b/CONTRIBUTOR_ARCHITECTURE_GUIDE.md @@ -0,0 +1,39 @@ +# Contributor Architecture Guide + +## First Run + +```bash +./gradlew test +``` + +The default `test` task is deterministic — it runs against the owned in-JVM provider fixture and +does not require external credentials. + +## Project Map + +| Area | Purpose | +| ---------------------------- | ------------------------------------------------------------------------- | +| `src/main/java` | Reusable API framework code: clients, config, reporting helpers | +| `src/test/java` | JUnit 5 API, contract, reliability, and seeded-defect tests | +| `src/test/resources` | Test data, schemas, Allure/JUnit resources | +| `docs/` | Architecture, execution, reliability, writing-tests, and debugging guides | +| `reliability/quarantine.yml` | Quarantine policy and known reliability exceptions | +| `portfolio/manifest.yml` | Portfolio metadata | + +## Commands + +- Main verification: `./gradlew test` +- Tagged tests: `./gradlew test -DincludeTags=smoke` +- Format: `./gradlew spotlessApply` +- Format check: `./gradlew spotlessCheck` +- Static checks: `./gradlew spotbugsMain spotbugsTest` +- Mutation score: `./gradlew pitest` +- Full quality gate: `./gradlew check` +- Dependency/security artifacts: `./gradlew cyclonedxBom` + +## Change Workflow + +1. Keep changes scoped to the framework layer under test; avoid unrelated cleanup. +2. Prefer targeted Gradle tasks and tagged tests over full-suite reruns while iterating. +3. Run `./gradlew check` before opening a PR — it aggregates format, static analysis, mutation + score, OpenAPI coverage, and Pact provider verification. diff --git a/docs/CONTRACT_STRATEGY.md b/docs/CONTRACT_STRATEGY.md index a0e0305..e4fba97 100644 --- a/docs/CONTRACT_STRATEGY.md +++ b/docs/CONTRACT_STRATEGY.md @@ -1,14 +1,14 @@ # Contract Strategy -ARIA uses three contract layers: +ARIA uses four contract layers: 1. JSON Schema assertions validate response shape in live endpoint tests. -2. Pact consumer tests define deterministic provider expectations for core flows. -3. The owned in-memory provider fixture verifies provider behavior for default-CI endpoint coverage and provider-state style contract checks. -4. OpenAPI coverage mapping verifies every endpoint in the checked-in API subsets has mapped default-CI tests and complete request/response contracts. +2. Pact consumer tests (`BookingConsumerPactTest`, `GithubConsumerPactTest`) define deterministic provider expectations for core flows, and `OwnedProviderPactVerificationTest` replays the generated pacts through the real Pact provider verifier against the owned fixture. +3. `OwnedProviderStateContractTest` verifies provider-state-style behavior (mass-assignment rejection, ownership checks, role/expiry handling) that sits outside what a Pact interaction expresses. +4. `OpenApiRuntimeValidationTest` validates every documented operation's live response against its OpenAPI schema (status, media type, and full body-schema conformance via `swagger-request-validator`), and OpenAPI coverage mapping verifies every endpoint in the checked-in API subsets has mapped default-CI tests and complete request/response contracts. Response DTOs for public third-party APIs intentionally tolerate unknown fields where the provider may add fields without a breaking change. GitHub response schemas are therefore permissive by policy and should not use `additionalProperties: false` unless a field subset is explicitly owned by ARIA. For owned APIs, use strict JSON Schema (`additionalProperties: false`), strict response DTOs, and avoid `@JsonIgnoreProperties(ignoreUnknown = true)` unless the API contract explicitly allows additive fields. -Current third-party Pact tests are consumer-side checks and are published as CI artifacts. ARIA also includes `OwnedProviderContractVerificationTest`, which starts the owned fixture and verifies the provider states represented by the core Restful Booker consumer contracts. For a deployed owned provider, add a provider-verification job that downloads those artifacts or broker pacts, starts the real provider, and runs the Pact verifier before deployment. +Pact provider verification runs against the owned in-JVM fixture by default, so it is part of the deterministic gate rather than a live, deployed-provider check. For a deployed owned provider, add a provider-verification job that downloads the pact artifacts or broker pacts, starts the real provider, and runs the Pact verifier before deployment. diff --git a/src/test/java/com/aria/framework/assertions/OpenApiResponseValidator.java b/src/test/java/com/aria/framework/assertions/OpenApiResponseValidator.java index 90abb64..8b6aef5 100644 --- a/src/test/java/com/aria/framework/assertions/OpenApiResponseValidator.java +++ b/src/test/java/com/aria/framework/assertions/OpenApiResponseValidator.java @@ -1,20 +1,27 @@ package com.aria.framework.assertions; +import com.atlassian.oai.validator.OpenApiInteractionValidator; +import com.atlassian.oai.validator.model.Request; +import com.atlassian.oai.validator.model.SimpleResponse; +import com.atlassian.oai.validator.report.ValidationReport; import io.restassured.response.Response; import org.yaml.snakeyaml.Yaml; import java.io.InputStream; import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; public final class OpenApiResponseValidator { private final Map spec; + private final OpenApiInteractionValidator bodyValidator; - private OpenApiResponseValidator(Map spec) { + private OpenApiResponseValidator(Map spec, OpenApiInteractionValidator bodyValidator) { this.spec = spec; + this.bodyValidator = bodyValidator; } public static OpenApiResponseValidator fromClasspath(String resourcePath) { @@ -23,7 +30,11 @@ public static OpenApiResponseValidator fromClasspath(String resourcePath) { assertThat(inputStream) .as("OpenAPI resource " + resourcePath) .isNotNull(); - return new OpenApiResponseValidator(new Yaml().load(inputStream)); + Map spec = new Yaml().load(inputStream); + OpenApiInteractionValidator bodyValidator = OpenApiInteractionValidator + .createFor(resourcePath) + .build(); + return new OpenApiResponseValidator(spec, bodyValidator); } catch (Exception exception) { throw new IllegalArgumentException("Failed to load OpenAPI resource " + resourcePath, exception); } @@ -50,6 +61,29 @@ public void assertResponse(String method, String path, Response response) { .as(method.toUpperCase(Locale.ROOT) + " " + path + " documented response media types") .anyMatch(mediaType::equalsIgnoreCase); } + + assertBodyMatchesSchema(method, path, response); + } + + private void assertBodyMatchesSchema(String method, String path, Response response) { + SimpleResponse.Builder builder = SimpleResponse.Builder.status(response.statusCode()) + .withBody(response.body().asString()); + for (String headerName : response.headers().asList().stream().map(header -> header.getName()).distinct().toList()) { + builder.withHeader(headerName, response.headers().getValues(headerName)); + } + + ValidationReport report = bodyValidator.validateResponse( + path, + Request.Method.valueOf(method.toUpperCase(Locale.ROOT)), + builder.build() + ); + + assertThat(report.hasErrors()) + .as(method.toUpperCase(Locale.ROOT) + " " + path + " response body matches the OpenAPI schema: " + + report.getMessages().stream() + .map(ValidationReport.Message::getMessage) + .collect(Collectors.joining("; "))) + .isFalse(); } private Map operation(String method, String path) {