"""Public CLI contracts and an executable portable-project smoke fixture.""" import json from pathlib import Path import subprocess import sys from unittest.mock import patch import pytest sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools/devkit")) from govoplan_devkit import cli, doctor, runner from govoplan_devkit.common import META_ROOT @pytest.mark.parametrize( "arguments", [["--json", "commands"], ["commands", "--json"], ["commands", "--format", "json"]], ) def test_global_output_flags_work_on_either_side_of_command(arguments, capsys): assert cli.main(arguments) == 0 output = json.loads(capsys.readouterr().out) names = {item["command"] for item in output["commands"]} assert {"context", "check", "doctor", "docs", "issues", "release", "git"} <= names @pytest.mark.parametrize( "command", [ "context", "doctor", "check", "resume", "recover", "docs", "review", "issues", "release", "git", ], ) def test_command_help_does_not_require_live_services(command, capsys): with pytest.raises(SystemExit) as stopped: cli.main([command, "--help"]) assert stopped.value.code == 0 assert "usage:" in capsys.readouterr().out def test_portable_project_executes_registered_commands_and_reads_receipt( tmp_path, capsys, monkeypatch ): monkeypatch.setenv("XDG_STATE_HOME", str(tmp_path / "state")) repo = tmp_path / "example" repo.mkdir() subprocess.run(["git", "init", "-q", str(repo)], check=True) subprocess.run( [ "git", "-C", str(repo), "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "--allow-empty", "-qm", "fixture", ], check=True, ) project = tmp_path / "project.json" project.write_text( json.dumps( { "schema_version": 1, "name": "Portable", "repositories": [{"name": "example", "path": "example"}], "checks": [ { "id": "test", "argv": ["{python}", "-c", "print('portable ok')"], "cwd": "example", } ], "profiles": {"quick": ["test"]}, } ) ) common = ["--workspace-root", str(tmp_path), "--project", str(project), "--json"] with patch.object(runner, "environment_fingerprint", return_value="fixture"): assert cli.main(common + ["check", "--profile", "quick"]) == 0 result = json.loads(capsys.readouterr().out) assert result["status"] == "passed" assert cli.main(["status", result["run_id"], *common]) == 0 status = json.loads(capsys.readouterr().out) assert status["snapshot_verified"] is True assert cli.main(["logs", result["run_id"], "--stage", "test", *common]) == 0 assert "portable ok" in capsys.readouterr().out def test_portable_example_matches_published_schema(): import jsonschema jsonschema.validate( json.loads((META_ROOT / "tools/devkit/examples/project.json").read_text()), json.loads((META_ROOT / "tools/devkit/project.schema.json").read_text()), ) def test_malformed_project_is_a_controlled_json_error(tmp_path, capsys): project = tmp_path / "bad.json" project.write_text('{"schema_version":true,"repositories":[]}') assert ( cli.main( [ "context", "--workspace-root", str(tmp_path), "--project", str(project), "--json", ] ) == 2 ) assert json.loads(capsys.readouterr().out)["status"] == "error" def test_doctor_is_read_only_and_preserves_dependency_warnings(tmp_path, capsys): project = tmp_path / "project.json" project.write_text( json.dumps( {"schema_version": 1, "repositories": [{"name": "example", "path": "."}]} ) ) (tmp_path / "package.json").write_text("{}") before = set(tmp_path.iterdir()) with ( patch.object(doctor, "tool_version", return_value="fixture"), patch.object(doctor, "inspect_repository", return_value={"errors": []}), ): assert ( cli.main( [ "doctor", "--workspace-root", str(tmp_path), "--project", str(project), "--json", ] ) == 0 ) output = json.loads(capsys.readouterr().out) assert any(item["status"] == "warning" for item in output["checks"]) assert set(tmp_path.iterdir()) == before