1+ import unittest
2+ from unittest .mock import AsyncMock , MagicMock
3+
4+ from bsv .broadcaster import BroadcastResponse , BroadcastFailure
5+ from bsv .broadcasters .arc import ARC , ARCConfig
6+ from bsv .http_client import HttpClient , HttpResponse , SyncHttpClient
7+ from bsv .transaction import Transaction
8+
9+
10+ class TestARCBroadcast (unittest .IsolatedAsyncioTestCase ):
11+
12+ def setUp (self ):
13+ self .URL = "https://api.taal.com/arc"
14+ self .api_key = "apikey_85678993923y454i4jhd803wsd02"
15+ self .tx = Transaction (tx_data = "Hello sCrypt" )
16+
17+ # Mocking the Transaction methods
18+ self .tx .hex = MagicMock (return_value = "hexFormat" )
19+
20+ async def test_broadcast_success (self ):
21+ mock_response = HttpResponse (
22+ ok = True ,
23+ status_code = 200 ,
24+ json_data = {
25+ "data" : {
26+ "txid" : "8e60c4143879918ed03b8fc67b5ac33b8187daa3b46022ee2a9e1eb67e2e46ec" ,
27+ "txStatus" : "success" ,
28+ "extraInfo" : "extra" ,
29+ }
30+ },
31+ )
32+ mock_http_client = AsyncMock (HttpClient )
33+ mock_http_client .fetch = AsyncMock (return_value = mock_response )
34+
35+ arc_config = ARCConfig (api_key = self .api_key , http_client = mock_http_client )
36+ arc = ARC (self .URL , arc_config )
37+ result = await arc .broadcast (self .tx )
38+
39+ self .assertIsInstance (result , BroadcastResponse )
40+ self .assertEqual (
41+ result .txid ,
42+ "8e60c4143879918ed03b8fc67b5ac33b8187daa3b46022ee2a9e1eb67e2e46ec" ,
43+ )
44+ self .assertEqual (result .message , "success extra" )
45+
46+ async def test_broadcast_failure (self ):
47+ mock_response = HttpResponse (
48+ ok = False ,
49+ status_code = 400 ,
50+ json_data = {
51+ "data" : {"status" : "ERR_BAD_REQUEST" , "detail" : "Invalid transaction" }
52+ },
53+ )
54+ mock_http_client = AsyncMock (HttpClient )
55+ mock_http_client .fetch = AsyncMock (return_value = mock_response )
56+
57+ arc_config = ARCConfig (api_key = self .api_key , http_client = mock_http_client )
58+ arc = ARC (self .URL , arc_config )
59+ result = await arc .broadcast (self .tx )
60+
61+ self .assertIsInstance (result , BroadcastFailure )
62+ self .assertEqual (result .code , "400" )
63+ self .assertEqual (result .description , "Invalid transaction" )
64+
65+ async def test_broadcast_exception (self ):
66+ mock_http_client = AsyncMock (HttpClient )
67+ mock_http_client .fetch = AsyncMock (side_effect = Exception ("Internal Error" ))
68+
69+ arc_config = ARCConfig (api_key = self .api_key , http_client = mock_http_client )
70+ arc = ARC (self .URL , arc_config )
71+ result = await arc .broadcast (self .tx )
72+
73+ self .assertIsInstance (result , BroadcastFailure )
74+ self .assertEqual (result .code , "500" )
75+ self .assertEqual (result .description , "Internal Error" )
76+
77+ def test_sync_broadcast_success (self ):
78+ mock_response = HttpResponse (
79+ ok = True ,
80+ status_code = 200 ,
81+ json_data = {
82+ "data" : {
83+ "txid" : "8e60c4143879918ed03b8fc67b5ac33b8187daa3b46022ee2a9e1eb67e2e46ec" ,
84+ "txStatus" : "success" ,
85+ "extraInfo" : "extra" ,
86+ }
87+ },
88+ )
89+ mock_sync_http_client = MagicMock (SyncHttpClient )
90+ mock_sync_http_client .post = MagicMock (return_value = mock_response ) # fetch → post
91+
92+ arc_config = ARCConfig (api_key = self .api_key , sync_http_client = mock_sync_http_client )
93+ arc = ARC (self .URL , arc_config )
94+ result = arc .sync_broadcast (self .tx )
95+
96+ self .assertIsInstance (result , BroadcastResponse )
97+ self .assertEqual (
98+ result .txid ,
99+ "8e60c4143879918ed03b8fc67b5ac33b8187daa3b46022ee2a9e1eb67e2e46ec" ,
100+ )
101+ self .assertEqual (result .message , "success extra" )
102+
103+ def test_sync_broadcast_failure (self ):
104+ mock_response = HttpResponse (
105+ ok = False ,
106+ status_code = 400 ,
107+ json_data = {
108+ "data" : {"status" : "ERR_BAD_REQUEST" , "detail" : "Invalid transaction" }
109+ },
110+ )
111+ mock_sync_http_client = MagicMock (SyncHttpClient )
112+ mock_sync_http_client .post = MagicMock (return_value = mock_response ) # fetch → post
113+
114+ arc_config = ARCConfig (api_key = self .api_key , sync_http_client = mock_sync_http_client )
115+ arc = ARC (self .URL , arc_config )
116+ result = arc .sync_broadcast (self .tx )
117+
118+ self .assertIsInstance (result , BroadcastFailure )
119+ self .assertEqual (result .code , "400" )
120+ self .assertEqual (result .description , "Invalid transaction" )
121+
122+ def test_sync_broadcast_timeout_error (self ):
123+ """408 time out error test"""
124+ mock_response = HttpResponse (
125+ ok = False ,
126+ status_code = 408 ,
127+ json_data = {"data" : {"status" : "ERR_TIMEOUT" , "detail" : "Request timed out" }}
128+ )
129+ mock_sync_http_client = MagicMock (SyncHttpClient )
130+ mock_sync_http_client .post = MagicMock (return_value = mock_response )
131+
132+ arc_config = ARCConfig (api_key = self .api_key , sync_http_client = mock_sync_http_client )
133+ arc = ARC (self .URL , arc_config )
134+ result = arc .sync_broadcast (self .tx , timeout = 5 )
135+
136+ self .assertIsInstance (result , BroadcastFailure )
137+ self .assertEqual (result .status , "failure" )
138+ self .assertEqual (result .code , "408" )
139+ self .assertEqual (result .description , "Transaction broadcast timed out after 5 seconds" )
140+
141+ def test_sync_broadcast_connection_error (self ):
142+ """503 error test"""
143+ mock_response = HttpResponse (
144+ ok = False ,
145+ status_code = 503 ,
146+ json_data = {"data" : {"status" : "ERR_CONNECTION" , "detail" : "Service unavailable" }}
147+ )
148+ mock_sync_http_client = MagicMock (SyncHttpClient )
149+ mock_sync_http_client .post = MagicMock (return_value = mock_response )
150+
151+ arc_config = ARCConfig (api_key = self .api_key , sync_http_client = mock_sync_http_client )
152+ arc = ARC (self .URL , arc_config )
153+ result = arc .sync_broadcast (self .tx )
154+
155+ self .assertIsInstance (result , BroadcastFailure )
156+ self .assertEqual (result .status , "failure" )
157+ self .assertEqual (result .code , "503" )
158+ self .assertEqual (result .description , "Failed to connect to ARC service" )
159+
160+ def test_sync_broadcast_exception (self ):
161+ mock_sync_http_client = MagicMock (SyncHttpClient )
162+ mock_sync_http_client .post = MagicMock (side_effect = Exception ("Internal Error" ))
163+
164+ arc_config = ARCConfig (api_key = self .api_key , sync_http_client = mock_sync_http_client )
165+ arc = ARC (self .URL , arc_config )
166+ result = arc .sync_broadcast (self .tx )
167+
168+ self .assertIsInstance (result , BroadcastFailure )
169+ self .assertEqual (result .code , "500" )
170+ self .assertEqual (result .description , "Internal Error" )
171+
172+ def test_check_transaction_status_success (self ):
173+ mock_response = HttpResponse (
174+ ok = True ,
175+ status_code = 200 ,
176+ json_data = {
177+ "data" : { # dataキーを追加
178+ "txid" : "8e60c4143879918ed03b8fc67b5ac33b8187daa3b46022ee2a9e1eb67e2e46ec" ,
179+ "txStatus" : "MINED" ,
180+ "blockHash" : "000000000000000001234567890abcdef" ,
181+ "blockHeight" : 800000
182+ }
183+ },
184+ )
185+ mock_sync_http_client = MagicMock (SyncHttpClient )
186+ mock_sync_http_client .get = MagicMock (return_value = mock_response ) # fetch → get
187+
188+ arc_config = ARCConfig (api_key = self .api_key , sync_http_client = mock_sync_http_client )
189+ arc = ARC (self .URL , arc_config )
190+ result = arc .check_transaction_status ("8e60c4143879918ed03b8fc67b5ac33b8187daa3b46022ee2a9e1eb67e2e46ec" )
191+
192+ self .assertEqual (result ["txid" ], "8e60c4143879918ed03b8fc67b5ac33b8187daa3b46022ee2a9e1eb67e2e46ec" )
193+ self .assertEqual (result ["txStatus" ], "MINED" )
194+ self .assertEqual (result ["blockHeight" ], 800000 )
195+
196+ def test_categorize_transaction_status_mined (self ):
197+ response = {
198+ "txStatus" : "MINED" ,
199+ "blockHeight" : 800000
200+ }
201+ result = ARC .categorize_transaction_status (response )
202+
203+ self .assertEqual (result ["status_category" ], "mined" )
204+ self .assertEqual (result ["tx_status" ], "MINED" )
205+
206+ def test_categorize_transaction_status_progressing (self ):
207+ response = {
208+ "txStatus" : "QUEUED"
209+ }
210+ result = ARC .categorize_transaction_status (response )
211+
212+ self .assertEqual (result ["status_category" ], "progressing" )
213+ self .assertEqual (result ["tx_status" ], "QUEUED" )
214+
215+ def test_categorize_transaction_status_warning (self ):
216+ response = {
217+ "txStatus" : "SEEN_ON_NETWORK" ,
218+ "competingTxs" : ["some_competing_tx" ]
219+ }
220+ result = ARC .categorize_transaction_status (response )
221+
222+ self .assertEqual (result ["status_category" ], "warning" )
223+ self .assertEqual (result ["tx_status" ], "SEEN_ON_NETWORK" )
224+
225+ def test_categorize_transaction_status_0confirmation (self ):
226+ response = {
227+ "txStatus" : "SEEN_ON_NETWORK"
228+ }
229+ result = ARC .categorize_transaction_status (response )
230+
231+ self .assertEqual (result ["status_category" ], "0confirmation" )
232+ self .assertEqual (result ["tx_status" ], "SEEN_ON_NETWORK" )
233+
234+
235+ if __name__ == "__main__" :
236+ unittest .main ()
0 commit comments