Skip to content

Commit 54776bb

Browse files
chgaoweiclaude
andauthored
feat: add example test runner and fix crawler tests (#40)
* refactor: remove anp/node module and related examples Remove the ANPNode unified server/client module that was experimental: - Delete anp/node/ directory (node.py, __init__.py, DESIGN.md, DESIGN.cn.md) - Delete examples/python/minimal_example/minimal_anp_node.py - Delete examples/python/minimal_example/minimal_two_nodes.py The functionality can be achieved by using FastANP and ANPClient separately. Co-Authored-By: Claude Opus 4.5 <[email protected]> * feat: add example test runner script and fix crawler tests - Add scripts/run_all_examples.py to run all example scripts - Supports standalone examples, server/client pairs, and configurable examples - Automatically starts servers before running clients - Reports detailed results and configuration requirements - Fix ANP crawler tests to use correct module paths (anp.* instead of octopus.*) - Fix simple_amap_example.py URL path - Update CLAUDE.md with run_all_tests.py commands - Update README.cn.md with crawler usage clarifications Co-Authored-By: Claude Opus 4.5 <[email protected]> * update blogs --------- Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent b6a413b commit 54776bb

File tree

13 files changed

+562
-1931
lines changed

13 files changed

+562
-1931
lines changed

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ uv run pytest --cov=anp # Run tests with coverage
2020
uv run pytest anp/unittest/ # Run core unit tests only
2121
uv run pytest anp/anp_crawler/test/ # Run ANP crawler tests only
2222
uv run pytest anp/fastanp/ # Run FastANP tests only
23+
uv run python run_all_tests.py # Run all unit tests (unified script)
24+
uv run python run_all_tests.py -v # Run all tests with verbose output
25+
uv run python run_all_tests.py --cov=anp # Run all tests with coverage
2326
```
2427

2528
**Build and Distribution:**

README.cn.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ result = await crawler.execute_json_rpc(
121121
)
122122
```
123123

124+
你可以将crawler的接口封装为LLM的tools,这样可以作为ANP客户端与ANP server进行交互。
125+
124126
**特性:**
125127
- **爬虫风格**:像网络爬虫一样爬取和解析ANP文档
126128
- **OpenAI Tools格式**:转换接口用于LLM集成
@@ -138,7 +140,7 @@ result = await crawler.execute_json_rpc(
138140
| **风格** | 代理对象(像本地方法) | 爬虫(爬取文档) |
139141
| **用法** | `agent.search(query="Tokyo")` | `crawler.execute_tool_call("search", {...})` |
140142
| **类型安全** | 完整类型提示,异常驱动 | 基于字典的返回 |
141-
| **适用场景** | 代码中的智能体间调用 | LLM工具集成,数据收集 |
143+
| **适用场景** | 使用代码访问固定的智能体,构建ANP的Skills | 使用LLM驱动的方式,访问远程的ANP智能体,并且与智能体进行交互 |
142144

143145
```python
144146
# RemoteAgent:方法调用像本地方法一样

anp/anp_crawler/test/test_anp_crawler.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
# Add parent directory to path for imports
2222
sys.path.append(str(Path(__file__).parent.parent.parent.parent))
2323

24-
from octopus.anp_sdk.anp_crawler.anp_crawler import ANPCrawler
25-
from octopus.anp_sdk.anp_crawler.anp_client import ANPClient
26-
from octopus.anp_sdk.anp_crawler.anp_parser import ANPDocumentParser
27-
from octopus.anp_sdk.anp_crawler.anp_interface import ANPInterface
24+
from anp.anp_crawler.anp_crawler import ANPCrawler
25+
from anp.anp_crawler.anp_client import ANPClient
26+
from anp.anp_crawler.anp_parser import ANPDocumentParser
27+
from anp.anp_crawler.anp_interface import ANPInterface, ANPInterfaceConverter
2828

2929

3030
class TestANPCrawler(unittest.IsolatedAsyncioTestCase):
@@ -59,7 +59,7 @@ def setUp(self):
5959
self.test_video_url = "https://grand-hotel.com/media/hotel-tour-video.mp4"
6060
self.test_audio_url = "https://grand-hotel.com/media/hotel-audio.mp3"
6161

62-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
62+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
6363
def test_anp_crawler_initialization(self, mock_auth_header):
6464
"""Test ANPCrawler initialization."""
6565
mock_auth_header.return_value = MagicMock()
@@ -78,7 +78,7 @@ def test_anp_crawler_initialization(self, mock_auth_header):
7878
self.assertEqual(len(crawler._visited_urls), 0)
7979
self.assertEqual(len(crawler._cache), 0)
8080

81-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
81+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
8282
async def test_fetch_text_agent_description(self, mock_auth_header):
8383
"""Test fetching Agent Description document."""
8484
mock_auth_header.return_value = MagicMock()
@@ -115,7 +115,7 @@ async def test_fetch_text_agent_description(self, mock_auth_header):
115115
# Agent Description should extract interface URLs but not actual tools yet
116116
# (would need to fetch the interface files)
117117

118-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
118+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
119119
async def test_fetch_text_openrpc_document(self, mock_auth_header):
120120
"""Test fetching and parsing OpenRPC document."""
121121
mock_auth_header.return_value = MagicMock()
@@ -156,7 +156,7 @@ async def test_fetch_text_openrpc_document(self, mock_auth_header):
156156
self.assertIn("description", interface["function"])
157157
self.assertIn("parameters", interface["function"])
158158

159-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
159+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
160160
async def test_fetch_text_with_ref_resolution(self, mock_auth_header):
161161
"""Test OpenRPC $ref resolution in schemas."""
162162
mock_auth_header.return_value = MagicMock()
@@ -207,7 +207,7 @@ async def test_fetch_text_with_ref_resolution(self, mock_auth_header):
207207
# At least one $ref should have been resolved
208208
self.assertTrue(found_ref_resolution, "$ref references should be resolved")
209209

210-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
210+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
211211
async def test_fetch_text_embedded_openrpc(self, mock_auth_header):
212212
"""Test fetching Agent Description with embedded OpenRPC content."""
213213
mock_auth_header.return_value = MagicMock()
@@ -269,7 +269,7 @@ async def test_fetch_text_embedded_openrpc(self, mock_auth_header):
269269
self.assertIn("firstName", guest_info["properties"])
270270
self.assertIn("email", guest_info["properties"])
271271

272-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
272+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
273273
async def test_fetch_text_error_handling(self, mock_auth_header):
274274
"""Test error handling in fetch_text."""
275275
mock_auth_header.return_value = MagicMock()
@@ -297,7 +297,7 @@ async def test_fetch_text_error_handling(self, mock_auth_header):
297297
self.assertIn("Error:", content_json["content"])
298298
self.assertEqual(len(interfaces_list), 0)
299299

300-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
300+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
301301
async def test_fetch_image(self, mock_auth_header):
302302
"""Test fetch_image method (pass implementation)."""
303303
mock_auth_header.return_value = MagicMock()
@@ -314,7 +314,7 @@ async def test_fetch_image(self, mock_auth_header):
314314
# Should return None for pass implementation
315315
self.assertIsNone(result)
316316

317-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
317+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
318318
async def test_fetch_video(self, mock_auth_header):
319319
"""Test fetch_video method (pass implementation)."""
320320
mock_auth_header.return_value = MagicMock()
@@ -331,7 +331,7 @@ async def test_fetch_video(self, mock_auth_header):
331331
# Should return None for pass implementation
332332
self.assertIsNone(result)
333333

334-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
334+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
335335
async def test_fetch_audio(self, mock_auth_header):
336336
"""Test fetch_audio method (pass implementation)."""
337337
mock_auth_header.return_value = MagicMock()
@@ -348,7 +348,7 @@ async def test_fetch_audio(self, mock_auth_header):
348348
# Should return None for pass implementation
349349
self.assertIsNone(result)
350350

351-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
351+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
352352
async def test_fetch_auto(self, mock_auth_header):
353353
"""Test fetch_auto method (pass implementation)."""
354354
mock_auth_header.return_value = MagicMock()
@@ -365,7 +365,7 @@ async def test_fetch_auto(self, mock_auth_header):
365365
# Should return None for pass implementation
366366
self.assertIsNone(result)
367367

368-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
368+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
369369
async def test_caching_functionality(self, mock_auth_header):
370370
"""Test URL caching functionality."""
371371
mock_auth_header.return_value = MagicMock()
@@ -402,7 +402,7 @@ async def test_caching_functionality(self, mock_auth_header):
402402
self.assertEqual(crawler.get_cache_size(), 0)
403403
self.assertEqual(len(crawler.get_visited_urls()), 0)
404404

405-
@patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader')
405+
@patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader')
406406
def test_session_management(self, mock_auth_header):
407407
"""Test session management functionality."""
408408
mock_auth_header.return_value = MagicMock()
@@ -428,7 +428,7 @@ def test_session_management(self, mock_auth_header):
428428
def test_url_parameter_removal(self):
429429
"""Test URL parameter removal functionality."""
430430
# Test with mock since we need to access private method
431-
with patch('octopus.anp_sdk.anp_crawler.anp_client.DIDWbaAuthHeader'):
431+
with patch('anp.anp_crawler.anp_client.DIDWbaAuthHeader'):
432432
crawler = ANPCrawler(
433433
did_document_path=self.mock_did_document_path,
434434
private_key_path=self.mock_private_key_path
@@ -529,12 +529,12 @@ def test_parse_invalid_json(self):
529529
self.assertEqual(len(result["interfaces"]), 0)
530530

531531

532-
class TestANPInterface(unittest.TestCase):
533-
"""Test cases for ANPInterface class."""
534-
532+
class TestANPInterfaceConverter(unittest.TestCase):
533+
"""Test cases for ANPInterfaceConverter class."""
534+
535535
def setUp(self):
536536
"""Set up test environment."""
537-
self.converter = ANPInterface()
537+
self.converter = ANPInterfaceConverter()
538538

539539
def test_convert_openrpc_method(self):
540540
"""Test converting OpenRPC method to OpenAI Tools format."""
@@ -674,7 +674,7 @@ def test_unsupported_interface_type(self):
674674
# Add test cases
675675
suite.addTests(loader.loadTestsFromTestCase(TestANPCrawler))
676676
suite.addTests(loader.loadTestsFromTestCase(TestANPDocumentParser))
677-
suite.addTests(loader.loadTestsFromTestCase(TestANPInterface))
677+
suite.addTests(loader.loadTestsFromTestCase(TestANPInterfaceConverter))
678678

679679
# Run tests
680680
runner = unittest.TextTestRunner(verbosity=2)

0 commit comments

Comments
 (0)