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.
74 lines
2.5 KiB
Python
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)
|