Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions AGENTS.md

This file was deleted.

2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
56 changes: 0 additions & 56 deletions CLAUDE.md

This file was deleted.

39 changes: 39 additions & 0 deletions CONTRIBUTOR_ARCHITECTURE_GUIDE.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 5 additions & 5 deletions docs/CONTRACT_STRATEGY.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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<String, Object> spec;
private final OpenApiInteractionValidator bodyValidator;

private OpenApiResponseValidator(Map<String, Object> spec) {
private OpenApiResponseValidator(Map<String, Object> spec, OpenApiInteractionValidator bodyValidator) {
this.spec = spec;
this.bodyValidator = bodyValidator;
}

public static OpenApiResponseValidator fromClasspath(String resourcePath) {
Expand All @@ -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<String, Object> 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);
}
Expand All @@ -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<String, Object> operation(String method, String path) {
Expand Down