2025-08-15 11:53:41 -06:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
"""Test script to verify the version parameter fix - direct imports only."""
|
|
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
|
import logging
|
|
|
|
|
|
import os
|
|
|
|
|
|
import re
|
2025-08-15 20:23:14 -06:00
|
|
|
|
import sys
|
2025-08-15 11:53:41 -06:00
|
|
|
|
from urllib.parse import quote
|
|
|
|
|
|
|
2025-08-15 20:23:14 -06:00
|
|
|
|
import httpx
|
|
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Add the project root to the Python path
|
|
|
|
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
|
|
|
|
|
|
|
|
|
|
# Set up logging
|
2025-08-15 20:23:14 -06:00
|
|
|
|
logging.basicConfig(
|
|
|
|
|
|
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
|
|
|
|
)
|
2025-08-15 11:53:41 -06:00
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SimplePackageNotFoundError(Exception):
|
|
|
|
|
|
"""Simple exception for package not found."""
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SimplePyPIClient:
|
|
|
|
|
|
"""Simplified PyPI client for testing."""
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.base_url = "https://pypi.org/pypi"
|
|
|
|
|
|
self.client = httpx.AsyncClient(
|
|
|
|
|
|
timeout=httpx.Timeout(30.0),
|
|
|
|
|
|
headers={
|
|
|
|
|
|
"User-Agent": "test-script/1.0.0",
|
|
|
|
|
|
"Accept": "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
follow_redirects=True,
|
|
|
|
|
|
)
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
async def __aenter__(self):
|
|
|
|
|
|
return self
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
|
|
|
|
await self.client.aclose()
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
async def get_package_info(self, package_name: str, version: str = None):
|
|
|
|
|
|
"""Get package info with optional version."""
|
|
|
|
|
|
if version:
|
|
|
|
|
|
url = f"{self.base_url}/{quote(package_name)}/{quote(version)}/json"
|
|
|
|
|
|
else:
|
|
|
|
|
|
url = f"{self.base_url}/{quote(package_name)}/json"
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
response = await self.client.get(url)
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
if response.status_code == 404:
|
|
|
|
|
|
if version:
|
2025-08-15 20:23:14 -06:00
|
|
|
|
raise SimplePackageNotFoundError(
|
|
|
|
|
|
f"Version {version} not found for package {package_name}"
|
|
|
|
|
|
)
|
2025-08-15 11:53:41 -06:00
|
|
|
|
else:
|
|
|
|
|
|
raise SimplePackageNotFoundError(f"Package {package_name} not found")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_version_format(version: str | None) -> bool:
|
|
|
|
|
|
"""Validate version format."""
|
|
|
|
|
|
if version is None:
|
|
|
|
|
|
return True
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
version_pattern = r"^[0-9]+(?:\.[0-9]+)*(?:[\.\-]?(?:a|b|rc|alpha|beta|dev|pre|post|final)[0-9]*)*$"
|
|
|
|
|
|
return bool(re.match(version_pattern, version.strip(), re.IGNORECASE))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_version_parameter_fix():
|
|
|
|
|
|
"""Test the version parameter functionality."""
|
|
|
|
|
|
logger.info("Testing version parameter fix...")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
async with SimplePyPIClient() as client:
|
|
|
|
|
|
# Test 1: Django 4.2.0 (specific version)
|
|
|
|
|
|
logger.info("Testing Django 4.2.0...")
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = await client.get_package_info("django", "4.2.0")
|
|
|
|
|
|
actual_version = data.get("info", {}).get("version", "")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
if actual_version in ["4.2", "4.2.0"]:
|
2025-08-15 20:23:14 -06:00
|
|
|
|
logger.info(
|
|
|
|
|
|
f"✅ Django 4.2.0 test passed (got version: {actual_version})"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Check dependencies
|
|
|
|
|
|
deps = data.get("info", {}).get("requires_dist", [])
|
|
|
|
|
|
logger.info(f" Dependencies found: {len(deps) if deps else 0}")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Print a few dependencies to show they're different from latest
|
|
|
|
|
|
if deps:
|
|
|
|
|
|
logger.info(f" Sample dependencies: {deps[:3]}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error(f"❌ Expected version 4.2.0, got {actual_version}")
|
|
|
|
|
|
return False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"❌ Django 4.2.0 test failed: {e}")
|
|
|
|
|
|
return False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Test 2: Django latest (no version)
|
|
|
|
|
|
logger.info("Testing Django latest...")
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = await client.get_package_info("django")
|
|
|
|
|
|
latest_version = data.get("info", {}).get("version", "")
|
|
|
|
|
|
logger.info(f"✅ Django latest test passed - version: {latest_version}")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Verify that latest != 4.2.0 (to prove we're getting different results)
|
|
|
|
|
|
if latest_version not in ["4.2", "4.2.0"]:
|
|
|
|
|
|
logger.info("✅ Confirmed: latest version is different from 4.2.0")
|
|
|
|
|
|
else:
|
2025-08-15 20:23:14 -06:00
|
|
|
|
logger.info(
|
|
|
|
|
|
"ℹ️ Latest version happens to be 4.2.0 (unlikely but possible)"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"❌ Django latest test failed: {e}")
|
|
|
|
|
|
return False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Test 3: FastAPI 0.100.0
|
|
|
|
|
|
logger.info("Testing FastAPI 0.100.0...")
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = await client.get_package_info("fastapi", "0.100.0")
|
|
|
|
|
|
actual_version = data.get("info", {}).get("version", "")
|
|
|
|
|
|
if actual_version == "0.100.0":
|
|
|
|
|
|
logger.info("✅ FastAPI 0.100.0 test passed")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Check dependencies
|
|
|
|
|
|
deps = data.get("info", {}).get("requires_dist", [])
|
|
|
|
|
|
logger.info(f" Dependencies found: {len(deps) if deps else 0}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error(f"❌ Expected version 0.100.0, got {actual_version}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"❌ FastAPI 0.100.0 test failed: {e}")
|
|
|
|
|
|
return False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Test 4: NumPy 1.20.0
|
|
|
|
|
|
logger.info("Testing NumPy 1.20.0...")
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = await client.get_package_info("numpy", "1.20.0")
|
|
|
|
|
|
actual_version = data.get("info", {}).get("version", "")
|
|
|
|
|
|
if actual_version == "1.20.0":
|
|
|
|
|
|
logger.info("✅ NumPy 1.20.0 test passed")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Check dependencies
|
|
|
|
|
|
deps = data.get("info", {}).get("requires_dist", [])
|
|
|
|
|
|
logger.info(f" Dependencies found: {len(deps) if deps else 0}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error(f"❌ Expected version 1.20.0, got {actual_version}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"❌ NumPy 1.20.0 test failed: {e}")
|
|
|
|
|
|
return False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Test 5: Non-existent version (should fail)
|
|
|
|
|
|
logger.info("Testing Django 999.999.999 (should fail)...")
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = await client.get_package_info("django", "999.999.999")
|
|
|
|
|
|
logger.error("❌ Expected error for non-existent version but got result")
|
|
|
|
|
|
return False
|
|
|
|
|
|
except SimplePackageNotFoundError:
|
|
|
|
|
|
logger.info("✅ Non-existent version test passed (correctly failed)")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"❌ Unexpected error type: {e}")
|
|
|
|
|
|
return False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Test 6: Pre-release version
|
|
|
|
|
|
logger.info("Testing Django 5.0a1 (pre-release)...")
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = await client.get_package_info("django", "5.0a1")
|
|
|
|
|
|
actual_version = data.get("info", {}).get("version", "")
|
|
|
|
|
|
logger.info(f"✅ Django 5.0a1 test passed - got version: {actual_version}")
|
|
|
|
|
|
except SimplePackageNotFoundError:
|
2025-08-15 20:23:14 -06:00
|
|
|
|
logger.info(
|
|
|
|
|
|
"ℹ️ Django 5.0a1 not found (this is expected for some pre-release versions)"
|
|
|
|
|
|
)
|
2025-08-15 11:53:41 -06:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"❌ Django 5.0a1 test failed: {e}")
|
|
|
|
|
|
return False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_version_validation():
|
|
|
|
|
|
"""Test version validation."""
|
|
|
|
|
|
logger.info("Testing version validation...")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
test_cases = [
|
|
|
|
|
|
("1.0.0", True),
|
|
|
|
|
|
("2.1", True),
|
|
|
|
|
|
("1.0.0a1", True),
|
|
|
|
|
|
("1.0.0b2", True),
|
|
|
|
|
|
("1.0.0rc1", True),
|
|
|
|
|
|
("2.0.0.dev1", True),
|
|
|
|
|
|
("1.0.0-dev", True),
|
|
|
|
|
|
("invalid.version!", False),
|
|
|
|
|
|
("", False),
|
|
|
|
|
|
(None, True),
|
|
|
|
|
|
]
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
all_passed = True
|
|
|
|
|
|
for version, expected in test_cases:
|
|
|
|
|
|
result = validate_version_format(version)
|
|
|
|
|
|
if result == expected:
|
|
|
|
|
|
logger.info(f"✅ Version validation for '{version}': {result}")
|
|
|
|
|
|
else:
|
2025-08-15 20:23:14 -06:00
|
|
|
|
logger.error(
|
|
|
|
|
|
f"❌ Version validation for '{version}': expected {expected}, got {result}"
|
|
|
|
|
|
)
|
2025-08-15 11:53:41 -06:00
|
|
|
|
all_passed = False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
return all_passed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def compare_dependencies():
|
|
|
|
|
|
"""Compare dependencies between different versions."""
|
|
|
|
|
|
logger.info("Comparing dependencies between Django versions...")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
async with SimplePyPIClient() as client:
|
|
|
|
|
|
# Get Django 4.2.0 dependencies
|
|
|
|
|
|
data_420 = await client.get_package_info("django", "4.2.0")
|
|
|
|
|
|
deps_420 = data_420.get("info", {}).get("requires_dist", [])
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Get Django latest dependencies
|
|
|
|
|
|
data_latest = await client.get_package_info("django")
|
|
|
|
|
|
deps_latest = data_latest.get("info", {}).get("requires_dist", [])
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
logger.info(f"Django 4.2.0 dependencies: {len(deps_420) if deps_420 else 0}")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
logger.info(
|
|
|
|
|
|
f"Django latest dependencies: {len(deps_latest) if deps_latest else 0}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Show some dependencies for comparison
|
|
|
|
|
|
if deps_420:
|
|
|
|
|
|
logger.info(f"Django 4.2.0 sample deps: {deps_420[:2]}")
|
|
|
|
|
|
if deps_latest:
|
|
|
|
|
|
logger.info(f"Django latest sample deps: {deps_latest[:2]}")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# They might be the same if 4.2.0 is latest, but structure should be correct
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
|
|
"""Run all tests."""
|
|
|
|
|
|
logger.info("🧪 Starting version parameter fix verification tests...")
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
success = True
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Test version validation
|
|
|
|
|
|
if test_version_validation():
|
|
|
|
|
|
logger.info("✅ Version validation tests passed")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error("❌ Version validation tests failed")
|
|
|
|
|
|
success = False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Test version parameter functionality
|
|
|
|
|
|
if await test_version_parameter_fix():
|
|
|
|
|
|
logger.info("✅ Version parameter fix tests passed")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error("❌ Version parameter fix tests failed")
|
|
|
|
|
|
success = False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
# Compare dependencies
|
|
|
|
|
|
if await compare_dependencies():
|
|
|
|
|
|
logger.info("✅ Dependency comparison test passed")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error("❌ Dependency comparison test failed")
|
|
|
|
|
|
success = False
|
2025-08-15 20:23:14 -06:00
|
|
|
|
|
2025-08-15 11:53:41 -06:00
|
|
|
|
if success:
|
2025-08-15 20:23:14 -06:00
|
|
|
|
logger.info(
|
|
|
|
|
|
"🎉 All tests passed! The version parameter fix is working correctly."
|
|
|
|
|
|
)
|
2025-08-15 11:53:41 -06:00
|
|
|
|
logger.info("")
|
|
|
|
|
|
logger.info("Summary of what was fixed:")
|
|
|
|
|
|
logger.info("- PyPIClient now supports version-specific queries")
|
|
|
|
|
|
logger.info("- query_package_dependencies now uses the version parameter")
|
|
|
|
|
|
logger.info("- Added version format validation")
|
|
|
|
|
|
logger.info("- Added proper error handling for non-existent versions")
|
|
|
|
|
|
return 0
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error("❌ Some tests failed!")
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
exit_code = asyncio.run(main())
|
2025-08-15 20:23:14 -06:00
|
|
|
|
sys.exit(exit_code)
|