Repository Validate / repository-checks (pull_request) Successful in 19s
Rewrote README.md as the delivered tool-calling contract for the calling AI agent and added tests/test_readme.py to keep it synchronized with the implementation. README.md now documents: - Native-process usage only: `python -m tools.example` with no Docker, container image or packaging stage, Python >= 3.11, no runtime third-party dependencies. - Every parameter exactly as produced by `--help`: positional `REQUEST.json`, `-t/--topic`, `--width`, `--height`, `-f/--format` (`png|ico`), `-s/--source` (default `wikimedia_commons`), `-c/--count` (1-50, default 10), `--download`, `-o/--output-dir`, `--resize`, `--dry-run`; width/height documented as minimums and the "document XOR flags" rule. - The JSON request document form used by `examples/input.json`: field table, types, defaults, optional `version`, and the document-level invalid_request rules. - The single-document JSON response schema: fixed keys `status`, `source`, `request`, `items`, `error`; item fields (actual format/width/height, `url`/`file_url`, `size_bytes`), download record, resize fragment and the verified-save failure record. - The fixed status vocabulary with its exit codes: `ok`=0, `dry_run`=0, `invalid_request`=2, `no_format`=3, `no_size`=4, `access_error`=5, `partial_download`=6, plus why no_match/outage are distinguishable. - Approved free sources (`wikimedia_commons`, `openverse`) with no API keys, tokens or registration, the approved HTTPS host allowlist, and the registry extension point (`SourceAdapter`, `SourceRegistry.register/unregister`, `build_registry()`/`default_registry()`, host allowlist extension). - Both decided behaviours: `--download` without `--output-dir` is an invalid request (invalid_request / exit 2), and partial saves are reported as `partial_download` (exit 6) with `download_incomplete` details while saved paths are still reported. - The real test entrypoint `pytest` plus the compileall/help/smoke commands. tests/test_readme.py asserts the README documents every long option the real parser accepts, the accepted formats, every status with its exit code from STATUS_EXIT_CODES, the response keys, every registered source id, the registry extension names, both product decisions, native-process wording, and the real pytest/compileall/help/smoke commands. Tests: Ran the facade check set with .venv/bin on PATH after `prepare_environment(component=tools, mode=restore)`: - `pytest -q` -> 163 passed, 1 skipped (pre-existing tests/test_live_source.py live-network skip: Wikimedia upload host returns HTTP 429 from this sandbox). - `python -m compileall -q tools tests` -> ok. - `python -m tools.example --help` -> exit 0. - `python -m tools.example examples/input.json` -> exit 0, one JSON dry_run envelope. - `pytest tests/test_readme.py -q` -> 5 passed. Also confirmed `git merge-base --is-ancestor origin/task/1-task HEAD` (target branch already integrated, zero diff), so no integration step was required. Progress: Delivered the documentation slice for issue 9. README.md was a two-line placeholder; it is now the full tool-calling contract: CLI parameters matching `--help` output, the JSON request-document form for examples/input.json, the single-document response schema, the fixed status vocabulary with exit codes (ok/dry_run=0, invalid_request=2, no_format=3, no_size=4, access_error=5, partial_download=6), credential-free sources and the registry extension point, the decided --download-without---output-dir invalid-request behaviour, partial_download reporting for incomplete saves, and plain native-process execution with the real pytest entrypoint. Added tests/test_readme.py (5 tests) so CI fails if documentation drifts from build_parser(), STATUS_EXIT_CODES, ACCEPTED_FORMATS or the default registry. No protected facade paths or source behaviour changed; existing obligations preserved (full suite green apart from the pre-existing live-source skip). Published by developer-agent through Tool Backend. Closes #9
93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
"""Keep README.md synchronized with the delivered CLI contract.
|
|
|
|
The documentation is part of the product: the calling agent relies on the
|
|
parameter list, the status vocabulary and the exit codes stated in README.md.
|
|
These checks fail when the implementation moves on and the documentation is
|
|
left describing something the tool no longer does.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from tools.envelope import STATUS_EXIT_CODES
|
|
from tools.errors import ACCEPTED_FORMATS
|
|
from tools.example import build_parser
|
|
from tools.sources import default_registry
|
|
|
|
README = Path(__file__).resolve().parent.parent / "README.md"
|
|
|
|
|
|
def readme_text() -> str:
|
|
assert README.is_file(), "README.md is missing"
|
|
text = README.read_text(encoding="utf-8")
|
|
assert text.strip(), "README.md is empty"
|
|
return text
|
|
|
|
|
|
def long_options() -> list[str]:
|
|
"""Return every ``--long`` option the real parser accepts."""
|
|
|
|
options: list[str] = []
|
|
for action in build_parser()._actions:
|
|
for option in action.option_strings:
|
|
if option.startswith("--"):
|
|
options.append(option)
|
|
return sorted(set(options))
|
|
|
|
|
|
def test_readme_documents_every_cli_parameter() -> None:
|
|
text = readme_text()
|
|
options = long_options()
|
|
assert {"--topic", "--width", "--height", "--format", "--source", "--count",
|
|
"--download", "--output-dir", "--resize", "--dry-run"} <= set(options)
|
|
missing = [option for option in options if option == "--help" and option not in text]
|
|
missing += [
|
|
option
|
|
for option in options
|
|
if option != "--help" and f"`{option}" not in text
|
|
]
|
|
assert not missing, f"README.md does not document parameters: {missing}"
|
|
# The positional request document and both invocation styles are documented.
|
|
assert "REQUEST.json" in text
|
|
assert "python -m tools.example examples/input.json" in text
|
|
# Format vocabulary in the README matches the accepted formats.
|
|
for image_format in ACCEPTED_FORMATS:
|
|
assert image_format in text
|
|
|
|
|
|
def test_readme_status_table_matches_exit_codes() -> None:
|
|
text = readme_text()
|
|
for status, exit_code in STATUS_EXIT_CODES.items():
|
|
assert f"| `{status}` | {exit_code} |" in text, (
|
|
f"README.md status table disagrees for {status!r} -> {exit_code}"
|
|
)
|
|
|
|
|
|
def test_readme_documents_response_schema_and_sources() -> None:
|
|
text = readme_text()
|
|
for key in ('"status"', '"source"', '"request"', '"items"', '"error"'):
|
|
assert key in text, f"README.md does not document response key {key}"
|
|
for source_id in default_registry().available_sources():
|
|
assert source_id in text, f"README.md does not document source {source_id!r}"
|
|
assert "SourceAdapter" in text and "SourceRegistry" in text
|
|
assert "API key" in text
|
|
|
|
|
|
def test_readme_documents_product_decisions() -> None:
|
|
text = readme_text()
|
|
# --download without --output-dir is an invalid request.
|
|
assert "--download" in text and "without `--output-dir` is an invalid request" in text
|
|
# Partial saves have their own status.
|
|
assert "partial_download" in text
|
|
assert "invalid_request" in text
|
|
# Native process only: no Docker or container packaging.
|
|
assert "Docker" in text and "container" in text
|
|
|
|
|
|
def test_readme_documents_the_real_test_entrypoint() -> None:
|
|
text = readme_text()
|
|
assert "\npytest\n" in text, "README.md must document the real pytest entrypoint"
|
|
assert "python -m compileall tools tests" in text
|
|
assert "python -m tools.example --help" in text
|