Skip to content

Latest commit

 

History

History
172 lines (132 loc) · 7.25 KB

File metadata and controls

172 lines (132 loc) · 7.25 KB

quantumvalidator — Project Instructions

Tech Stack

Layer Technology Version
Language Python ≥ 3.11
CLI framework Typer ≥ 0.12
Terminal output Rich ≥ 13.7
TLS probe openssl binary ≥ 3.0 (external, must be on PATH)
Testing pytest + pytest-cov ≥ 8 / ≥ 5

Build & Run

pip install -e ".[dev]"                                     # editable install with dev extras
quantumvalidator check cloudflare.com                       # HTTPS probe
quantumvalidator check smtp.gmail.com --port 587            # SMTP/STARTTLS auto-detected
quantumvalidator check example.com --json                   # JSON output
quantumvalidator --version

python -m pytest                                            # full suite with coverage
python -m pytest --tb=short -q                              # quick iteration
pytest tests/test_tls_utils.py -v                           # single module

Project Structure

quantumvalidator/
  __init__.py     Package version, NullHandler
  assessor.py     assess() — public API; orchestrates probe + verdict + report
  checker.py      check_tls() — thin probe_tls wrapper; STARTTLS auto-detected
  cli.py          Typer CLI: check command; --json, --output, --version flags
  constants.py    PQC_GROUPS, SAFE_GROUPS, PROBE_GROUPS, check_openssl()
  models.py       Verdict, Status, CheckResult, QuantumReport dataclasses
  reporter.py     print_full_report() — Rich terminal renderer (no I/O of its own)
  tls_utils.py    probe_tls() — subprocess openssl s_client; sole I/O boundary
  verdict.py      determine_verdict() / build_checks() — pure logic
tests/
  conftest.py     make_probe_result() factory; SAFE_PROBE / UNSAFE_* / ERROR_PROBE
  test_*.py       pytest, AAA pattern, class-per-feature grouping

Architecture

Request lifecycle:

  1. CLI check calls assess(target, port, timeout, progress_cb) from assessor.py
  2. assess() calls check_tls(host, port, timeout) from checker.py
  3. check_tls() calls probe_tls(host, port, timeout) from tls_utils.py
  4. probe_tls() reads the server's opening bytes via a plain TCP socket (1 s cap); if a STARTTLS banner is detected (smtp/imap/pop3), passes -starttls <mode> to openssl; otherwise uses raw TLS; parses Protocol version: and Negotiated TLS1.3 group: from combined stdout+stderr
  5. assess() calls build_checks() and determine_verdict() from verdict.py; derives QuantumReport
  6. Returns QuantumReport; CLI calls print_full_report() from reporter.py

Single I/O boundary: all network calls go through tls_utils.probe_tls. Two mock targets in tests:

  • quantumvalidator.tls_utils.subprocess.run — for openssl s_client calls
  • quantumvalidator.tls_utils.socket.create_connection — for raw banner reads (banner-first probe)

Testing Conventions

  • Mock boundary: quantumvalidator.tls_utils.subprocess.run (openssl) and quantumvalidator.tls_utils.socket.create_connection (banner read) via monkeypatch
  • Fixture factory: make_probe_result() in conftest.py — builds TLSProbeResult with defaults
  • Module-level fixtures: SAFE_PROBE, UNSAFE_PROBE_CLASSICAL, UNSAFE_PROBE_TLS12, ERROR_PROBE
  • Assessor mocking: patch "quantumvalidator.assessor.check_tls" (imported at module top)
  • CLI mocking: patch "quantumvalidator.assessor.assess" and "quantumvalidator.reporter.print_full_report" (both are lazy imports inside check())
  • Reporter tests: use Console(file=StringIO(), no_color=True, width=200) to capture output; pass console as trailing positional arg — private helpers use con, not console
  • Test class naming: class TestFeatureName:, snake_case methods, AAA structure
  • Coverage target: 100% — enforced via pyproject.toml addopts

Exit Codes

  • 0 — SAFE: PQC hybrid key exchange negotiated
  • 1 — UNSAFE: classical KEX only or TLS < 1.3
  • 2 — Error: connection refused/timeout, DNS failure, openssl not found, invalid host/port

Conventions

  • from __future__ import annotations at the top of every module
  • Snake_case for all files, functions, and variables
  • Sphinx-style docstrings: :param name:, :returns:, :rtype: (no :type: — annotations on signatures are sufficient)
  • Conventional commits: feat:, fix:, refactor:, test:, docs:
  • assess() in assessor.py is the sole public entry point for library users
  • openssl binary is a hard external dependency; missing → RuntimeError raised inside probe_tls()
  • OSErrors from subprocess are caught in probe_tls() and returned as TLSProbeResult(error=...) — they never propagate to the CLI
  • No CI config currently present

Before Every Commit

Run these checks and update these files as needed — do not skip any step:

# 1. Verify tests pass and coverage is still 100%
pytest

If the test count changed, update both occurrences in README.md:

  • Badge line (near top): ![Tests](https://img.shields.io/badge/tests-NNN%20passing-brightgreen)
  • Running Tests section: "The test suite has NNN tests…" sentence
# 2. Verify the CLI still works
quantumvalidator --version
quantumvalidator --help

Before pushing, update CHANGELOG.md: add your changes under ## [Unreleased] using the standard sections (### Added, ### Changed, ### Fixed, ### Removed). When bumping the version, move unreleased items to a new ## [x.y.z] — YYYY-MM-DD section and update the comparison links at the bottom of CHANGELOG.md.

Version Bumping

When committing a set of changes, bump the version using semver:

  • patch (0.1.x) — bug fixes, refactor, docs, test-only changes
  • minor (0.x.0) — new PQC groups, new CLI flags, new features
  • major (x.0.0) — breaking API changes

Two files must always be updated together:

  • pyproject.tomlversion = "x.y.z"
  • quantumvalidator/__init__.py → fallback __version__ = "x.y.z" (the except branch)

GitHub Release

Every version bump must be followed by a GitHub release. Do not leave a version tag without a release.

After bumping the version, committing, and pushing:

# Tag the version commit and push
git tag vX.Y.Z
git push origin vX.Y.Z

# Create the GitHub release
gh release create vX.Y.Z \
  --title "vX.Y.Z" \
  --notes "$(cat <<'EOF'
## What's changed

<Copy the ### Added / ### Changed / ### Fixed / ### Removed blocks verbatim
from the [X.Y.Z] section in CHANGELOG.md>

## Impact

<1–3 sentences: what this means for users — what improves, what breaks,
whether the upgrade is urgent (e.g. new PQC group support, probe fix, etc.)>

## Migration

<Only for minor/major bumps: list any CLI flags, `assess()` parameters, or
output-format changes that require user action. Omit for patch releases.>

---

**Full changelog:** https://github.com/NC3-TestingPlatform/quantumvalidator/blob/master/CHANGELOG.md
EOF
)"

Release body checklist:

  • Changelog entries for this version copied verbatim
  • Impact note written (even one sentence is enough)
  • Migration note present if CLI flags or assess() signature changed
  • Full changelog link at the bottom

Conventions:

  • Tag and title: vX.Y.Z — semver, v-prefixed, must match pyproject.toml version
  • Do not mark as draft or pre-release for normal semver releases