from __future__ import annotations import json import subprocess import sys from http.client import BadStatusLine from pathlib import Path from types import SimpleNamespace from typing import Any import pytest from typer.testing import CliRunner from k1link.device_plugins.xgrids_k1 import cli from k1link.device_plugins.xgrids_k1.cli import app runner = CliRunner() class _FakeServeLease: def __init__(self) -> None: self.active = False def __enter__(self) -> _FakeServeLease: assert self.active is False self.active = True return self def __exit__(self, *_: object) -> None: assert self.active is True self.active = False def _install_fake_serve_lease( monkeypatch: Any, *, error: BaseException | None = None, ) -> _FakeServeLease: lease = _FakeServeLease() def acquire(_: Path) -> _FakeServeLease: if error is not None: raise error return lease monkeypatch.setattr(cli, "_acquire_mission_core_serve_lease", acquire) return lease def test_help() -> None: result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "evidence-led" in result.stdout def test_doctor_json() -> None: result = runner.invoke(app, ["doctor", "--json"]) assert result.exit_code == 0 payload = json.loads(result.stdout) assert payload["k1link_version"] == "0.1.0" assert isinstance(payload["tools"], list) assert isinstance(payload["network"], dict) assert any(item["name"] == "tcpdump" for item in payload["tools"]) def test_serve_resolves_frontend_from_repository_root(monkeypatch: Any) -> None: captured: dict[str, object] = {} lease = _install_fake_serve_lease(monkeypatch) def fake_run(application: str, **kwargs: object) -> None: assert lease.active is True captured.update({"application": application, **kwargs}) monkeypatch.setattr(cli.uvicorn, "run", fake_run) monkeypatch.setattr(cli, "_local_server_status", lambda _: "free") result = runner.invoke(app, ["serve"]) assert result.exit_code == 0 assert captured == { "application": "k1link.web.app:app", "host": "127.0.0.1", "port": 8000, "log_level": "info", "access_log": True, "timeout_graceful_shutdown": 10, } assert lease.active is False def test_serve_releases_singleton_lease_when_uvicorn_fails(monkeypatch: Any) -> None: lease = _install_fake_serve_lease(monkeypatch) monkeypatch.setattr(cli, "_local_server_status", lambda _: "free") def failed_run(*_: object, **__: object) -> None: assert lease.active is True raise RuntimeError("startup failed") monkeypatch.setattr(cli.uvicorn, "run", failed_run) result = runner.invoke(app, ["serve"]) assert result.exit_code == 1 assert isinstance(result.exception, RuntimeError) assert lease.active is False def test_serve_reuses_an_existing_mission_core_backend(monkeypatch: Any) -> None: lease = _install_fake_serve_lease(monkeypatch) monkeypatch.setattr(cli, "_local_server_status", lambda _: "mission-core") def unexpected_run(*_: object, **__: object) -> None: raise AssertionError("a second backend must not be started") monkeypatch.setattr(cli.uvicorn, "run", unexpected_run) result = runner.invoke(app, ["serve"]) assert result.exit_code == 0 assert "уже запущен" in result.stdout assert "второй процесс не создан" in result.stdout assert lease.active is False def test_serve_rejects_an_unrelated_loopback_listener(monkeypatch: Any) -> None: lease = _install_fake_serve_lease(monkeypatch) monkeypatch.setattr(cli, "_local_server_status", lambda _: "occupied") def unexpected_run(*_: object, **__: object) -> None: raise AssertionError("an occupied port must fail before Uvicorn startup") monkeypatch.setattr(cli.uvicorn, "run", unexpected_run) result = runner.invoke(app, ["serve"]) assert result.exit_code == 2 assert "уже занят" in result.stdout assert "Mission Core не стал создавать второй backend" in result.stdout assert "--port" not in result.stdout assert lease.active is False def test_serve_rejects_noncanonical_port_before_lock_or_health(monkeypatch: Any) -> None: def unexpected_call(*_: object, **__: object) -> None: raise AssertionError("noncanonical port must fail before lock or health inspection") monkeypatch.setattr(cli, "_acquire_mission_core_serve_lease", unexpected_call) monkeypatch.setattr(cli, "_local_server_status", unexpected_call) monkeypatch.setattr(cli.uvicorn, "run", unexpected_call) result = runner.invoke(app, ["serve", "--port", "8001"]) assert result.exit_code == 2 assert "только на каноническом порту 8000" in result.stdout assert "Другой локальный backend не создан" in result.stdout def test_serve_busy_lock_reuses_confirmed_mission_core(monkeypatch: Any) -> None: _install_fake_serve_lease( monkeypatch, error=cli._MissionCoreServeLeaseUnavailable("busy"), ) monkeypatch.setattr(cli, "_local_server_status", lambda _: "mission-core") def unexpected_run(*_: object, **__: object) -> None: raise AssertionError("busy singleton lease must not start Uvicorn") monkeypatch.setattr(cli.uvicorn, "run", unexpected_run) result = runner.invoke(app, ["serve"]) assert result.exit_code == 0 assert "уже запущен" in result.stdout assert "второй процесс не создан" in result.stdout def test_serve_busy_lock_reports_in_progress_when_health_is_not_listening( monkeypatch: Any, ) -> None: _install_fake_serve_lease( monkeypatch, error=cli._MissionCoreServeLeaseUnavailable("busy"), ) monkeypatch.setattr(cli, "_local_server_status", lambda _: "free") def unexpected_run(*_: object, **__: object) -> None: raise AssertionError("busy singleton lease must not start Uvicorn") monkeypatch.setattr(cli.uvicorn, "run", unexpected_run) result = runner.invoke(app, ["serve"]) assert result.exit_code == 2 assert "уже запускается или завершает работу" in result.stdout assert "Второй backend не создан" in result.stdout def test_serve_busy_lock_rejects_unconfirmed_health_listener(monkeypatch: Any) -> None: _install_fake_serve_lease( monkeypatch, error=cli._MissionCoreServeLeaseUnavailable("busy"), ) monkeypatch.setattr(cli, "_local_server_status", lambda _: "occupied") def unexpected_run(*_: object, **__: object) -> None: raise AssertionError("busy singleton lease must not start Uvicorn") monkeypatch.setattr(cli.uvicorn, "run", unexpected_run) result = runner.invoke(app, ["serve"]) assert result.exit_code == 2 assert "канонический health endpoint" in result.stdout assert "подтверждён" in result.stdout assert "Второй backend не создан" in result.stdout def test_serve_lock_integrity_error_fails_before_health_or_uvicorn(monkeypatch: Any) -> None: _install_fake_serve_lease( monkeypatch, error=cli._MissionCoreServeLeaseError("unsafe lock"), ) def unexpected_call(*_: object, **__: object) -> None: raise AssertionError("an untrusted lock must block health and Uvicorn") monkeypatch.setattr(cli, "_local_server_status", unexpected_call) monkeypatch.setattr(cli.uvicorn, "run", unexpected_call) result = runner.invoke(app, ["serve"]) assert result.exit_code == 2 assert "безопасно получить блокировку" in result.stdout assert "Второй backend не создан" in result.stdout def test_serve_lock_is_stable_private_and_cross_process(tmp_path: Path) -> None: expected_path = tmp_path / ".runtime" / "mission-core" / ".serve.lock" with cli._MissionCoreServeLease.acquire(tmp_path) as first: assert first.path == expected_path first_identity = (expected_path.stat().st_dev, expected_path.stat().st_ino) assert expected_path.stat().st_mode & 0o777 == 0o600 assert expected_path.parent.stat().st_mode & 0o777 == 0o700 child = subprocess.run( [ sys.executable, "-c", ( "import sys; from pathlib import Path; " "from k1link.device_plugins.xgrids_k1.cli import " "_MissionCoreServeLease, _MissionCoreServeLeaseUnavailable; " "root=Path(sys.argv[1]); " "\ntry: lease=_MissionCoreServeLease.acquire(root)" "\nexcept _MissionCoreServeLeaseUnavailable: raise SystemExit(0)" "\nelse: lease.release(); raise SystemExit(1)" ), str(tmp_path), ], check=False, capture_output=True, text=True, timeout=10, ) assert child.returncode == 0, child.stderr assert expected_path.is_file() with cli._MissionCoreServeLease.acquire(tmp_path) as second: assert (second.path.stat().st_dev, second.path.stat().st_ino) == first_identity def test_serve_lock_rejects_nonprivate_lock_directory(tmp_path: Path) -> None: lock_dir = tmp_path / ".runtime" / "mission-core" lock_dir.mkdir(mode=0o755, parents=True) lock_dir.chmod(0o755) with pytest.raises(cli._MissionCoreServeLeaseError, match="not private"): cli._MissionCoreServeLease.acquire(tmp_path) def test_serve_lock_rejects_symlinked_lock_directory(tmp_path: Path) -> None: runtime_dir = tmp_path / ".runtime" runtime_dir.mkdir() redirected = tmp_path / "redirected" redirected.mkdir(mode=0o700) (runtime_dir / "mission-core").symlink_to(redirected, target_is_directory=True) with pytest.raises(cli._MissionCoreServeLeaseError, match="not private"): cli._MissionCoreServeLease.acquire(tmp_path) class _FakeHealthResponse: def __init__(self, *, status: int = 200, body: bytes = b"") -> None: self.status = status self.body = body def read(self, limit: int) -> bytes: assert limit == 16_385 return self.body[:limit] class _FakeHealthConnection: def __init__( self, *, response: _FakeHealthResponse | None = None, error: BaseException | None = None, ) -> None: self.response = response self.error = error self.request_call: tuple[str, str, dict[str, str]] | None = None self.closed = False def request(self, method: str, path: str, *, headers: dict[str, str]) -> None: self.request_call = (method, path, headers) if self.error is not None: raise self.error def getresponse(self) -> _FakeHealthResponse: assert self.response is not None return self.response def close(self) -> None: self.closed = True def _install_fake_health_connection( monkeypatch: Any, connection: _FakeHealthConnection, ) -> None: def fake_connection(host: str, port: int, *, timeout: float) -> _FakeHealthConnection: assert (host, port, timeout) == ("127.0.0.1", 8000, 0.75) return connection monkeypatch.setattr(cli.http.client, "HTTPConnection", fake_connection) def test_local_server_status_recognizes_exact_mission_core_health(monkeypatch: Any) -> None: connection = _FakeHealthConnection( response=_FakeHealthResponse( body=json.dumps({"service": "mission-core-control-plane"}).encode("utf-8") ) ) _install_fake_health_connection(monkeypatch, connection) assert cli._local_server_status(8000) == "mission-core" assert connection.request_call == ( "GET", "/api/health", {"Accept": "application/json", "Connection": "close"}, ) assert connection.closed is True def test_local_server_status_rejects_a_different_health_identity(monkeypatch: Any) -> None: connection = _FakeHealthConnection( response=_FakeHealthResponse(body=json.dumps({"service": "other"}).encode("utf-8")) ) _install_fake_health_connection(monkeypatch, connection) assert cli._local_server_status(8000) == "occupied" assert connection.closed is True def test_local_server_status_only_treats_explicit_refusal_as_free(monkeypatch: Any) -> None: connection = _FakeHealthConnection(error=ConnectionRefusedError()) _install_fake_health_connection(monkeypatch, connection) assert cli._local_server_status(8000) == "free" assert connection.closed is True def test_local_server_status_fails_closed_on_listener_timeout(monkeypatch: Any) -> None: connection = _FakeHealthConnection(error=TimeoutError()) _install_fake_health_connection(monkeypatch, connection) assert cli._local_server_status(8000) == "occupied" assert connection.closed is True def test_local_server_status_fails_closed_on_non_http_listener(monkeypatch: Any) -> None: connection = _FakeHealthConnection(error=BadStatusLine("not HTTP")) _install_fake_health_connection(monkeypatch, connection) assert cli._local_server_status(8000) == "occupied" assert connection.closed is True def test_local_server_status_rejects_oversized_health_payload(monkeypatch: Any) -> None: connection = _FakeHealthConnection( response=_FakeHealthResponse(body=b" " * 16_385) ) _install_fake_health_connection(monkeypatch, connection) assert cli._local_server_status(8000) == "occupied" assert connection.closed is True def test_authority_provision_requires_explicit_reviewed_value_confirmation() -> None: result = runner.invoke(app, ["authority", "provision"]) assert result.exit_code == 2 assert "provisioning not confirmed" in result.stdout def test_authority_provision_never_accepts_the_secret_as_a_cli_value(monkeypatch: Any) -> None: calls = 0 class FakeSnapshot: service = "fixed-service" account = "fixed-account" class FakeProvisioner: def provision_interactively(self) -> FakeSnapshot: nonlocal calls calls += 1 return FakeSnapshot() monkeypatch.setattr( "k1link.device_plugins.xgrids_k1.cli.MacOSKeychainApplicationAuthorityProvisioner", FakeProvisioner, ) result = runner.invoke( app, ["authority", "provision", "--confirm-reviewed-authority"], ) assert result.exit_code == 0 assert calls == 1 assert "Keychain item validated" in result.stdout assert "No K1 command was sent" in " ".join(result.stdout.split()) def test_mqtt_capture_requires_owned_device_confirmation(tmp_path: Path) -> None: result = runner.invoke( app, [ "net", "mqtt-capture", "--host", "192.168.1.20", "--out", str(tmp_path / "capture"), ], ) assert result.exit_code == 2 assert "ownership not confirmed" in result.stdout assert not (tmp_path / "capture").exists() def test_mqtt_capture_cli_uses_bounded_read_only_capture( monkeypatch: Any, tmp_path: Path, ) -> None: captured: dict[str, object] = {} def fake_capture(host: str, out: Path, **kwargs: object) -> dict[str, object]: on_ready = kwargs.pop("on_ready") assert callable(on_ready) on_ready() captured.update({"host": host, "out": out, **kwargs}) return { "stop_reason": "duration_elapsed", "message_count": 2, "payload_bytes": 128, } monkeypatch.setattr("k1link.device_plugins.xgrids_k1.cli.capture_mqtt", fake_capture) out = tmp_path / "capture" result = runner.invoke( app, [ "net", "mqtt-capture", "--host", "10.0.0.42", "--out", str(out), "--duration", "5", "--max-message-bytes", "1024", "--confirm-owned-device", ], ) assert result.exit_code == 0 assert captured == { "host": "10.0.0.42", "out": out, "port": 1883, "duration_seconds": 5.0, "max_message_bytes": 1024, } assert "messages: 2" in result.stdout assert "subscriptions active" in result.stdout @pytest.mark.parametrize("ledger_status", ["resolved", "corrupt"]) def test_cli_wifi_configure_blocks_retired_or_untrusted_audit_before_credentials_and_ble( monkeypatch: Any, tmp_path: Path, ledger_status: str, ) -> None: repository_root = tmp_path / "repository" repository_root.mkdir() monkeypatch.setenv("MISSIONCORE_DATA_DIR", str(tmp_path / "private-data")) monkeypatch.setattr(cli, "_configure_ble_process_lease", lambda: repository_root) retired_transport_ref = "F89438FA-55ED-85AD-EED7-734AC84746D8" record = ( SimpleNamespace( operator_retirements=( SimpleNamespace( retirement_id="retirement-cli-blocked-target", retired_transport_ref=retired_transport_ref, ), ), operator_reconciliation_reopens=(), ) if ledger_status == "resolved" else None ) monkeypatch.setattr( cli, "PhysicalCommandLedger", lambda _root: SimpleNamespace( snapshot=lambda: SimpleNamespace(status=ledger_status, record=record) ), ) calls: list[str] = [] monkeypatch.setattr( cli, "prompt_wifi_credentials", lambda: calls.append("credentials") or ("ssid", "secret"), ) async def forbidden_provision(*_args: object, **_kwargs: object) -> dict[str, object]: calls.append("ble-write") raise AssertionError("retired CLI target must not reach BLE") monkeypatch.setattr(cli, "provision_wifi_once", forbidden_provision) result = runner.invoke( app, [ "ble", "wifi-configure", "--device", retired_transport_ref.lower(), "--out", str(tmp_path / "wifi-result.json"), "--profile", cli.PROFILE_ID, "--confirm-write", ], ) assert result.exit_code == 2 assert "blocked before credential or device access" in result.stdout assert calls == [] assert not (tmp_path / "wifi-result.json").exists()