Skip to content

Commit e7d25d7

Browse files
committed
Add unit tests and vector generation for WOC, auth, and certificates
This commit introduces comprehensive testing and utility scripts across multiple components in the project. New tests cover authentication, master certificates, request/response vectors, and package build processes, while new vector generation scripts create reproducible test data for What's On Chain and auth-related scenarios. All additions are geared toward improving reliability and maintainability.
1 parent 98c2a51 commit e7d25d7

File tree

103 files changed

+10126
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+10126
-2
lines changed

tests/bsv/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

tests/bsv/auth/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

tests/bsv/auth/clients/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import pytest
2+
from unittest.mock import AsyncMock, MagicMock
3+
from bsv.auth.clients.auth_fetch import AuthFetch, SimplifiedFetchRequestOptions
4+
from bsv.auth.requested_certificate_set import RequestedCertificateSet
5+
from bsv.auth.auth_message import AuthMessage
6+
from bsv.auth.peer import Peer, PeerOptions
7+
8+
9+
class DummyWallet:
10+
def get_public_key(self, ctx, args, originator):
11+
return {"publicKey": "02a1633c...", "derivationPrefix": "m/0"}
12+
13+
def create_action(self, ctx, args, originator):
14+
return {"tx": "0100000001abcdef..."}
15+
16+
def create_signature(self, ctx, args, originator):
17+
return {"signature": b"dummy_signature"}
18+
19+
def verify_signature(self, ctx, args, originator):
20+
return {"valid": True}
21+
22+
23+
@pytest.mark.asyncio
24+
async def test_fetch_basic_request():
25+
wallet = DummyWallet()
26+
requested_certs = RequestedCertificateSet()
27+
auth_fetch = AuthFetch(wallet, requested_certs)
28+
url = "https://example.com/api"
29+
config = SimplifiedFetchRequestOptions(method="GET", headers={"Accept": "application/json"})
30+
31+
# モックのPeerとTransport
32+
mock_transport = MagicMock()
33+
mock_transport.send = AsyncMock(return_value=None)
34+
mock_transport.on_data = MagicMock(return_value=None)
35+
peer_options = PeerOptions(wallet=wallet, transport=mock_transport, certificates_to_request=requested_certs)
36+
mock_peer = Peer(peer_options)
37+
mock_peer.get_authenticated_session = AsyncMock(return_value=MagicMock(peer_nonce="dummy", is_authenticated=True, peer_identity_key="dummy"))
38+
mock_peer.to_peer = MagicMock(return_value=None) # 同期メソッドとしてモック
39+
mock_peer.listen_for_general_messages = MagicMock(return_value=1)
40+
mock_peer.stop_listening_for_general_messages = MagicMock()
41+
42+
# peersにセット
43+
auth_peer = auth_fetch.peers["https://example.com"] = MagicMock()
44+
auth_peer.peer = mock_peer
45+
46+
auth_fetch.fetch(None, url, config)
47+
mock_peer.to_peer.assert_called_once()
48+
49+
@pytest.mark.asyncio
50+
async def test_fetch_with_auth_headers():
51+
wallet = DummyWallet()
52+
requested_certs = RequestedCertificateSet()
53+
auth_fetch = AuthFetch(wallet, requested_certs)
54+
url = "https://example.com/api"
55+
config = SimplifiedFetchRequestOptions(
56+
method="POST",
57+
headers={"Content-Type": "application/json", "X-Auth-Required": "true"},
58+
body=b'{"test": "data"}'
59+
)
60+
mock_transport = MagicMock()
61+
mock_transport.send = AsyncMock(return_value=None)
62+
mock_transport.on_data = MagicMock(return_value=None)
63+
peer_options = PeerOptions(wallet=wallet, transport=mock_transport, certificates_to_request=requested_certs)
64+
mock_peer = Peer(peer_options)
65+
mock_peer.get_authenticated_session = AsyncMock(return_value=MagicMock(peer_nonce="dummy", is_authenticated=True, peer_identity_key="dummy"))
66+
mock_peer.to_peer = MagicMock(return_value=None) # 同期メソッドとしてモック
67+
mock_peer.listen_for_general_messages = MagicMock(return_value=1)
68+
mock_peer.stop_listening_for_general_messages = MagicMock()
69+
auth_peer = auth_fetch.peers["https://example.com"] = MagicMock()
70+
auth_peer.peer = mock_peer
71+
auth_fetch.fetch(None, url, config)
72+
mock_peer.to_peer.assert_called_once()
73+
74+
@pytest.mark.asyncio
75+
async def test_fetch_error_handling():
76+
wallet = DummyWallet()
77+
requested_certs = RequestedCertificateSet()
78+
auth_fetch = AuthFetch(wallet, requested_certs)
79+
url = "https://example.com/api"
80+
config = SimplifiedFetchRequestOptions(method="GET")
81+
mock_transport = MagicMock()
82+
mock_transport.send = AsyncMock(side_effect=Exception("Network error"))
83+
mock_transport.on_data = MagicMock(return_value=None)
84+
peer_options = PeerOptions(wallet=wallet, transport=mock_transport, certificates_to_request=requested_certs)
85+
mock_peer = Peer(peer_options)
86+
mock_peer.get_authenticated_session = AsyncMock(return_value=MagicMock(peer_nonce="dummy", is_authenticated=True, peer_identity_key="dummy"))
87+
mock_peer.to_peer = MagicMock(side_effect=Exception("Network error")) # 同期メソッドとして例外
88+
mock_peer.listen_for_general_messages = MagicMock(return_value=1)
89+
mock_peer.stop_listening_for_general_messages = MagicMock()
90+
auth_peer = auth_fetch.peers["https://example.com"] = MagicMock()
91+
auth_peer.peer = mock_peer
92+
with pytest.raises(RuntimeError, match="Network error"):
93+
auth_fetch.fetch(None, url, config)
94+
95+
def test_consume_received_certificates():
96+
wallet = DummyWallet()
97+
requested_certs = RequestedCertificateSet()
98+
auth_fetch = AuthFetch(wallet, requested_certs)
99+
mock_cert = {"type": "authrite", "validationKey": "test_key", "serialNumber": "123", "validFrom": 1000, "validUntil": 2000}
100+
auth_fetch.certificates_received = [mock_cert]
101+
certs = auth_fetch.consume_received_certificates()
102+
assert len(certs) == 1
103+
assert certs[0]["type"] == "authrite"
104+
assert certs[0]["serialNumber"] == "123"
105+
assert len(auth_fetch.certificates_received) == 0
106+
107+
def test_validate_request_options():
108+
config = SimplifiedFetchRequestOptions()
109+
assert config.method == "GET"
110+
assert isinstance(config.headers, dict)
111+
assert config.body is None
112+
assert config.retry_counter is None
113+
config = SimplifiedFetchRequestOptions(method="POST")
114+
assert config.method == "POST"
115+
config = SimplifiedFetchRequestOptions(headers={"X-Test": "value"})
116+
assert config.headers["X-Test"] == "value"
117+
config = SimplifiedFetchRequestOptions(body=b"test")
118+
assert config.body == b"test"
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import pytest
2+
import json
3+
from aiohttp import web
4+
from bsv.auth.clients.auth_fetch import AuthFetch, SimplifiedFetchRequestOptions
5+
from bsv.auth.requested_certificate_set import RequestedCertificateSet
6+
from bsv.auth.peer import PeerOptions
7+
import asyncio
8+
9+
class DummyWallet:
10+
def get_public_key(self, ctx, args, originator):
11+
return {"publicKey": "02a1633c...", "derivationPrefix": "m/0"}
12+
def create_action(self, ctx, args, originator):
13+
return {"tx": "0100000001abcdef..."}
14+
def create_signature(self, ctx, args, originator):
15+
return {"signature": b"dummy_signature"}
16+
def verify_signature(self, ctx, args, originator):
17+
return {"valid": True}
18+
19+
import json
20+
import pytest
21+
from aiohttp import web
22+
23+
import pytest_asyncio
24+
25+
@pytest_asyncio.fixture
26+
async def auth_server(unused_tcp_port):
27+
async def handle_authfetch(request):
28+
print("[auth_server] /authfetch called")
29+
body = await request.json()
30+
print(f"[auth_server] received body: {body}")
31+
# emulate processing delay so the test actually waits
32+
await asyncio.sleep(0.3)
33+
# 最小応答(initialRequestに対するinitialResponse)
34+
resp = {
35+
"message_type": "initialResponse",
36+
"server_nonce": "c2VydmVyX25vbmNl",
37+
}
38+
print(f"[auth_server] sending: {resp}")
39+
return web.json_response(resp)
40+
41+
app = web.Application()
42+
app.router.add_post("/authfetch", handle_authfetch)
43+
runner = web.AppRunner(app)
44+
await runner.setup()
45+
port = unused_tcp_port
46+
site = web.TCPSite(runner, "127.0.0.1", port)
47+
await site.start()
48+
try:
49+
yield f"http://127.0.0.1:{port}"
50+
finally:
51+
await runner.cleanup()
52+
53+
@pytest.mark.asyncio
54+
async def test_authfetch_e2e(auth_server):
55+
wallet = DummyWallet()
56+
requested_certs = RequestedCertificateSet()
57+
auth_fetch = AuthFetch(wallet, requested_certs)
58+
59+
from bsv.auth.clients.auth_fetch import AuthPeer
60+
61+
base = auth_server.rstrip("/")
62+
# 既存のキーを消してから、フォールバック指定のPeerを登録
63+
auth_fetch.peers.pop(base, None)
64+
ap = AuthPeer()
65+
ap.supports_mutual_auth = False # ← 有効化
66+
auth_fetch.peers[base] = ap
67+
68+
headers = {"Content-Type": "application/json"}
69+
config = SimplifiedFetchRequestOptions(
70+
method="POST",
71+
headers=headers,
72+
body=b'{"message_type":"initialRequest","initial_nonce":"dGVzdF9ub25jZQ==","identity_key":"test_client_key"}'
73+
)
74+
print(f"[test] calling fetch to {base}/authfetch")
75+
resp = await asyncio.wait_for(
76+
asyncio.to_thread(auth_fetch.fetch, None, f"{base}/authfetch", config),
77+
timeout=10,
78+
)
79+
print(f"[test] got response: status={getattr(resp,'status_code',None)} text={getattr(resp,'text',None)}")
80+
assert resp is not None and resp.status_code == 200
81+
data = json.loads(resp.text)
82+
assert data.get("message_type") == "initialResponse"

0 commit comments

Comments
 (0)