"""A bootstrap that dies mid-setup must not leave its transport behind. The bootstrap now runs concurrently with the scan start, so teardown can cancel it at any await — including inside ``Client.connect()``, where the client exists but no caller will ever see it to close it. """ from __future__ import annotations import asyncio import sys import types from typing import Any import pytest from strix.runtime.caido_bootstrap import bootstrap_caido class _FakeExecResult: stderr = b"" exit_code = 0 def __init__(self, stdout: str) -> None: self.stdout = stdout def ok(self) -> bool: return True class _FakeSession: async def exec(self, *_args: Any, **_kwargs: Any) -> _FakeExecResult: return _FakeExecResult('{"data":{"loginAsGuest":{"token":{"accessToken":"t"}}}}') class _FakeClient: def __init__(self, connect_error: BaseException) -> None: self.connect_error = connect_error self.closed = False async def connect(self) -> None: raise self.connect_error async def aclose(self) -> None: self.closed = True async def _bootstrap_expecting( monkeypatch: pytest.MonkeyPatch, error: BaseException ) -> _FakeClient: """Run a bootstrap whose ``connect()`` fails with ``error``.""" client = _FakeClient(error) # The SDK is imported inside bootstrap_caido (it is slow to import), so the # fakes are injected as the modules it imports. sdk = types.ModuleType("caido_sdk_client") sdk.Client = lambda *_a, **_k: client # type: ignore[attr-defined] sdk.TokenAuthOptions = lambda token: token # type: ignore[attr-defined] sdk_types = types.ModuleType("caido_sdk_client.types") sdk_types.CreateProjectOptions = lambda **_k: None # type: ignore[attr-defined] monkeypatch.setitem(sys.modules, "caido_sdk_client", sdk) monkeypatch.setitem(sys.modules, "caido_sdk_client.types", sdk_types) with pytest.raises(type(error)): await bootstrap_caido( _FakeSession(), # type: ignore[arg-type] host_url="http://host", container_url="http://container", ) return client async def test_cancellation_during_connect_closes_the_client( monkeypatch: pytest.MonkeyPatch, ) -> None: client = await _bootstrap_expecting(monkeypatch, asyncio.CancelledError()) assert client.closed async def test_failed_connect_closes_the_client(monkeypatch: pytest.MonkeyPatch) -> None: client = await _bootstrap_expecting(monkeypatch, RuntimeError("no listener")) assert client.closed