Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
176 lines
9.0 KiB
Python
Executable File
176 lines
9.0 KiB
Python
Executable File
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools/devkit"))
|
|
from govoplan_devkit import review
|
|
from govoplan_devkit.cli import main
|
|
|
|
|
|
@pytest.fixture
|
|
def args(tmp_path):
|
|
repo = tmp_path / "optional-feature"
|
|
repo.mkdir()
|
|
subprocess.run(["git", "init", "-q", str(repo)], check=True)
|
|
paths = ["webui/src/pages/CampaignPage.tsx", "webui/src/dialogs/SettingsDialog.tsx",
|
|
"webui/src/settings/AdminSettings.tsx", "webui/src/widgets/SummaryWidget.tsx", "webui/src/module.ts"]
|
|
for relative in paths:
|
|
path = repo / relative
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("// fixture only\n")
|
|
manifest = repo / "src/optional_feature/backend/manifest.py"
|
|
manifest.parent.mkdir(parents=True)
|
|
manifest.write_text("raise RuntimeError('MUST NEVER IMPORT MODULE CODE')\n")
|
|
(tmp_path / "principles.md").write_text("# Principles\nRevision **UI-2026-09-08**\n\n## UI-01 — Help beside text\n\n## UI-02 — Display first; edit deliberately\n")
|
|
inventory = {"schema_version": 1, "epic": {"repository": "meta", "number": 4, "url": "https://gitea.invalid/team/meta/issues/4"},
|
|
"issues": [{"scope_id": "campaigns", "name": "Campaign", "kind": "manifest", "repository": "optional-feature", "number": 8,
|
|
"url": "https://gitea.invalid/team/optional-feature/issues/8", "state_at_verification": "closed", "operation": "created"}]}
|
|
(tmp_path / "issues.json").write_text(json.dumps(inventory))
|
|
project = tmp_path / "project.json"
|
|
project.write_text(json.dumps({"schema_version": 1, "name": "Portable project", "repositories": [
|
|
{"name": "optional-feature", "path": "optional-feature", "aliases": ["campaign"]},
|
|
{"name": "absent-optional", "path": "absent-optional", "aliases": ["absent"]}],
|
|
"review": {"issue_inventory": "issues.json", "principles": "principles.md"},
|
|
"checks": [{"id": "ui-check", "argv": ["never-execute-this"], "repos": ["optional-feature"], "cwd": "optional-feature"}],
|
|
"profiles": {"ui": ["ui-check"]}}))
|
|
return argparse.Namespace(workspace_root=tmp_path, project=project, state_dir=tmp_path / "state",
|
|
module="campaign", bundle_module=None, profile="ui", evidence=None, output=None)
|
|
|
|
|
|
def test_bundle_contains_guidance_links_revision_and_unexecuted_check_plan(args):
|
|
result = review.handle_review(args)
|
|
assert result["module"] == "optional-feature"
|
|
assert result["inventory"]["ui_source_count"] == 5
|
|
assert not result["inventory"]["module_code_imported"]
|
|
assert result["inventory"]["manifest_paths"] == ["src/optional_feature/backend/manifest.py"]
|
|
assert result["principles"]["revision"] == "UI-2026-09-08"
|
|
assert [rule["id"] for rule in result["principles"]["rules"]] == ["UI-01", "UI-02"]
|
|
assert result["check_plan"]["stages"][0]["argv"] == ["never-execute-this"]
|
|
assert not result["check_plan"]["executed"]
|
|
assert result["review_completion"].startswith("Not assessed")
|
|
assert len(result["manual_checklist"]) >= 9
|
|
assert all("checked" not in item for item in result["manual_checklist"])
|
|
assert "state_at_verification" not in json.dumps(result)
|
|
assert "operation" not in result["issue_links"][0]
|
|
assert result["central_issue"]["number"] == 4
|
|
assert not args.state_dir.exists()
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["campaign", "campaigns", "optional-feature"])
|
|
def test_alias_scope_id_and_repository_resolve_to_same_registered_module(args, name):
|
|
args.module = name
|
|
assert review.handle_review(args)["module"] == "optional-feature"
|
|
|
|
|
|
def test_bundle_alias_and_direct_cli_work_with_global_options_anywhere(args, capsys):
|
|
assert main(["--workspace-root", str(args.workspace_root), "review", "campaign", "--project", str(args.project), "--json"]) == 0
|
|
direct = json.loads(capsys.readouterr().out)
|
|
assert direct["module"] == "optional-feature"
|
|
assert main(["review", "bundle", "campaigns", "--workspace-root", str(args.workspace_root), "--project", str(args.project), "--format", "json"]) == 0
|
|
assert json.loads(capsys.readouterr().out)["module"] == direct["module"]
|
|
|
|
|
|
def test_absent_optional_repository_and_no_ui_do_not_complete_review(args):
|
|
args.module = "absent"
|
|
result = review.handle_review(args)
|
|
assert not result["repository"]["exists"]
|
|
assert result["repository"]["errors"]
|
|
assert result["inventory"]["ui_source_count"] == 0
|
|
assert result["check_plan"]["stages"] == []
|
|
assert result["review_completion"].startswith("Not assessed")
|
|
assert any("not automatic N/A" in warning for warning in result["warnings"])
|
|
assert any("not a passing" in warning for warning in result["warnings"])
|
|
|
|
|
|
def test_portable_project_does_not_inherit_govoplan_links_or_principles(args):
|
|
payload = json.loads(args.project.read_text())
|
|
payload.pop("review")
|
|
args.project.write_text(json.dumps(payload))
|
|
result = review.handle_review(args)
|
|
assert result["issue_links"] == [] and result["central_issue"] is None
|
|
assert not result["principles"]["available"]
|
|
assert "git.add-ideas.de" not in json.dumps(result)
|
|
|
|
|
|
@pytest.mark.parametrize("kind", ["symlink", "outside", "bad-url", "duplicate-scope", "foreign-repository"])
|
|
def test_review_inputs_cannot_escape_or_silently_misbind_scope(args, kind):
|
|
if kind in {"symlink", "outside"}:
|
|
payload = json.loads(args.project.read_text())
|
|
if kind == "symlink":
|
|
alias = args.workspace_root / "alias.json"
|
|
alias.symlink_to(args.workspace_root / "issues.json")
|
|
payload["review"]["issue_inventory"] = "alias.json"
|
|
else:
|
|
payload["review"]["principles"] = "../outside.md"
|
|
args.project.write_text(json.dumps(payload))
|
|
else:
|
|
path = args.workspace_root / "issues.json"
|
|
payload = json.loads(path.read_text())
|
|
if kind == "bad-url":
|
|
payload["issues"][0]["url"] = "https://gitea.invalid/team/wrong/issues/8"
|
|
elif kind == "duplicate-scope":
|
|
payload["issues"].append(payload["issues"][0])
|
|
else:
|
|
payload["issues"][0].update(repository="foreign", url="https://gitea.invalid/team/foreign/issues/8")
|
|
args.module = "campaigns"
|
|
path.write_text(json.dumps(payload))
|
|
with pytest.raises(ValueError):
|
|
review.handle_review(args)
|
|
|
|
|
|
def test_source_inventory_never_follows_symlinked_ancestors(args):
|
|
root = args.workspace_root / "indirect"
|
|
root.mkdir()
|
|
(root / "webui").symlink_to(args.workspace_root / "optional-feature/webui", target_is_directory=True)
|
|
result = review.source_inventory(root)
|
|
assert result["ui_source_count"] == 0
|
|
assert result["skipped_symlinks"] == ["webui/src"]
|
|
|
|
|
|
def test_explicit_artifact_is_only_local_and_preserves_issue_discovery_snapshot(args):
|
|
inventory = args.workspace_root / "issues.json"
|
|
original = inventory.read_bytes()
|
|
args.output = args.workspace_root / "artifacts/review.json"
|
|
result = review.handle_review(args)
|
|
assert json.loads(args.output.read_text()) == result
|
|
assert inventory.read_bytes() == original
|
|
assert args.output.stat().st_mode & 0o777 == 0o600
|
|
assert not args.state_dir.exists()
|
|
|
|
|
|
def test_attached_historical_receipt_stays_separate_from_current_review(args):
|
|
path = args.workspace_root / "historical.json"
|
|
path.write_text(json.dumps({"schema_version": 1, "run_id": "historical", "workspace_root": str(args.workspace_root),
|
|
"project_file": str(args.project), "source_fingerprint": "a" * 64, "status": "passed", "snapshot_verified": True, "generated_at": "2026-09-08",
|
|
"finished_at": "2026-09-08", "stages": [{"id": "old-check", "status": "passed", "exit_code": 0, "duration_seconds": 1, "log_path": "/not-read"}]}))
|
|
args.evidence = str(path)
|
|
result = review.handle_review(args)
|
|
assert result["evidence"]["origin"] == "external-unverified"
|
|
assert result["evidence"]["source_state"] == "historical-source-differs"
|
|
assert result["review_completion"].startswith("Not assessed")
|
|
assert not result["check_plan"]["executed"]
|
|
|
|
|
|
def test_unknown_module_and_extra_positionals_are_errors(args):
|
|
args.module = "typo"
|
|
with pytest.raises(ValueError, match="Unknown repository"):
|
|
review.handle_review(args)
|
|
args.module, args.bundle_module = "campaign", "extra"
|
|
with pytest.raises(ValueError, match="one module"):
|
|
review.handle_review(args)
|
|
|
|
|
|
def test_compact_review_exposes_scoped_coverage_limits_without_hiding_full_list(args, monkeypatch):
|
|
notes = [f"Separate suite {number} was not included." for number in range(12)]
|
|
monkeypatch.setattr("govoplan_devkit.catalog.build_stages", lambda *_args, **_kwargs: [
|
|
{"id": "source-only", "argv": ["never-execute"], "coverage_notes": notes}])
|
|
result = review.handle_review(args)
|
|
assert result["coverage_notes"] == notes
|
|
assert sum(line.startswith("Coverage limitation:") for line in result["summary"]) == 8
|
|
assert any("4 additional coverage limitations" in line for line in result["summary"])
|
|
assert not result["check_plan"]["executed"]
|