pre_upload_tests.py
1 #!/usr/bin/env python3 2 """ 3 Comprehensive pre-upload tests for fx-sdk. 4 Run this before uploading to PyPI to catch any issues. 5 """ 6 7 import sys 8 import os 9 import subprocess 10 import importlib 11 from pathlib import Path 12 13 print("=" * 70) 14 print("Pre-Upload Test Suite for fx-sdk") 15 print("=" * 70) 16 17 errors = [] 18 warnings = [] 19 20 def test(name, func): 21 """Run a test and track results.""" 22 try: 23 result = func() 24 if result is False: 25 errors.append(f"❌ {name}: FAILED") 26 return False 27 elif result is True: 28 print(f"✓ {name}") 29 return True 30 else: 31 print(f"✓ {name}: {result}") 32 return True 33 except Exception as e: 34 errors.append(f"❌ {name}: ERROR - {str(e)}") 35 return False 36 37 def warn(name, message): 38 """Add a warning.""" 39 warnings.append(f"⚠ {name}: {message}") 40 41 # Test 1: Package structure 42 print("\n1. Testing Package Structure...") 43 def test_package_structure(): 44 required_files = [ 45 "fx_sdk/__init__.py", 46 "fx_sdk/client.py", 47 "fx_sdk/constants.py", 48 "fx_sdk/utils.py", 49 "fx_sdk/exceptions.py", 50 ] 51 missing = [] 52 for file in required_files: 53 if not os.path.exists(file): 54 missing.append(file) 55 if missing: 56 return f"Missing files: {', '.join(missing)}" 57 return True 58 59 test("Package structure", test_package_structure) 60 61 # Test 2: ABI files 62 print("\n2. Testing ABI Files...") 63 def test_abi_files(): 64 abi_dir = Path("fx_sdk/abis") 65 if not abi_dir.exists(): 66 return "ABI directory not found" 67 68 abi_files = list(abi_dir.glob("*.json")) 69 if len(abi_files) == 0: 70 return "No ABI files found" 71 72 expected_abis = [ 73 "erc20.json", 74 "fxusd.json", 75 "fxusd_base_pool.json", 76 "diamond.json", 77 "market.json", 78 "rebalance_pool.json", 79 "rebalance_pool_registry.json", 80 "steth_treasury.json", 81 "fxn.json", 82 "vefxn.json", 83 "gauge_controller.json", 84 "multipath_converter.json", 85 "steth_gateway.json", 86 "vesting.json", 87 "liquidity_gauge.json", 88 "pool_manager.json", 89 "reserve_pool.json", 90 "saving_fxusd.json", 91 ] 92 93 missing = [] 94 for abi in expected_abis: 95 if not (abi_dir / abi).exists(): 96 missing.append(abi) 97 98 if missing: 99 warn("ABI files", f"Missing ABIs: {', '.join(missing)}") 100 101 return f"Found {len(abi_files)} ABI files" 102 test("ABI files", test_abi_files) 103 104 # Test 3: Import tests 105 print("\n3. Testing Imports...") 106 def test_imports(): 107 try: 108 import fx_sdk 109 if not hasattr(fx_sdk, '__version__'): 110 return "Missing __version__" 111 if not hasattr(fx_sdk, 'ProtocolClient'): 112 return "Missing ProtocolClient export" 113 if not hasattr(fx_sdk, 'constants'): 114 return "Missing constants export" 115 if not hasattr(fx_sdk, 'utils'): 116 return "Missing utils export" 117 if not hasattr(fx_sdk, 'exceptions'): 118 return "Missing exceptions export" 119 return f"Version: {fx_sdk.__version__}" 120 except ImportError as e: 121 return f"Import error: {str(e)}" 122 123 test("Main package import", test_imports) 124 125 def test_submodule_imports(): 126 modules = [ 127 "fx_sdk.client", 128 "fx_sdk.constants", 129 "fx_sdk.utils", 130 "fx_sdk.exceptions", 131 ] 132 failed = [] 133 for module in modules: 134 try: 135 importlib.import_module(module) 136 except ImportError as e: 137 failed.append(f"{module}: {str(e)}") 138 if failed: 139 return f"Failed imports: {', '.join(failed)}" 140 return True 141 142 test("Submodule imports", test_submodule_imports) 143 144 # Test 4: Constants 145 print("\n4. Testing Constants...") 146 def test_constants(): 147 from fx_sdk import constants 148 149 required_constants = [ 150 "FXUSD", 151 "FETH", 152 "RUSD", 153 "FXN", 154 "VEFXN", 155 "XETH", 156 "XCVX", 157 "XWBTC", 158 ] 159 160 missing = [] 161 for const in required_constants: 162 if not hasattr(constants, const): 163 missing.append(const) 164 165 if missing: 166 return f"Missing constants: {', '.join(missing)}" 167 168 # Check that addresses are valid (start with 0x and are 42 chars) 169 for const in required_constants: 170 addr = getattr(constants, const) 171 if not isinstance(addr, str) or not addr.startswith("0x") or len(addr) != 42: 172 return f"Invalid address format for {const}: {addr}" 173 174 return f"All {len(required_constants)} required constants present" 175 176 test("Constants", test_constants) 177 178 # Test 5: Utils 179 print("\n5. Testing Utility Functions...") 180 def test_utils(): 181 from fx_sdk import utils 182 from decimal import Decimal 183 184 # Test wei_to_decimal 185 wei = 1500000000000000000 186 eth = utils.wei_to_decimal(wei) 187 if eth != Decimal("1.5"): 188 return f"wei_to_decimal failed: expected 1.5, got {eth}" 189 190 # Test decimal_to_wei 191 wei_result = utils.decimal_to_wei(Decimal("1.5")) 192 if wei_result != wei: 193 return f"decimal_to_wei failed: expected {wei}, got {wei_result}" 194 195 # Test to_checksum_address 196 addr = "0x742d35cc6634c0532925a3b844bc9e2385c6b0e0" 197 checksum = utils.to_checksum_address(addr) 198 if not checksum.startswith("0x") or len(checksum) != 42: 199 return f"to_checksum_address failed: invalid format" 200 201 return True 202 203 test("Utility functions", test_utils) 204 205 # Test 6: Client initialization 206 print("\n6. Testing Client Initialization...") 207 def test_client_init(): 208 from fx_sdk import ProtocolClient 209 210 # Test read-only initialization 211 try: 212 client = ProtocolClient("https://eth.llamarpc.com") 213 if not hasattr(client, 'w3'): 214 return "Client missing w3 attribute" 215 if not hasattr(client, 'contracts'): 216 return "Client missing contracts attribute" 217 return "Read-only client initialized" 218 except Exception as e: 219 return f"Client initialization failed: {str(e)}" 220 221 test("Client initialization (read-only)", test_client_init) 222 223 # Test 7: Package metadata 224 print("\n7. Testing Package Metadata...") 225 def test_package_metadata(): 226 try: 227 from fx_sdk import __version__ 228 if not __version__ or __version__ == "0.0.0": 229 return "Invalid version number" 230 return f"Version: {__version__}" 231 except: 232 return "Could not read version" 233 234 test("Package version", test_package_metadata) 235 236 # Test 8: Check for common issues 237 print("\n8. Checking for Common Issues...") 238 def test_common_issues(): 239 issues = [] 240 241 # Check for hardcoded paths 242 import re 243 with open("fx_sdk/client.py", "r") as f: 244 content = f.read() 245 if "/Users/" in content or "/home/" in content: 246 issues.append("Hardcoded user paths found") 247 248 # Check for TODO/FIXME in production code 249 with open("fx_sdk/client.py", "r") as f: 250 for i, line in enumerate(f, 1): 251 if "TODO" in line.upper() or "FIXME" in line.upper(): 252 if not line.strip().startswith("#"): 253 issues.append(f"TODO/FIXME in code at line {i}") 254 255 if issues: 256 return f"Issues found: {', '.join(issues)}" 257 return True 258 259 test("Common issues check", test_common_issues) 260 261 # Test 9: Build verification 262 print("\n9. Verifying Build Files...") 263 def test_build_files(): 264 dist_dir = Path("dist") 265 if not dist_dir.exists(): 266 return "dist/ directory not found - run 'python3 -m build' first" 267 268 wheel_file = dist_dir / "fx_sdk-0.1.0-py3-none-any.whl" 269 tar_file = dist_dir / "fx_sdk-0.1.0.tar.gz" 270 271 if not wheel_file.exists(): 272 return "Wheel file not found" 273 if not tar_file.exists(): 274 return "Source distribution not found" 275 276 return f"Build files present (wheel: {wheel_file.stat().st_size/1024:.1f}KB, tar: {tar_file.stat().st_size/1024:.1f}KB)" 277 278 test("Build files", test_build_files) 279 280 # Test 10: Dependencies 281 print("\n10. Testing Dependencies...") 282 def test_dependencies(): 283 try: 284 import web3 285 import eth_account 286 import eth_typing 287 import eth_utils 288 return f"All dependencies importable (web3 {web3.__version__})" 289 except ImportError as e: 290 return f"Missing dependency: {str(e)}" 291 292 test("Dependencies", test_dependencies) 293 294 # Test 11: Optional dependencies 295 print("\n11. Testing Optional Dependencies...") 296 def test_optional_deps(): 297 results = [] 298 299 try: 300 import dotenv 301 results.append("python-dotenv: available") 302 except ImportError: 303 results.append("python-dotenv: not installed (optional)") 304 305 try: 306 from google.colab import userdata 307 results.append("google.colab: available") 308 except ImportError: 309 results.append("google.colab: not available (expected outside Colab)") 310 311 return ", ".join(results) 312 313 test("Optional dependencies", test_optional_deps) 314 315 # Summary 316 print("\n" + "=" * 70) 317 print("Test Summary") 318 print("=" * 70) 319 320 if errors: 321 print(f"\n❌ {len(errors)} ERROR(S) FOUND:") 322 for error in errors: 323 print(f" {error}") 324 print("\n⚠️ DO NOT UPLOAD TO PYPI UNTIL ERRORS ARE FIXED!") 325 sys.exit(1) 326 else: 327 print("\n✅ All critical tests passed!") 328 329 if warnings: 330 print(f"\n⚠️ {len(warnings)} WARNING(S):") 331 for warning in warnings: 332 print(f" {warning}") 333 print("\n⚠️ Review warnings before uploading.") 334 335 print("\n" + "=" * 70) 336 print("✅ Package is ready for PyPI upload!") 337 print("=" * 70) 338 print("\nNext steps:") 339 print("1. Review any warnings above") 340 print("2. Run: python3 -m twine upload dist/*") 341 print("3. Verify on: https://pypi.org/project/fx-sdk/") 342