/ tests / pre_upload_tests.py
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