main
py 113 lines 3.09 KB
Raw
1 import sys
2 import threading
3 from pathlib import Path
4
5 import pytest
6
7 PROJECT_ROOT = Path(__file__).resolve().parents[1]
8 if str(PROJECT_ROOT) not in sys.path:
9 sys.path.insert(0, str(PROJECT_ROOT))
10
11 from helpers.ws_manager import WsResult
12
13
14 class _FakeSocketIO:
15 async def emit(self, *_args, **_kwargs): # pragma: no cover - helper stub
16 return None
17
18 async def disconnect(self, *_args, **_kwargs): # pragma: no cover - helper stub
19 return None
20
21
22 def test_ws_result_ok_clones_payload():
23 payload = {"value": 1}
24 result = WsResult.ok(payload)
25
26 assert result.as_result(
27 handler_id="handler",
28 fallback_correlation_id="corr",
29 )["data"] == payload
30
31 payload["value"] = 2
32 assert result.as_result(
33 handler_id="handler",
34 fallback_correlation_id="corr",
35 )["data"] == {"value": 1}
36
37
38 def test_ws_result_error_contains_metadata():
39 result = WsResult.error(
40 code="E_TEST",
41 message="failure",
42 details="additional",
43 correlation_id="corr",
44 duration_ms=12.5,
45 )
46
47 as_payload = result.as_result(handler_id="handler", fallback_correlation_id=None)
48 assert as_payload["ok"] is False
49 assert as_payload["error"] == {
50 "code": "E_TEST",
51 "error": "failure",
52 "details": "additional",
53 }
54 assert as_payload["correlationId"] == "corr"
55 assert as_payload["durationMs"] == pytest.approx(12.5, rel=1e-3)
56
57
58 def test_ws_result_applies_fallback_correlation_and_duration():
59 result = WsResult.ok(duration_ms=5.4321)
60 payload = result.as_result(
61 handler_id="handler",
62 fallback_correlation_id="corr-fallback",
63 )
64 assert payload["correlationId"] == "corr-fallback"
65 assert payload["durationMs"] == pytest.approx(5.4321, rel=1e-3)
66
67
68 def test_result_error_requires_error_payload():
69 with pytest.raises(ValueError):
70 WsResult(ok=False)
71
72 with pytest.raises(ValueError):
73 WsResult.error(code="", message="boom")
74
75
76 @pytest.mark.asyncio
77 async def test_state_sync_handler_registers_and_routes_state_request():
78 from helpers.ws_manager import WsManager
79 from api.ws_webui import WsWebui
80 from helpers.state_monitor import _reset_state_monitor_for_testing
81
82 _reset_state_monitor_for_testing()
83
84 socketio = _FakeSocketIO()
85 lock = threading.RLock()
86 manager = WsManager(socketio, lock)
87
88 namespace = "/ws"
89 handler = WsWebui(socketio, lock, manager=manager, namespace=namespace)
90
91 # Register connection with manager (sets up dispatcher loop)
92 await manager.handle_connect(namespace, "sid-1")
93 # Trigger StateMonitor binding via extension
94 await handler.on_connect("sid-1")
95
96 result = await handler.process(
97 "state_request",
98 {
99 "correlationId": "smoke-1",
100 "context": None,
101 "log_from": 0,
102 "notifications_from": 0,
103 "timezone": "UTC",
104 },
105 "sid-1",
106 )
107
108 assert result is not None
109 assert "runtime_epoch" in result
110 assert result.get("seq_base") == 1
111
112 await handler.on_disconnect("sid-1")
113 await manager.handle_disconnect(namespace, "sid-1")