Files
metin-release-cli/tests/test_sign.py
Jan Nedbal d2dd2c88b6 tests: cover phase 1 commands end to end
Pytest suite with a tiny_client fixture, an ephemeral Ed25519 keypair
fixture, and a threaded HTTPServer helper. Exercises cli dispatch,
inspect (including excluded-path handling), build-manifest and sign
against the real m2dev-client scripts, diff-remote via a local server,
and the full release publish composite against a local rsync target.
2026-04-14 18:59:50 +02:00

74 lines
2.5 KiB
Python

from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import pytest
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from metin_release.commands import sign
from metin_release.errors import IntegrityError, KeyPermissionError, ValidationError
from metin_release.workspace import Context
def _ctx(script: Path) -> Context:
return Context(sign_manifest_script=script)
def _write_toy_manifest(path: Path) -> None:
path.write_bytes(json.dumps({"version": "test", "files": []}, indent=2).encode() + b"\n")
@pytest.mark.skipif(
not Path("/home/jann/metin/repos/m2dev-client/scripts/sign-manifest.py").is_file(),
reason="real sign-manifest.py not available",
)
def test_sign_produces_valid_signature(tmp_path: Path, test_keypair, sign_manifest_script: Path):
key_path, pub_hex = test_keypair
manifest_path = tmp_path / "manifest.json"
_write_toy_manifest(manifest_path)
ctx = _ctx(sign_manifest_script)
args = argparse.Namespace(manifest=manifest_path, key=key_path, out=None)
result = sign.run(ctx, args)
sig_path = Path(result.data["artifacts"]["signature_path"])
assert sig_path.is_file()
sig = sig_path.read_bytes()
assert len(sig) == 64
# Verify it actually checks out against the public key.
pub = Ed25519PublicKey.from_public_bytes(bytes.fromhex(pub_hex))
pub.verify(sig, manifest_path.read_bytes())
# Now corrupt the manifest; signature no longer verifies.
manifest_path.write_bytes(b"tampered")
with pytest.raises(InvalidSignature):
pub.verify(sig, manifest_path.read_bytes())
def test_sign_rejects_loose_key_permissions(tmp_path: Path, sign_manifest_script: Path):
key = tmp_path / "key"
key.write_bytes(b"\x00" * 32)
os.chmod(key, 0o644)
manifest = tmp_path / "manifest.json"
_write_toy_manifest(manifest)
ctx = _ctx(sign_manifest_script)
args = argparse.Namespace(manifest=manifest, key=key, out=None)
with pytest.raises(KeyPermissionError):
sign.run(ctx, args)
def test_sign_relative_key_path_rejected(tmp_path: Path, sign_manifest_script: Path):
manifest = tmp_path / "manifest.json"
_write_toy_manifest(manifest)
ctx = _ctx(sign_manifest_script)
args = argparse.Namespace(manifest=manifest, key=Path("relative/key"), out=None)
with pytest.raises(ValidationError):
sign.run(ctx, args)