"""Pure-unit tests — no network, no ISPConfig. We swap in a fake transport so the client-level logic (session management, retry on expired session, fault translation) can be tested in isolation. """ from __future__ import annotations from collections.abc import Iterable from typing import Any import pytest from ispconfig import ( AuthError, FaultError, ISPConfigClient, NotFoundError, PermissionError, ) from ispconfig._soap import SoapFault, SoapTransport class _FakeTransport: def __init__(self, scripted: list[Any]) -> None: self.scripted = list(scripted) self.calls: list[tuple[str, tuple[tuple[str, Any], ...]]] = [] def call(self, method: str, args: Iterable[tuple[str, Any]]) -> Any: self.calls.append((method, tuple(args))) if not self.scripted: raise AssertionError(f"unexpected call {method}") result = self.scripted.pop(0) if isinstance(result, Exception): raise result return result def _make_client(transport: _FakeTransport) -> ISPConfigClient: c = ISPConfigClient("http://fake/remote/index.php", "user", "pass") c._transport = transport # type: ignore[assignment] return c def test_login_stores_session_and_logout_clears() -> None: t = _FakeTransport(["sid-abc", True]) c = _make_client(t) c.login() assert c.session_id == "sid-abc" assert c.logout() is True assert c.session_id is None def test_context_manager_auto_login_logout() -> None: t = _FakeTransport(["sid-123", {"domain": "x.com"}, True]) c = _make_client(t) with c: assert c.session_id == "sid-123" # first positional arg passed through is the session id. result = c.sites.web_domain_get(1) assert result == {"domain": "x.com"} assert c.session_id is None assert [call[0] for call in t.calls] == ["login", "sites_web_domain_get", "logout"] def test_session_expired_retry() -> None: t = _FakeTransport( [ "sid-first", # login SoapFault("Server", "Session not valid"), # first _call fails "sid-second", # re-login {"domain": "x.com"}, # retry succeeds ] ) c = _make_client(t) c.login() result = c.sites.web_domain_get(1) assert result == {"domain": "x.com"} # 4 transport calls: login, failed get, login, successful get. assert [call[0] for call in t.calls] == [ "login", "sites_web_domain_get", "login", "sites_web_domain_get", ] def test_session_expired_no_retry_when_disabled() -> None: t = _FakeTransport( [ "sid-first", SoapFault("Server", "Session expired"), ] ) c = ISPConfigClient("http://fake/", "u", "p", max_retries=0) c._transport = t # type: ignore[assignment] c.login() with pytest.raises(AuthError): c.sites.web_domain_get(1) def test_fault_mapping_auth() -> None: t = _FakeTransport([SoapFault("Server", "Login failed")]) c = _make_client(t) with pytest.raises(AuthError): c.login() def test_fault_mapping_permission() -> None: t = _FakeTransport(["sid", SoapFault("Server", "Permission denied")]) c = _make_client(t) c.login() with pytest.raises(PermissionError): c.sites.web_domain_get(1) def test_fault_mapping_not_found() -> None: t = _FakeTransport(["sid", SoapFault("Server", "No records found")]) c = _make_client(t) c.login() with pytest.raises(NotFoundError): c.sites.web_domain_get(999) def test_fault_mapping_generic() -> None: t = _FakeTransport(["sid", SoapFault("Server", "something weird")]) c = _make_client(t) c.login() with pytest.raises(FaultError): c.sites.web_domain_get(1) def test_update_client_id_footgun_passes_through() -> None: """``web_domain_update`` must send ``client_id`` as the 2nd positional arg.""" t = _FakeTransport(["sid", 1]) c = _make_client(t) c.login() c.sites.web_domain_update(0, 156, {"php": "fast-cgi"}) _, args = t.calls[-1] assert args[0][0] == "session_id" assert args[1] == ("client_id", 0) assert args[2] == ("primary_id", 156) assert args[3][0] == "params" def test_envelope_encoding_map_and_scalars() -> None: """Smoke test for the XML encoder — catches regressions.""" xml = SoapTransport._build_envelope( "sites_web_domain_update", ( ("session_id", "abc"), ("client_id", 0), ("primary_id", 156), ("params", {"php": "fast-cgi", "active": "y"}), ), ) assert "abc<" in xml assert '0' in xml assert "ns2:Map" in xml assert 'php' in xml assert 'fast-cgi' in xml def test_response_parsing_map() -> None: body = b""" domainmcb.com activey """ result = SoapTransport._parse_response("sites_web_domain_get", body) assert result == {"domain": "mcb.com", "active": "y"} def test_response_parsing_fault() -> None: body = b""" SOAP-ENV:Server Login failed. """ with pytest.raises(SoapFault) as excinfo: SoapTransport._parse_response("login", body) assert "Login failed" in excinfo.value.faultstring