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.
152 lines
4.5 KiB
Python
Executable File
152 lines
4.5 KiB
Python
Executable File
import json
|
|
import os
|
|
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.common import atomic_json, read_json, redact, safe_output
|
|
from govoplan_devkit.context import build_context
|
|
from govoplan_devkit.workspace import (
|
|
load_project,
|
|
selected_repositories,
|
|
git_bytes,
|
|
inspect_repository,
|
|
Repository,
|
|
)
|
|
|
|
|
|
def test_portable_project_rejects_escape_alias_collisions_and_duplicate_json(tmp_path):
|
|
config = tmp_path / "project.json"
|
|
for repositories in (
|
|
[{"name": "repo", "path": "../outside"}],
|
|
[
|
|
{"name": "repo", "path": "repo", "aliases": ["other"]},
|
|
{"name": "other", "path": "other"},
|
|
],
|
|
):
|
|
config.write_text(
|
|
json.dumps({"schema_version": 1, "repositories": repositories})
|
|
)
|
|
with pytest.raises(ValueError):
|
|
load_project(tmp_path, config)
|
|
config.write_text('{"schema_version":1,"schema_version":2}')
|
|
with pytest.raises(ValueError, match="Duplicate"):
|
|
read_json(config)
|
|
|
|
|
|
def test_missing_repository_is_error_not_clean(tmp_path):
|
|
config = tmp_path / "project.json"
|
|
config.write_text(
|
|
json.dumps(
|
|
{
|
|
"schema_version": 1,
|
|
"name": "Test",
|
|
"repositories": [{"name": "missing", "path": "missing"}],
|
|
}
|
|
)
|
|
)
|
|
result = build_context(tmp_path, config, [], True)
|
|
assert result["_exit_code"] == 1
|
|
assert result["repositories"][0]["errors"]
|
|
assert not result["remote_checked"]
|
|
|
|
|
|
def test_unknown_repo_filter_is_not_silently_ignored(tmp_path):
|
|
config = tmp_path / "project.json"
|
|
config.write_text(
|
|
json.dumps(
|
|
{"schema_version": 1, "repositories": [{"name": "repo", "path": "repo"}]}
|
|
)
|
|
)
|
|
with pytest.raises(ValueError, match="Unknown"):
|
|
selected_repositories(load_project(tmp_path, config), ["typo"])
|
|
|
|
|
|
def test_atomic_output_does_not_chmod_existing_parent(tmp_path):
|
|
folder = tmp_path / "public"
|
|
folder.mkdir(mode=0o755)
|
|
atomic_json(folder / "private.json", {"safe": True})
|
|
assert folder.stat().st_mode & 0o777 == 0o755
|
|
assert (folder / "private.json").stat().st_mode & 0o777 == 0o600
|
|
|
|
|
|
def test_read_rejects_fifo_and_symlink(tmp_path):
|
|
fifo = tmp_path / "fifo"
|
|
os.mkfifo(fifo)
|
|
with pytest.raises(ValueError, match="regular"):
|
|
read_json(fifo)
|
|
target = tmp_path / "target.json"
|
|
target.write_text("{}")
|
|
alias = tmp_path / "alias.json"
|
|
alias.symlink_to(target)
|
|
with pytest.raises(ValueError, match="symlink"):
|
|
read_json(alias)
|
|
|
|
|
|
def test_generic_secret_display_hygiene():
|
|
assert "topsecret" not in redact(
|
|
"Authorization: Bearer topsecret\nhttps://user:topsecret@example.invalid/"
|
|
)
|
|
|
|
|
|
def test_separate_secret_arguments_are_redacted_in_nested_json():
|
|
value = {
|
|
"stages": [{"argv": ["check", "--token", "hidden-value"]}],
|
|
"password": "hidden-password",
|
|
}
|
|
output = json.dumps(safe_output(value))
|
|
assert "hidden-value" not in output and "hidden-password" not in output
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"name",
|
|
[
|
|
"GIT_DIR",
|
|
"GIT_NAMESPACE",
|
|
"GIT_COMMON_DIR",
|
|
"GIT_CONFIG_PARAMETERS",
|
|
"GIT_AUTHOR_NAME",
|
|
],
|
|
)
|
|
def test_inherited_git_redirects_are_rejected(tmp_path, monkeypatch, name):
|
|
monkeypatch.setenv(name, "fixture")
|
|
assert inspect_repository(Repository("example", tmp_path))["errors"]
|
|
with pytest.raises(ValueError, match="overrides"):
|
|
git_bytes(tmp_path, "status")
|
|
|
|
|
|
def test_deeply_nested_json_fails_as_controlled_input_error(tmp_path):
|
|
path = tmp_path / "deep.json"
|
|
path.write_text("[" * 10000 + "]" * 10000)
|
|
with pytest.raises(ValueError, match="nesting"):
|
|
read_json(path)
|
|
|
|
|
|
def test_clean_commit_without_upstream_is_still_changed(tmp_path):
|
|
subprocess.run(["git", "init", "-q", str(tmp_path)], check=True)
|
|
subprocess.run(
|
|
[
|
|
"git",
|
|
"-C",
|
|
str(tmp_path),
|
|
"-c",
|
|
"user.name=Fixture",
|
|
"-c",
|
|
"user.email=fixture@example.invalid",
|
|
"commit",
|
|
"--allow-empty",
|
|
"-qm",
|
|
"fixture",
|
|
],
|
|
check=True,
|
|
)
|
|
from govoplan_devkit.workspace import Project
|
|
|
|
repo = Repository("example", tmp_path)
|
|
assert selected_repositories(Project("Example", (repo,), {}), [], changed=True) == [
|
|
repo
|
|
]
|