test_transactions.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. #!/usr/bin/env python3
  2. """
  3. Test suite for CDK FFI wallet and transaction operations
  4. """
  5. import asyncio
  6. import os
  7. import sys
  8. import tempfile
  9. from pathlib import Path
  10. # Setup paths before importing cdk_ffi
  11. repo_root = Path(__file__).parent.parent.parent.parent
  12. bindings_path = repo_root / "target" / "bindings" / "python"
  13. lib_path = repo_root / "target" / "release"
  14. # Copy the library to the bindings directory so Python can find it
  15. import shutil
  16. lib_file = "libcdk_ffi.dylib" if sys.platform == "darwin" else "libcdk_ffi.so"
  17. src_lib = lib_path / lib_file
  18. dst_lib = bindings_path / lib_file
  19. if src_lib.exists() and not dst_lib.exists():
  20. shutil.copy2(src_lib, dst_lib)
  21. # Add target/bindings/python to path to load cdk_ffi module
  22. sys.path.insert(0, str(bindings_path))
  23. import cdk_ffi
  24. # Transaction Tests (using explicit transactions)
  25. async def test_increment_keyset_counter_commit():
  26. """Test that increment_keyset_counter works and persists after commit"""
  27. print("\n=== Test: Increment Keyset Counter with Commit ===")
  28. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  29. db_path = tmp.name
  30. try:
  31. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  32. db = cdk_ffi.create_wallet_db(backend)
  33. keyset_id = cdk_ffi.Id(hex="004146bdf4a9afab")
  34. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  35. keyset_info = cdk_ffi.KeySetInfo(
  36. id=keyset_id.hex,
  37. unit=cdk_ffi.CurrencyUnit.SAT(),
  38. active=True,
  39. input_fee_ppk=0
  40. )
  41. # Setup
  42. tx = await db.begin_db_transaction()
  43. await tx.add_mint(mint_url, None)
  44. await tx.add_mint_keysets(mint_url, [keyset_info])
  45. await tx.commit()
  46. # Increment counter in transaction
  47. tx = await db.begin_db_transaction()
  48. counter1 = await tx.increment_keyset_counter(keyset_id, 1)
  49. counter2 = await tx.increment_keyset_counter(keyset_id, 5)
  50. await tx.commit()
  51. assert counter1 == 1, f"Expected counter 1, got {counter1}"
  52. assert counter2 == 6, f"Expected counter 6, got {counter2}"
  53. print("✓ Counters incremented correctly")
  54. # Verify persistence
  55. tx_read = await db.begin_db_transaction()
  56. counter3 = await tx_read.increment_keyset_counter(keyset_id, 0)
  57. await tx_read.rollback()
  58. assert counter3 == 6, f"Expected persisted counter 6, got {counter3}"
  59. print("✓ Counter persisted after commit")
  60. print("✓ Test passed: Counter increments and commits work")
  61. finally:
  62. if os.path.exists(db_path):
  63. os.unlink(db_path)
  64. async def test_implicit_rollback_on_drop():
  65. """Test that transactions are implicitly rolled back when dropped"""
  66. print("\n=== Test: Implicit Rollback on Drop ===")
  67. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  68. db_path = tmp.name
  69. try:
  70. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  71. db = cdk_ffi.create_wallet_db(backend)
  72. keyset_id = cdk_ffi.Id(hex="004146bdf4a9afab")
  73. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  74. # Setup
  75. tx = await db.begin_db_transaction()
  76. await tx.add_mint(mint_url, None)
  77. keyset_info = cdk_ffi.KeySetInfo(
  78. id=keyset_id.hex,
  79. unit=cdk_ffi.CurrencyUnit.SAT(),
  80. active=True,
  81. input_fee_ppk=0
  82. )
  83. await tx.add_mint_keysets(mint_url, [keyset_info])
  84. await tx.commit()
  85. # Get initial counter
  86. tx_read = await db.begin_db_transaction()
  87. initial_counter = await tx_read.increment_keyset_counter(keyset_id, 0)
  88. await tx_read.rollback()
  89. print(f"Initial counter: {initial_counter}")
  90. # Increment without commit
  91. tx_no_commit = await db.begin_db_transaction()
  92. incremented = await tx_no_commit.increment_keyset_counter(keyset_id, 10)
  93. print(f"Counter incremented to {incremented} (not committed)")
  94. del tx_no_commit
  95. await asyncio.sleep(0.5)
  96. print("Transaction dropped (should trigger implicit rollback)")
  97. # Verify rollback
  98. tx_verify = await db.begin_db_transaction()
  99. final_counter = await tx_verify.increment_keyset_counter(keyset_id, 0)
  100. await tx_verify.rollback()
  101. assert final_counter == initial_counter, \
  102. f"Expected counter to rollback to {initial_counter}, got {final_counter}"
  103. print("✓ Implicit rollback works correctly")
  104. print("✓ Test passed: Implicit rollback on drop works")
  105. finally:
  106. if os.path.exists(db_path):
  107. os.unlink(db_path)
  108. async def test_explicit_rollback():
  109. """Test explicit rollback of transaction changes"""
  110. print("\n=== Test: Explicit Rollback ===")
  111. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  112. db_path = tmp.name
  113. try:
  114. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  115. db = cdk_ffi.create_wallet_db(backend)
  116. keyset_id = cdk_ffi.Id(hex="004146bdf4a9afab")
  117. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  118. # Setup
  119. tx = await db.begin_db_transaction()
  120. await tx.add_mint(mint_url, None)
  121. keyset_info = cdk_ffi.KeySetInfo(
  122. id=keyset_id.hex,
  123. unit=cdk_ffi.CurrencyUnit.SAT(),
  124. active=True,
  125. input_fee_ppk=0
  126. )
  127. await tx.add_mint_keysets(mint_url, [keyset_info])
  128. counter_initial = await tx.increment_keyset_counter(keyset_id, 5)
  129. await tx.commit()
  130. print(f"Initial counter: {counter_initial}")
  131. # Increment and rollback
  132. tx_rollback = await db.begin_db_transaction()
  133. counter_incremented = await tx_rollback.increment_keyset_counter(keyset_id, 100)
  134. print(f"Counter incremented to {counter_incremented} in transaction")
  135. await tx_rollback.rollback()
  136. print("Explicitly rolled back transaction")
  137. # Verify rollback
  138. tx_verify = await db.begin_db_transaction()
  139. counter_after = await tx_verify.increment_keyset_counter(keyset_id, 0)
  140. await tx_verify.rollback()
  141. assert counter_after == counter_initial, \
  142. f"Expected counter {counter_initial}, got {counter_after}"
  143. print("✓ Explicit rollback works correctly")
  144. print("✓ Test passed: Explicit rollback works")
  145. finally:
  146. if os.path.exists(db_path):
  147. os.unlink(db_path)
  148. async def test_transaction_reads():
  149. """Test reading data within transactions"""
  150. print("\n=== Test: Transaction Reads ===")
  151. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  152. db_path = tmp.name
  153. try:
  154. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  155. db = cdk_ffi.create_wallet_db(backend)
  156. keyset_id = cdk_ffi.Id(hex="004146bdf4a9afab")
  157. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  158. # Add keyset in transaction and read within same transaction
  159. tx = await db.begin_db_transaction()
  160. await tx.add_mint(mint_url, None)
  161. keyset_info = cdk_ffi.KeySetInfo(
  162. id=keyset_id.hex,
  163. unit=cdk_ffi.CurrencyUnit.SAT(),
  164. active=True,
  165. input_fee_ppk=0
  166. )
  167. await tx.add_mint_keysets(mint_url, [keyset_info])
  168. keyset_read = await tx.get_keyset_by_id(keyset_id)
  169. assert keyset_read is not None, "Should read within transaction"
  170. assert keyset_read.id == keyset_id.hex, "Keyset ID should match"
  171. print("✓ Read keyset within transaction")
  172. await tx.commit()
  173. # Read from new transaction
  174. tx_new = await db.begin_db_transaction()
  175. keyset_read2 = await tx_new.get_keyset_by_id(keyset_id)
  176. assert keyset_read2 is not None, "Should read committed keyset"
  177. await tx_new.rollback()
  178. print("✓ Read keyset in new transaction")
  179. print("✓ Test passed: Transaction reads work")
  180. finally:
  181. if os.path.exists(db_path):
  182. os.unlink(db_path)
  183. async def test_multiple_increments_same_transaction():
  184. """Test multiple increments in same transaction"""
  185. print("\n=== Test: Multiple Increments in Same Transaction ===")
  186. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  187. db_path = tmp.name
  188. try:
  189. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  190. db = cdk_ffi.create_wallet_db(backend)
  191. keyset_id = cdk_ffi.Id(hex="004146bdf4a9afab")
  192. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  193. # Setup
  194. tx = await db.begin_db_transaction()
  195. await tx.add_mint(mint_url, None)
  196. keyset_info = cdk_ffi.KeySetInfo(
  197. id=keyset_id.hex,
  198. unit=cdk_ffi.CurrencyUnit.SAT(),
  199. active=True,
  200. input_fee_ppk=0
  201. )
  202. await tx.add_mint_keysets(mint_url, [keyset_info])
  203. await tx.commit()
  204. # Multiple increments in one transaction
  205. tx = await db.begin_db_transaction()
  206. counters = []
  207. for i in range(1, 6):
  208. counter = await tx.increment_keyset_counter(keyset_id, 1)
  209. counters.append(counter)
  210. expected = list(range(1, 6))
  211. assert counters == expected, f"Expected {expected}, got {counters}"
  212. print(f"✓ Counters incremented: {counters}")
  213. await tx.commit()
  214. # Verify final value
  215. tx_verify = await db.begin_db_transaction()
  216. final = await tx_verify.increment_keyset_counter(keyset_id, 0)
  217. await tx_verify.rollback()
  218. assert final == 5, f"Expected final counter 5, got {final}"
  219. print("✓ Final counter value correct")
  220. print("✓ Test passed: Multiple increments work")
  221. finally:
  222. if os.path.exists(db_path):
  223. os.unlink(db_path)
  224. async def test_transaction_atomicity():
  225. """Test that transaction rollback reverts all changes"""
  226. print("\n=== Test: Transaction Atomicity ===")
  227. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  228. db_path = tmp.name
  229. try:
  230. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  231. db = cdk_ffi.create_wallet_db(backend)
  232. mint_url1 = cdk_ffi.MintUrl(url="https://mint1.example.com")
  233. mint_url2 = cdk_ffi.MintUrl(url="https://mint2.example.com")
  234. keyset_id = cdk_ffi.Id(hex="004146bdf4a9afab")
  235. # Transaction with multiple operations
  236. tx = await db.begin_db_transaction()
  237. await tx.add_mint(mint_url1, None)
  238. await tx.add_mint(mint_url2, None)
  239. keyset_info = cdk_ffi.KeySetInfo(
  240. id=keyset_id.hex,
  241. unit=cdk_ffi.CurrencyUnit.SAT(),
  242. active=True,
  243. input_fee_ppk=0
  244. )
  245. await tx.add_mint_keysets(mint_url1, [keyset_info])
  246. await tx.increment_keyset_counter(keyset_id, 42)
  247. print("✓ Performed multiple operations")
  248. # Rollback
  249. await tx.rollback()
  250. print("✓ Rolled back transaction")
  251. # Verify nothing persisted
  252. tx_read = await db.begin_db_transaction()
  253. keyset_read = await tx_read.get_keyset_by_id(keyset_id)
  254. await tx_read.rollback()
  255. assert keyset_read is None, "Keyset should not exist after rollback"
  256. print("✓ Nothing persisted after rollback")
  257. # Now commit
  258. tx2 = await db.begin_db_transaction()
  259. await tx2.add_mint(mint_url1, None)
  260. await tx2.add_mint(mint_url2, None)
  261. await tx2.add_mint_keysets(mint_url1, [keyset_info])
  262. await tx2.increment_keyset_counter(keyset_id, 42)
  263. await tx2.commit()
  264. print("✓ Committed transaction")
  265. # Verify persistence
  266. tx_verify = await db.begin_db_transaction()
  267. keyset_after = await tx_verify.get_keyset_by_id(keyset_id)
  268. assert keyset_after is not None, "Keyset should exist after commit"
  269. counter_after = await tx_verify.increment_keyset_counter(keyset_id, 0)
  270. await tx_verify.rollback()
  271. assert counter_after == 42, f"Expected counter 42, got {counter_after}"
  272. print("✓ All operations persisted after commit")
  273. print("✓ Test passed: Transaction atomicity works")
  274. finally:
  275. if os.path.exists(db_path):
  276. os.unlink(db_path)
  277. # Wallet Tests (using direct wallet methods without explicit transactions)
  278. async def test_wallet_creation():
  279. """Test creating a wallet with SQLite backend"""
  280. print("\n=== Test: Wallet Creation ===")
  281. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  282. db_path = tmp.name
  283. try:
  284. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  285. db = cdk_ffi.create_wallet_db(backend)
  286. print("✓ Wallet database created")
  287. # Verify database is accessible
  288. mint_quotes = await db.get_mint_quotes()
  289. assert isinstance(mint_quotes, list), "get_mint_quotes should return a list"
  290. print("✓ Wallet database accessible")
  291. print("✓ Test passed: Wallet creation works")
  292. finally:
  293. if os.path.exists(db_path):
  294. os.unlink(db_path)
  295. async def test_wallet_mint_management():
  296. """Test adding and querying mints"""
  297. print("\n=== Test: Wallet Mint Management ===")
  298. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  299. db_path = tmp.name
  300. try:
  301. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  302. db = cdk_ffi.create_wallet_db(backend)
  303. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  304. # Add mint (using transaction)
  305. tx = await db.begin_db_transaction()
  306. await tx.add_mint(mint_url, None)
  307. await tx.commit()
  308. print("✓ Added mint to wallet")
  309. # Get specific mint (read-only, can use db directly)
  310. await db.get_mint(mint_url)
  311. print("✓ Retrieved mint from database")
  312. # Remove mint (using transaction)
  313. tx = await db.begin_db_transaction()
  314. await tx.remove_mint(mint_url)
  315. await tx.commit()
  316. print("✓ Removed mint from wallet")
  317. # Verify removal
  318. mint_info_after = await db.get_mint(mint_url)
  319. assert mint_info_after is None, "Mint should be removed"
  320. print("✓ Verified mint removal")
  321. print("✓ Test passed: Mint management works")
  322. finally:
  323. if os.path.exists(db_path):
  324. os.unlink(db_path)
  325. async def test_wallet_keyset_management():
  326. """Test adding and querying keysets"""
  327. print("\n=== Test: Wallet Keyset Management ===")
  328. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  329. db_path = tmp.name
  330. try:
  331. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  332. db = cdk_ffi.create_wallet_db(backend)
  333. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  334. keyset_id = cdk_ffi.Id(hex="004146bdf4a9afab")
  335. # Add mint and keyset (using transaction)
  336. tx = await db.begin_db_transaction()
  337. await tx.add_mint(mint_url, None)
  338. keyset_info = cdk_ffi.KeySetInfo(
  339. id=keyset_id.hex,
  340. unit=cdk_ffi.CurrencyUnit.SAT(),
  341. active=True,
  342. input_fee_ppk=0
  343. )
  344. await tx.add_mint_keysets(mint_url, [keyset_info])
  345. await tx.commit()
  346. print("✓ Added mint and keyset")
  347. # Query keyset by ID (read-only)
  348. keyset = await db.get_keyset_by_id(keyset_id)
  349. assert keyset is not None, "Keyset should exist"
  350. assert keyset.id == keyset_id.hex, "Keyset ID should match"
  351. print(f"✓ Retrieved keyset: {keyset.id}")
  352. # Query keysets for mint (read-only)
  353. keysets = await db.get_mint_keysets(mint_url)
  354. assert keysets is not None and len(keysets) > 0, "Should have keysets for mint"
  355. print(f"✓ Retrieved {len(keysets)} keyset(s) for mint")
  356. print("✓ Test passed: Keyset management works")
  357. finally:
  358. if os.path.exists(db_path):
  359. os.unlink(db_path)
  360. async def test_wallet_keyset_counter():
  361. """Test keyset counter operations"""
  362. print("\n=== Test: Wallet Keyset Counter ===")
  363. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  364. db_path = tmp.name
  365. try:
  366. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  367. db = cdk_ffi.create_wallet_db(backend)
  368. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  369. keyset_id = cdk_ffi.Id(hex="004146bdf4a9afab")
  370. # Setup (using transaction)
  371. tx = await db.begin_db_transaction()
  372. await tx.add_mint(mint_url, None)
  373. keyset_info = cdk_ffi.KeySetInfo(
  374. id=keyset_id.hex,
  375. unit=cdk_ffi.CurrencyUnit.SAT(),
  376. active=True,
  377. input_fee_ppk=0
  378. )
  379. await tx.add_mint_keysets(mint_url, [keyset_info])
  380. await tx.commit()
  381. print("✓ Setup complete")
  382. # Increment counter (using transaction)
  383. tx = await db.begin_db_transaction()
  384. counter1 = await tx.increment_keyset_counter(keyset_id, 1)
  385. counter2 = await tx.increment_keyset_counter(keyset_id, 5)
  386. counter3 = await tx.increment_keyset_counter(keyset_id, 0)
  387. await tx.commit()
  388. print(f"✓ Counter after +1: {counter1}")
  389. assert counter1 == 1, f"Expected counter 1, got {counter1}"
  390. print(f"✓ Counter after +5: {counter2}")
  391. assert counter2 == 6, f"Expected counter 6, got {counter2}"
  392. print(f"✓ Current counter: {counter3}")
  393. assert counter3 == 6, f"Expected counter 6, got {counter3}"
  394. print("✓ Test passed: Keyset counter works")
  395. finally:
  396. if os.path.exists(db_path):
  397. os.unlink(db_path)
  398. async def test_wallet_quotes():
  399. """Test mint and melt quote operations"""
  400. print("\n=== Test: Wallet Quote Operations ===")
  401. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  402. db_path = tmp.name
  403. try:
  404. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  405. db = cdk_ffi.create_wallet_db(backend)
  406. mint_url = cdk_ffi.MintUrl(url="https://testmint.example.com")
  407. # Add mint (using transaction)
  408. tx = await db.begin_db_transaction()
  409. await tx.add_mint(mint_url, None)
  410. await tx.commit()
  411. print("✓ Added mint")
  412. # Query quotes (read-only)
  413. mint_quotes = await db.get_mint_quotes()
  414. assert isinstance(mint_quotes, list), "get_mint_quotes should return a list"
  415. print(f"✓ Retrieved {len(mint_quotes)} mint quote(s)")
  416. melt_quotes = await db.get_melt_quotes()
  417. assert isinstance(melt_quotes, list), "get_melt_quotes should return a list"
  418. print(f"✓ Retrieved {len(melt_quotes)} melt quote(s)")
  419. print("✓ Test passed: Quote operations work")
  420. finally:
  421. if os.path.exists(db_path):
  422. os.unlink(db_path)
  423. async def test_wallet_proofs_by_ys():
  424. """Test retrieving proofs by Y values"""
  425. print("\n=== Test: Wallet Get Proofs by Y Values ===")
  426. with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
  427. db_path = tmp.name
  428. try:
  429. backend = cdk_ffi.WalletDbBackend.SQLITE(path=db_path)
  430. db = cdk_ffi.create_wallet_db(backend)
  431. # Test with empty list
  432. proofs = await db.get_proofs_by_ys([])
  433. assert len(proofs) == 0, f"Expected 0 proofs, got {len(proofs)}"
  434. print("✓ get_proofs_by_ys returns empty for empty input")
  435. print("✓ Test passed: get_proofs_by_ys works")
  436. finally:
  437. if os.path.exists(db_path):
  438. os.unlink(db_path)
  439. async def main():
  440. """Run all tests"""
  441. print("Starting CDK FFI Wallet and Transaction Tests")
  442. print("=" * 50)
  443. tests = [
  444. # Transaction tests
  445. ("Increment Counter with Commit", test_increment_keyset_counter_commit),
  446. ("Implicit Rollback on Drop", test_implicit_rollback_on_drop),
  447. ("Explicit Rollback", test_explicit_rollback),
  448. ("Transaction Reads", test_transaction_reads),
  449. ("Multiple Increments", test_multiple_increments_same_transaction),
  450. ("Transaction Atomicity", test_transaction_atomicity),
  451. # Wallet tests (read methods + write via transactions)
  452. ("Wallet Creation", test_wallet_creation),
  453. ("Wallet Mint Management", test_wallet_mint_management),
  454. ("Wallet Keyset Management", test_wallet_keyset_management),
  455. ("Wallet Keyset Counter", test_wallet_keyset_counter),
  456. ("Wallet Quote Operations", test_wallet_quotes),
  457. ("Wallet Get Proofs by Y Values", test_wallet_proofs_by_ys),
  458. ]
  459. passed = 0
  460. failed = 0
  461. for test_name, test_func in tests:
  462. try:
  463. await test_func()
  464. passed += 1
  465. except Exception as e:
  466. failed += 1
  467. print(f"\n✗ Test failed: {test_name}")
  468. print(f"Error: {e}")
  469. import traceback
  470. traceback.print_exc()
  471. print("\n" + "=" * 50)
  472. print(f"Test Results: {passed} passed, {failed} failed")
  473. print("=" * 50)
  474. return 0 if failed == 0 else 1
  475. if __name__ == "__main__":
  476. exit_code = asyncio.run(main())
  477. sys.exit(exit_code)