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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ jobs:
- name: flake8
run: flake8 aura tests
- name: pytest
run: pytest
run: pytest --cov=aura --cov-report=term-missing
2 changes: 1 addition & 1 deletion .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
pip install -e ".[dev]"
black --check aura tests
flake8 aura tests
pytest
pytest --cov=aura --cov-report=term-missing

publish:
needs: test
Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **PR CI workflow** ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) — `lint-test` job on pull requests and pushes to `main` (black, flake8, pytest); read-only permissions, no PyPI secrets.
- **Test suite infrastructure** — shared `tests/conftest.py` (`aura_home`, `run_aura` CLI helper), `skillware` pytest marker.
- **`tests/test_cli.py`** — CLI coverage for version, agent CRUD, run, logs, export, export-otel, compare.
- **`tests/test_core_gaps.py`** — config merge, registry archive/legacy ids, hash-chain tamper, compare/export/OTel/runtime/middleware/session modes.
- **`tests/test_examples_smoke.py`** — parametrized smoke runs for all `examples/*/main.py` scripts.
- **CI** — `pytest --cov=aura` report-only output in `lint-test` job.
- **PR CI workflow** ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) — `lint-test` on pull requests and pushes to `main` (merged via #5).

## [0.3.2] - 2026-08-20

Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ Follow the [Code of Conduct](CODE_OF_CONDUCT.md). We welcome autonomous logical
flake8 aura tests
```

- CI runs on PRs via [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (job **`lint-test`**: pytest, black, flake8). See [TESTING.md](docs/TESTING.md).
- Shared fixtures: **`tests/conftest.py`** (`aura_home`, `run_aura` for CLI subprocess tests).
- CI runs on PRs via [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (job **`lint-test`**: pytest with coverage report, black, flake8). See [TESTING.md](docs/TESTING.md).
- Wait for green checks before requesting review.

### CHANGELOG
Expand Down
32 changes: 27 additions & 5 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pip install -e ".[dev]"

```bash
pytest
pytest -v tests/test_v03.py
pytest --cov=aura --cov-report=term-missing
```

## Lint (required before PR)
Expand Down Expand Up @@ -46,14 +46,36 @@ pytest

**Maintainers:** after the first green `lint-test` run on `main`, enable **branch protection** → required status check **`lint-test`**.

## Coverage expectations

- **New behavior needs a test** — extend the closest file (`test_core.py`, `test_v02.py`, `test_v03.py`, `test_cli.py`, or `test_core_gaps.py`).
- Shared fixtures live in **`tests/conftest.py`** — do not duplicate `aura_home` in test modules.
- Optional Skillware-only tests use `@pytest.mark.skillware` and `pytest.importorskip("skillware")`.
- CI prints **`pytest --cov=aura --cov-report=term-missing`** for visibility; there is **no coverage gate** yet.

## Test layout

| File | Focus |
|---|---|
| `conftest.py` | `aura_home`, `run_aura`, example runner |
| `test_core.py` | Registry, spine, constraints, session export (v0.1) |
| `test_v02.py` | Sequencer, observers, membrane, Skillware host |
| `test_v03.py` | Identity, audit report, hash chain, compare |
| `test_cli.py` | `aura` CLI commands and exit codes |
| `test_core_gaps.py` | Config, exporters, runtime, middleware, archive, tamper |
| `test_examples_smoke.py` | Runnable example scripts |

## What we test

| Area | Tests |
|---|---|
| Identity | ULID ids, `agent_ref`, custom `aura_id`, resolve lookup |
| Audit | Hash chain, audit report, approver principal, session export |
| Core | Registry, spine, constraints, conformance, sequencer (see `test_core.py`, `test_v02.py`) |
| Compare / OTel | `test_v03.py` |
| Identity | ULID ids, `agent_ref`, custom `aura_id`, resolve lookup, legacy `AURA-000n`, archive |
| Audit | Hash chain (valid + tamper), audit report, approver principal, session export |
| Core | Registry, spine, constraints, conformance, sequencer (`test_core.py`, `test_v02.py`) |
| CLI | Version, agent CRUD, run, logs, export, export-otel, compare (`test_cli.py`) |
| Config / runtime | YAML merge, `run_script`, middleware, session modes (`test_core_gaps.py`) |
| Compare / OTel | Summary diff, OTel JSONL export (`test_v03.py`, `test_core_gaps.py`) |
| Examples | Smoke run all `examples/*/main.py` (`test_examples_smoke.py`) |

## Pre-PR checklist

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ include = ["aura*"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
markers = [
"skillware: requires skillware extra (pip install -e '.[skillware]')",
]

[tool.black]
line-length = 100
Expand Down
68 changes: 68 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Shared pytest fixtures and helpers."""

from __future__ import annotations

import os
import subprocess
import sys
from collections.abc import Sequence
from pathlib import Path

import pytest

from aura import configure


@pytest.fixture
def aura_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""Isolated AURA_HOME with configure() applied."""
home = tmp_path / "aura_home"
home.mkdir()
monkeypatch.setenv("AURA_HOME", str(home))
configure()
return home


@pytest.fixture
def project_dir(tmp_path: Path) -> Path:
proj = tmp_path / "project"
proj.mkdir()
return proj


@pytest.fixture
def run_aura(aura_home: Path):
"""Run `python -m aura.cli.main` with isolated AURA_HOME."""

def _run(*args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["AURA_HOME"] = str(aura_home)
return subprocess.run(
[sys.executable, "-m", "aura.cli.main", *args],
env=env,
cwd=str(cwd) if cwd else None,
capture_output=True,
text=True,
)

return _run


def run_example(main_py: Path, aura_home: Path) -> subprocess.CompletedProcess[str]:
"""Run an example script with AURA_HOME set."""
env = os.environ.copy()
env["AURA_HOME"] = str(aura_home)
return subprocess.run(
[sys.executable, str(main_py)],
env=env,
capture_output=True,
text=True,
cwd=str(main_py.parent),
timeout=30,
)


@pytest.fixture
def example_scripts() -> Sequence[Path]:
root = Path(__file__).resolve().parents[1] / "examples"
return sorted(root.glob("*/main.py"))
97 changes: 97 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""CLI integration tests."""

from __future__ import annotations

import json
from pathlib import Path

from aura import agent


def test_cli_version(run_aura):
result = run_aura("version")
assert result.returncode == 0
assert "aura-harness" in result.stdout
assert "0.3." in result.stdout


def test_cli_agent_create_list_show(run_aura):
create = run_aura(
"agent",
"create",
"demo-bot",
"--ref",
"acme/demo",
"--policy-version",
"2",
)
assert create.returncode == 0
data = json.loads(create.stdout)
assert data["agent_ref"] == "acme/demo"
assert data["policy_version"] == "2"

listing = run_aura("agent", "list")
assert listing.returncode == 0
assert "acme/demo" in listing.stdout
assert "demo-bot" in listing.stdout

show = run_aura("agent", "show", "acme/demo")
assert show.returncode == 0
profile = json.loads(show.stdout)
assert profile["name"] == "demo-bot"

missing = run_aura("agent", "show", "no-such-agent")
assert missing.returncode == 1
assert "not found" in missing.stderr


def test_cli_run_script(run_aura, tmp_path: Path):
script = tmp_path / "hello.py"
script.write_text("# run under aura session\n", encoding="utf-8")
result = run_aura("run", "cli-runner", str(script))
assert result.returncode == 0
payload = json.loads(result.stdout)
assert payload["session_id"].startswith("aura_sess_")
assert "summary" in payload["exports"]


def test_cli_logs_export_compare_otel(run_aura, aura_home: Path):
ag = agent("cli-export", agent_ref="test/cli")
with ag.session() as run:
run.emit("turn.start", {})
run.emit("turn.end", {"tokens": 1})
session_id = run.session_id

logs = run_aura("logs", session_id)
assert logs.returncode == 0
rows = [json.loads(line) for line in logs.stdout.splitlines() if line.strip()]
assert any(r["kind"] == "turn.start" for r in rows)

export = run_aura("export", session_id)
assert export.returncode == 0
summary = json.loads(export.stdout)
assert summary["agent_ref"] == "test/cli"
assert summary["audit_report"]["verdict"] == "pass"

missing = run_aura("export", "missing-session")
assert missing.returncode == 1

with ag.session() as run2:
run2.emit("turn.start", {})
otel = run_aura("export-otel", run2.session_id)
assert otel.returncode == 0
assert "turn.start" in otel.stdout
otel_path = aura_home / "sessions" / f"{run2.session_id}.otel.jsonl"
assert otel_path.is_file()

compare = run_aura("compare", session_id, run2.session_id)
assert compare.returncode == 0
diff = json.loads(compare.stdout)
assert diff["session_a"] == session_id
assert diff["event_count"]["b"] < diff["event_count"]["a"]


def test_cli_run_requires_script(run_aura):
result = run_aura("run", "agent-only")
assert result.returncode == 1
assert "script" in result.stderr.lower()
11 changes: 1 addition & 10 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,14 @@

import pytest

from aura import agent, configure, ApprovalRequired
from aura import agent, ApprovalRequired
from aura.agents.registry import AgentRegistry, DuplicateAgentError
from aura.core.constraints import ConstraintEngine, ConstraintContext
from aura.core.ids import is_ulid
from aura.core.spine import AuditSpine
from aura.core.spectrum import Spectrum


@pytest.fixture
def aura_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
home = tmp_path / "aura_home"
home.mkdir()
monkeypatch.setenv("AURA_HOME", str(home))
configure()
return home


def test_spectrum_from_manifest_defaults():
s = Spectrum.from_manifest({})
assert s.level == "mid"
Expand Down
Loading
Loading