change: Implement 1: Bootstrap the toolkit tree, project config and initial dependency lock
Repository Validate / repository-checks (pull_request) Successful in 2s
Repository Validate / repository-checks (pull_request) Successful in 2s
Bootstrapped the repository-root toolkit tree required by the facade. - `tools/__init__.py`: minimal importable package exposing `__version__` only. - `tools/example.py`: offline CLI (`python -m tools.example REQUEST`) that loads and validates a JSON request document with a validate-only `dry_run` semantics, prints exactly one structured JSON envelope (sorted keys) and fixes the status/exit vocabulary now (0 ok, 1 invalid request document, 2 usage error). `--help` supported. - `examples/input.json`: canonical offline validate-only request document. - `tests/test_example.py`: focused tests for the smoke command, envelope shape, request-document validation, `--help`, and the module entrypoint (13 tests). - `pyproject.toml`: project metadata, setuptools build backend with `packages = ["tools"]`, `dev` extra with `pytest>=8.0`, pytest options `pythonpath=["."]` / `testpaths=["tests"]` so the repository root is importable. - `uv.lock`: created through the supported dependency update operation for the `tools` component; pinned only from the approved dependency mirror (no public-index fallbacks, no credentials). - `.gitignore` / `.env.example`: keep build artifacts out of the tree and document that no environment variables are required. Note: `requires-python` was raised to `>=3.11` because the approved mirror only offers `exceptiongroup<1.0.0rc8`, which cannot satisfy pytest on Python <3.11; the prepared environment (CPython 3.12.3) and CI bootstrap interpreter satisfy this. No protected facade paths were modified. Tests: Ran the facade commands with the prepared environment (`.venv/bin/python`): - `python -m compileall -q tools tests` -> OK (tools.validate) - `python -m pytest -q` -> 13 passed (tools.test) - `python -m tools.example examples/input.json` -> single JSON envelope, exit 0 (tools.smoke) - `python -m tools.example --help` -> usage text, exit 0 (help-command) Dependency lock/materialization performed via prepare_environment(component='tools', mode='update'): resolved + materialized, `uv.lock` written. Target branch `origin/task/1-task` is already an ancestor of HEAD, so the branch contains the current target. Progress: Created tools package (tools/__init__.py, tools/example.py), examples/input.json, tests/test_example.py, .gitignore, .env.example; updated pyproject.toml (metadata, setuptools packages, dev pytest extra, pytest pythonpath/testpaths, requires-python >=3.11 due to approved-mirror exceptiongroup constraint) and generated uv.lock via prepare_environment update. Facade validate/test/smoke and --help all pass locally. Published by developer-agent through Tool Backend. Closes #3
This commit was merged in pull request #11.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""Tests for the placeholder toolkit CLI and its request document."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tools import example
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
EXAMPLE_REQUEST = REPO_ROOT / "examples" / "input.json"
|
||||
|
||||
|
||||
def run_cli(*args: str) -> subprocess.CompletedProcess[str]:
|
||||
"""Run the CLI in a subprocess so stdout/exit codes are asserted end to end."""
|
||||
return subprocess.run(
|
||||
[sys.executable, "-m", "tools.example", *args],
|
||||
cwd=REPO_ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def test_example_request_document_is_valid_json_object() -> None:
|
||||
document = json.loads(EXAMPLE_REQUEST.read_text(encoding="utf-8"))
|
||||
|
||||
assert isinstance(document, dict)
|
||||
assert set(example.REQUIRED_FIELDS) <= set(document)
|
||||
assert document["dry_run"] is True
|
||||
assert document["mode"] == "validate"
|
||||
|
||||
|
||||
def test_example_request_document_passes_validation() -> None:
|
||||
request = example.validate_request(example.load_request(EXAMPLE_REQUEST))
|
||||
|
||||
assert request["mode"] == "validate"
|
||||
assert request["dry_run"] is True
|
||||
assert request["arguments"]["request"] == "examples/input.json"
|
||||
|
||||
|
||||
def test_cli_prints_single_json_envelope_and_exits_zero() -> None:
|
||||
result = run_cli("examples/input.json")
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert result.stderr == ""
|
||||
lines = [line for line in result.stdout.splitlines() if line.strip()]
|
||||
assert len(lines) == 1
|
||||
|
||||
envelope = json.loads(lines[0])
|
||||
assert envelope == {
|
||||
"status": "ok",
|
||||
"command": "example",
|
||||
"mode": "validate",
|
||||
"dry_run": True,
|
||||
"version": 1,
|
||||
"request": "examples/input.json",
|
||||
}
|
||||
|
||||
|
||||
def test_cli_help_exits_zero() -> None:
|
||||
result = run_cli("--help")
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "REQUEST" in result.stdout
|
||||
|
||||
|
||||
def test_cli_reports_missing_request_document(tmp_path: Path) -> None:
|
||||
missing = tmp_path / "missing.json"
|
||||
|
||||
result = run_cli(str(missing))
|
||||
|
||||
assert result.returncode == example.EXIT_INVALID_REQUEST
|
||||
envelope = json.loads(result.stdout)
|
||||
assert envelope["status"] == "error"
|
||||
assert envelope["error"]["code"] == "request_not_found"
|
||||
|
||||
|
||||
def test_cli_reports_malformed_json(tmp_path: Path) -> None:
|
||||
malformed = tmp_path / "malformed.json"
|
||||
malformed.write_text("{not json", encoding="utf-8")
|
||||
|
||||
result = run_cli(str(malformed))
|
||||
|
||||
assert result.returncode == example.EXIT_INVALID_REQUEST
|
||||
assert json.loads(result.stdout)["error"]["code"] == "request_not_json"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("document", "code"),
|
||||
[
|
||||
([], "request_not_object"),
|
||||
({"mode": "validate", "dry_run": True, "command": "example", "arguments": {}}, "request_field_missing"),
|
||||
({"version": 1, "mode": "validate", "dry_run": False, "command": "example", "arguments": {"request": "x"}}, "request_not_dry_run"),
|
||||
({"version": 1, "mode": "execute", "dry_run": True, "command": "example", "arguments": {"request": "x"}}, "request_mode_unsupported"),
|
||||
({"version": 1, "mode": "validate", "dry_run": True, "command": "example", "arguments": {}}, "request_field_missing"),
|
||||
({"version": 1, "mode": "validate", "dry_run": True, "command": "", "arguments": {"request": "x"}}, "request_field_invalid"),
|
||||
],
|
||||
)
|
||||
def test_validate_request_rejects_invalid_documents(document: object, code: str) -> None:
|
||||
with pytest.raises(example.RequestError) as excinfo:
|
||||
example.validate_request(document)
|
||||
|
||||
assert excinfo.value.code == code
|
||||
|
||||
|
||||
def test_envelope_is_deterministic() -> None:
|
||||
request = example.validate_request(example.load_request(EXAMPLE_REQUEST))
|
||||
|
||||
first = example.build_envelope(request, "examples/input.json")
|
||||
second = example.build_envelope(request, "examples/input.json")
|
||||
|
||||
assert first == second
|
||||
assert json.dumps(first, sort_keys=True) == json.dumps(second, sort_keys=True)
|
||||
Reference in New Issue
Block a user