75 lines
3.0 KiB
Python
75 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
import copy
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from jsonschema import Draft202012Validator
|
|
|
|
from scripts.audit_projectbrain_contracts import audit
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SNAPSHOT = ROOT / "tests/fixtures/projectbrain-discovery-20260908.json"
|
|
|
|
|
|
def test_live_discovery_fields_are_available_in_reviewed_catalog() -> None:
|
|
result = audit(json.loads(SNAPSHOT.read_text()), ROOT / "catalog")
|
|
assert result == {"passed": True, "toolCount": 57, "issues": []}
|
|
|
|
|
|
def test_every_required_projectbrain_argument_has_a_field_definition() -> None:
|
|
for path in (ROOT / "catalog/tools").glob("projectbrain-*.yaml"):
|
|
schema = json.loads(path.read_text())["spec"]["inputSchema"]
|
|
Draft202012Validator.check_schema(schema)
|
|
assert set(schema.get("required", [])) <= set(schema.get("properties", {})), path.name
|
|
|
|
|
|
@pytest.mark.parametrize("drift", ["field", "required", "tool", "duplicate", "missing"])
|
|
def test_audit_detects_upstream_evolution_without_publishing_it(drift: str) -> None:
|
|
discovery = copy.deepcopy(json.loads(SNAPSHOT.read_text()))
|
|
tool = discovery["tools"][0]
|
|
if drift == "field":
|
|
tool["inputSchema"]["properties"]["future_option"] = {"type": "boolean"}
|
|
elif drift == "required":
|
|
tool["inputSchema"]["required"] = ["future_option"]
|
|
elif drift == "tool":
|
|
tool["name"] = "projectbrain.unreviewed"
|
|
elif drift == "duplicate":
|
|
discovery["tools"].append(tool)
|
|
else:
|
|
discovery["tools"].pop()
|
|
assert audit(discovery, ROOT / "catalog")["passed"] is False
|
|
|
|
|
|
def validator(slug: str) -> Draft202012Validator:
|
|
spec = json.loads((ROOT / "catalog/tools" / f"projectbrain-{slug}.yaml").read_text())["spec"]
|
|
return Draft202012Validator(spec["inputSchema"])
|
|
|
|
|
|
def test_compact_state_and_run_budget_options_are_usable_and_bounded() -> None:
|
|
state = validator("autonomy-state-get")
|
|
state.validate({"project_id": "demo", "view": "summary", "item_limit": 1, "event_limit": 0})
|
|
assert not state.is_valid({"project_id": "demo", "item_limit": 26})
|
|
assert not state.is_valid({"project_id": "demo", "view": "everything"})
|
|
configure = validator("autonomy-configure")
|
|
configure.validate({"project_id": "demo", "max_work_items_per_run": 3, "max_run_minutes": 15})
|
|
assert not configure.is_valid({"project_id": "demo", "max_run_minutes": 56})
|
|
|
|
|
|
def test_changeset_contract_rejects_unfenced_update_and_unknown_fields() -> None:
|
|
schema = validator("delivery-changeset-apply")
|
|
payload = {
|
|
"project_id": "demo",
|
|
"delivery_id": "delivery-1",
|
|
"branch": "chatgpt/demo",
|
|
"expected_head_sha": "a" * 40,
|
|
"message": "Update docs",
|
|
"operations": [{"operation": "update", "path": "docs/example.md", "content": "text"}],
|
|
}
|
|
assert not schema.is_valid(payload)
|
|
payload["operations"][0]["expected_sha"] = "b" * 40
|
|
schema.validate(payload)
|
|
payload["operations"][0]["unknown_option"] = True
|
|
assert not schema.is_valid(payload)
|