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) {