Skip to content

Commit 607f779

Browse files
committed
test: update start_daemon_services test to target renamed helper after rebase
The rebase onto post-#276 main removed src/orb/interface/serve_command_handler.py and moved the daemon-services init call into server_command_handlers._initialize_application. Redirect the test at the new target and drop unused sys/types imports.
1 parent 6bebeb5 commit 607f779

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
"""Unit tests for Application.start_daemon_services lifecycle behaviour."""
2+
3+
from __future__ import annotations
4+
5+
import asyncio
6+
from typing import Any
7+
from unittest.mock import AsyncMock, MagicMock, patch
8+
9+
import pytest
10+
11+
# ---------------------------------------------------------------------------
12+
# Minimal thin-fake Application that avoids heavy DI / file-system bootstrap
13+
# ---------------------------------------------------------------------------
14+
15+
16+
class _FakeApplication:
17+
"""Thin substitute for :class:`orb.bootstrap.Application`.
18+
19+
Exposes only the state and logic exercised by ``start_daemon_services``
20+
so tests run without the full DI container.
21+
"""
22+
23+
def __init__(
24+
self, *, initialized: bool = False, provider_instances: list | None = None
25+
) -> None:
26+
self._initialized = initialized
27+
self._provider_instances = provider_instances or []
28+
29+
self.logger = MagicMock()
30+
31+
# Fake config manager returns a fake provider config.
32+
fake_provider_config = MagicMock()
33+
fake_provider_config.get_active_providers.return_value = list(self._provider_instances)
34+
self._config_manager = MagicMock()
35+
self._config_manager.get_provider_config.return_value = fake_provider_config
36+
37+
# Fake provider registry returns strategy by name.
38+
self._strategy_by_name: dict[str, Any] = {}
39+
self._provider_registry = MagicMock()
40+
self._provider_registry.get_or_create_strategy.side_effect = lambda name, **_kw: (
41+
self._strategy_by_name.get(name)
42+
)
43+
44+
def register_strategy(self, name: str, strategy: Any) -> None:
45+
self._strategy_by_name[name] = strategy
46+
47+
# Copied verbatim from orb.bootstrap.Application.start_daemon_services
48+
# so that the tests exercise the real logic in isolation.
49+
async def start_daemon_services(self) -> bool:
50+
if not self._initialized:
51+
self.logger.warning(
52+
"start_daemon_services called before Application.initialize succeeded; skipping."
53+
)
54+
return False
55+
try:
56+
provider_config = self._config_manager.get_provider_config()
57+
if not provider_config:
58+
return True
59+
ok = True
60+
for provider_instance in provider_config.get_active_providers():
61+
strategy = self._provider_registry.get_or_create_strategy(provider_instance.name)
62+
if strategy is None:
63+
self.logger.warning(
64+
"start_daemon_services: no strategy resolved for %s",
65+
provider_instance.name,
66+
)
67+
ok = False
68+
continue
69+
try:
70+
await strategy.start_daemon_services()
71+
self.logger.info(
72+
"Daemon services started for provider instance %s",
73+
provider_instance.name,
74+
)
75+
except Exception as exc:
76+
self.logger.error(
77+
"Failed to start daemon services for %s: %s",
78+
provider_instance.name,
79+
exc,
80+
exc_info=True,
81+
)
82+
ok = False
83+
return ok
84+
except Exception as exc:
85+
self.logger.error("start_daemon_services failed: %s", exc, exc_info=True)
86+
return False
87+
88+
89+
def _make_provider_instance(name: str) -> MagicMock:
90+
inst = MagicMock()
91+
inst.name = name
92+
return inst
93+
94+
95+
# ---------------------------------------------------------------------------
96+
# Tests
97+
# ---------------------------------------------------------------------------
98+
99+
100+
@pytest.mark.asyncio
101+
async def test_returns_false_when_called_before_initialize() -> None:
102+
"""start_daemon_services returns False and logs a warning when the application
103+
has not yet completed initialize()."""
104+
app = _FakeApplication(initialized=False)
105+
result = await app.start_daemon_services()
106+
assert result is False
107+
warning_texts = [str(c) for c in app.logger.warning.call_args_list]
108+
assert any("before Application.initialize" in t for t in warning_texts)
109+
110+
111+
@pytest.mark.asyncio
112+
async def test_one_strategy_failure_does_not_abort_others() -> None:
113+
"""When one provider strategy's start_daemon_services raises, the remaining
114+
strategies are still awaited and the method returns False overall."""
115+
inst_a = _make_provider_instance("prov-a")
116+
inst_b = _make_provider_instance("prov-b")
117+
app = _FakeApplication(initialized=True, provider_instances=[inst_a, inst_b])
118+
119+
second_started = asyncio.Event()
120+
121+
strategy_a = MagicMock()
122+
strategy_a.start_daemon_services = AsyncMock(side_effect=RuntimeError("prov-a exploded"))
123+
124+
strategy_b = MagicMock()
125+
126+
async def _b_start() -> None:
127+
second_started.set()
128+
129+
strategy_b.start_daemon_services = AsyncMock(side_effect=_b_start)
130+
131+
app.register_strategy("prov-a", strategy_a)
132+
app.register_strategy("prov-b", strategy_b)
133+
134+
result = await app.start_daemon_services()
135+
136+
assert result is False
137+
assert second_started.is_set(), "strategy_b.start_daemon_services was not awaited"
138+
139+
140+
@pytest.mark.asyncio
141+
async def test_returns_true_when_all_strategies_succeed() -> None:
142+
"""start_daemon_services returns True when every registered strategy completes
143+
without raising."""
144+
inst_a = _make_provider_instance("prov-a")
145+
inst_b = _make_provider_instance("prov-b")
146+
app = _FakeApplication(initialized=True, provider_instances=[inst_a, inst_b])
147+
148+
strategy_a = MagicMock()
149+
strategy_a.start_daemon_services = AsyncMock(return_value=None)
150+
strategy_b = MagicMock()
151+
strategy_b.start_daemon_services = AsyncMock(return_value=None)
152+
153+
app.register_strategy("prov-a", strategy_a)
154+
app.register_strategy("prov-b", strategy_b)
155+
156+
result = await app.start_daemon_services()
157+
158+
assert result is True
159+
strategy_a.start_daemon_services.assert_awaited_once()
160+
strategy_b.start_daemon_services.assert_awaited_once()
161+
162+
163+
@pytest.mark.asyncio
164+
async def test_server_initialize_application_calls_start_daemon_services() -> None:
165+
"""The REST server initialisation path invokes orb_app.start_daemon_services()
166+
after Application.initialize() completes successfully."""
167+
fake_app = MagicMock()
168+
fake_app.initialize = AsyncMock(return_value=True)
169+
fake_app.start_daemon_services = AsyncMock(return_value=True)
170+
171+
fake_container = MagicMock()
172+
fake_config_manager = MagicMock()
173+
fake_config_manager._config_file = None
174+
fake_container.get.return_value = fake_config_manager
175+
176+
with (
177+
patch("orb.infrastructure.di.container.get_container", return_value=fake_container),
178+
patch("orb.bootstrap.Application", return_value=fake_app),
179+
):
180+
from orb.interface.server_command_handlers import _initialize_application # noqa: PLC0415
181+
182+
await _initialize_application()
183+
184+
fake_app.start_daemon_services.assert_awaited_once()
185+
186+
187+
@pytest.mark.asyncio
188+
async def test_cli_command_handler_does_not_call_start_daemon_services() -> None:
189+
"""A CLI command path (handle_request_machines) does NOT call start_daemon_services."""
190+
# We check the source code does not contain a direct invocation of
191+
# start_daemon_services. This is a static/lint-style assertion that
192+
# the CLI handler is free of the daemon-start call.
193+
import inspect
194+
195+
from orb.interface import request_command_handlers
196+
197+
source = inspect.getsource(request_command_handlers)
198+
assert "start_daemon_services" not in source, (
199+
"CLI command handler request_command_handlers must not call start_daemon_services"
200+
)

0 commit comments

Comments
 (0)