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.
Files changed (31) hide show
  1. examples/customagent_cognitive_discovery_example.py +49 -8
  2. examples/customagent_distributed_example.py +140 -1
  3. examples/fastapi_integration_example.py +70 -7
  4. jarviscore/__init__.py +1 -1
  5. jarviscore/core/mesh.py +149 -0
  6. jarviscore/data/examples/customagent_cognitive_discovery_example.py +49 -8
  7. jarviscore/data/examples/customagent_distributed_example.py +140 -1
  8. jarviscore/data/examples/fastapi_integration_example.py +70 -7
  9. jarviscore/docs/API_REFERENCE.md +547 -5
  10. jarviscore/docs/CHANGELOG.md +89 -0
  11. jarviscore/docs/CONFIGURATION.md +1 -1
  12. jarviscore/docs/CUSTOMAGENT_GUIDE.md +347 -2
  13. jarviscore/docs/TROUBLESHOOTING.md +1 -1
  14. jarviscore/docs/USER_GUIDE.md +286 -5
  15. jarviscore/p2p/coordinator.py +36 -7
  16. jarviscore/p2p/messages.py +13 -0
  17. jarviscore/p2p/peer_client.py +355 -23
  18. jarviscore/p2p/peer_tool.py +17 -11
  19. jarviscore/profiles/customagent.py +9 -2
  20. jarviscore/testing/__init__.py +35 -0
  21. jarviscore/testing/mocks.py +578 -0
  22. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/METADATA +2 -2
  23. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/RECORD +31 -24
  24. tests/test_17_session_context.py +489 -0
  25. tests/test_18_mesh_diagnostics.py +465 -0
  26. tests/test_19_async_requests.py +516 -0
  27. tests/test_20_load_balancing.py +546 -0
  28. tests/test_21_mock_testing.py +776 -0
  29. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/WHEEL +0 -0
  30. {jarviscore_framework-0.3.1.dist-info → jarviscore_framework-0.3.2.dist-info}/licenses/LICENSE +0 -0
  31. {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"])