tests: cover m2pack cli and mcp additions
Add discovery-helper tests (env var override, PATH fallback, missing binary) and command tests that point M2PACK_BINARY at a Python stub script echoing canned JSON per subcommand. Cover success paths, non-zero exit, non-JSON output, JSON-array output, and missing binary. Extend the MCP schema mirror test to cover all 12 tools and add dispatch tests for the new m2pack_* argv translation.
This commit is contained in:
@@ -199,6 +199,80 @@ def test_dispatch_invalid_input_returns_wrapper_error():
|
|||||||
assert envelope["error"]["code"] == "invalid_tool_input"
|
assert envelope["error"]["code"] == "invalid_tool_input"
|
||||||
|
|
||||||
|
|
||||||
|
def test_m2pack_build_translates_all_flags():
|
||||||
|
spec = TOOLS_BY_NAME["m2pack_build"]
|
||||||
|
argv = build_cli_args(
|
||||||
|
spec,
|
||||||
|
{
|
||||||
|
"input": "/src",
|
||||||
|
"output": "/out/a.m2p",
|
||||||
|
"key": "/ck",
|
||||||
|
"sign_secret_key": "/sk",
|
||||||
|
"key_id": 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert argv[0:3] == ["m2pack", "build", "--json"]
|
||||||
|
assert "--input" in argv and argv[argv.index("--input") + 1] == "/src"
|
||||||
|
assert "--output" in argv and argv[argv.index("--output") + 1] == "/out/a.m2p"
|
||||||
|
assert "--sign-secret-key" in argv
|
||||||
|
assert argv[argv.index("--sign-secret-key") + 1] == "/sk"
|
||||||
|
assert "--key-id" in argv and argv[argv.index("--key-id") + 1] == "2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_m2pack_verify_omits_optional_keys():
|
||||||
|
spec = TOOLS_BY_NAME["m2pack_verify"]
|
||||||
|
argv = build_cli_args(spec, {"archive": "/a.m2p"})
|
||||||
|
assert argv == ["m2pack", "verify", "--json", "--archive", "/a.m2p"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_m2pack_diff_requires_both_sides():
|
||||||
|
from metin_release_mcp.errors import InvalidToolInputError
|
||||||
|
|
||||||
|
spec = TOOLS_BY_NAME["m2pack_diff"]
|
||||||
|
with pytest.raises(InvalidToolInputError):
|
||||||
|
build_cli_args(spec, {"left": "/L"})
|
||||||
|
argv = build_cli_args(spec, {"left": "/L", "right": "/R"})
|
||||||
|
assert argv == ["m2pack", "diff", "--json", "--left", "/L", "--right", "/R"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_m2pack_export_runtime_key_with_format():
|
||||||
|
spec = TOOLS_BY_NAME["m2pack_export_runtime_key"]
|
||||||
|
argv = build_cli_args(
|
||||||
|
spec,
|
||||||
|
{
|
||||||
|
"key": "/ck",
|
||||||
|
"public_key": "/pk",
|
||||||
|
"output": "/out",
|
||||||
|
"format": "blob",
|
||||||
|
"key_id": 4,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert argv[0:3] == ["m2pack", "export-runtime-key", "--json"]
|
||||||
|
assert "--format" in argv and argv[argv.index("--format") + 1] == "blob"
|
||||||
|
assert "--key-id" in argv and argv[argv.index("--key-id") + 1] == "4"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch_m2pack_build_through_fake_runner(stub_runner):
|
||||||
|
runner = stub_runner(
|
||||||
|
{"ok": True, "command": "m2pack build", "status": "built", "artifacts": {"archive_path": "/x.m2p"}}
|
||||||
|
)
|
||||||
|
envelope, _ = server.dispatch(
|
||||||
|
"m2pack_build",
|
||||||
|
{
|
||||||
|
"input": "/src",
|
||||||
|
"output": "/x.m2p",
|
||||||
|
"key": "/ck",
|
||||||
|
"sign_secret_key": "/sk",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert envelope["ok"] is True
|
||||||
|
call = runner.calls[0]
|
||||||
|
assert "m2pack" in call and "build" in call
|
||||||
|
assert "--json" in call
|
||||||
|
assert "--input" in call and "/src" in call
|
||||||
|
assert "--sign-secret-key" in call and "/sk" in call
|
||||||
|
|
||||||
|
|
||||||
def test_dispatch_unparseable_output_returns_wrapper_error(monkeypatch):
|
def test_dispatch_unparseable_output_returns_wrapper_error(monkeypatch):
|
||||||
def boom(argv_tail):
|
def boom(argv_tail):
|
||||||
from metin_release_mcp.errors import CliUnparseableOutputError
|
from metin_release_mcp.errors import CliUnparseableOutputError
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ from metin_release.commands import (
|
|||||||
build_manifest,
|
build_manifest,
|
||||||
diff_remote,
|
diff_remote,
|
||||||
inspect,
|
inspect,
|
||||||
|
m2pack_build,
|
||||||
|
m2pack_diff,
|
||||||
|
m2pack_export_runtime_key,
|
||||||
|
m2pack_verify,
|
||||||
promote,
|
promote,
|
||||||
publish,
|
publish,
|
||||||
sign,
|
sign,
|
||||||
@@ -35,17 +39,21 @@ EXPECTED_TOOL_NAMES = {
|
|||||||
"release_promote",
|
"release_promote",
|
||||||
"release_verify_public",
|
"release_verify_public",
|
||||||
"release_publish",
|
"release_publish",
|
||||||
|
"m2pack_build",
|
||||||
|
"m2pack_verify",
|
||||||
|
"m2pack_diff",
|
||||||
|
"m2pack_export_runtime_key",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_tool_catalogue_is_exactly_the_phase_one_set():
|
def test_tool_catalogue_is_exactly_the_phase_one_plus_four_set():
|
||||||
assert {t.name for t in TOOL_SPECS} == EXPECTED_TOOL_NAMES
|
assert {t.name for t in TOOL_SPECS} == EXPECTED_TOOL_NAMES
|
||||||
assert len(TOOL_SPECS) == 8
|
assert len(TOOL_SPECS) == 12
|
||||||
|
|
||||||
|
|
||||||
def test_every_tool_has_release_subcommand_and_description():
|
def test_every_tool_has_known_group_and_description():
|
||||||
for spec in TOOL_SPECS:
|
for spec in TOOL_SPECS:
|
||||||
assert spec.subcommand[0] == "release"
|
assert spec.subcommand[0] in {"release", "m2pack"}
|
||||||
assert spec.description.strip()
|
assert spec.description.strip()
|
||||||
schema = json_schema(spec)
|
schema = json_schema(spec)
|
||||||
assert schema["type"] == "object"
|
assert schema["type"] == "object"
|
||||||
@@ -82,6 +90,10 @@ _COMMAND_MODULES = {
|
|||||||
"release_promote": promote,
|
"release_promote": promote,
|
||||||
"release_verify_public": verify_public,
|
"release_verify_public": verify_public,
|
||||||
"release_publish": publish,
|
"release_publish": publish,
|
||||||
|
"m2pack_build": m2pack_build,
|
||||||
|
"m2pack_verify": m2pack_verify,
|
||||||
|
"m2pack_diff": m2pack_diff,
|
||||||
|
"m2pack_export_runtime_key": m2pack_export_runtime_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -121,7 +133,7 @@ def test_unknown_tool_rejected_by_dispatch():
|
|||||||
def test_unknown_tool_name_not_in_catalogue():
|
def test_unknown_tool_name_not_in_catalogue():
|
||||||
assert "release_rollback" not in TOOLS_BY_NAME
|
assert "release_rollback" not in TOOLS_BY_NAME
|
||||||
assert "erp_reserve" not in TOOLS_BY_NAME
|
assert "erp_reserve" not in TOOLS_BY_NAME
|
||||||
assert "m2pack_build" not in TOOLS_BY_NAME
|
assert "launcher_publish" not in TOOLS_BY_NAME
|
||||||
|
|
||||||
|
|
||||||
def test_build_cli_args_rejects_missing_required():
|
def test_build_cli_args_rejects_missing_required():
|
||||||
|
|||||||
68
tests/test_m2pack_binary.py
Normal file
68
tests/test_m2pack_binary.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
"""Tests for the m2pack binary discovery helper."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from metin_release.errors import ValidationError
|
||||||
|
from metin_release.m2pack_binary import ENV_VAR, resolve_m2pack_binary
|
||||||
|
|
||||||
|
|
||||||
|
def _make_stub(tmp_path: Path, name: str = "m2pack") -> Path:
|
||||||
|
stub = tmp_path / name
|
||||||
|
stub.write_text("#!/bin/sh\necho '{}'\n")
|
||||||
|
stub.chmod(stub.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
|
||||||
|
return stub
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_var_override_wins(tmp_path: Path, monkeypatch):
|
||||||
|
stub = _make_stub(tmp_path)
|
||||||
|
# Scrub PATH so shutil.which can't resolve m2pack
|
||||||
|
monkeypatch.setenv("PATH", "/nonexistent")
|
||||||
|
monkeypatch.setenv(ENV_VAR, str(stub))
|
||||||
|
resolved = resolve_m2pack_binary()
|
||||||
|
assert resolved == stub.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_var_empty_falls_through_to_path(tmp_path: Path, monkeypatch):
|
||||||
|
stub = _make_stub(tmp_path)
|
||||||
|
monkeypatch.setenv(ENV_VAR, " ") # blank-ish
|
||||||
|
monkeypatch.setenv("PATH", str(tmp_path))
|
||||||
|
resolved = resolve_m2pack_binary()
|
||||||
|
assert resolved == stub.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_var_pointing_nowhere_raises(tmp_path: Path, monkeypatch):
|
||||||
|
monkeypatch.setenv(ENV_VAR, str(tmp_path / "does-not-exist"))
|
||||||
|
monkeypatch.setenv("PATH", "/nonexistent")
|
||||||
|
with pytest.raises(ValidationError) as exc:
|
||||||
|
resolve_m2pack_binary()
|
||||||
|
assert exc.value.error_code == "m2pack_not_found"
|
||||||
|
|
||||||
|
|
||||||
|
def test_path_fallback_used_when_env_unset(tmp_path: Path, monkeypatch):
|
||||||
|
stub = _make_stub(tmp_path)
|
||||||
|
monkeypatch.delenv(ENV_VAR, raising=False)
|
||||||
|
monkeypatch.setenv("PATH", str(tmp_path))
|
||||||
|
resolved = resolve_m2pack_binary()
|
||||||
|
assert resolved == stub.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_binary_raises_validation_error(monkeypatch):
|
||||||
|
monkeypatch.delenv(ENV_VAR, raising=False)
|
||||||
|
monkeypatch.setenv("PATH", "/nonexistent")
|
||||||
|
with pytest.raises(ValidationError) as exc:
|
||||||
|
resolve_m2pack_binary()
|
||||||
|
assert exc.value.error_code == "m2pack_not_found"
|
||||||
|
assert "M2PACK_BINARY" in str(exc.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_env_mapping_parameter(tmp_path: Path):
|
||||||
|
stub = _make_stub(tmp_path)
|
||||||
|
fake_env = {ENV_VAR: str(stub)}
|
||||||
|
resolved = resolve_m2pack_binary(env=fake_env)
|
||||||
|
assert resolved == stub.resolve()
|
||||||
307
tests/test_m2pack_commands.py
Normal file
307
tests/test_m2pack_commands.py
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
"""Tests for the m2pack wrapper subcommands.
|
||||||
|
|
||||||
|
All tests use a stub binary — a small Python script that echoes a canned
|
||||||
|
JSON envelope based on the subcommand name — pointed at via the
|
||||||
|
``M2PACK_BINARY`` env var. The real m2pack-secure binary is never invoked.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from metin_release.cli import main as cli_main
|
||||||
|
|
||||||
|
|
||||||
|
STUB_TEMPLATE = r"""#!{python}
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
sub = argv[0] if argv else "unknown"
|
||||||
|
MODE = {mode!r}
|
||||||
|
|
||||||
|
if MODE == "fail":
|
||||||
|
sys.stderr.write("boom\n")
|
||||||
|
sys.exit(2)
|
||||||
|
if MODE == "nonjson":
|
||||||
|
sys.stdout.write("not json at all\n")
|
||||||
|
sys.exit(0)
|
||||||
|
if MODE == "notobject":
|
||||||
|
sys.stdout.write(json.dumps([1, 2, 3]) + "\n")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# mode == "ok": echo a canned envelope per subcommand
|
||||||
|
if sub == "build":
|
||||||
|
env = {{"ok": True, "command": "build", "stats": {{"files": 3, "bytes": 12345}}}}
|
||||||
|
elif sub == "verify":
|
||||||
|
env = {{"ok": True, "command": "verify", "signature": "valid"}}
|
||||||
|
elif sub == "diff":
|
||||||
|
env = {{"ok": True, "command": "diff", "added": ["a"], "removed": [], "changed": ["b", "c"], "unchanged": 7}}
|
||||||
|
elif sub == "export-runtime-key":
|
||||||
|
env = {{"ok": True, "command": "export-runtime-key", "key_id": 1, "format": "json"}}
|
||||||
|
else:
|
||||||
|
env = {{"ok": True, "command": sub}}
|
||||||
|
|
||||||
|
# Record argv so the test can assert translation
|
||||||
|
import os
|
||||||
|
log = os.environ.get("M2PACK_STUB_LOG")
|
||||||
|
if log:
|
||||||
|
with open(log, "a") as fh:
|
||||||
|
fh.write(json.dumps(argv) + "\n")
|
||||||
|
|
||||||
|
sys.stdout.write(json.dumps(env) + "\n")
|
||||||
|
sys.exit(0)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _install_stub(tmp_path: Path, monkeypatch, mode: str = "ok") -> tuple[Path, Path]:
|
||||||
|
stub = tmp_path / "m2pack_stub.py"
|
||||||
|
stub.write_text(STUB_TEMPLATE.format(python=sys.executable, mode=mode))
|
||||||
|
stub.chmod(stub.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
|
||||||
|
log = tmp_path / "stub.log"
|
||||||
|
monkeypatch.setenv("M2PACK_BINARY", str(stub))
|
||||||
|
monkeypatch.setenv("M2PACK_STUB_LOG", str(log))
|
||||||
|
return stub, log
|
||||||
|
|
||||||
|
|
||||||
|
def _run_cli(argv: list[str], capsys) -> dict:
|
||||||
|
rc = cli_main(argv)
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
envelope = json.loads(out)
|
||||||
|
return {"rc": rc, "env": envelope}
|
||||||
|
|
||||||
|
|
||||||
|
def _read_stub_call(log: Path) -> list[str]:
|
||||||
|
lines = [ln for ln in log.read_text().splitlines() if ln.strip()]
|
||||||
|
assert lines, "stub was never invoked"
|
||||||
|
return json.loads(lines[-1])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Success paths
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_success(tmp_path, monkeypatch, capsys):
|
||||||
|
_, log = _install_stub(tmp_path, monkeypatch)
|
||||||
|
(tmp_path / "in").mkdir()
|
||||||
|
key = tmp_path / "k.hex"
|
||||||
|
key.write_text("ff")
|
||||||
|
sk = tmp_path / "sk.hex"
|
||||||
|
sk.write_text("aa")
|
||||||
|
out = tmp_path / "out.m2p"
|
||||||
|
|
||||||
|
r = _run_cli(
|
||||||
|
[
|
||||||
|
"--json",
|
||||||
|
"m2pack",
|
||||||
|
"build",
|
||||||
|
"--input", str(tmp_path / "in"),
|
||||||
|
"--output", str(out),
|
||||||
|
"--key", str(key),
|
||||||
|
"--sign-secret-key", str(sk),
|
||||||
|
"--key-id", "7",
|
||||||
|
],
|
||||||
|
capsys,
|
||||||
|
)
|
||||||
|
assert r["rc"] == 0
|
||||||
|
assert r["env"]["ok"] is True
|
||||||
|
assert r["env"]["command"] == "m2pack build"
|
||||||
|
assert r["env"]["status"] == "built"
|
||||||
|
assert r["env"]["artifacts"]["archive_path"] == str(out)
|
||||||
|
assert r["env"]["m2pack"]["stats"]["files"] == 3
|
||||||
|
|
||||||
|
call = _read_stub_call(log)
|
||||||
|
assert call[0] == "build"
|
||||||
|
assert "--input" in call and str(tmp_path / "in") in call
|
||||||
|
assert "--output" in call and str(out) in call
|
||||||
|
assert "--key" in call and str(key) in call
|
||||||
|
assert "--sign-secret-key" in call and str(sk) in call
|
||||||
|
assert "--key-id" in call and "7" in call
|
||||||
|
assert "--json" in call
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_omits_optional_key_id(tmp_path, monkeypatch, capsys):
|
||||||
|
_, log = _install_stub(tmp_path, monkeypatch)
|
||||||
|
(tmp_path / "in").mkdir()
|
||||||
|
(tmp_path / "k").write_text("a")
|
||||||
|
(tmp_path / "sk").write_text("b")
|
||||||
|
_run_cli(
|
||||||
|
[
|
||||||
|
"--json", "m2pack", "build",
|
||||||
|
"--input", str(tmp_path / "in"),
|
||||||
|
"--output", str(tmp_path / "o.m2p"),
|
||||||
|
"--key", str(tmp_path / "k"),
|
||||||
|
"--sign-secret-key", str(tmp_path / "sk"),
|
||||||
|
],
|
||||||
|
capsys,
|
||||||
|
)
|
||||||
|
call = _read_stub_call(log)
|
||||||
|
assert "--key-id" not in call
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_success_without_keys(tmp_path, monkeypatch, capsys):
|
||||||
|
_, log = _install_stub(tmp_path, monkeypatch)
|
||||||
|
archive = tmp_path / "a.m2p"
|
||||||
|
archive.write_bytes(b"x")
|
||||||
|
r = _run_cli(
|
||||||
|
["--json", "m2pack", "verify", "--archive", str(archive)], capsys
|
||||||
|
)
|
||||||
|
assert r["rc"] == 0
|
||||||
|
assert r["env"]["status"] == "verified"
|
||||||
|
assert r["env"]["m2pack"]["signature"] == "valid"
|
||||||
|
call = _read_stub_call(log)
|
||||||
|
assert call[0] == "verify"
|
||||||
|
assert "--public-key" not in call
|
||||||
|
assert "--key" not in call
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_with_public_and_content_keys(tmp_path, monkeypatch, capsys):
|
||||||
|
_, log = _install_stub(tmp_path, monkeypatch)
|
||||||
|
archive = tmp_path / "a.m2p"
|
||||||
|
archive.write_bytes(b"x")
|
||||||
|
pk = tmp_path / "pub"
|
||||||
|
pk.write_text("ff")
|
||||||
|
ck = tmp_path / "ck"
|
||||||
|
ck.write_text("aa")
|
||||||
|
_run_cli(
|
||||||
|
[
|
||||||
|
"--json", "m2pack", "verify",
|
||||||
|
"--archive", str(archive),
|
||||||
|
"--public-key", str(pk),
|
||||||
|
"--key", str(ck),
|
||||||
|
],
|
||||||
|
capsys,
|
||||||
|
)
|
||||||
|
call = _read_stub_call(log)
|
||||||
|
assert "--public-key" in call and str(pk) in call
|
||||||
|
assert "--key" in call and str(ck) in call
|
||||||
|
|
||||||
|
|
||||||
|
def test_diff_success_promotes_counts(tmp_path, monkeypatch, capsys):
|
||||||
|
_, log = _install_stub(tmp_path, monkeypatch)
|
||||||
|
left = tmp_path / "L"
|
||||||
|
left.mkdir()
|
||||||
|
right = tmp_path / "R.m2p"
|
||||||
|
right.write_bytes(b"x")
|
||||||
|
r = _run_cli(
|
||||||
|
[
|
||||||
|
"--json", "m2pack", "diff",
|
||||||
|
"--left", str(left),
|
||||||
|
"--right", str(right),
|
||||||
|
],
|
||||||
|
capsys,
|
||||||
|
)
|
||||||
|
assert r["rc"] == 0
|
||||||
|
assert r["env"]["status"] == "diffed"
|
||||||
|
stats = r["env"]["stats"]
|
||||||
|
assert stats["added_count"] == 1
|
||||||
|
assert stats["removed_count"] == 0
|
||||||
|
assert stats["changed_count"] == 2
|
||||||
|
assert stats["unchanged_count"] == 7
|
||||||
|
call = _read_stub_call(log)
|
||||||
|
assert call[0] == "diff"
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_runtime_key_success(tmp_path, monkeypatch, capsys):
|
||||||
|
_, log = _install_stub(tmp_path, monkeypatch)
|
||||||
|
key = tmp_path / "ck"
|
||||||
|
key.write_text("aa")
|
||||||
|
pk = tmp_path / "pk"
|
||||||
|
pk.write_text("ff")
|
||||||
|
out = tmp_path / "runtime.json"
|
||||||
|
r = _run_cli(
|
||||||
|
[
|
||||||
|
"--json", "m2pack", "export-runtime-key",
|
||||||
|
"--key", str(key),
|
||||||
|
"--public-key", str(pk),
|
||||||
|
"--output", str(out),
|
||||||
|
"--key-id", "3",
|
||||||
|
"--format", "blob",
|
||||||
|
],
|
||||||
|
capsys,
|
||||||
|
)
|
||||||
|
assert r["rc"] == 0
|
||||||
|
assert r["env"]["status"] == "exported"
|
||||||
|
assert r["env"]["artifacts"]["runtime_key_path"] == str(out)
|
||||||
|
call = _read_stub_call(log)
|
||||||
|
assert call[0] == "export-runtime-key"
|
||||||
|
assert "--format" in call and "blob" in call
|
||||||
|
assert "--key-id" in call and "3" in call
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_runtime_key_rejects_bad_format(tmp_path, monkeypatch, capsys):
|
||||||
|
_install_stub(tmp_path, monkeypatch)
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
cli_main(
|
||||||
|
[
|
||||||
|
"--json", "m2pack", "export-runtime-key",
|
||||||
|
"--key", str(tmp_path / "k"),
|
||||||
|
"--public-key", str(tmp_path / "p"),
|
||||||
|
"--output", str(tmp_path / "o"),
|
||||||
|
"--format", "yaml",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Error paths
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_nonzero_exit_maps_to_subprocess_failed(tmp_path, monkeypatch, capsys):
|
||||||
|
_install_stub(tmp_path, monkeypatch, mode="fail")
|
||||||
|
(tmp_path / "in").mkdir()
|
||||||
|
(tmp_path / "k").write_text("a")
|
||||||
|
(tmp_path / "sk").write_text("b")
|
||||||
|
rc = cli_main(
|
||||||
|
[
|
||||||
|
"--json", "m2pack", "build",
|
||||||
|
"--input", str(tmp_path / "in"),
|
||||||
|
"--output", str(tmp_path / "o.m2p"),
|
||||||
|
"--key", str(tmp_path / "k"),
|
||||||
|
"--sign-secret-key", str(tmp_path / "sk"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert rc == 1
|
||||||
|
env = json.loads(capsys.readouterr().out)
|
||||||
|
assert env["ok"] is False
|
||||||
|
assert env["error"]["code"] == "m2pack_failed"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nonjson_output_maps_to_invalid_json(tmp_path, monkeypatch, capsys):
|
||||||
|
_install_stub(tmp_path, monkeypatch, mode="nonjson")
|
||||||
|
archive = tmp_path / "a.m2p"
|
||||||
|
archive.write_bytes(b"x")
|
||||||
|
rc = cli_main(["--json", "m2pack", "verify", "--archive", str(archive)])
|
||||||
|
assert rc == 1
|
||||||
|
env = json.loads(capsys.readouterr().out)
|
||||||
|
assert env["ok"] is False
|
||||||
|
assert env["error"]["code"] == "m2pack_invalid_json"
|
||||||
|
|
||||||
|
|
||||||
|
def test_json_array_output_maps_to_invalid_json(tmp_path, monkeypatch, capsys):
|
||||||
|
_install_stub(tmp_path, monkeypatch, mode="notobject")
|
||||||
|
archive = tmp_path / "a.m2p"
|
||||||
|
archive.write_bytes(b"x")
|
||||||
|
rc = cli_main(["--json", "m2pack", "verify", "--archive", str(archive)])
|
||||||
|
assert rc == 1
|
||||||
|
env = json.loads(capsys.readouterr().out)
|
||||||
|
assert env["error"]["code"] == "m2pack_invalid_json"
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_binary_raises_validation_error(tmp_path, monkeypatch, capsys):
|
||||||
|
monkeypatch.delenv("M2PACK_BINARY", raising=False)
|
||||||
|
monkeypatch.setenv("PATH", "/nonexistent")
|
||||||
|
archive = tmp_path / "a.m2p"
|
||||||
|
archive.write_bytes(b"x")
|
||||||
|
rc = cli_main(["--json", "m2pack", "verify", "--archive", str(archive)])
|
||||||
|
assert rc == 1
|
||||||
|
env = json.loads(capsys.readouterr().out)
|
||||||
|
assert env["ok"] is False
|
||||||
|
assert env["error"]["code"] == "m2pack_not_found"
|
||||||
Reference in New Issue
Block a user