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,776 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test 21: MockMesh and MockPeerClient Testing Utilities (Feature F8)
|
|
3
|
+
|
|
4
|
+
Tests the testing utilities:
|
|
5
|
+
- MockPeerClient discovery
|
|
6
|
+
- MockPeerClient.set_mock_response()
|
|
7
|
+
- MockPeerClient assertion helpers
|
|
8
|
+
- MockPeerClient.inject_message()
|
|
9
|
+
- MockMesh.add() and start()
|
|
10
|
+
- MockMesh auto-injects peers
|
|
11
|
+
|
|
12
|
+
Run with: pytest tests/test_21_mock_testing.py -v -s
|
|
13
|
+
"""
|
|
14
|
+
import asyncio
|
|
15
|
+
import sys
|
|
16
|
+
import pytest
|
|
17
|
+
import logging
|
|
18
|
+
from unittest.mock import MagicMock
|
|
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: MOCK PEER CLIENT DISCOVERY
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
class TestMockPeerClientDiscovery:
|
|
32
|
+
"""Test MockPeerClient discovery functionality."""
|
|
33
|
+
|
|
34
|
+
def test_get_peer_returns_configured_peer(self):
|
|
35
|
+
"""Test get_peer() returns a configured mock peer."""
|
|
36
|
+
from jarviscore.testing import MockPeerClient
|
|
37
|
+
from jarviscore.p2p.messages import PeerInfo
|
|
38
|
+
|
|
39
|
+
client = MockPeerClient(
|
|
40
|
+
mock_peers=[
|
|
41
|
+
{"role": "analyst", "capabilities": ["analysis", "reporting"]}
|
|
42
|
+
]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
peer = client.get_peer("analyst")
|
|
46
|
+
|
|
47
|
+
assert peer is not None
|
|
48
|
+
assert isinstance(peer, PeerInfo)
|
|
49
|
+
assert peer.role == "analyst"
|
|
50
|
+
assert "analysis" in peer.capabilities
|
|
51
|
+
|
|
52
|
+
def test_get_peer_returns_none_for_unknown_role(self):
|
|
53
|
+
"""Test get_peer() returns None for unconfigured role."""
|
|
54
|
+
from jarviscore.testing import MockPeerClient
|
|
55
|
+
|
|
56
|
+
client = MockPeerClient(mock_peers=[])
|
|
57
|
+
|
|
58
|
+
peer = client.get_peer("nonexistent")
|
|
59
|
+
|
|
60
|
+
assert peer is None
|
|
61
|
+
|
|
62
|
+
def test_discover_filters_by_role(self):
|
|
63
|
+
"""Test discover() filters by role."""
|
|
64
|
+
from jarviscore.testing import MockPeerClient
|
|
65
|
+
|
|
66
|
+
client = MockPeerClient(
|
|
67
|
+
mock_peers=[
|
|
68
|
+
{"role": "analyst", "capabilities": ["analysis"]},
|
|
69
|
+
{"role": "scout", "capabilities": ["research"]},
|
|
70
|
+
{"role": "analyst", "agent_id": "analyst-2", "capabilities": ["analysis"]}
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
analysts = client.discover(role="analyst")
|
|
75
|
+
|
|
76
|
+
assert len(analysts) == 2
|
|
77
|
+
for peer in analysts:
|
|
78
|
+
assert peer.role == "analyst"
|
|
79
|
+
|
|
80
|
+
def test_discover_filters_by_capability(self):
|
|
81
|
+
"""Test discover() filters by capability."""
|
|
82
|
+
from jarviscore.testing import MockPeerClient
|
|
83
|
+
|
|
84
|
+
client = MockPeerClient(
|
|
85
|
+
mock_peers=[
|
|
86
|
+
{"role": "agent1", "capabilities": ["cap_a", "cap_shared"]},
|
|
87
|
+
{"role": "agent2", "capabilities": ["cap_b"]},
|
|
88
|
+
{"role": "agent3", "capabilities": ["cap_c", "cap_shared"]}
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
shared_cap_peers = client.discover(capability="cap_shared")
|
|
93
|
+
|
|
94
|
+
assert len(shared_cap_peers) == 2
|
|
95
|
+
for peer in shared_cap_peers:
|
|
96
|
+
assert "cap_shared" in peer.capabilities
|
|
97
|
+
|
|
98
|
+
def test_list_roles(self):
|
|
99
|
+
"""Test list_roles() returns unique roles."""
|
|
100
|
+
from jarviscore.testing import MockPeerClient
|
|
101
|
+
|
|
102
|
+
client = MockPeerClient(
|
|
103
|
+
mock_peers=[
|
|
104
|
+
{"role": "analyst", "capabilities": []},
|
|
105
|
+
{"role": "scout", "capabilities": []},
|
|
106
|
+
{"role": "analyst", "agent_id": "analyst-2", "capabilities": []}
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
roles = client.list_roles()
|
|
111
|
+
|
|
112
|
+
assert set(roles) == {"analyst", "scout"}
|
|
113
|
+
|
|
114
|
+
def test_list_peers(self):
|
|
115
|
+
"""Test list_peers() returns all peers with details."""
|
|
116
|
+
from jarviscore.testing import MockPeerClient
|
|
117
|
+
|
|
118
|
+
client = MockPeerClient(
|
|
119
|
+
mock_peers=[
|
|
120
|
+
{"role": "analyst", "capabilities": ["analysis"], "description": "Data analyst"},
|
|
121
|
+
{"role": "scout", "capabilities": ["research"]}
|
|
122
|
+
]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
peers = client.list_peers()
|
|
126
|
+
|
|
127
|
+
assert len(peers) == 2
|
|
128
|
+
assert all("role" in p for p in peers)
|
|
129
|
+
assert all("agent_id" in p for p in peers)
|
|
130
|
+
assert all("capabilities" in p for p in peers)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# =============================================================================
|
|
134
|
+
# TEST: MOCK PEER CLIENT SET_MOCK_RESPONSE
|
|
135
|
+
# =============================================================================
|
|
136
|
+
|
|
137
|
+
class TestMockPeerClientSetMockResponse:
|
|
138
|
+
"""Test MockPeerClient.set_mock_response() method."""
|
|
139
|
+
|
|
140
|
+
@pytest.mark.asyncio
|
|
141
|
+
async def test_set_mock_response_returns_configured_response(self):
|
|
142
|
+
"""Test set_mock_response() configures response for specific target."""
|
|
143
|
+
from jarviscore.testing import MockPeerClient
|
|
144
|
+
|
|
145
|
+
client = MockPeerClient(
|
|
146
|
+
mock_peers=[{"role": "analyst", "capabilities": []}]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
expected_response = {"result": "analysis complete", "score": 95}
|
|
150
|
+
client.set_mock_response("analyst", expected_response)
|
|
151
|
+
|
|
152
|
+
response = await client.request("analyst", {"query": "analyze"})
|
|
153
|
+
|
|
154
|
+
assert response == expected_response
|
|
155
|
+
|
|
156
|
+
@pytest.mark.asyncio
|
|
157
|
+
async def test_set_mock_response_per_target(self):
|
|
158
|
+
"""Test different responses for different targets."""
|
|
159
|
+
from jarviscore.testing import MockPeerClient
|
|
160
|
+
|
|
161
|
+
client = MockPeerClient(
|
|
162
|
+
mock_peers=[
|
|
163
|
+
{"role": "analyst", "capabilities": []},
|
|
164
|
+
{"role": "scout", "capabilities": []}
|
|
165
|
+
]
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
client.set_mock_response("analyst", {"type": "analysis", "data": [1, 2, 3]})
|
|
169
|
+
client.set_mock_response("scout", {"type": "reconnaissance", "findings": "clear"})
|
|
170
|
+
|
|
171
|
+
analyst_response = await client.request("analyst", {})
|
|
172
|
+
scout_response = await client.request("scout", {})
|
|
173
|
+
|
|
174
|
+
assert analyst_response["type"] == "analysis"
|
|
175
|
+
assert scout_response["type"] == "reconnaissance"
|
|
176
|
+
|
|
177
|
+
@pytest.mark.asyncio
|
|
178
|
+
async def test_set_default_response(self):
|
|
179
|
+
"""Test set_default_response() for unconfigured targets."""
|
|
180
|
+
from jarviscore.testing import MockPeerClient
|
|
181
|
+
|
|
182
|
+
client = MockPeerClient(
|
|
183
|
+
mock_peers=[{"role": "agent", "capabilities": []}],
|
|
184
|
+
auto_respond=True
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
default = {"default": True, "message": "fallback response"}
|
|
188
|
+
client.set_default_response(default)
|
|
189
|
+
|
|
190
|
+
response = await client.request("agent", {})
|
|
191
|
+
|
|
192
|
+
assert response == default
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# =============================================================================
|
|
196
|
+
# TEST: MOCK PEER CLIENT ASSERTION HELPERS
|
|
197
|
+
# =============================================================================
|
|
198
|
+
|
|
199
|
+
class TestMockPeerClientAssertionHelpers:
|
|
200
|
+
"""Test MockPeerClient assertion helper methods."""
|
|
201
|
+
|
|
202
|
+
@pytest.mark.asyncio
|
|
203
|
+
async def test_assert_notified_passes(self):
|
|
204
|
+
"""Test assert_notified() passes when notification was sent."""
|
|
205
|
+
from jarviscore.testing import MockPeerClient
|
|
206
|
+
|
|
207
|
+
client = MockPeerClient(
|
|
208
|
+
mock_peers=[{"role": "receiver", "capabilities": []}]
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
await client.notify("receiver", {"event": "test_event"})
|
|
212
|
+
|
|
213
|
+
# Should not raise
|
|
214
|
+
client.assert_notified("receiver")
|
|
215
|
+
|
|
216
|
+
@pytest.mark.asyncio
|
|
217
|
+
async def test_assert_notified_fails_when_not_sent(self):
|
|
218
|
+
"""Test assert_notified() fails when notification wasn't sent."""
|
|
219
|
+
from jarviscore.testing import MockPeerClient
|
|
220
|
+
|
|
221
|
+
client = MockPeerClient()
|
|
222
|
+
|
|
223
|
+
with pytest.raises(AssertionError, match="not found"):
|
|
224
|
+
client.assert_notified("never_notified")
|
|
225
|
+
|
|
226
|
+
@pytest.mark.asyncio
|
|
227
|
+
async def test_assert_notified_with_message_contains(self):
|
|
228
|
+
"""Test assert_notified() with message_contains filter."""
|
|
229
|
+
from jarviscore.testing import MockPeerClient
|
|
230
|
+
|
|
231
|
+
client = MockPeerClient(
|
|
232
|
+
mock_peers=[{"role": "receiver", "capabilities": []}]
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
await client.notify("receiver", {"event": "specific_event", "data": 123})
|
|
236
|
+
|
|
237
|
+
# Should pass - message contains this key-value
|
|
238
|
+
client.assert_notified("receiver", message_contains={"event": "specific_event"})
|
|
239
|
+
|
|
240
|
+
@pytest.mark.asyncio
|
|
241
|
+
async def test_assert_requested_passes(self):
|
|
242
|
+
"""Test assert_requested() passes when request was sent."""
|
|
243
|
+
from jarviscore.testing import MockPeerClient
|
|
244
|
+
|
|
245
|
+
client = MockPeerClient(
|
|
246
|
+
mock_peers=[{"role": "analyst", "capabilities": []}]
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
await client.request("analyst", {"query": "test"})
|
|
250
|
+
|
|
251
|
+
client.assert_requested("analyst")
|
|
252
|
+
|
|
253
|
+
@pytest.mark.asyncio
|
|
254
|
+
async def test_assert_requested_fails_when_not_sent(self):
|
|
255
|
+
"""Test assert_requested() fails when request wasn't sent."""
|
|
256
|
+
from jarviscore.testing import MockPeerClient
|
|
257
|
+
|
|
258
|
+
client = MockPeerClient()
|
|
259
|
+
|
|
260
|
+
with pytest.raises(AssertionError, match="not found"):
|
|
261
|
+
client.assert_requested("never_requested")
|
|
262
|
+
|
|
263
|
+
@pytest.mark.asyncio
|
|
264
|
+
async def test_assert_broadcasted_passes(self):
|
|
265
|
+
"""Test assert_broadcasted() passes when broadcast was sent."""
|
|
266
|
+
from jarviscore.testing import MockPeerClient
|
|
267
|
+
|
|
268
|
+
client = MockPeerClient(
|
|
269
|
+
mock_peers=[
|
|
270
|
+
{"role": "peer1", "capabilities": []},
|
|
271
|
+
{"role": "peer2", "capabilities": []}
|
|
272
|
+
]
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
await client.broadcast({"alert": "test"})
|
|
276
|
+
|
|
277
|
+
client.assert_broadcasted()
|
|
278
|
+
|
|
279
|
+
@pytest.mark.asyncio
|
|
280
|
+
async def test_assert_broadcasted_fails_when_not_sent(self):
|
|
281
|
+
"""Test assert_broadcasted() fails when no broadcast was sent."""
|
|
282
|
+
from jarviscore.testing import MockPeerClient
|
|
283
|
+
|
|
284
|
+
client = MockPeerClient()
|
|
285
|
+
|
|
286
|
+
with pytest.raises(AssertionError, match="No broadcasts"):
|
|
287
|
+
client.assert_broadcasted()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# =============================================================================
|
|
291
|
+
# TEST: MOCK PEER CLIENT INJECT_MESSAGE
|
|
292
|
+
# =============================================================================
|
|
293
|
+
|
|
294
|
+
class TestMockPeerClientInjectMessage:
|
|
295
|
+
"""Test MockPeerClient.inject_message() method."""
|
|
296
|
+
|
|
297
|
+
@pytest.mark.asyncio
|
|
298
|
+
async def test_inject_message_notify(self):
|
|
299
|
+
"""Test inject_message() with NOTIFY type."""
|
|
300
|
+
from jarviscore.testing import MockPeerClient
|
|
301
|
+
from jarviscore.p2p.messages import MessageType
|
|
302
|
+
|
|
303
|
+
client = MockPeerClient()
|
|
304
|
+
|
|
305
|
+
client.inject_message(
|
|
306
|
+
sender="external_agent",
|
|
307
|
+
message_type=MessageType.NOTIFY,
|
|
308
|
+
data={"event": "external_event", "value": 42}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
msg = await client.receive(timeout=1)
|
|
312
|
+
|
|
313
|
+
assert msg is not None
|
|
314
|
+
assert msg.sender == "external_agent"
|
|
315
|
+
assert msg.type == MessageType.NOTIFY
|
|
316
|
+
assert msg.data == {"event": "external_event", "value": 42}
|
|
317
|
+
|
|
318
|
+
@pytest.mark.asyncio
|
|
319
|
+
async def test_inject_message_request(self):
|
|
320
|
+
"""Test inject_message() with REQUEST type."""
|
|
321
|
+
from jarviscore.testing import MockPeerClient
|
|
322
|
+
from jarviscore.p2p.messages import MessageType
|
|
323
|
+
|
|
324
|
+
client = MockPeerClient()
|
|
325
|
+
|
|
326
|
+
client.inject_message(
|
|
327
|
+
sender="requester",
|
|
328
|
+
message_type=MessageType.REQUEST,
|
|
329
|
+
data={"query": "please respond"},
|
|
330
|
+
correlation_id="corr-123"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
msg = await client.receive(timeout=1)
|
|
334
|
+
|
|
335
|
+
assert msg is not None
|
|
336
|
+
assert msg.type == MessageType.REQUEST
|
|
337
|
+
assert msg.correlation_id == "corr-123"
|
|
338
|
+
assert msg.is_request is True
|
|
339
|
+
|
|
340
|
+
@pytest.mark.asyncio
|
|
341
|
+
async def test_inject_message_with_context(self):
|
|
342
|
+
"""Test inject_message() with context."""
|
|
343
|
+
from jarviscore.testing import MockPeerClient
|
|
344
|
+
from jarviscore.p2p.messages import MessageType
|
|
345
|
+
|
|
346
|
+
client = MockPeerClient()
|
|
347
|
+
|
|
348
|
+
context = {"mission_id": "m-inject", "trace_id": "t-inject"}
|
|
349
|
+
client.inject_message(
|
|
350
|
+
sender="sender",
|
|
351
|
+
message_type=MessageType.NOTIFY,
|
|
352
|
+
data={"test": True},
|
|
353
|
+
context=context
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
msg = await client.receive(timeout=1)
|
|
357
|
+
|
|
358
|
+
assert msg.context == context
|
|
359
|
+
|
|
360
|
+
@pytest.mark.asyncio
|
|
361
|
+
async def test_inject_multiple_messages(self):
|
|
362
|
+
"""Test injecting multiple messages for receive loop testing."""
|
|
363
|
+
from jarviscore.testing import MockPeerClient
|
|
364
|
+
from jarviscore.p2p.messages import MessageType
|
|
365
|
+
|
|
366
|
+
client = MockPeerClient()
|
|
367
|
+
|
|
368
|
+
# Inject multiple messages
|
|
369
|
+
for i in range(3):
|
|
370
|
+
client.inject_message(
|
|
371
|
+
sender=f"sender-{i}",
|
|
372
|
+
message_type=MessageType.NOTIFY,
|
|
373
|
+
data={"index": i}
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Receive all
|
|
377
|
+
messages = []
|
|
378
|
+
for _ in range(3):
|
|
379
|
+
msg = await client.receive(timeout=1)
|
|
380
|
+
if msg:
|
|
381
|
+
messages.append(msg)
|
|
382
|
+
|
|
383
|
+
assert len(messages) == 3
|
|
384
|
+
indices = [m.data["index"] for m in messages]
|
|
385
|
+
assert set(indices) == {0, 1, 2}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# =============================================================================
|
|
389
|
+
# TEST: MOCK PEER CLIENT RESET
|
|
390
|
+
# =============================================================================
|
|
391
|
+
|
|
392
|
+
class TestMockPeerClientReset:
|
|
393
|
+
"""Test MockPeerClient.reset() method."""
|
|
394
|
+
|
|
395
|
+
@pytest.mark.asyncio
|
|
396
|
+
async def test_reset_clears_tracking(self):
|
|
397
|
+
"""Test reset() clears all tracking state."""
|
|
398
|
+
from jarviscore.testing import MockPeerClient
|
|
399
|
+
from jarviscore.p2p.messages import MessageType
|
|
400
|
+
|
|
401
|
+
client = MockPeerClient(
|
|
402
|
+
mock_peers=[{"role": "target", "capabilities": []}]
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Generate some state
|
|
406
|
+
await client.notify("target", {"n": 1})
|
|
407
|
+
await client.request("target", {"r": 1})
|
|
408
|
+
await client.broadcast({"b": 1})
|
|
409
|
+
client.set_mock_response("target", {"custom": True})
|
|
410
|
+
client.inject_message("sender", MessageType.NOTIFY, {"injected": True})
|
|
411
|
+
|
|
412
|
+
# Reset
|
|
413
|
+
client.reset()
|
|
414
|
+
|
|
415
|
+
# Verify cleared
|
|
416
|
+
assert len(client.get_sent_notifications()) == 0
|
|
417
|
+
assert len(client.get_sent_requests()) == 0
|
|
418
|
+
assert len(client.get_sent_broadcasts()) == 0
|
|
419
|
+
assert client._mock_responses == {}
|
|
420
|
+
assert not client.has_pending_messages()
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
# =============================================================================
|
|
424
|
+
# TEST: MOCK PEER CLIENT ADD_MOCK_PEER
|
|
425
|
+
# =============================================================================
|
|
426
|
+
|
|
427
|
+
class TestMockPeerClientAddMockPeer:
|
|
428
|
+
"""Test MockPeerClient.add_mock_peer() method."""
|
|
429
|
+
|
|
430
|
+
def test_add_mock_peer_dynamically(self):
|
|
431
|
+
"""Test add_mock_peer() adds peers after construction."""
|
|
432
|
+
from jarviscore.testing import MockPeerClient
|
|
433
|
+
|
|
434
|
+
client = MockPeerClient()
|
|
435
|
+
|
|
436
|
+
assert len(client.list_peers()) == 0
|
|
437
|
+
|
|
438
|
+
client.add_mock_peer("analyst", capabilities=["analysis", "charting"])
|
|
439
|
+
|
|
440
|
+
peers = client.list_peers()
|
|
441
|
+
assert len(peers) == 1
|
|
442
|
+
assert peers[0]["role"] == "analyst"
|
|
443
|
+
assert "analysis" in peers[0]["capabilities"]
|
|
444
|
+
|
|
445
|
+
def test_add_multiple_mock_peers(self):
|
|
446
|
+
"""Test adding multiple peers dynamically."""
|
|
447
|
+
from jarviscore.testing import MockPeerClient
|
|
448
|
+
|
|
449
|
+
client = MockPeerClient()
|
|
450
|
+
|
|
451
|
+
client.add_mock_peer("analyst", capabilities=["analysis"])
|
|
452
|
+
client.add_mock_peer("scout", capabilities=["research"])
|
|
453
|
+
client.add_mock_peer("reporter", capabilities=["reporting"])
|
|
454
|
+
|
|
455
|
+
roles = client.list_roles()
|
|
456
|
+
assert set(roles) == {"analyst", "scout", "reporter"}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
# =============================================================================
|
|
460
|
+
# TEST: MOCK MESH ADD AND START
|
|
461
|
+
# =============================================================================
|
|
462
|
+
|
|
463
|
+
class TestMockMeshAddAndStart:
|
|
464
|
+
"""Test MockMesh.add() and start() methods."""
|
|
465
|
+
|
|
466
|
+
@pytest.mark.asyncio
|
|
467
|
+
async def test_mock_mesh_add_agent_class(self):
|
|
468
|
+
"""Test MockMesh.add() with agent class."""
|
|
469
|
+
from jarviscore.testing import MockMesh
|
|
470
|
+
from jarviscore.profiles import CustomAgent
|
|
471
|
+
|
|
472
|
+
class TestAgent(CustomAgent):
|
|
473
|
+
role = "test_role"
|
|
474
|
+
capabilities = ["test_cap"]
|
|
475
|
+
|
|
476
|
+
async def on_peer_request(self, msg):
|
|
477
|
+
return {}
|
|
478
|
+
|
|
479
|
+
mesh = MockMesh()
|
|
480
|
+
agent = mesh.add(TestAgent)
|
|
481
|
+
|
|
482
|
+
assert agent is not None
|
|
483
|
+
assert agent.role == "test_role"
|
|
484
|
+
assert len(mesh.agents) == 1
|
|
485
|
+
|
|
486
|
+
@pytest.mark.asyncio
|
|
487
|
+
async def test_mock_mesh_add_agent_instance(self):
|
|
488
|
+
"""Test MockMesh.add() with pre-instantiated agent."""
|
|
489
|
+
from jarviscore.testing import MockMesh
|
|
490
|
+
from jarviscore.profiles import CustomAgent
|
|
491
|
+
|
|
492
|
+
class TestAgent(CustomAgent):
|
|
493
|
+
role = "instance_role"
|
|
494
|
+
capabilities = ["testing"]
|
|
495
|
+
|
|
496
|
+
async def on_peer_request(self, msg):
|
|
497
|
+
return {}
|
|
498
|
+
|
|
499
|
+
mesh = MockMesh()
|
|
500
|
+
instance = TestAgent()
|
|
501
|
+
result = mesh.add(instance)
|
|
502
|
+
|
|
503
|
+
assert result is instance
|
|
504
|
+
assert len(mesh.agents) == 1
|
|
505
|
+
|
|
506
|
+
@pytest.mark.asyncio
|
|
507
|
+
async def test_mock_mesh_start_runs_setup(self):
|
|
508
|
+
"""Test MockMesh.start() runs agent setup."""
|
|
509
|
+
from jarviscore.testing import MockMesh
|
|
510
|
+
from jarviscore.profiles import CustomAgent
|
|
511
|
+
|
|
512
|
+
setup_called = False
|
|
513
|
+
|
|
514
|
+
class TestAgent(CustomAgent):
|
|
515
|
+
role = "setup_test"
|
|
516
|
+
capabilities = ["testing"]
|
|
517
|
+
|
|
518
|
+
async def setup(self):
|
|
519
|
+
nonlocal setup_called
|
|
520
|
+
setup_called = True
|
|
521
|
+
await super().setup()
|
|
522
|
+
|
|
523
|
+
async def on_peer_request(self, msg):
|
|
524
|
+
return {}
|
|
525
|
+
|
|
526
|
+
mesh = MockMesh()
|
|
527
|
+
mesh.add(TestAgent)
|
|
528
|
+
await mesh.start()
|
|
529
|
+
|
|
530
|
+
assert setup_called is True
|
|
531
|
+
await mesh.stop()
|
|
532
|
+
|
|
533
|
+
@pytest.mark.asyncio
|
|
534
|
+
async def test_mock_mesh_get_agent(self):
|
|
535
|
+
"""Test MockMesh.get_agent() returns agent by role."""
|
|
536
|
+
from jarviscore.testing import MockMesh
|
|
537
|
+
from jarviscore.profiles import CustomAgent
|
|
538
|
+
|
|
539
|
+
class AgentA(CustomAgent):
|
|
540
|
+
role = "agent_a"
|
|
541
|
+
capabilities = ["cap_a"]
|
|
542
|
+
async def on_peer_request(self, msg):
|
|
543
|
+
return {}
|
|
544
|
+
|
|
545
|
+
class AgentB(CustomAgent):
|
|
546
|
+
role = "agent_b"
|
|
547
|
+
capabilities = ["cap_b"]
|
|
548
|
+
async def on_peer_request(self, msg):
|
|
549
|
+
return {}
|
|
550
|
+
|
|
551
|
+
mesh = MockMesh()
|
|
552
|
+
mesh.add(AgentA)
|
|
553
|
+
mesh.add(AgentB)
|
|
554
|
+
await mesh.start()
|
|
555
|
+
|
|
556
|
+
agent_a = mesh.get_agent("agent_a")
|
|
557
|
+
agent_b = mesh.get_agent("agent_b")
|
|
558
|
+
|
|
559
|
+
assert agent_a is not None
|
|
560
|
+
assert agent_a.role == "agent_a"
|
|
561
|
+
assert agent_b is not None
|
|
562
|
+
assert agent_b.role == "agent_b"
|
|
563
|
+
|
|
564
|
+
await mesh.stop()
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
# =============================================================================
|
|
568
|
+
# TEST: MOCK MESH AUTO-INJECTS PEERS
|
|
569
|
+
# =============================================================================
|
|
570
|
+
|
|
571
|
+
class TestMockMeshAutoInjectsPeers:
|
|
572
|
+
"""Test MockMesh automatically injects MockPeerClient with peer info."""
|
|
573
|
+
|
|
574
|
+
@pytest.mark.asyncio
|
|
575
|
+
async def test_mock_mesh_injects_mock_peer_client(self):
|
|
576
|
+
"""Test MockMesh injects MockPeerClient into agents."""
|
|
577
|
+
from jarviscore.testing import MockMesh, MockPeerClient
|
|
578
|
+
from jarviscore.profiles import CustomAgent
|
|
579
|
+
|
|
580
|
+
class TestAgent(CustomAgent):
|
|
581
|
+
role = "inject_test"
|
|
582
|
+
capabilities = ["testing"]
|
|
583
|
+
|
|
584
|
+
async def on_peer_request(self, msg):
|
|
585
|
+
return {}
|
|
586
|
+
|
|
587
|
+
mesh = MockMesh()
|
|
588
|
+
mesh.add(TestAgent)
|
|
589
|
+
await mesh.start()
|
|
590
|
+
|
|
591
|
+
agent = mesh.get_agent("inject_test")
|
|
592
|
+
|
|
593
|
+
assert agent.peers is not None
|
|
594
|
+
assert isinstance(agent.peers, MockPeerClient)
|
|
595
|
+
|
|
596
|
+
await mesh.stop()
|
|
597
|
+
|
|
598
|
+
@pytest.mark.asyncio
|
|
599
|
+
async def test_mock_mesh_peers_see_each_other(self):
|
|
600
|
+
"""Test agents in MockMesh can discover each other."""
|
|
601
|
+
from jarviscore.testing import MockMesh
|
|
602
|
+
from jarviscore.profiles import CustomAgent
|
|
603
|
+
|
|
604
|
+
class AgentA(CustomAgent):
|
|
605
|
+
role = "discoverer"
|
|
606
|
+
capabilities = ["discovery"]
|
|
607
|
+
async def on_peer_request(self, msg):
|
|
608
|
+
return {}
|
|
609
|
+
|
|
610
|
+
class AgentB(CustomAgent):
|
|
611
|
+
role = "discoverable"
|
|
612
|
+
capabilities = ["being_found"]
|
|
613
|
+
async def on_peer_request(self, msg):
|
|
614
|
+
return {}
|
|
615
|
+
|
|
616
|
+
mesh = MockMesh()
|
|
617
|
+
mesh.add(AgentA)
|
|
618
|
+
mesh.add(AgentB)
|
|
619
|
+
await mesh.start()
|
|
620
|
+
|
|
621
|
+
discoverer = mesh.get_agent("discoverer")
|
|
622
|
+
|
|
623
|
+
# Should see AgentB
|
|
624
|
+
peers = discoverer.peers.discover(role="discoverable")
|
|
625
|
+
|
|
626
|
+
assert len(peers) == 1
|
|
627
|
+
assert peers[0].role == "discoverable"
|
|
628
|
+
|
|
629
|
+
await mesh.stop()
|
|
630
|
+
|
|
631
|
+
@pytest.mark.asyncio
|
|
632
|
+
async def test_mock_mesh_agent_excludes_self(self):
|
|
633
|
+
"""Test agent's peer list excludes itself."""
|
|
634
|
+
from jarviscore.testing import MockMesh
|
|
635
|
+
from jarviscore.profiles import CustomAgent
|
|
636
|
+
|
|
637
|
+
class AgentA(CustomAgent):
|
|
638
|
+
role = "self_check"
|
|
639
|
+
capabilities = ["checking"]
|
|
640
|
+
async def on_peer_request(self, msg):
|
|
641
|
+
return {}
|
|
642
|
+
|
|
643
|
+
class AgentB(CustomAgent):
|
|
644
|
+
role = "other"
|
|
645
|
+
capabilities = ["other"]
|
|
646
|
+
async def on_peer_request(self, msg):
|
|
647
|
+
return {}
|
|
648
|
+
|
|
649
|
+
mesh = MockMesh()
|
|
650
|
+
mesh.add(AgentA)
|
|
651
|
+
mesh.add(AgentB)
|
|
652
|
+
await mesh.start()
|
|
653
|
+
|
|
654
|
+
agent_a = mesh.get_agent("self_check")
|
|
655
|
+
peers = agent_a.peers.list_peers()
|
|
656
|
+
|
|
657
|
+
# Should only see AgentB, not itself
|
|
658
|
+
assert len(peers) == 1
|
|
659
|
+
assert peers[0]["role"] == "other"
|
|
660
|
+
|
|
661
|
+
await mesh.stop()
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
# =============================================================================
|
|
665
|
+
# TEST: MOCK PEER CLIENT SET_REQUEST_HANDLER
|
|
666
|
+
# =============================================================================
|
|
667
|
+
|
|
668
|
+
class TestMockPeerClientSetRequestHandler:
|
|
669
|
+
"""Test MockPeerClient.set_request_handler() for custom responses."""
|
|
670
|
+
|
|
671
|
+
@pytest.mark.asyncio
|
|
672
|
+
async def test_set_request_handler_custom_logic(self):
|
|
673
|
+
"""Test set_request_handler() with custom response logic."""
|
|
674
|
+
from jarviscore.testing import MockPeerClient
|
|
675
|
+
|
|
676
|
+
async def custom_handler(target, message, context):
|
|
677
|
+
# Echo the query back with modification
|
|
678
|
+
return {
|
|
679
|
+
"echo": message.get("query", ""),
|
|
680
|
+
"processed": True,
|
|
681
|
+
"target_was": target
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
client = MockPeerClient(
|
|
685
|
+
mock_peers=[{"role": "echo", "capabilities": []}]
|
|
686
|
+
)
|
|
687
|
+
client.set_request_handler(custom_handler)
|
|
688
|
+
|
|
689
|
+
response = await client.request("echo", {"query": "hello world"})
|
|
690
|
+
|
|
691
|
+
assert response["echo"] == "hello world"
|
|
692
|
+
assert response["processed"] is True
|
|
693
|
+
assert response["target_was"] == "echo"
|
|
694
|
+
|
|
695
|
+
@pytest.mark.asyncio
|
|
696
|
+
async def test_set_request_handler_overrides_mock_response(self):
|
|
697
|
+
"""Test set_request_handler() takes precedence over set_mock_response()."""
|
|
698
|
+
from jarviscore.testing import MockPeerClient
|
|
699
|
+
|
|
700
|
+
async def handler(target, message, context):
|
|
701
|
+
return {"from": "handler"}
|
|
702
|
+
|
|
703
|
+
client = MockPeerClient(
|
|
704
|
+
mock_peers=[{"role": "target", "capabilities": []}]
|
|
705
|
+
)
|
|
706
|
+
client.set_mock_response("target", {"from": "mock_response"})
|
|
707
|
+
client.set_request_handler(handler)
|
|
708
|
+
|
|
709
|
+
response = await client.request("target", {})
|
|
710
|
+
|
|
711
|
+
# Handler should take precedence
|
|
712
|
+
assert response["from"] == "handler"
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
# =============================================================================
|
|
716
|
+
# TEST: INTEGRATION - MOCK TESTING WORKFLOW
|
|
717
|
+
# =============================================================================
|
|
718
|
+
|
|
719
|
+
class TestMockTestingWorkflow:
|
|
720
|
+
"""Integration test for complete mock testing workflow."""
|
|
721
|
+
|
|
722
|
+
@pytest.mark.asyncio
|
|
723
|
+
async def test_complete_testing_workflow(self):
|
|
724
|
+
"""Test complete workflow: setup, configure, test, verify."""
|
|
725
|
+
from jarviscore.testing import MockMesh
|
|
726
|
+
from jarviscore.profiles import CustomAgent
|
|
727
|
+
|
|
728
|
+
class Coordinator(CustomAgent):
|
|
729
|
+
role = "coordinator"
|
|
730
|
+
capabilities = ["coordination"]
|
|
731
|
+
|
|
732
|
+
async def on_peer_request(self, msg):
|
|
733
|
+
# Coordinator asks analyst for help
|
|
734
|
+
response = await self.peers.request("analyst", {
|
|
735
|
+
"task": "analyze",
|
|
736
|
+
"data": msg.data.get("data")
|
|
737
|
+
})
|
|
738
|
+
return {"coordinated": True, "analysis": response}
|
|
739
|
+
|
|
740
|
+
class Analyst(CustomAgent):
|
|
741
|
+
role = "analyst"
|
|
742
|
+
capabilities = ["analysis"]
|
|
743
|
+
|
|
744
|
+
async def on_peer_request(self, msg):
|
|
745
|
+
return {"analyzed": msg.data.get("data", ""), "confidence": 0.95}
|
|
746
|
+
|
|
747
|
+
# Setup
|
|
748
|
+
mesh = MockMesh()
|
|
749
|
+
mesh.add(Coordinator)
|
|
750
|
+
mesh.add(Analyst)
|
|
751
|
+
await mesh.start()
|
|
752
|
+
|
|
753
|
+
# Get coordinator and configure mock response for analyst
|
|
754
|
+
coordinator = mesh.get_agent("coordinator")
|
|
755
|
+
coordinator.peers.set_mock_response("analyst", {
|
|
756
|
+
"analyzed": "test_data",
|
|
757
|
+
"confidence": 0.99
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
# Test
|
|
761
|
+
response = await coordinator.peers.request("analyst", {"data": "test_data"})
|
|
762
|
+
|
|
763
|
+
# Verify
|
|
764
|
+
assert response["analyzed"] == "test_data"
|
|
765
|
+
assert response["confidence"] == 0.99
|
|
766
|
+
coordinator.peers.assert_requested("analyst")
|
|
767
|
+
|
|
768
|
+
await mesh.stop()
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
# =============================================================================
|
|
772
|
+
# RUN TESTS
|
|
773
|
+
# =============================================================================
|
|
774
|
+
|
|
775
|
+
if __name__ == "__main__":
|
|
776
|
+
pytest.main([__file__, "-v", "-s"])
|