주요 기능: - Kafka 이벤트 컨슈머 및 프로듀서 통합 - 지수 백오프 재시도 메커니즘 구현 - Dead Letter Queue (DLQ) 설정 - 이벤트 스키마 레지스트리 (Pydantic v2 호환) - Console 서비스에 이벤트 관리 API 추가 - 실시간 이벤트 통계 및 모니터링 - 엔드-투-엔드 테스트 스크립트 구현된 이벤트 타입: - USER_CREATED, USER_UPDATED, USER_DELETED - OAUTH_APP_CREATED, OAUTH_TOKEN_ISSUED - IMAGE_UPLOADED, IMAGE_PROCESSED 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
253 lines
7.9 KiB
Python
253 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Step 9 이벤트 흐름 테스트 스크립트
|
|
"""
|
|
import asyncio
|
|
import httpx
|
|
import json
|
|
from datetime import datetime
|
|
import time
|
|
|
|
# Service URLs
|
|
CONSOLE_URL = "http://localhost:8011"
|
|
USERS_URL = "http://localhost:8001"
|
|
OAUTH_URL = "http://localhost:8003"
|
|
|
|
# Test credentials
|
|
TEST_USERNAME = "admin"
|
|
TEST_PASSWORD = "admin123"
|
|
|
|
async def get_auth_token():
|
|
"""Console에서 인증 토큰 획득"""
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.post(
|
|
f"{CONSOLE_URL}/api/auth/login",
|
|
data={
|
|
"username": TEST_USERNAME,
|
|
"password": TEST_PASSWORD
|
|
}
|
|
)
|
|
if response.status_code == 200:
|
|
token_data = response.json()
|
|
return token_data["access_token"]
|
|
else:
|
|
print(f"Failed to get auth token: {response.status_code}")
|
|
return None
|
|
|
|
async def test_user_create_event():
|
|
"""사용자 생성 이벤트 테스트"""
|
|
print("\n=== Testing User Create Event ===")
|
|
|
|
# Create a new user
|
|
async with httpx.AsyncClient() as client:
|
|
user_data = {
|
|
"username": f"test_user_{int(time.time())}",
|
|
"email": f"test_{int(time.time())}@example.com",
|
|
"full_name": "Test User for Event",
|
|
"profile_picture": "https://example.com/test.jpg",
|
|
"bio": "Testing event system",
|
|
"location": "Test City"
|
|
}
|
|
|
|
response = await client.post(
|
|
f"{USERS_URL}/users",
|
|
json=user_data
|
|
)
|
|
|
|
if response.status_code == 201:
|
|
user = response.json()
|
|
print(f"✅ User created: {user['username']} (ID: {user['id']})")
|
|
|
|
# Wait for event processing
|
|
await asyncio.sleep(2)
|
|
|
|
return user['id']
|
|
else:
|
|
print(f"❌ Failed to create user: {response.status_code}")
|
|
print(response.text)
|
|
return None
|
|
|
|
async def test_user_update_event(user_id: str):
|
|
"""사용자 업데이트 이벤트 테스트"""
|
|
print("\n=== Testing User Update Event ===")
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
update_data = {
|
|
"bio": "Updated bio for event testing",
|
|
"profile_picture": "https://example.com/updated.jpg",
|
|
"location": "Updated City"
|
|
}
|
|
|
|
response = await client.put(
|
|
f"{USERS_URL}/users/{user_id}",
|
|
json=update_data
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
user = response.json()
|
|
print(f"✅ User updated: {user['username']}")
|
|
|
|
# Wait for event processing
|
|
await asyncio.sleep(2)
|
|
|
|
return True
|
|
else:
|
|
print(f"❌ Failed to update user: {response.status_code}")
|
|
return False
|
|
|
|
async def test_oauth_app_create_event():
|
|
"""OAuth 앱 생성 이벤트 테스트"""
|
|
print("\n=== Testing OAuth App Create Event ===")
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
app_data = {
|
|
"name": f"Test App {int(time.time())}",
|
|
"description": "Testing event system",
|
|
"redirect_uris": ["http://localhost:3000/callback"],
|
|
"owner_id": "test_owner_123"
|
|
}
|
|
|
|
response = await client.post(
|
|
f"{OAUTH_URL}/applications",
|
|
json=app_data
|
|
)
|
|
|
|
if response.status_code in [200, 201]:
|
|
app = response.json()
|
|
print(f"✅ OAuth app created: {app['name']} (ID: {app['id']})")
|
|
|
|
# Wait for event processing
|
|
await asyncio.sleep(2)
|
|
|
|
return app['client_id']
|
|
else:
|
|
print(f"❌ Failed to create OAuth app: {response.status_code}")
|
|
print(response.text)
|
|
return None
|
|
|
|
async def check_event_stats(token: str):
|
|
"""이벤트 통계 확인"""
|
|
print("\n=== Checking Event Statistics ===")
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
|
|
# Get event stats
|
|
response = await client.get(
|
|
f"{CONSOLE_URL}/api/events/stats",
|
|
headers=headers
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
stats = response.json()
|
|
print(f"✅ Event Statistics:")
|
|
print(f" - Processed: {stats['stats']['processed']}")
|
|
print(f" - Failed: {stats['stats']['failed']}")
|
|
print(f" - Retried: {stats['stats']['retried']}")
|
|
print(f" - DLQ: {stats['stats']['dlq_sent']}")
|
|
else:
|
|
print(f"❌ Failed to get event stats: {response.status_code}")
|
|
|
|
async def check_dlq_messages(token: str):
|
|
"""DLQ 메시지 확인"""
|
|
print("\n=== Checking Dead Letter Queue ===")
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
|
|
response = await client.get(
|
|
f"{CONSOLE_URL}/api/events/dlq?limit=5",
|
|
headers=headers
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
dlq_data = response.json()
|
|
print(f"✅ DLQ Messages: {dlq_data['count']} messages")
|
|
|
|
for msg in dlq_data['messages']:
|
|
print(f" - Event ID: {msg.get('event_id', 'N/A')}")
|
|
print(f" Error: {msg.get('error', 'N/A')}")
|
|
print(f" Retry Count: {msg.get('retry_count', 0)}")
|
|
else:
|
|
print(f"❌ Failed to get DLQ messages: {response.status_code}")
|
|
|
|
async def check_event_schemas():
|
|
"""이벤트 스키마 확인"""
|
|
print("\n=== Checking Event Schemas ===")
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(f"{CONSOLE_URL}/api/events/schemas")
|
|
|
|
if response.status_code == 200:
|
|
schemas_data = response.json()
|
|
print(f"✅ Available Event Schemas:")
|
|
|
|
for schema_name in schemas_data['schemas'].keys():
|
|
print(f" - {schema_name}")
|
|
else:
|
|
print(f"❌ Failed to get event schemas: {response.status_code}")
|
|
|
|
async def test_user_delete_event(user_id: str):
|
|
"""사용자 삭제 이벤트 테스트"""
|
|
print("\n=== Testing User Delete Event ===")
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.delete(f"{USERS_URL}/users/{user_id}")
|
|
|
|
if response.status_code == 200:
|
|
print(f"✅ User deleted: {user_id}")
|
|
|
|
# Wait for event processing
|
|
await asyncio.sleep(2)
|
|
|
|
return True
|
|
else:
|
|
print(f"❌ Failed to delete user: {response.status_code}")
|
|
return False
|
|
|
|
async def main():
|
|
"""메인 테스트 실행"""
|
|
print("=" * 50)
|
|
print("Step 9: Advanced Event Processing Test")
|
|
print("=" * 50)
|
|
|
|
# Wait for services to be ready
|
|
print("\nWaiting for services to be ready...")
|
|
await asyncio.sleep(5)
|
|
|
|
# Get auth token
|
|
token = await get_auth_token()
|
|
if not token:
|
|
print("Failed to authenticate. Exiting.")
|
|
return
|
|
|
|
print(f"✅ Authentication successful")
|
|
|
|
# Check event schemas first
|
|
await check_event_schemas()
|
|
|
|
# Test user events
|
|
user_id = await test_user_create_event()
|
|
if user_id:
|
|
await test_user_update_event(user_id)
|
|
|
|
# Check stats after user events
|
|
await check_event_stats(token)
|
|
|
|
# Delete user
|
|
await test_user_delete_event(user_id)
|
|
|
|
# Test OAuth events
|
|
client_id = await test_oauth_app_create_event()
|
|
|
|
# Final statistics
|
|
await asyncio.sleep(3) # Wait for all events to process
|
|
await check_event_stats(token)
|
|
await check_dlq_messages(token)
|
|
|
|
print("\n" + "=" * 50)
|
|
print("Test completed!")
|
|
print("=" * 50)
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main()) |