Phase A1: bootstrap Python ci_orchestrator package
- pyproject.toml (hatchling) with ruff/mypy/pytest dev deps - src/ci_orchestrator/: config, credentials, backends (Protocol + WorkstationVmrunBackend), transport (WinRM via pypsrp, SSH via paramiko) - CLI entry point with PoC 'wait-ready' subcommand (Windows + Linux guests) - tests/python/: 35 unit tests, 83% coverage, all backends/transport mocked - gitea/workflows/lint.yml: new 'python' job (ruff + mypy --strict + pytest --cov-fail-under=70) - config.example.toml + README setup section + .gitignore Python entries Neutral VmBackend Protocol prepared for Phase C ESXi extension. See plans/implementation-plan-A-B.md step A1.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
"""Smoke tests for the click CLI entry point.
|
||||
|
||||
These do not hit any real VM — backend, transport, credentials and
|
||||
``time.sleep`` are all monkeypatched.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
import ci_orchestrator.__main__ as cli_module
|
||||
from ci_orchestrator import __version__
|
||||
from ci_orchestrator.backends.protocol import VmHandle
|
||||
from ci_orchestrator.credentials import Credential
|
||||
|
||||
|
||||
class _FakeBackend:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
running: bool = True,
|
||||
ip: str | None = "10.0.0.42",
|
||||
) -> None:
|
||||
self._running = running
|
||||
self._ip = ip
|
||||
|
||||
def is_running(self, _h: VmHandle) -> bool:
|
||||
return self._running
|
||||
|
||||
def get_ip(self, _h: VmHandle, timeout: float = 0.0) -> str | None:
|
||||
return self._ip
|
||||
|
||||
|
||||
class _FakeTransport:
|
||||
def __init__(self, *_a: Any, **_kw: Any) -> None:
|
||||
pass
|
||||
|
||||
def __enter__(self) -> _FakeTransport:
|
||||
return self
|
||||
|
||||
def __exit__(self, *_e: object) -> None:
|
||||
pass
|
||||
|
||||
def is_ready(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class _FakeStore:
|
||||
def get(self, _target: str) -> Credential:
|
||||
return Credential("user", "pwd")
|
||||
|
||||
|
||||
def _patch_common(monkeypatch: pytest.MonkeyPatch, backend: _FakeBackend) -> None:
|
||||
monkeypatch.setattr(cli_module, "load_backend", lambda _cfg: backend)
|
||||
monkeypatch.setattr(cli_module, "KeyringCredentialStore", lambda: _FakeStore())
|
||||
monkeypatch.setattr(cli_module, "WinRmTransport", _FakeTransport)
|
||||
monkeypatch.setattr(cli_module, "SshTransport", _FakeTransport)
|
||||
monkeypatch.setattr(cli_module.time, "sleep", lambda _s: None)
|
||||
|
||||
|
||||
def test_help_lists_wait_ready() -> None:
|
||||
result = CliRunner().invoke(cli_module.cli, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "wait-ready" in result.output
|
||||
|
||||
|
||||
def test_version() -> None:
|
||||
result = CliRunner().invoke(cli_module.cli, ["--version"])
|
||||
assert result.exit_code == 0
|
||||
assert __version__ in result.output
|
||||
|
||||
|
||||
def test_wait_ready_windows_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_patch_common(monkeypatch, _FakeBackend(running=True, ip="10.0.0.42"))
|
||||
result = CliRunner().invoke(
|
||||
cli_module.cli,
|
||||
["wait-ready", "--vmx", "x.vmx", "--guest-os", "windows", "--timeout", "1"],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "WinRM ready" in result.output
|
||||
|
||||
|
||||
def test_wait_ready_linux_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_patch_common(monkeypatch, _FakeBackend(running=True, ip="10.0.0.42"))
|
||||
result = CliRunner().invoke(
|
||||
cli_module.cli,
|
||||
["wait-ready", "--vmx", "x.vmx", "--guest-os", "linux", "--timeout", "1"],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "SSH ready" in result.output
|
||||
|
||||
|
||||
def test_wait_ready_timeout_when_not_running(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_patch_common(monkeypatch, _FakeBackend(running=False))
|
||||
result = CliRunner().invoke(
|
||||
cli_module.cli,
|
||||
[
|
||||
"wait-ready",
|
||||
"--vmx",
|
||||
"x.vmx",
|
||||
"--guest-os",
|
||||
"windows",
|
||||
"--timeout",
|
||||
"0.01",
|
||||
"--poll-interval",
|
||||
"0.001",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 2
|
||||
assert "timeout waiting for VM" in result.output
|
||||
|
||||
|
||||
def test_wait_ready_timeout_no_ip(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_patch_common(monkeypatch, _FakeBackend(running=True, ip=None))
|
||||
result = CliRunner().invoke(
|
||||
cli_module.cli,
|
||||
[
|
||||
"wait-ready",
|
||||
"--vmx",
|
||||
"x.vmx",
|
||||
"--guest-os",
|
||||
"windows",
|
||||
"--timeout",
|
||||
"0.01",
|
||||
"--poll-interval",
|
||||
"0.001",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 3
|
||||
assert "timeout waiting for guest IP" in result.output
|
||||
Reference in New Issue
Block a user