amaye15's picture
Feat - More Users - Loads
dc21ba4
import asyncio
import aiohttp
import time
import argparse
from faker import Faker
class AuthClient:
"""
Asynchronous Python client for interacting with the Authentication API
"""
def __init__(self, base_url="http://localhost:7860/api"):
"""
Initialize the client with the API base URL
Args:
base_url (str): The base URL of the API
"""
self.base_url = base_url
self.token = None
self.session = None
async def __aenter__(self):
"""Create and enter an aiohttp session"""
self.session = aiohttp.ClientSession()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Close the aiohttp session"""
if self.session:
await self.session.close()
async def _get_session(self):
"""Get or create an aiohttp session"""
if self.session is None:
self.session = aiohttp.ClientSession()
return self.session
async def register(self, email, password):
"""
Register a new user
Args:
email (str): User's email
password (str): User's password (should be at least 8 characters)
Returns:
dict: The user data returned by the API
Raises:
Exception: If registration fails
"""
url = f"{self.base_url}/register"
data = {
"email": email,
"password": password
}
session = await self._get_session()
async with session.post(url, json=data) as response:
if response.status == 201:
return await response.json()
else:
error_data = await response.json()
error_detail = error_data.get("detail", "Unknown error")
raise Exception(f"Registration failed: {error_detail} (Status: {response.status})")
async def login(self, email, password):
"""
Login to obtain an authentication token
Args:
email (str): User's email
password (str): User's password
Returns:
dict: The token data returned by the API
Raises:
Exception: If login fails
"""
url = f"{self.base_url}/login"
data = {
"email": email,
"password": password
}
session = await self._get_session()
async with session.post(url, json=data) as response:
if response.status == 200:
token_data = await response.json()
self.token = token_data["access_token"]
return token_data
else:
error_data = await response.json()
error_detail = error_data.get("detail", "Unknown error")
raise Exception(f"Login failed: {error_detail} (Status: {response.status})")
async def get_current_user(self):
"""
Get information about the current logged-in user
Returns:
dict: The user data returned by the API
Raises:
Exception: If not authenticated or request fails
"""
if not self.token:
raise Exception("Not authenticated. Please login first.")
url = f"{self.base_url}/users/me"
headers = {"Authorization": f"Bearer {self.token}"}
session = await self._get_session()
async with session.get(url, headers=headers) as response:
if response.status == 200:
return await response.json()
else:
error_data = await response.json()
error_detail = error_data.get("detail", "Unknown error")
raise Exception(f"Failed to get user info: {error_detail} (Status: {response.status})")
def logout(self):
"""Clear the authentication token"""
self.token = None
async def load_test(num_users=10, concurrency=5, base_url="http://localhost:7860/api"):
"""
Run a load test with multiple simulated users
Args:
num_users (int): Total number of users to simulate
concurrency (int): Number of concurrent users
base_url (str): The base URL of the API
"""
fake = Faker()
start_time = time.time()
completed = 0
success_count = 0
failure_count = 0
# Semaphore to limit concurrency
sem = asyncio.Semaphore(concurrency)
# For progress tracking
progress_lock = asyncio.Lock()
async def run_single_user():
nonlocal completed, success_count, failure_count
async with sem: # This limits concurrency
async with AuthClient(base_url) as client:
try:
# Generate random user data
email = fake.email()
password = fake.password(length=12, special_chars=True, digits=True,
upper_case=True, lower_case=True)
# Complete user flow
await client.register(email, password)
await client.login(email, password)
await client.get_current_user()
client.logout()
async with progress_lock:
completed += 1
success_count += 1
# Print progress
print(f"Progress: {completed}/{num_users} users completed", end="\r")
except Exception as e:
async with progress_lock:
completed += 1
failure_count += 1
print(f"Error: {e}")
print(f"Progress: {completed}/{num_users} users completed", end="\r")
# Create all tasks
tasks = [run_single_user() for _ in range(num_users)]
# Display start message
print(f"Starting load test with {num_users} users (max {concurrency} concurrent)...")
# Run all tasks
await asyncio.gather(*tasks)
# Calculate stats
end_time = time.time()
duration = end_time - start_time
# Display results
print("\n\n--- Load Test Results ---")
print(f"Total users: {num_users}")
print(f"Concurrency level: {concurrency}")
print(f"Successful flows: {success_count} ({success_count/num_users*100:.1f}%)")
print(f"Failed flows: {failure_count} ({failure_count/num_users*100:.1f}%)")
print(f"Total duration: {duration:.2f} seconds")
if success_count > 0:
print(f"Average time per successful user: {duration/success_count:.2f} seconds")
print(f"Requests per second: {success_count/duration:.2f}")
async def single_user_test(base_url):
"""Run a test with a single user"""
fake = Faker()
async with AuthClient(base_url) as client:
# Generate random user data
first_name = fake.first_name()
last_name = fake.last_name()
email = fake.email()
password = fake.password(length=12, special_chars=True, digits=True, upper_case=True, lower_case=True)
try:
# Register a new user
print(f"Registering a new user: {first_name} {last_name}...")
user = await client.register(email, password)
print(f"Registered user: {user}")
# Login
print("\nLogging in...")
token_data = await client.login(email, password)
print(f"Login successful, token: {token_data['access_token'][:10]}...")
# Get current user
print("\nGetting current user info...")
user_info = await client.get_current_user()
print(f"Current user: {user_info}")
# Logout
print("\nLogging out...")
client.logout()
print("Logged out successfully")
except Exception as e:
print(f"Error: {e}")
async def custom_user_test(email, password, base_url):
"""Test with specific user credentials"""
async with AuthClient(base_url) as client:
try:
# Try to login first (for existing users)
print(f"\nAttempting to login with email: {email}...")
try:
token_data = await client.login(email, password)
print(f"Login successful, token: {token_data['access_token'][:10]}...")
except Exception as e:
print(f"Login failed: {e}")
# If login fails, try to register
print(f"\nAttempting to register with email: {email}...")
user = await client.register(email, password)
print(f"Registered user: {user}")
# Now try login again after registration
print("\nLogging in after registration...")
token_data = await client.login(email, password)
print(f"Login successful, token: {token_data['access_token'][:10]}...")
# Get current user
print("\nGetting current user info...")
user_info = await client.get_current_user()
print(f"Current user: {user_info}")
# Logout
print("\nLogging out...")
client.logout()
print("Logged out successfully")
except Exception as e:
print(f"Error: {e}")
async def main():
# Define command-line arguments
parser = argparse.ArgumentParser(description='Authentication API Client CLI')
# Main command options
parser.add_argument('--url', type=str, default="http://localhost:7860/api",
help='Base URL for the API (default: http://localhost:7860/api)')
parser.add_argument('--remote', action='store_true',
help='Use the remote API endpoint')
# Create subparsers for different commands
subparsers = parser.add_subparsers(dest='command', help='Command to execute')
# Single user test command
single_parser = subparsers.add_parser('single', help='Run a test with a single random user')
# Load test command
load_parser = subparsers.add_parser('load', help='Run a load test with multiple users')
load_parser.add_argument('--users', type=int, default=100,
help='Number of users to simulate (default: 10)')
load_parser.add_argument('--concurrency', type=int, default=10,
help='Maximum number of concurrent users (default: 5)')
# Custom user test command
custom_parser = subparsers.add_parser('custom', help='Test with specific user credentials')
custom_parser.add_argument('--email', type=str, required=True, help='User email')
custom_parser.add_argument('--password', type=str, required=True, help='User password')
# Parse arguments
args = parser.parse_args()
# Set remote URL if specified
if args.remote:
base_url = "https://amaye15-authenticationapp.hf.space/api"
print(f"Using remote API at: {base_url}")
else:
base_url = args.url
print(f"Using API at: {base_url}")
# Execute the appropriate command
if args.command == 'single':
await single_user_test(base_url)
elif args.command == 'load':
await load_test(args.users, args.concurrency, base_url)
elif args.command == 'custom':
await custom_user_test(args.email, args.password, base_url)
else:
# If no command specified, show help
parser.print_help()
if __name__ == "__main__":
asyncio.run(main())