Files
aitbc/tests/e2e/test_aitbc_e2e_fixed.py
aitbc1 f0535d3881
Some checks failed
AITBC CI/CD Pipeline / lint-and-test (3.11) (push) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.12) (push) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.13) (push) Has been cancelled
AITBC CI/CD Pipeline / test-cli (push) Has been cancelled
AITBC CI/CD Pipeline / test-services (push) Has been cancelled
AITBC CI/CD Pipeline / test-production-services (push) Has been cancelled
AITBC CI/CD Pipeline / security-scan (push) Has been cancelled
AITBC CI/CD Pipeline / build (push) Has been cancelled
AITBC CI/CD Pipeline / deploy-staging (push) Has been cancelled
AITBC CI/CD Pipeline / deploy-production (push) Has been cancelled
AITBC CI/CD Pipeline / performance-test (push) Has been cancelled
AITBC CI/CD Pipeline / docs (push) Has been cancelled
AITBC CI/CD Pipeline / release (push) Has been cancelled
AITBC CI/CD Pipeline / notify (push) Has been cancelled
Security Scanning / Bandit Security Scan (apps/coordinator-api/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (cli/aitbc_cli) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-core/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-crypto/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-sdk/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (tests) (push) Has been cancelled
Security Scanning / CodeQL Security Analysis (javascript) (push) Has been cancelled
Security Scanning / CodeQL Security Analysis (python) (push) Has been cancelled
Security Scanning / Dependency Security Scan (push) Has been cancelled
Security Scanning / Container Security Scan (push) Has been cancelled
Security Scanning / OSSF Scorecard (push) Has been cancelled
Security Scanning / Security Summary Report (push) Has been cancelled
fix: add missing database commit and remove unused agent service files
- Add conn.commit() to agent registration in agent-registry
- Remove unused integration_layer.py and coordinator.py from agent-services
- Fix blockchain RPC endpoint from /rpc/sync to /rpc/syncStatus
- Replace Annotated[Session, Depends(get_session)] with Session = Depends(get_session) for cleaner dependency injection syntax across marketplace routers
2026-03-24 13:20:56 +01:00

354 lines
13 KiB
Python
Executable File

#!/usr/bin/env python3
"""
End-to-End Test for AITBC GPU Marketplace
Tests the complete workflow: User Registration → GPU Booking → Task Execution → Payment
"""
import requests
import json
import time
import uuid
import sys
from typing import Dict, Optional
class AITBCE2ETest:
def __init__(self, base_url: str = "http://localhost:8000"):
self.base_url = base_url
self.session = requests.Session()
self.test_user = None
self.auth_token = None
self.gpu_id = None
self.booking_id = None
def log(self, message: str, level: str = "INFO"):
"""Log test progress"""
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] {level}: {message}")
def make_request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
"""Make HTTP request with error handling"""
url = f"{self.base_url}/v1{endpoint}" # All API routes are under /v1
headers = kwargs.get('headers', {})
if self.auth_token:
headers['Authorization'] = f'Bearer {self.auth_token}'
kwargs['headers'] = headers
try:
response = self.session.request(method, url, timeout=30, **kwargs)
self.log(f"{method} {endpoint}{response.status_code}")
return response
except requests.exceptions.RequestException as e:
self.log(f"Request failed: {e}", "ERROR")
raise
def test_health_check(self) -> bool:
"""Test if services are healthy"""
self.log("Checking service health...")
try:
# Check coordinator health
resp = self.session.get(f"{self.base_url}/health", timeout=10)
if resp.status_code == 200:
self.log("✓ Coordinator API healthy")
else:
self.log(f"✗ Coordinator API unhealthy: {resp.status_code}", "ERROR")
return False
# Check blockchain health
try:
resp = self.session.get('http://localhost:8026/health', timeout=10)
if resp.status_code == 200:
self.log("✓ Blockchain node healthy")
else:
self.log(f"⚠ Blockchain health check failed: {resp.status_code}", "WARN")
except:
self.log("⚠ Could not reach blockchain health endpoint", "WARN")
return True
except Exception as e:
self.log(f"Health check failed: {e}", "ERROR")
return False
def test_user_registration(self) -> bool:
"""Test user registration"""
self.log("Testing user registration...")
# Generate unique test user
unique_id = str(uuid.uuid4())[:8]
self.test_user = {
"username": f"e2e_test_user_{unique_id}",
"email": f"e2e_test_{unique_id}@aitbc.test",
"password": "SecurePass123!",
"full_name": "E2E Test User"
}
try:
resp = self.make_request(
'POST',
'/users/register',
json=self.test_user
)
if resp.status_code in [200, 201]:
data = resp.json()
# Extract token from response if available
if isinstance(data, dict) and 'access_token' in data:
self.auth_token = data['access_token']
elif isinstance(data, dict) and 'token' in data:
self.auth_token = data['token']
self.log("✓ User registration successful")
return True
elif resp.status_code == 409:
# User might already exist, try login
self.log("User already exists, attempting login...", "WARN")
return self.test_user_login()
else:
self.log(f"✗ Registration failed: {resp.status_code} - {resp.text}", "ERROR")
return False
except Exception as e:
self.log(f"Registration error: {e}", "ERROR")
return False
def test_user_login(self) -> bool:
"""Test user login"""
self.log("Testing user login...")
if not self.test_user:
self.log("No test user defined", "ERROR")
return False
try:
resp = self.make_request(
'POST',
'/users/login',
json={
"username": self.test_user["username"],
"password": self.test_user["password"]
}
)
if resp.status_code == 200:
data = resp.json()
if isinstance(data, dict) and 'access_token' in data:
self.auth_token = data['access_token']
elif isinstance(data, dict) and 'token' in data:
self.auth_token = data['token']
self.log("✓ User login successful")
return True
else:
self.log(f"✗ Login failed: {resp.status_code} - {resp.text}", "ERROR")
return False
except Exception as e:
self.log(f"Login error: {e}", "ERROR")
return False
def test_get_available_gpus(self) -> bool:
"""Test retrieving available GPUs"""
self.log("Testing GPU availability...")
try:
resp = self.make_request('GET', '/marketplace/gpu/list')
if resp.status_code == 200:
data = resp.json()
# Handle different possible response formats
if isinstance(data, list):
gpus = data
elif isinstance(data, dict) and 'gpus' in data:
gpus = data['gpus']
elif isinstance(data, dict) and 'data' in data:
gpus = data['data']
else:
gpus = [data] if data else []
if gpus:
# Select first available GPU for testing
self.gpu_id = gpus[0].get('id') if isinstance(gpus[0], dict) else gpus[0]
self.log(f"✓ Found {len(gpus)} available GPUs, selected GPU {self.gpu_id}")
return True
else:
self.log("⚠ No GPUs available for testing", "WARN")
return False
else:
self.log(f"✗ Failed to get GPUs: {resp.status_code} - {resp.text}", "ERROR")
return False
except Exception as e:
self.log(f"Error getting GPUs: {e}", "ERROR")
return False
def test_book_gpu(self) -> bool:
"""Test booking a GPU"""
self.log("Testing GPU booking...")
if not self.gpu_id:
self.log("No GPU ID available for booking", "ERROR")
return False
try:
booking_data = {
"gpu_id": str(self.gpu_id),
"duration_hours": 1, # Short duration for testing
"max_price_per_hour": 10.0
}
resp = self.make_request(
'POST',
f'/marketplace/gpu/{self.gpu_id}/book',
json=booking_data
)
if resp.status_code in [200, 201]:
data = resp.json()
# Extract booking ID from response
if isinstance(data, dict):
self.booking_id = data.get('booking_id') or data.get('id')
self.log(f"✓ GPU booked successfully: {self.booking_id}")
return True
else:
self.log(f"✗ GPU booking failed: {resp.status_code} - {resp.text}", "ERROR")
return False
except Exception as e:
self.log(f"Booking error: {e}", "ERROR")
return False
def test_submit_task(self) -> bool:
"""Test submitting a task to the booked GPU"""
self.log("Testing task submission...")
if not self.gpu_id:
self.log("No GPU ID available", "ERROR")
return False
try:
# Simple test task - using the ollama task endpoint from marketplace_gpu
task_data = {
"gpu_id": str(self.gpu_id),
"prompt": "Hello AITBC E2E Test! Please respond with confirmation.",
"model": "llama2",
"max_tokens": 50
}
resp = self.make_request(
'POST',
'/tasks/ollama',
json=task_data
)
if resp.status_code in [200, 201]:
data = resp.json()
self.log(f"✓ Task submitted successfully")
return True
else:
self.log(f"✗ Task submission failed: {resp.status_code} - {resp.text}", "ERROR")
return False
except Exception as e:
self.log(f"Task submission error: {e}", "ERROR")
return False
def test_get_task_result(self) -> bool:
"""Test retrieving task result"""
self.log("Testing task result retrieval...")
# In a real test, we would wait for task completion
# For now, we'll just test that we can make the attempt
self.log("⚠ Skipping task result check (would require waiting for completion)", "INFO")
return True
def test_cleanup(self) -> bool:
"""Clean up test resources"""
self.log("Cleaning up test resources...")
success = True
# Release GPU if booked
if self.booking_id and self.gpu_id:
try:
resp = self.make_request(
'POST',
f'/marketplace/gpu/{self.gpu_id}/release'
)
if resp.status_code in [200, 204]:
self.log("✓ GPU booking released")
else:
self.log(f"⚠ Failed to release booking: {resp.status_code}", "WARN")
except Exception as e:
self.log(f"Error releasing booking: {e}", "WARN")
success = False
return success
def run_full_test(self) -> bool:
"""Run the complete E2E test"""
self.log("=" * 60)
self.log("Starting AITBC End-to-End Test")
self.log("=" * 60)
test_steps = [
("Health Check", self.test_health_check),
("User Registration/Login", self.test_user_registration),
("Get Available GPUs", self.test_get_available_gpus),
("Book GPU", self.test_book_gpu),
("Submit Task", self.test_submit_task),
("Get Task Result", self.test_get_task_result),
("Cleanup", self.test_cleanup)
]
passed = 0
total = len(test_steps)
for step_name, test_func in test_steps:
self.log(f"\n--- {step_name} ---")
try:
if test_func():
passed += 1
self.log(f"{step_name} PASSED")
else:
self.log(f"{step_name} FAILED", "ERROR")
except Exception as e:
self.log(f"{step_name} ERROR: {e}", "ERROR")
self.log("\n" + "=" * 60)
self.log(f"E2E Test Results: {passed}/{total} steps passed")
self.log("=" * 60)
if passed == total:
self.log("🎉 ALL TESTS PASSED!")
return True
else:
self.log(f"{total - passed} TEST(S) FAILED")
return False
def main():
"""Main test runner"""
import argparse
parser = argparse.ArgumentParser(description='AITBC End-to-End Test')
parser.add_argument('--url', default='http://localhost:8000',
help='Base URL for AITBC services')
parser.add_argument('--verbose', '-v', action='store_true',
help='Enable verbose logging')
args = parser.parse_args()
test = AITBCE2ETest(base_url=args.url)
try:
success = test.run_full_test()
sys.exit(0 if success else 1)
except KeyboardInterrupt:
print("\nTest interrupted by user")
sys.exit(1)
except Exception as e:
print(f"Unexpected error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()