11import os
2+ import time
23from pytest import fixture
34from dotenv import load_dotenv
45from dataclasses import dataclass
5- from typing import Optional
6+ from typing import Callable , Optional , TypeVar
67from hiero_sdk_python .account .account_id import AccountId
78from hiero_sdk_python .client .client import Client
89from hiero_sdk_python .client .network import Network
1112from hiero_sdk_python .logger .log_level import LogLevel
1213from hiero_sdk_python .response_code import ResponseCode
1314from hiero_sdk_python .tokens .supply_type import SupplyType
14- from hiero_sdk_python .tokens .token_create_transaction import TokenCreateTransaction , TokenKeys , TokenParams
15- from hiero_sdk_python .tokens .token_associate_transaction import TokenAssociateTransaction
15+ from hiero_sdk_python .tokens .token_create_transaction import (
16+ TokenCreateTransaction ,
17+ TokenKeys ,
18+ TokenParams ,
19+ )
20+ from hiero_sdk_python .tokens .token_associate_transaction import (
21+ TokenAssociateTransaction ,
22+ )
1623from hiero_sdk_python .account .account_create_transaction import AccountCreateTransaction
1724from hiero_sdk_python .transaction .transfer_transaction import TransferTransaction
18- from hiero_sdk_python .hbar import Hbar
25+ from hiero_sdk_python .hbar import Hbar
26+
27+ T = TypeVar ("T" )
1928
2029load_dotenv (override = True )
2130
31+
2232@fixture
2333def env ():
2434 """Integration test environment with client/operator set up."""
2535 e = IntegrationTestEnv ()
2636 yield e
2737 e .close ()
2838
39+
2940@dataclass
3041class Account :
31- id : AccountId
32- key : PrivateKey
42+ id : AccountId
43+ key : PrivateKey
44+
3345
3446class IntegrationTestEnv :
3547
3648 def __init__ (self ) -> None :
37- network = Network (os .getenv (' NETWORK' ))
49+ network = Network (os .getenv (" NETWORK" ))
3850 self .client = Client (network )
3951 self .operator_id : Optional [AccountId ] = None
4052 self .operator_key : Optional [PrivateKey ] = None
41- operator_id = os .getenv (' OPERATOR_ID' )
42- operator_key = os .getenv (' OPERATOR_KEY' )
53+ operator_id = os .getenv (" OPERATOR_ID" )
54+ operator_key = os .getenv (" OPERATOR_KEY" )
4355 if operator_id and operator_key :
4456 self .operator_id = AccountId .from_string (operator_id )
4557 self .operator_key = PrivateKey .from_string (operator_key )
4658 self .client .set_operator (self .operator_id , self .operator_key )
4759
4860 self .client .logger .set_level (LogLevel .ERROR )
4961 self .public_operator_key = self .operator_key .public_key ()
50-
62+
5163 def close (self ):
5264 self .client .close ()
5365
@@ -56,8 +68,8 @@ def create_account(self, initial_hbar: float = 1.0) -> Account:
5668 key = PrivateKey .generate ()
5769 tx = (
5870 AccountCreateTransaction ()
59- .set_key_without_alias (key .public_key ())
60- .set_initial_balance (Hbar (initial_hbar ))
71+ .set_key_without_alias (key .public_key ())
72+ .set_initial_balance (Hbar (initial_hbar ))
6173 )
6274 receipt = tx .execute (self .client )
6375 if receipt .status != ResponseCode .SUCCESS :
@@ -66,18 +78,20 @@ def create_account(self, initial_hbar: float = 1.0) -> Account:
6678 )
6779 return Account (id = receipt .account_id , key = key )
6880
69- def associate_and_transfer (self , receiver : AccountId , receiver_key : PrivateKey , token_id , amount : int ):
81+ def associate_and_transfer (
82+ self , receiver : AccountId , receiver_key : PrivateKey , token_id , amount : int
83+ ):
7084 """
7185 Associate the token with `receiver`, then transfer `amount` of the token
7286 from the operator to that receiver.
7387 """
7488 assoc_receipt = (
7589 TokenAssociateTransaction ()
76- .set_account_id (receiver )
77- .add_token_id (token_id )
78- .freeze_with (self .client )
79- .sign (receiver_key )
80- .execute (self .client )
90+ .set_account_id (receiver )
91+ .add_token_id (token_id )
92+ .freeze_with (self .client )
93+ .sign (receiver_key )
94+ .execute (self .client )
8195 )
8296 if assoc_receipt .status != ResponseCode .SUCCESS :
8397 raise AssertionError (
@@ -86,15 +100,16 @@ def associate_and_transfer(self, receiver: AccountId, receiver_key: PrivateKey,
86100
87101 transfer_receipt = (
88102 TransferTransaction ()
89- .add_token_transfer (token_id , self .operator_id , - amount )
90- .add_token_transfer (token_id , receiver , amount )
91- .execute (self .client ) # auto-signs with operator’s key
103+ .add_token_transfer (token_id , self .operator_id , - amount )
104+ .add_token_transfer (token_id , receiver , amount )
105+ .execute (self .client ) # auto-signs with operator’s key
92106 )
93107 if transfer_receipt .status != ResponseCode .SUCCESS :
94108 raise AssertionError (
95109 f"Transfer failed: { ResponseCode (transfer_receipt .status ).name } "
96110 )
97111
112+
98113def create_fungible_token (env , opts = []):
99114 """
100115 Create a fungible token with the given options.
@@ -106,36 +121,39 @@ def create_fungible_token(env, opts=[]):
106121 lambda tx: tx.set_treasury_account_id(custom_treasury_id).freeze_with(client)
107122 """
108123 token_params = TokenParams (
109- token_name = "PTokenTest34" ,
110- token_symbol = "PTT34" ,
111- decimals = 2 ,
112- initial_supply = 1000 ,
113- treasury_account_id = env .operator_id ,
114- token_type = TokenType .FUNGIBLE_COMMON ,
115- supply_type = SupplyType .FINITE ,
116- max_supply = 10000
117- )
118-
124+ token_name = "PTokenTest34" ,
125+ token_symbol = "PTT34" ,
126+ decimals = 2 ,
127+ initial_supply = 1000 ,
128+ treasury_account_id = env .operator_id ,
129+ token_type = TokenType .FUNGIBLE_COMMON ,
130+ supply_type = SupplyType .FINITE ,
131+ max_supply = 10000 ,
132+ )
133+
119134 token_keys = TokenKeys (
120- admin_key = env .operator_key ,
121- supply_key = env .operator_key ,
122- freeze_key = env .operator_key ,
123- wipe_key = env .operator_key
124- # pause_key= None # implicitly “no pause key” use opts to add one
125- )
126-
135+ admin_key = env .operator_key ,
136+ supply_key = env .operator_key ,
137+ freeze_key = env .operator_key ,
138+ wipe_key = env .operator_key ,
139+ # pause_key= None # implicitly “no pause key” use opts to add one
140+ )
141+
127142 token_transaction = TokenCreateTransaction (token_params , token_keys )
128-
143+
129144 # Apply optional functions to the token creation transaction
130145 for opt in opts :
131146 opt (token_transaction )
132-
147+
133148 token_receipt = token_transaction .execute (env .client )
134-
135- assert token_receipt .status == ResponseCode .SUCCESS , f"Token creation failed with status: { ResponseCode (token_receipt .status ).name } "
136-
149+
150+ assert (
151+ token_receipt .status == ResponseCode .SUCCESS
152+ ), f"Token creation failed with status: { ResponseCode (token_receipt .status ).name } "
153+
137154 return token_receipt .token_id
138155
156+
139157def create_nft_token (env , opts = []):
140158 """
141159 Create a non-fungible token (NFT) with the given options.
@@ -154,15 +172,14 @@ def create_nft_token(env, opts=[]):
154172 treasury_account_id = env .operator_id ,
155173 token_type = TokenType .NON_FUNGIBLE_UNIQUE ,
156174 supply_type = SupplyType .FINITE ,
157- max_supply = 10000
175+ max_supply = 10000 ,
158176 )
159-
177+
160178 token_keys = TokenKeys (
161179 admin_key = env .operator_key ,
162180 supply_key = env .operator_key ,
163- freeze_key = env .operator_key
181+ freeze_key = env .operator_key ,
164182 # pause_key= None # implicitly “no pause key” use opts to add one
165-
166183 )
167184
168185 transaction = TokenCreateTransaction (token_params , token_keys )
@@ -172,7 +189,51 @@ def create_nft_token(env, opts=[]):
172189 opt (transaction )
173190
174191 token_receipt = transaction .execute (env .client )
175-
176- assert token_receipt .status == ResponseCode .SUCCESS , f"Token creation failed with status: { ResponseCode (token_receipt .status ).name } "
177-
178- return token_receipt .token_id
192+
193+ assert (
194+ token_receipt .status == ResponseCode .SUCCESS
195+ ), f"Token creation failed with status: { ResponseCode (token_receipt .status ).name } "
196+
197+ return token_receipt .token_id
198+
199+
200+ def wait_for_mirror_node (
201+ fn : Callable [[], T ],
202+ predicate : Callable [[T ], bool ],
203+ timeout : float = 5 ,
204+ interval : float = 1 ,
205+ ) -> T :
206+ """
207+ Polls fn until predicate(result) returns True or timeout is reached
208+
209+ Args:
210+ fn: Function that fetches data from mirror node.
211+ predicate: Condition that determines success.
212+ timeout: Max time to wait (seconds).
213+ interval: Sleep interval between retries (seconds).
214+
215+ Returns:
216+ T: The successful result.
217+ """
218+ deadline = time .monotonic () + timeout
219+ last_response = None
220+ last_exception = None
221+
222+ while time .monotonic () < deadline :
223+ try :
224+ last_response = fn ()
225+ if predicate (last_response ):
226+ return last_response
227+ except Exception as e :
228+ last_exception = e
229+
230+ time .sleep (interval )
231+
232+ if last_exception is not None :
233+ raise TimeoutError (
234+ "Timed out waiting for mirror node, Last call raised an exception"
235+ ) from last_exception
236+
237+ raise TimeoutError (
238+ f"Timed out waiting for mirror node. Last response: { last_response } "
239+ )
0 commit comments