jarviscore-framework 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- examples/customagent_cognitive_discovery_example.py +49 -8
- examples/customagent_distributed_example.py +140 -1
- examples/fastapi_integration_example.py +70 -7
- jarviscore/__init__.py +1 -1
- jarviscore/core/mesh.py +149 -0
- jarviscore/data/examples/customagent_cognitive_discovery_example.py +49 -8
- jarviscore/data/examples/customagent_distributed_example.py +140 -1
- jarviscore/data/examples/fastapi_integration_example.py +70 -7
- jarviscore/docs/API_REFERENCE.md +547 -5
- jarviscore/docs/CHANGELOG.md +89 -0
- jarviscore/docs/CONFIGURATION.md +1 -1
- jarviscore/docs/CUSTOMAGENT_GUIDE.md +347 -2
- jarviscore/docs/TROUBLESHOOTING.md +1 -1
- jarviscore/docs/USER_GUIDE.md +286 -5
- jarviscore/p2p/coordinator.py +36 -7
- jarviscore/p2p/messages.py +13 -0
- jarviscore/p2p/peer_client.py +355 -23
- jarviscore/p2p/peer_tool.py +17 -11
- jarviscore/profiles/customagent.py +9 -2
- jarviscore/testing/__init__.py +35 -0
- jarviscore/testing/mocks.py +578 -0
- {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/METADATA +2 -2
- {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/RECORD +31 -24
- tests/test_17_session_context.py +489 -0
- tests/test_18_mesh_diagnostics.py +465 -0
- tests/test_19_async_requests.py +516 -0
- tests/test_20_load_balancing.py +546 -0
- tests/test_21_mock_testing.py +776 -0
- {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/WHEEL +0 -0
- {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test 19: Async Request Pattern (Feature F2)
|
|
3
|
+
|
|
4
|
+
Tests the async request methods:
|
|
5
|
+
- ask_async() returns request_id immediately
|
|
6
|
+
- check_inbox() returns None when not ready
|
|
7
|
+
- check_inbox() returns response when available
|
|
8
|
+
- check_inbox() with timeout
|
|
9
|
+
- get_pending_async_requests()
|
|
10
|
+
- clear_inbox()
|
|
11
|
+
|
|
12
|
+
Run with: pytest tests/test_19_async_requests.py -v -s
|
|
13
|
+
"""
|
|
14
|
+
import asyncio
|
|
15
|
+
import sys
|
|
16
|
+
import pytest
|
|
17
|
+
import logging
|
|
18
|
+
from unittest.mock import MagicMock, AsyncMock
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, '.')
|
|
21
|
+
|
|
22
|
+
# Setup logging
|
|
23
|
+
logging.basicConfig(level=logging.INFO)
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# =============================================================================
|
|
28
|
+
# TEST: ASK_ASYNC BASICS
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
class TestAskAsync:
|
|
32
|
+
"""Test ask_async() method."""
|
|
33
|
+
|
|
34
|
+
@pytest.mark.asyncio
|
|
35
|
+
async def test_ask_async_returns_request_id(self):
|
|
36
|
+
"""Test ask_async() returns a request ID string."""
|
|
37
|
+
from jarviscore.testing import MockPeerClient
|
|
38
|
+
|
|
39
|
+
client = MockPeerClient(
|
|
40
|
+
agent_id="requester-1",
|
|
41
|
+
agent_role="requester",
|
|
42
|
+
mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
request_id = await client.ask_async("analyst", {"query": "test"})
|
|
46
|
+
|
|
47
|
+
assert request_id is not None
|
|
48
|
+
assert isinstance(request_id, str)
|
|
49
|
+
assert len(request_id) > 0
|
|
50
|
+
|
|
51
|
+
@pytest.mark.asyncio
|
|
52
|
+
async def test_ask_async_request_id_format(self):
|
|
53
|
+
"""Test ask_async() returns properly formatted request ID."""
|
|
54
|
+
from jarviscore.testing import MockPeerClient
|
|
55
|
+
|
|
56
|
+
client = MockPeerClient(
|
|
57
|
+
agent_id="requester-1",
|
|
58
|
+
agent_role="requester",
|
|
59
|
+
mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
request_id = await client.ask_async("analyst", {"query": "test"})
|
|
63
|
+
|
|
64
|
+
# MockPeerClient uses "mock-" prefix
|
|
65
|
+
assert request_id.startswith("mock-")
|
|
66
|
+
|
|
67
|
+
@pytest.mark.asyncio
|
|
68
|
+
async def test_ask_async_tracks_request(self):
|
|
69
|
+
"""Test ask_async() tracks the request in sent_requests."""
|
|
70
|
+
from jarviscore.testing import MockPeerClient
|
|
71
|
+
|
|
72
|
+
client = MockPeerClient(
|
|
73
|
+
agent_id="requester-1",
|
|
74
|
+
agent_role="requester",
|
|
75
|
+
mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
request_id = await client.ask_async("analyst", {"query": "important"})
|
|
79
|
+
|
|
80
|
+
requests = client.get_sent_requests()
|
|
81
|
+
assert len(requests) == 1
|
|
82
|
+
assert requests[0]["target"] == "analyst"
|
|
83
|
+
assert requests[0]["message"] == {"query": "important"}
|
|
84
|
+
assert requests[0].get("async") is True
|
|
85
|
+
|
|
86
|
+
@pytest.mark.asyncio
|
|
87
|
+
async def test_ask_async_with_context(self):
|
|
88
|
+
"""Test ask_async() accepts context parameter."""
|
|
89
|
+
from jarviscore.testing import MockPeerClient
|
|
90
|
+
|
|
91
|
+
client = MockPeerClient(
|
|
92
|
+
agent_id="requester-1",
|
|
93
|
+
agent_role="requester",
|
|
94
|
+
mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
context = {"mission_id": "m-123", "priority": "high"}
|
|
98
|
+
request_id = await client.ask_async("analyst", {"query": "test"}, context=context)
|
|
99
|
+
|
|
100
|
+
requests = client.get_sent_requests()
|
|
101
|
+
assert requests[0]["context"] == context
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# =============================================================================
|
|
105
|
+
# TEST: CHECK_INBOX
|
|
106
|
+
# =============================================================================
|
|
107
|
+
|
|
108
|
+
class TestCheckInbox:
|
|
109
|
+
"""Test check_inbox() method."""
|
|
110
|
+
|
|
111
|
+
@pytest.mark.asyncio
|
|
112
|
+
async def test_check_inbox_returns_response(self):
|
|
113
|
+
"""Test check_inbox() returns response when auto_respond is True."""
|
|
114
|
+
from jarviscore.testing import MockPeerClient
|
|
115
|
+
|
|
116
|
+
client = MockPeerClient(
|
|
117
|
+
agent_id="requester-1",
|
|
118
|
+
agent_role="requester",
|
|
119
|
+
mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}],
|
|
120
|
+
auto_respond=True
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
request_id = await client.ask_async("analyst", {"query": "test"})
|
|
124
|
+
response = await client.check_inbox(request_id)
|
|
125
|
+
|
|
126
|
+
# MockPeerClient with auto_respond returns default response
|
|
127
|
+
assert response is not None
|
|
128
|
+
assert "mock" in response or "status" in response
|
|
129
|
+
|
|
130
|
+
@pytest.mark.asyncio
|
|
131
|
+
async def test_check_inbox_returns_none_when_no_auto_respond(self):
|
|
132
|
+
"""Test check_inbox() returns None when auto_respond is False."""
|
|
133
|
+
from jarviscore.testing import MockPeerClient
|
|
134
|
+
|
|
135
|
+
client = MockPeerClient(
|
|
136
|
+
agent_id="requester-1",
|
|
137
|
+
agent_role="requester",
|
|
138
|
+
mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}],
|
|
139
|
+
auto_respond=False
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
request_id = await client.ask_async("analyst", {"query": "test"})
|
|
143
|
+
response = await client.check_inbox(request_id, timeout=0)
|
|
144
|
+
|
|
145
|
+
assert response is None
|
|
146
|
+
|
|
147
|
+
@pytest.mark.asyncio
|
|
148
|
+
async def test_check_inbox_with_timeout(self):
|
|
149
|
+
"""Test check_inbox() with timeout parameter."""
|
|
150
|
+
from jarviscore.testing import MockPeerClient
|
|
151
|
+
|
|
152
|
+
client = MockPeerClient(
|
|
153
|
+
agent_id="requester-1",
|
|
154
|
+
agent_role="requester",
|
|
155
|
+
mock_peers=[{"role": "analyst", "capabilities": ["analysis"]}],
|
|
156
|
+
auto_respond=True
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
request_id = await client.ask_async("analyst", {"query": "test"})
|
|
160
|
+
|
|
161
|
+
# With timeout, should return response immediately (mock)
|
|
162
|
+
response = await client.check_inbox(request_id, timeout=5)
|
|
163
|
+
|
|
164
|
+
assert response is not None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# =============================================================================
|
|
168
|
+
# TEST: REAL PEER CLIENT ASYNC REQUESTS
|
|
169
|
+
# =============================================================================
|
|
170
|
+
|
|
171
|
+
class TestRealPeerClientAsyncRequests:
|
|
172
|
+
"""Test async requests with real PeerClient."""
|
|
173
|
+
|
|
174
|
+
@pytest.mark.asyncio
|
|
175
|
+
async def test_ask_async_sends_request(self):
|
|
176
|
+
"""Test ask_async() sends request to target."""
|
|
177
|
+
from jarviscore.p2p.peer_client import PeerClient
|
|
178
|
+
from jarviscore.p2p.messages import MessageType
|
|
179
|
+
|
|
180
|
+
mock_coordinator = MagicMock()
|
|
181
|
+
mock_coordinator._remote_agent_registry = {}
|
|
182
|
+
|
|
183
|
+
# Create target agent with PeerClient
|
|
184
|
+
class MockTargetAgent:
|
|
185
|
+
def __init__(self):
|
|
186
|
+
self.agent_id = "analyst-1"
|
|
187
|
+
self.role = "analyst"
|
|
188
|
+
self.capabilities = ["analysis"]
|
|
189
|
+
self.peers = None
|
|
190
|
+
|
|
191
|
+
target = MockTargetAgent()
|
|
192
|
+
target_client = PeerClient(
|
|
193
|
+
coordinator=mock_coordinator,
|
|
194
|
+
agent_id="analyst-1",
|
|
195
|
+
agent_role="analyst",
|
|
196
|
+
agent_registry={},
|
|
197
|
+
node_id="localhost:7946"
|
|
198
|
+
)
|
|
199
|
+
target.peers = target_client
|
|
200
|
+
|
|
201
|
+
# Create requester with target in registry
|
|
202
|
+
agent_registry = {"analyst": [target]}
|
|
203
|
+
requester_client = PeerClient(
|
|
204
|
+
coordinator=mock_coordinator,
|
|
205
|
+
agent_id="requester-1",
|
|
206
|
+
agent_role="requester",
|
|
207
|
+
agent_registry=agent_registry,
|
|
208
|
+
node_id="localhost:7946"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Send async request
|
|
212
|
+
request_id = await requester_client.ask_async("analyst", {"query": "analyze this"})
|
|
213
|
+
|
|
214
|
+
assert request_id is not None
|
|
215
|
+
assert request_id.startswith("async-")
|
|
216
|
+
|
|
217
|
+
# Verify message was delivered to target
|
|
218
|
+
msg = await target_client.receive(timeout=1)
|
|
219
|
+
assert msg is not None
|
|
220
|
+
assert msg.type == MessageType.REQUEST
|
|
221
|
+
assert msg.data == {"query": "analyze this"}
|
|
222
|
+
|
|
223
|
+
@pytest.mark.asyncio
|
|
224
|
+
async def test_ask_async_raises_on_invalid_target(self):
|
|
225
|
+
"""Test ask_async() raises ValueError for invalid target."""
|
|
226
|
+
from jarviscore.p2p.peer_client import PeerClient
|
|
227
|
+
|
|
228
|
+
mock_coordinator = MagicMock()
|
|
229
|
+
mock_coordinator._remote_agent_registry = {}
|
|
230
|
+
mock_coordinator.get_remote_agent = MagicMock(return_value=None)
|
|
231
|
+
|
|
232
|
+
client = PeerClient(
|
|
233
|
+
coordinator=mock_coordinator,
|
|
234
|
+
agent_id="requester-1",
|
|
235
|
+
agent_role="requester",
|
|
236
|
+
agent_registry={},
|
|
237
|
+
node_id="localhost:7946"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
with pytest.raises(ValueError, match="No peer found"):
|
|
241
|
+
await client.ask_async("nonexistent", {"query": "test"})
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# =============================================================================
|
|
245
|
+
# TEST: GET_PENDING_ASYNC_REQUESTS
|
|
246
|
+
# =============================================================================
|
|
247
|
+
|
|
248
|
+
class TestGetPendingAsyncRequests:
|
|
249
|
+
"""Test get_pending_async_requests() method."""
|
|
250
|
+
|
|
251
|
+
@pytest.mark.asyncio
|
|
252
|
+
async def test_pending_requests_initially_empty(self):
|
|
253
|
+
"""Test pending requests list is empty initially."""
|
|
254
|
+
from jarviscore.testing import MockPeerClient
|
|
255
|
+
|
|
256
|
+
client = MockPeerClient()
|
|
257
|
+
pending = client.get_pending_async_requests()
|
|
258
|
+
|
|
259
|
+
assert pending == []
|
|
260
|
+
|
|
261
|
+
@pytest.mark.asyncio
|
|
262
|
+
async def test_real_client_tracks_pending_requests(self):
|
|
263
|
+
"""Test real PeerClient tracks pending async requests."""
|
|
264
|
+
from jarviscore.p2p.peer_client import PeerClient
|
|
265
|
+
|
|
266
|
+
mock_coordinator = MagicMock()
|
|
267
|
+
mock_coordinator._remote_agent_registry = {}
|
|
268
|
+
|
|
269
|
+
# Create target agent
|
|
270
|
+
class MockTargetAgent:
|
|
271
|
+
def __init__(self):
|
|
272
|
+
self.agent_id = "target-1"
|
|
273
|
+
self.role = "target"
|
|
274
|
+
self.capabilities = []
|
|
275
|
+
self.peers = None
|
|
276
|
+
|
|
277
|
+
target = MockTargetAgent()
|
|
278
|
+
target_client = PeerClient(
|
|
279
|
+
coordinator=mock_coordinator,
|
|
280
|
+
agent_id="target-1",
|
|
281
|
+
agent_role="target",
|
|
282
|
+
agent_registry={},
|
|
283
|
+
node_id="localhost:7946"
|
|
284
|
+
)
|
|
285
|
+
target.peers = target_client
|
|
286
|
+
|
|
287
|
+
agent_registry = {"target": [target]}
|
|
288
|
+
client = PeerClient(
|
|
289
|
+
coordinator=mock_coordinator,
|
|
290
|
+
agent_id="requester-1",
|
|
291
|
+
agent_role="requester",
|
|
292
|
+
agent_registry=agent_registry,
|
|
293
|
+
node_id="localhost:7946"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Send async request
|
|
297
|
+
request_id = await client.ask_async("target", {"query": "test"})
|
|
298
|
+
|
|
299
|
+
pending = client.get_pending_async_requests()
|
|
300
|
+
|
|
301
|
+
assert len(pending) == 1
|
|
302
|
+
assert pending[0]["request_id"] == request_id
|
|
303
|
+
assert pending[0]["target"] == "target"
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# =============================================================================
|
|
307
|
+
# TEST: CLEAR_INBOX
|
|
308
|
+
# =============================================================================
|
|
309
|
+
|
|
310
|
+
class TestClearInbox:
|
|
311
|
+
"""Test clear_inbox() method."""
|
|
312
|
+
|
|
313
|
+
@pytest.mark.asyncio
|
|
314
|
+
async def test_clear_inbox_all(self):
|
|
315
|
+
"""Test clear_inbox() with no argument clears all."""
|
|
316
|
+
from jarviscore.testing import MockPeerClient
|
|
317
|
+
|
|
318
|
+
client = MockPeerClient(
|
|
319
|
+
mock_peers=[{"role": "target", "capabilities": []}]
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Create some async requests
|
|
323
|
+
await client.ask_async("target", {"q": 1})
|
|
324
|
+
await client.ask_async("target", {"q": 2})
|
|
325
|
+
|
|
326
|
+
# Clear all
|
|
327
|
+
client.clear_inbox()
|
|
328
|
+
|
|
329
|
+
# Verify cleared (no error)
|
|
330
|
+
# MockPeerClient.clear_inbox is a no-op but should not raise
|
|
331
|
+
|
|
332
|
+
@pytest.mark.asyncio
|
|
333
|
+
async def test_clear_inbox_specific(self):
|
|
334
|
+
"""Test clear_inbox() with specific request_id."""
|
|
335
|
+
from jarviscore.testing import MockPeerClient
|
|
336
|
+
|
|
337
|
+
client = MockPeerClient(
|
|
338
|
+
mock_peers=[{"role": "target", "capabilities": []}]
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
req_id = await client.ask_async("target", {"q": 1})
|
|
342
|
+
|
|
343
|
+
# Clear specific request
|
|
344
|
+
client.clear_inbox(req_id)
|
|
345
|
+
|
|
346
|
+
# Should not raise
|
|
347
|
+
|
|
348
|
+
@pytest.mark.asyncio
|
|
349
|
+
async def test_real_client_clear_inbox(self):
|
|
350
|
+
"""Test real PeerClient clear_inbox removes entries."""
|
|
351
|
+
from jarviscore.p2p.peer_client import PeerClient
|
|
352
|
+
|
|
353
|
+
mock_coordinator = MagicMock()
|
|
354
|
+
mock_coordinator._remote_agent_registry = {}
|
|
355
|
+
|
|
356
|
+
# Create target
|
|
357
|
+
class MockTargetAgent:
|
|
358
|
+
def __init__(self):
|
|
359
|
+
self.agent_id = "target-1"
|
|
360
|
+
self.role = "target"
|
|
361
|
+
self.capabilities = []
|
|
362
|
+
self.peers = None
|
|
363
|
+
|
|
364
|
+
target = MockTargetAgent()
|
|
365
|
+
target_client = PeerClient(
|
|
366
|
+
coordinator=mock_coordinator,
|
|
367
|
+
agent_id="target-1",
|
|
368
|
+
agent_role="target",
|
|
369
|
+
agent_registry={},
|
|
370
|
+
node_id="localhost:7946"
|
|
371
|
+
)
|
|
372
|
+
target.peers = target_client
|
|
373
|
+
|
|
374
|
+
agent_registry = {"target": [target]}
|
|
375
|
+
client = PeerClient(
|
|
376
|
+
coordinator=mock_coordinator,
|
|
377
|
+
agent_id="requester-1",
|
|
378
|
+
agent_role="requester",
|
|
379
|
+
agent_registry=agent_registry,
|
|
380
|
+
node_id="localhost:7946"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Send async requests
|
|
384
|
+
req1 = await client.ask_async("target", {"q": 1})
|
|
385
|
+
req2 = await client.ask_async("target", {"q": 2})
|
|
386
|
+
|
|
387
|
+
assert len(client.get_pending_async_requests()) == 2
|
|
388
|
+
|
|
389
|
+
# Clear specific
|
|
390
|
+
client.clear_inbox(req1)
|
|
391
|
+
assert len(client.get_pending_async_requests()) == 1
|
|
392
|
+
|
|
393
|
+
# Clear all
|
|
394
|
+
client.clear_inbox()
|
|
395
|
+
assert len(client.get_pending_async_requests()) == 0
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# =============================================================================
|
|
399
|
+
# TEST: ASYNC REQUEST WITH RESPONSE
|
|
400
|
+
# =============================================================================
|
|
401
|
+
|
|
402
|
+
class TestAsyncRequestResponse:
|
|
403
|
+
"""Test complete async request-response flow."""
|
|
404
|
+
|
|
405
|
+
@pytest.mark.asyncio
|
|
406
|
+
async def test_async_request_receives_response(self):
|
|
407
|
+
"""Test async request receives response via check_inbox."""
|
|
408
|
+
from jarviscore.p2p.peer_client import PeerClient
|
|
409
|
+
from jarviscore.p2p.messages import MessageType
|
|
410
|
+
|
|
411
|
+
mock_coordinator = MagicMock()
|
|
412
|
+
mock_coordinator._remote_agent_registry = {}
|
|
413
|
+
|
|
414
|
+
# Create responder
|
|
415
|
+
class MockResponder:
|
|
416
|
+
def __init__(self):
|
|
417
|
+
self.agent_id = "responder-1"
|
|
418
|
+
self.role = "responder"
|
|
419
|
+
self.capabilities = []
|
|
420
|
+
self.peers = None
|
|
421
|
+
|
|
422
|
+
responder = MockResponder()
|
|
423
|
+
responder_client = PeerClient(
|
|
424
|
+
coordinator=mock_coordinator,
|
|
425
|
+
agent_id="responder-1",
|
|
426
|
+
agent_role="responder",
|
|
427
|
+
agent_registry={},
|
|
428
|
+
node_id="localhost:7946"
|
|
429
|
+
)
|
|
430
|
+
responder.peers = responder_client
|
|
431
|
+
|
|
432
|
+
# Create requester
|
|
433
|
+
class MockRequester:
|
|
434
|
+
def __init__(self):
|
|
435
|
+
self.agent_id = "requester-1"
|
|
436
|
+
self.role = "requester"
|
|
437
|
+
self.capabilities = []
|
|
438
|
+
self.peers = None
|
|
439
|
+
|
|
440
|
+
requester = MockRequester()
|
|
441
|
+
requester_client = PeerClient(
|
|
442
|
+
coordinator=mock_coordinator,
|
|
443
|
+
agent_id="requester-1",
|
|
444
|
+
agent_role="requester",
|
|
445
|
+
agent_registry={"responder": [responder], "requester": [requester]},
|
|
446
|
+
node_id="localhost:7946"
|
|
447
|
+
)
|
|
448
|
+
requester.peers = requester_client
|
|
449
|
+
|
|
450
|
+
# Also add requester to responder's registry for response routing
|
|
451
|
+
responder_client._agent_registry = {"requester": [requester], "responder": [responder]}
|
|
452
|
+
|
|
453
|
+
# Send async request
|
|
454
|
+
request_id = await requester_client.ask_async("responder", {"query": "what is 2+2?"})
|
|
455
|
+
|
|
456
|
+
# Responder receives and responds
|
|
457
|
+
msg = await responder_client.receive(timeout=1)
|
|
458
|
+
assert msg is not None
|
|
459
|
+
assert msg.is_request
|
|
460
|
+
|
|
461
|
+
await responder_client.respond(msg, {"answer": "4"})
|
|
462
|
+
|
|
463
|
+
# Give time for async handler to process response
|
|
464
|
+
await asyncio.sleep(0.2)
|
|
465
|
+
|
|
466
|
+
# Requester checks inbox
|
|
467
|
+
response = await requester_client.check_inbox(request_id, timeout=1)
|
|
468
|
+
|
|
469
|
+
assert response is not None
|
|
470
|
+
assert response == {"answer": "4"}
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# =============================================================================
|
|
474
|
+
# TEST: MULTIPLE ASYNC REQUESTS
|
|
475
|
+
# =============================================================================
|
|
476
|
+
|
|
477
|
+
class TestMultipleAsyncRequests:
|
|
478
|
+
"""Test handling multiple concurrent async requests."""
|
|
479
|
+
|
|
480
|
+
@pytest.mark.asyncio
|
|
481
|
+
async def test_multiple_async_requests_tracked(self):
|
|
482
|
+
"""Test multiple async requests are tracked independently."""
|
|
483
|
+
from jarviscore.testing import MockPeerClient
|
|
484
|
+
|
|
485
|
+
client = MockPeerClient(
|
|
486
|
+
agent_id="requester-1",
|
|
487
|
+
agent_role="requester",
|
|
488
|
+
mock_peers=[
|
|
489
|
+
{"role": "analyst1", "capabilities": ["analysis"]},
|
|
490
|
+
{"role": "analyst2", "capabilities": ["analysis"]},
|
|
491
|
+
{"role": "analyst3", "capabilities": ["analysis"]}
|
|
492
|
+
]
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# Send multiple async requests
|
|
496
|
+
req1 = await client.ask_async("analyst1", {"query": "q1"})
|
|
497
|
+
req2 = await client.ask_async("analyst2", {"query": "q2"})
|
|
498
|
+
req3 = await client.ask_async("analyst3", {"query": "q3"})
|
|
499
|
+
|
|
500
|
+
# All should have unique IDs
|
|
501
|
+
assert req1 != req2 != req3
|
|
502
|
+
|
|
503
|
+
requests = client.get_sent_requests()
|
|
504
|
+
assert len(requests) == 3
|
|
505
|
+
|
|
506
|
+
# Targets should be correct
|
|
507
|
+
targets = {r["target"] for r in requests}
|
|
508
|
+
assert targets == {"analyst1", "analyst2", "analyst3"}
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
# =============================================================================
|
|
512
|
+
# RUN TESTS
|
|
513
|
+
# =============================================================================
|
|
514
|
+
|
|
515
|
+
if __name__ == "__main__":
|
|
516
|
+
pytest.main([__file__, "-v", "-s"])
|