Files
govoplan/tests/test_focused_phases.py
zemion 2ffdb23f69
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 11m30s
feat(devkit): add resumable workspace automation and UI review tooling
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.
2026-09-09 02:03:17 +02:00

394 lines
13 KiB
Python
Executable File

"""Canonical phase dispatch tested with inert tools in disposable workspaces."""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import pytest
META_ROOT = Path(__file__).resolve().parents[1]
SCRIPT = META_ROOT / "tools/checks/check-focused.sh"
METADATA = META_ROOT / "tools/checks/focused-phases.json"
PHASE_IDS = [
"preflight",
"tooling",
"backend",
"core-ui",
"module-builds",
"browser",
"module-ui",
]
# These hashes bind the original working-copy check bodies at the phase split.
# Only two explicit Core/webui cd lines were added for independent invocation.
# Future deliberate changes to the canonical gate must update this contract.
LEGACY_BODY_SHA256 = {
"preflight": "b9c90fd3c84df788de5f2c001443672f683a9918459e1a7955ed4a225e6f20dd",
"tooling": "3d9a5bf32acbe97134f51327ed4b2457a69ad23b723c15a2b9bd0dce7f82396b",
"backend": "dd57c33919f06bd240516bbe47861be022e0b1c047334eedc7f6c596c2c49632",
"core-ui": "613f806a602970f1dbfb243f4c96ccc6ca4836849bb1f1a2c4cff97aea6f3aeb",
"module-builds": "1f2cd1e2c336f748fbf2972a2da87a0efec58ef9b084a9433511f794a456fa84",
"browser": "aa20d1e1d4ec9f00bc27c06cdee7ffa2456d3d3b8a7155da4e888a7e2209306d",
"module-ui": "c47794d82eb7bb47de114e86a95264e5898886f743c31eb7bdf82a079c827c9f",
}
EXTRA_TEST_COMMAND = '"$PYTHON" -m pytest -q tests/test_focused_phases.py\n'
def definitions():
source = SCRIPT.read_text()
return dict(
re.findall(
r"^# devkit-phase: ([a-z-]+) begin\n(.*?)^# devkit-phase: \1 end$",
source,
re.MULTILINE | re.DOTALL,
)
)
def test_original_commands_remain_exactly_once_in_original_order():
bodies = definitions()
metadata = json.loads(METADATA.read_text())
assert list(bodies) == PHASE_IDS
assert [phase["id"] for phase in metadata["phases"]] == PHASE_IDS
assert SCRIPT.read_text().count(EXTRA_TEST_COMMAND) == 1
for identity, body in bodies.items():
assert SCRIPT.read_text().count(f"# devkit-phase: {identity} begin") == 1
if identity == "tooling":
assert body.count(EXTRA_TEST_COMMAND) == 1
body = body.replace(EXTRA_TEST_COMMAND, "")
assert hashlib.sha256(body.encode()).hexdigest() == LEGACY_BODY_SHA256[identity]
def test_ordering_and_artifact_dependencies_are_distinct():
phases = json.loads(METADATA.read_text())["phases"]
assert [phase["order_after"] for phase in phases] == [
[],
*[[identity] for identity in PHASE_IDS[:-1]],
]
assert all(phase["depends_on"] == [] for phase in phases)
by_id = {phase["id"]: phase for phase in phases}
assert "{core}/webui/dist" in by_id["module-builds"]["outputs"]
assert any(
"does not serve or require module-builds dist" in note
for note in by_id["browser"]["notes"]
)
assert "port:4174" in by_id["browser"]["resources"]
cwd_lines = {
"core": 'cd "$ROOT"',
"meta": 'cd "$META_ROOT"',
"core-webui": 'cd "$ROOT/webui"',
"access-webui": 'cd "${WORKSPACE_ROOT}/govoplan-access/webui"',
}
for phase in phases:
assert definitions()[phase["id"]].splitlines()[0] == cwd_lines[phase["cwd"]]
FAKE_TOOL = r"""
import json
import os
from pathlib import Path
import sys
log = Path(os.environ["FOCUSED_FIXTURE_LOG"])
record = {
"tool": Path(sys.argv[0]).name,
"argv": sys.argv[1:],
"cwd": os.getcwd(),
"env": {key: os.environ.get(key) for key in (
"GOVOPLAN_WORKSPACE_ROOT", "NPM_CONFIG_USERCONFIG", "GOVOPLAN_NPM_USERCONFIG",
"NPM_CONFIG_TMP", "npm_config_tmp", "PYTHONPATH", "PATH",
)},
}
if "-" in sys.argv[1:]:
record["stdin"] = sys.stdin.read()
with log.open("a") as handle:
handle.write(json.dumps(record) + "\n")
if os.environ.get("FOCUSED_FIXTURE_FAIL_TOKEN") in sys.argv[1:]:
raise SystemExit(7)
"""
@pytest.fixture
def fixture_workspace(tmp_path):
workspace = tmp_path / "workspace"
meta = workspace / "govoplan"
core = workspace / "govoplan-core"
copied = meta / "tools/checks/check-focused.sh"
copied.parent.mkdir(parents=True)
shutil.copyfile(SCRIPT, copied)
shutil.copyfile(METADATA, copied.with_name("focused-phases.json"))
for name in (
"govoplan",
"govoplan-core",
"govoplan-access",
"govoplan-payments",
"govoplan-dataflow",
"govoplan-datasources",
"govoplan-workflow",
"govoplan-dashboard",
"govoplan-approvals",
"govoplan-postbox",
"govoplan-mail",
"govoplan-files",
"govoplan-campaign",
"govoplan-policy",
"govoplan-wiki",
):
(workspace / name / "webui").mkdir(parents=True, exist_ok=True)
(workspace / name / "src").mkdir(exist_ok=True)
(meta / "tests").mkdir()
for name in ("test_devkit_alpha.py", "test_devkit_beta.py"):
(meta / "tests" / name).write_text("# inert glob fixture\n")
fake_bin = tmp_path / "bin"
fake_bin.mkdir()
for target in [
*(fake_bin / name for name in ("python-check", "node", "npm", "bash")),
core / "webui/node_modules/.bin/tsc",
meta / "tools/checks/check_dependency_boundaries.py",
]:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(f"#!{sys.executable}\n" + FAKE_TOOL)
target.chmod(0o700)
temporary = tmp_path / "temporary"
temporary.mkdir()
log = tmp_path / "calls.jsonl"
env = {
**os.environ,
"PATH": str(fake_bin) + os.pathsep + os.environ["PATH"],
"GOVOPLAN_WORKSPACE_ROOT": str(workspace),
"GOVOPLAN_CORE_ROOT": str(core),
"PYTHON": str(fake_bin / "python-check"),
"NODE": str(fake_bin / "node"),
"NPM": str(fake_bin / "npm"),
"TMPDIR": str(temporary),
"FOCUSED_FIXTURE_LOG": str(log),
"PYTHONPATH": "inherited-fixture-tail",
"NPM_CONFIG_TMP": "must-be-unset",
"npm_config_tmp": "must-be-unset",
}
env.pop("FOCUSED_FIXTURE_FAIL_TOKEN", None)
return {
"workspace": workspace,
"meta": meta,
"core": core,
"script": copied,
"env": env,
"log": log,
"temporary": temporary,
}
def invoke(fixture, *arguments, env=None):
return subprocess.run(
["/bin/bash", str(fixture["script"]), *arguments],
cwd=fixture["workspace"].parent,
env=env or fixture["env"],
capture_output=True,
text=True,
timeout=30,
)
def records(fixture):
path = fixture["log"]
return (
[json.loads(line) for line in path.read_text().splitlines()]
if path.exists()
else []
)
def normalized(calls):
result = []
for call in calls:
call = json.loads(json.dumps(call))
call["env"].pop("NPM_CONFIG_USERCONFIG")
call["env"].pop("GOVOPLAN_NPM_USERCONFIG")
result.append(call)
return result
def test_default_gate_equals_sequential_independent_phases(fixture_workspace):
fixture = fixture_workspace
full = invoke(fixture)
assert full.returncode == 0, full.stderr
original = records(fixture)
offset = len(original)
for identity in PHASE_IDS:
isolated = invoke(fixture, "--phase", identity)
assert isolated.returncode == 0, isolated.stderr
assert normalized(original) == normalized(records(fixture)[offset:])
assert len({call["env"]["NPM_CONFIG_USERCONFIG"] for call in original}) == len(
PHASE_IDS
)
assert not list(fixture["temporary"].iterdir())
assert sum("test:conformance" in call["argv"] for call in original) == 1
assert sum("test:module-permutations" in call["argv"] for call in original) == 1
assert sum("tests/test_focused_phases.py" in call["argv"] for call in original) == 1
# The here-document remains one Python invocation, not executed shell text.
ast_scan = [call for call in original if "stdin" in call]
assert len(ast_scan) == 1
assert "AST syntax check passed for" in ast_scan[0]["stdin"]
@pytest.mark.parametrize("identity", PHASE_IDS)
def test_each_phase_initializes_its_own_cwd_and_environment(
fixture_workspace, identity
):
fixture = fixture_workspace
result = invoke(fixture, "--phase", identity)
assert result.returncode == 0, result.stderr
calls = records(fixture)
assert calls
phase = next(
item
for item in json.loads(METADATA.read_text())["phases"]
if item["id"] == identity
)
starts = {
"core": fixture["core"],
"meta": fixture["meta"],
"core-webui": fixture["core"] / "webui",
"access-webui": fixture["workspace"] / "govoplan-access/webui",
}
assert calls[0]["cwd"] == str(starts[phase["cwd"]])
for call in calls:
env = call["env"]
assert env["GOVOPLAN_WORKSPACE_ROOT"] == str(fixture["workspace"])
assert env["NPM_CONFIG_USERCONFIG"] == env["GOVOPLAN_NPM_USERCONFIG"]
assert Path(env["NPM_CONFIG_USERCONFIG"]).parent == fixture["temporary"]
assert not Path(env["NPM_CONFIG_USERCONFIG"]).exists()
assert env["NPM_CONFIG_TMP"] is None and env["npm_config_tmp"] is None
assert env["PATH"].split(os.pathsep)[0] == str(
fixture["core"] / "webui/node_modules/.bin"
)
assert env["PYTHONPATH"].endswith(os.pathsep + "inherited-fixture-tail")
assert str(fixture["core"] / "src") in env["PYTHONPATH"].split(os.pathsep)
assert not list(fixture["temporary"].iterdir())
def test_full_is_fail_fast_and_failure_cleans_setup(fixture_workspace):
fixture = fixture_workspace
result = invoke(
fixture,
env={
**fixture["env"],
"FOCUSED_FIXTURE_FAIL_TOKEN": "test:module-permutations",
},
)
assert result.returncode == 7
calls = records(fixture)
assert calls[-1]["argv"] == ["run", "test:module-permutations"]
assert not any("test:conformance" in call["argv"] for call in calls)
assert not any("test:passwords" in call["argv"] for call in calls)
assert not list(fixture["temporary"].iterdir())
@pytest.mark.parametrize(
"arguments",
[
["--unknown"],
["--phase"],
["--phase", ""],
["--phase", "not-a-phase"],
["--phase", "browser", "--phase", "tooling"],
["--list-phases", "--phase", "browser"],
["--phase", "browser", "--list-phases"],
["--list-phases", "--list-phases"],
["--json"],
["--list-phases", "--json", "--json"],
["--phase", "browser; touch unexpected"],
],
)
def test_invalid_selection_is_rejected_before_setup(fixture_workspace, arguments):
fixture = fixture_workspace
env = {
**fixture["env"],
"GOVOPLAN_CORE_ROOT": str(fixture["workspace"] / "absent-core"),
"NODE": "absent-node",
"NPM": "absent-npm",
}
result = invoke(fixture, *arguments, env=env)
assert result.returncode == 2
assert "check-focused:" in result.stderr
assert not fixture["log"].exists()
assert not list(fixture["temporary"].iterdir())
@pytest.mark.parametrize(
"arguments",
[["--list-phases"], ["--list-phases", "--json"], ["--json", "--list-phases"]],
)
def test_listing_is_read_only_without_product_tools(fixture_workspace, arguments):
fixture = fixture_workspace
env = {
**fixture["env"],
"GOVOPLAN_CORE_ROOT": str(fixture["workspace"] / "absent-core"),
"PYTHON": "/absent-python",
"NODE": "absent-node",
"NPM": "absent-npm",
}
result = invoke(fixture, *arguments, env=env)
assert result.returncode == 0, result.stderr
if "--json" in arguments:
assert json.loads(result.stdout) == json.loads(METADATA.read_text())
else:
assert [line.split("\t")[0] for line in result.stdout.splitlines()] == PHASE_IDS
assert not fixture["log"].exists()
assert not list(fixture["temporary"].iterdir())
def test_metadata_loading_never_imports_project_python_modules(fixture_workspace):
fixture = fixture_workspace
foreign = fixture["workspace"].parent / "json.py"
foreign.write_text(
"raise AssertionError('project module imported during listing')\n"
)
result = invoke(
fixture,
"--list-phases",
"--json",
env={**fixture["env"], "PYTHONPATH": str(foreign.parent)},
)
assert result.returncode == 0, result.stderr
assert json.loads(result.stdout)["schema_version"] == 1
assert not fixture["log"].exists()
@pytest.mark.parametrize(
"change",
[
"unknown-fields",
"duplicate-id",
"unknown-cwd",
"later-prerequisite",
"invalid-version",
],
)
def test_malformed_metadata_fails_before_any_check(fixture_workspace, change):
fixture = fixture_workspace
path = fixture["script"].with_name("focused-phases.json")
catalog = json.loads(path.read_text())
if change == "unknown-fields":
catalog["phases"][0]["shell"] = "touch should-not-run"
elif change == "duplicate-id":
catalog["phases"][1]["id"] = catalog["phases"][0]["id"]
elif change == "unknown-cwd":
catalog["phases"][0]["cwd"] = "elsewhere"
elif change == "later-prerequisite":
catalog["phases"][0]["depends_on"] = ["browser"]
else:
catalog["schema_version"] = True
path.write_text(json.dumps(catalog))
result = invoke(fixture, "--phase", "browser")
assert result.returncode == 2
assert not fixture["log"].exists()
assert not list(fixture["temporary"].iterdir())