Testing¶
Opencomplai uses pytest for Python tests and pnpm test for the Node.js gateway service. This page covers how to run the full test suite, where tests live, and what to write for new contributions.
Python tests¶
Run all Python tests¶
Run tests for a specific package¶
Run a specific test file¶
Run with coverage¶
Test layout¶
Tests mirror the source structure under each package:
packages/
core/
src/opencomplai_core/ source
tests/
test_engine.py engine + rule tests
test_models.py Pydantic model validation tests
cli/
src/opencomplai_cli/
tests/
test_main.py CLI command tests (via typer.testing.CliRunner)
sdk-python/
src/opencomplai/
tests/
test_sdk.py SDK public API tests
Writing tests for a new rule¶
When you add a rule, add tests to packages/core/tests/test_engine.py. Use the _make_input helper pattern:
from opencomplai_core.engine import assess
from opencomplai_core.models import AssessmentInput, ModelMetadata
def _make_input(use_case: str, answers: dict | None = None) -> AssessmentInput:
return AssessmentInput(
model=ModelMetadata(
name="test-model",
version="1.0",
modality="text",
use_case=use_case,
deployment_context="test",
),
answers=answers or {},
)
def test_my_rule_passes():
result = assess(_make_input("safe use case"))
rule = next(r for r in result.rule_results if r.rule_id == "EU_AIA_MY_RULE")
assert rule.passed
def test_my_rule_fails():
result = assess(_make_input("triggering use case"))
rule = next(r for r in result.rule_results if r.rule_id == "EU_AIA_MY_RULE")
assert not rule.passed
assert "keyword" in rule.rationale # verify the rationale is informative
Writing tests for a new CLI command¶
Use typer.testing.CliRunner:
from typer.testing import CliRunner
from opencomplai_cli.main import app
runner = CliRunner()
def test_validate_manifest_valid(tmp_path):
manifest = tmp_path / "manifest.json"
manifest.write_text('{"system_id":"x","intended_purpose":"p","compliance_target":"EU_AI_ACT","high_risk_presumption":false,"commit_ref":"HEAD"}')
result = runner.invoke(app, ["validate-manifest", str(manifest)])
assert result.exit_code == 0
assert "valid" in result.output
def test_validate_manifest_missing_file(tmp_path):
result = runner.invoke(app, ["validate-manifest", str(tmp_path / "missing.json")])
assert result.exit_code == 2
Node.js tests (gateway-api)¶
The gateway-api uses Jest. Test files live under services/gateway-api/src/__tests__/.
CI matrix¶
Tests run automatically on every pull request via .github/workflows/ci-python.yml and .github/workflows/ci-node.yml. The matrix covers:
| Workflow | Scope | Tools |
|---|---|---|
ci-python.yml | All Python packages | uv run pytest, ruff check, ruff format --check |
ci-node.yml | services/gateway-api | pnpm test, pnpm lint |
Both must be green before a PR can merge. There is no ci-docker.yml workflow yet — the Docker Compose stack is not currently built or smoke-tested in CI.
The test suites import from source, so they cannot see a module, dependency or data file the published wheels fail to ship. bash scripts/smoke_wheel_install.sh covers that: it builds the core, CLI and SDK wheels, installs only those into a clean venv and runs --version, init, check --with-gaps (EU AI Act and NIST AI RMF targets, checking framework_reports, gap_report and nist_rmf_report), an offline docs generate and a check --gate NIST_AI_RMF that must fail, from outside the repo. It needs uv and runs on Linux and Git Bash on Windows. It also runs --version from a uv tool install and a pipx install of the same wheels, so the meta-package's own executable is covered.
Pre-commit hooks¶
The project uses pre-commit to run checks before each commit. Install once:
Hooks run ruff check, ruff format, and other checks on changed files. Run manually: