Skip to content

test: fuzz api responses against openapi contracts - #228

Open
aditeyabaral wants to merge 3 commits into
pesu-dev:devfrom
aditeyabaral:test/openapi-fuzz
Open

aditeyabaral wants to merge 3 commits into
pesu-dev:devfrom
aditeyabaral:test/openapi-fuzz

Conversation

@aditeyabaral

@aditeyabaral aditeyabaral commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

📌 Description

Add deterministic OpenAPI contract fuzzing to the offline suite, plus URL-based pytest and CLI runs against an isolated local server. All four documented operations are checked for server errors, undocumented statuses, content-type mismatches, and response schema violations.

An explicit pre-commit hook and CI job run the URL/CLI checks, own server startup and cleanup, and publish JUnit, JSON, and informational TraceCov HTML reports. A credential-dependent Schemathesis test generates six positive profile/field combinations and calls the real PESU Academy backend using TEST_EMAIL and TEST_PASSWORD from .env. Existing authentication integration responses are also checked against the contract. The live generator uses no PESU mocks and keeps credentials out of generated cases and failure reproductions.

Fuzzing found malformed non-UTF-8 JSON returning FastAPI's detail body. It now returns the documented 400 error envelope and records a RequestBodyParseError. Metrics JSON now references MetricsModel; /readme correctly documents a bodyless 308 and its Location header.

Closes #218. Credential length limits remain in #192.

🧱 Type of Change

  • 🐛 Non-breaking bug fix
  • 📝 Documentation update
  • 🧪 Test suite change
  • ⚙️ CI/CD pipeline update
  • 🔧 Developer tooling
  • 🧰 Development dependency update
  • ⚠️ Breaking change

🧪 How Has This Been Tested?

  • Unit tests
  • Functional tests against PESU Academy
  • Integration tests with real .env credentials
  • Manual Docker and report checks

Linux, Python 3.14 via uv:

  • Canonical suite through pre-commit: 335 passed, 5 fuzz subtests passed; application coverage 100%. All 12 credential-required tests ran locally, including the new Schemathesis-generated real-account test.
  • URL pytest: all four operation tests passed with 500 fuzz examples per operation and seed 218.
  • CLI: all four operations exercised; JUnit, JSON, and TraceCov HTML reports generated.
  • All pre-commit hooks passed, including the explicit OpenAPI URL/CLI hook.
  • Docker image built; Swagger and /health returned 200.
  • Version check passed: 4.15.5 → 4.16.0, with uv.lock aligned.
  • Independent correctness and security reviews completed.

✅ Checklist

  • Contribution conventions followed
  • Comments, docstrings, README, OpenAPI docs, and CONTRIBUTING updated
  • Tests cover the fixes and show all four contract checks detecting deliberate faults
  • Full local tests and pre-commit passed
  • Docker image builds and serves Swagger and health
  • Backwards compatible
  • Version bumped once

🛠️ Affected API Behaviour

  • app/app.py: unreadable bodies use the standard 400 response
  • app/docs/: metrics schema, redirect headers, and parse-error documentation
  • GitHub Actions and pre-commit
  • pyproject.toml / uv.lock: dev tools and minor version
  • Upstream scraping/login implementation
  • Runtime dependencies or Dockerfile

📸 API Demo

A JSON request body containing an invalid UTF-8 byte now receives status 400 with:

{"status": false, "message": "Could not parse request body.", "timestamp": "<ISO timestamp>"}

🧠 Additional Notes

The arbitrary-input gate stays offline as required by the issue. Real-account Schemathesis and ordinary contract checks run serially in the integration suite and use sanitized, title-only failure output with exception context suppressed. Live generation disables schema examples, boundary probes, shrinking, and persistent replay; its budget is six generated examples, while arbitrary offline fuzzing retains the larger 200/500-example budgets. Fork CI skips credential-required tests because it has no secrets; they were verified locally.

Unsanitized reproductions are confined to the synthetic loopback harness, which rejects other target URLs/tokens and blocks PESU HTTP requests. The CLI may emit authentication/validation warnings for generated inputs rejected by the mocked backend; these are not conformance failures.

TraceCov is development-only and uses the TraceCov Community License. Its schema-coverage report is informational and does not replace the existing application-code coverage gate.

Generate deterministic offline requests through the real ASGI app and a mocked loopback server, validating status codes, content types, response schemas and server errors. Run the URL and CLI paths through a shared pre-commit and CI runner with owned server cleanup and JUnit, JSON and TraceCov reports.

Validate existing credential-driven integration responses against the same contract without adding PESU logins. Normalize unreadable request bodies to the standard 400 error shape and correct metrics JSON and bodyless redirect documentation.

Validation: pre-commit passed; 330 tests and 4 fuzz subtests passed with real credentials and 100% application coverage. URL fuzz tests passed and CLI exercised all four operations. Docker build and Swagger/health smoke checks passed.
Raise the minor version once for the OpenAPI fuzz suite and its contract fixes, keeping the application metadata and lockfile aligned.
@aditeyabaral
aditeyabaral requested a review from a team as a code owner October 3, 2026 01:01
Copilot AI balanced review requested due to automatic review settings October 3, 2026 01:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Use TEST_EMAIL and TEST_PASSWORD loaded from .env for a bounded Schemathesis authentication test against the real PESU Academy backend. Generate six profile and field combinations serially, without mocking upstream calls, and discover the test through the canonical pre-commit runner.

Keep actual credentials outside generated cases and validate responses using a separate schema with sanitized, context-free failure messages. Add offline regressions for safe pytest reporting and document the real-account command alongside the arbitrary-input offline suite.

Verification: pre-commit passed with 335 tests, 5 fuzz subtests and 100% application coverage. All 12 credential-required tests ran locally, including the generated live test. URL pytest and CLI checks also passed; independent correctness and privacy reviews approved.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Two moderate findings and three documentation/reporting nits remain unresolved.

Review effort: Lite
Findings: 1 Medium severity

Open (1)

Comment on lines +240 to 241
live_contract(response)
assert response.status_code in (200, 401, 500)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fuzz the API against its OpenAPI schema with Schemathesis

2 participants