átlagos kiegészítséek jó sok
This commit is contained in:
0
backend/app/tests/__init__.py
Normal file
0
backend/app/tests/__init__.py
Normal file
0
backend/app/tests/e2e/__init__.py
Normal file
0
backend/app/tests/e2e/__init__.py
Normal file
379
backend/app/tests/e2e/conftest.py
Normal file
379
backend/app/tests/e2e/conftest.py
Normal file
@@ -0,0 +1,379 @@
|
||||
"""
|
||||
Pytest fixtures for E2E testing of Core modules.
|
||||
Provides an authenticated client that goes through the full user journey.
|
||||
"""
|
||||
import asyncio
|
||||
import httpx
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
import uuid
|
||||
import re
|
||||
import logging
|
||||
import time
|
||||
from typing import AsyncGenerator, Optional, List, Dict
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.exc import DBAPIError
|
||||
from app.db.session import AsyncSessionLocal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "http://sf_api:8000"
|
||||
MAILPIT_URL = "http://sf_mailpit:8025"
|
||||
TEST_EMAIL_DOMAIN = "example.com"
|
||||
|
||||
|
||||
class MailpitClient:
|
||||
"""Client for interacting with Mailpit API."""
|
||||
|
||||
def __init__(self, base_url: str = MAILPIT_URL):
|
||||
self.base_url = base_url
|
||||
self.client = httpx.AsyncClient(timeout=30.0)
|
||||
|
||||
async def delete_all_messages(self) -> bool:
|
||||
"""Delete all messages in Mailpit to ensure clean state."""
|
||||
try:
|
||||
response = await self.client.delete(f"{self.base_url}/api/v1/messages")
|
||||
response.raise_for_status()
|
||||
logger.debug("Mailpit cleaned (all messages deleted).")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Mailpit clean failed: {e}, continuing anyway.")
|
||||
return False
|
||||
|
||||
async def get_messages(self, limit: int = 50) -> Optional[Dict]:
|
||||
"""Fetch messages from Mailpit."""
|
||||
try:
|
||||
response = await self.client.get(f"{self.base_url}/api/v1/messages?limit={limit}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch messages: {e}")
|
||||
return None
|
||||
|
||||
async def get_latest_message(self) -> Optional[dict]:
|
||||
"""Fetch the latest email message from Mailpit."""
|
||||
data = await self.get_messages(limit=1)
|
||||
if data and data.get("messages"):
|
||||
return data["messages"][0]
|
||||
return None
|
||||
|
||||
async def get_message_content(self, message_id: str) -> Optional[str]:
|
||||
"""Get the full content (HTML and text) of a specific message."""
|
||||
try:
|
||||
response = await self.client.get(f"{self.base_url}/api/v1/message/{message_id}")
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
# Prefer text over HTML
|
||||
return data.get("Text") or data.get("HTML") or ""
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch message content: {e}")
|
||||
return None
|
||||
|
||||
async def poll_for_verification_email(self, email: str, max_attempts: int = 5, wait_seconds: int = 3) -> Optional[str]:
|
||||
"""
|
||||
Poll Mailpit for a verification email sent to the given email address.
|
||||
Returns the verification token if found, otherwise None.
|
||||
"""
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
logger.debug(f"Polling for verification email (attempt {attempt}/{max_attempts})...")
|
||||
data = await self.get_messages(limit=20)
|
||||
if not data or not data.get("messages"):
|
||||
logger.debug(f"No emails in Mailpit, waiting {wait_seconds}s...")
|
||||
await asyncio.sleep(wait_seconds)
|
||||
continue
|
||||
|
||||
# Search for email sent to the target address
|
||||
for msg in data.get("messages", []):
|
||||
to_list = msg.get("To", [])
|
||||
email_found = False
|
||||
for recipient in to_list:
|
||||
if isinstance(recipient, dict) and recipient.get("Address") == email:
|
||||
email_found = True
|
||||
break
|
||||
elif isinstance(recipient, str) and recipient == email:
|
||||
email_found = True
|
||||
break
|
||||
|
||||
if email_found:
|
||||
msg_id = msg.get("ID")
|
||||
if not msg_id:
|
||||
continue
|
||||
content = await self.get_message_content(msg_id)
|
||||
if content:
|
||||
token = extract_verification_token(content)
|
||||
if token:
|
||||
logger.debug(f"Token found on attempt {attempt}: {token}")
|
||||
return token
|
||||
else:
|
||||
logger.debug(f"Email found but no token pattern matched.")
|
||||
else:
|
||||
logger.debug(f"Could not fetch email content.")
|
||||
|
||||
logger.debug(f"No verification email found yet, waiting {wait_seconds}s...")
|
||||
await asyncio.sleep(wait_seconds)
|
||||
|
||||
logger.error(f"Could not retrieve verification token after {max_attempts} attempts.")
|
||||
return None
|
||||
|
||||
async def cleanup(self):
|
||||
"""Close the HTTP client."""
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
class APIClient:
|
||||
"""Client for interacting with the Service Finder API."""
|
||||
|
||||
def __init__(self, base_url: str = BASE_URL):
|
||||
self.base_url = base_url
|
||||
self.client = httpx.AsyncClient(timeout=30.0)
|
||||
self.token = None
|
||||
|
||||
async def register(self, email: str, password: str = "TestPassword123!") -> httpx.Response:
|
||||
"""Register a new user."""
|
||||
payload = {
|
||||
"email": email,
|
||||
"password": password,
|
||||
"first_name": "Test",
|
||||
"last_name": "User",
|
||||
"region_code": "HU",
|
||||
"lang": "hu"
|
||||
}
|
||||
response = await self.client.post(f"{self.base_url}/api/v1/auth/register", json=payload)
|
||||
return response
|
||||
|
||||
async def login(self, email: str, password: str = "TestPassword123!") -> Optional[str]:
|
||||
"""Login and return JWT token."""
|
||||
payload = {
|
||||
"username": email,
|
||||
"password": password
|
||||
}
|
||||
response = await self.client.post(f"{self.base_url}/api/v1/auth/login", data=payload)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
self.token = data.get("access_token")
|
||||
return self.token
|
||||
return None
|
||||
|
||||
async def verify_email(self, token: str) -> httpx.Response:
|
||||
"""Verify email with token."""
|
||||
response = await self.client.post(
|
||||
f"{self.base_url}/api/v1/auth/verify-email",
|
||||
json={"token": token}
|
||||
)
|
||||
return response
|
||||
|
||||
async def complete_kyc(self, token: str) -> httpx.Response:
|
||||
"""Complete KYC with dummy data (matching Sandbox script)."""
|
||||
payload = {
|
||||
"phone_number": "+36123456789",
|
||||
"birth_place": "Budapest",
|
||||
"birth_date": "1990-01-01",
|
||||
"mothers_last_name": "Kovács",
|
||||
"mothers_first_name": "Éva",
|
||||
"address_zip": "1051",
|
||||
"address_city": "Budapest",
|
||||
"address_street_name": "Váci",
|
||||
"address_street_type": "utca",
|
||||
"address_house_number": "1",
|
||||
"address_stairwell": None,
|
||||
"address_floor": None,
|
||||
"address_door": None,
|
||||
"address_hrsz": None,
|
||||
"identity_docs": {
|
||||
"ID_CARD": {
|
||||
"number": "123456AB",
|
||||
"expiry_date": "2030-12-31"
|
||||
}
|
||||
},
|
||||
"ice_contact": {
|
||||
"name": "John Doe",
|
||||
"phone": "+36198765432",
|
||||
"relationship": "friend"
|
||||
},
|
||||
"preferred_language": "hu",
|
||||
"preferred_currency": "HUF"
|
||||
}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = await self.client.post(
|
||||
f"{self.base_url}/api/v1/auth/complete-kyc",
|
||||
json=payload,
|
||||
headers=headers
|
||||
)
|
||||
return response
|
||||
|
||||
async def get_authenticated_client(self, token: str) -> httpx.AsyncClient:
|
||||
"""Return a new httpx.AsyncClient with Authorization header set."""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
return httpx.AsyncClient(base_url=self.base_url, headers=headers, timeout=30.0)
|
||||
|
||||
async def cleanup(self):
|
||||
"""Close the HTTP client."""
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
def extract_verification_token(email_content: str) -> Optional[str]:
|
||||
"""Extract verification token from email content."""
|
||||
# Look for token in URL patterns
|
||||
patterns = [
|
||||
r"token=([a-zA-Z0-9\-_]+)",
|
||||
r"/verify/([a-zA-Z0-9\-_]+)",
|
||||
r"verification code: ([a-zA-Z0-9\-_]+)",
|
||||
]
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, email_content)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Create an instance of the default event loop for the test session."""
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def db() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""
|
||||
Database session fixture with automatic rollback after each test.
|
||||
This prevents InFailedSQLTransactionError between tests.
|
||||
"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
yield session
|
||||
# EZ A KULCS: Minden teszt után takarítunk!
|
||||
try:
|
||||
await session.rollback()
|
||||
except DBAPIError as e:
|
||||
logger.warning(f"Rollback failed (likely already aborted): {e}. Closing session.")
|
||||
await session.close()
|
||||
# Opcionálisan: await session.close()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def authenticated_client() -> AsyncGenerator[httpx.AsyncClient, None]:
|
||||
"""
|
||||
Fixture that performs full user journey and returns an authenticated httpx.AsyncClient.
|
||||
|
||||
Steps:
|
||||
1. Clean Mailpit to ensure only new emails
|
||||
2. Register a new user with unique email (time-based to avoid duplicate key)
|
||||
3. Poll Mailpit for verification email and extract token
|
||||
4. Verify email
|
||||
5. Login to get JWT token
|
||||
6. Complete KYC
|
||||
7. Return authenticated client with Authorization header
|
||||
"""
|
||||
# Generate unique email using timestamp to avoid duplicate key errors in user_stats
|
||||
unique_id = int(time.time() * 1000) # milliseconds
|
||||
email = f"test_{unique_id}@{TEST_EMAIL_DOMAIN}"
|
||||
password = "TestPassword123!"
|
||||
|
||||
api_client = APIClient()
|
||||
mailpit = MailpitClient()
|
||||
|
||||
try:
|
||||
# 0. Clean Mailpit before registration
|
||||
logger.debug("Cleaning Mailpit before registration...")
|
||||
await mailpit.delete_all_messages()
|
||||
|
||||
# 1. Register
|
||||
logger.debug(f"Registering user with email: {email}")
|
||||
reg_response = await api_client.register(email, password)
|
||||
assert reg_response.status_code in (200, 201), f"Registration failed: {reg_response.text}"
|
||||
|
||||
# 2. Poll for verification email and extract token
|
||||
logger.debug("Polling Mailpit for verification email...")
|
||||
token = await mailpit.poll_for_verification_email(email, max_attempts=5, wait_seconds=3)
|
||||
assert token is not None, "Could not retrieve verification token after polling"
|
||||
|
||||
# 3. Verify email
|
||||
verify_response = await api_client.verify_email(token)
|
||||
assert verify_response.status_code == 200, f"Email verification failed: {verify_response.text}"
|
||||
|
||||
# 4. Login
|
||||
access_token = await api_client.login(email, password)
|
||||
assert access_token is not None, "Login failed"
|
||||
|
||||
# 5. Complete KYC (optional, log failure but continue)
|
||||
kyc_response = await api_client.complete_kyc(access_token)
|
||||
if kyc_response.status_code != 200:
|
||||
logger.warning(f"KYC completion returned {kyc_response.status_code}: {kyc_response.text}. Continuing anyway.")
|
||||
|
||||
# 6. Create authenticated client
|
||||
auth_client = await api_client.get_authenticated_client(access_token)
|
||||
yield auth_client
|
||||
|
||||
# Cleanup
|
||||
await auth_client.aclose()
|
||||
|
||||
finally:
|
||||
await api_client.cleanup()
|
||||
await mailpit.cleanup()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def setup_organization(authenticated_client):
|
||||
"""Létrehoz egy céget a jármű/költség tesztekhez."""
|
||||
import time
|
||||
import random
|
||||
unique_id = int(time.time() * 1000) + random.randint(1, 9999)
|
||||
# Generate a valid Hungarian tax number format: 8 digits + "-1-42"
|
||||
tax_prefix = random.randint(10000000, 99999999)
|
||||
payload = {
|
||||
"name": f"Test Fleet {unique_id}",
|
||||
"display_name": f"Test Fleet {unique_id}",
|
||||
"full_name": f"Test Fleet Kft. {unique_id}",
|
||||
"tax_number": f"{tax_prefix}-1-42",
|
||||
"registration_number": f"01-09-{unique_id}"[:6],
|
||||
"org_type": "business",
|
||||
"country_code": "HU",
|
||||
"address_zip": "1051",
|
||||
"address_city": "Budapest",
|
||||
"address_street_name": "Váci",
|
||||
"address_street_type": "utca",
|
||||
"address_house_number": "1",
|
||||
"address_stairwell": None,
|
||||
"address_floor": None,
|
||||
"address_door": None,
|
||||
"address_hrsz": None,
|
||||
}
|
||||
response = await authenticated_client.post("/api/v1/organizations/onboard", json=payload)
|
||||
# Accept 409 as well (already exists) and try to fetch existing organization
|
||||
if response.status_code == 409:
|
||||
# Maybe we can reuse the existing organization? For simplicity, we'll just skip and raise.
|
||||
# But we need an organization ID. Let's try to get the user's organizations.
|
||||
# For now, raise a specific error.
|
||||
raise ValueError(f"Organization with tax number already exists: {payload['tax_number']}")
|
||||
assert response.status_code in (200, 201), f"Organization creation failed: {response.text}"
|
||||
data = response.json()
|
||||
# Try multiple possible keys for organization ID
|
||||
if "id" in data:
|
||||
return data["id"]
|
||||
elif "organization_id" in data:
|
||||
return data["organization_id"]
|
||||
elif "organization" in data and "id" in data["organization"]:
|
||||
return data["organization"]["id"]
|
||||
else:
|
||||
# Fallback: extract from location header or raise
|
||||
raise ValueError(f"Could not find organization ID in response: {data}")
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def setup_vehicle(authenticated_client, setup_organization):
|
||||
import time
|
||||
unique_vin = f"WBA0000000{int(time.time())}"[:17].ljust(17, '0')
|
||||
payload = {
|
||||
"vin": unique_vin,
|
||||
"license_plate": "TEST-123",
|
||||
"organization_id": setup_organization,
|
||||
"purchase_price_net": 10000000,
|
||||
"purchase_date": "2023-01-01",
|
||||
"initial_mileage": 10000,
|
||||
"fuel_type": "petrol",
|
||||
"transmission": "manual"
|
||||
}
|
||||
response = await authenticated_client.post("/api/v1/assets/vehicles", json=payload)
|
||||
assert response.status_code in (200, 201), f"Vehicle creation failed: {response.text}"
|
||||
return response.json()["id"]
|
||||
85
backend/app/tests/e2e/test_admin_security.py
Normal file
85
backend/app/tests/e2e/test_admin_security.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
E2E teszt az admin végpontok biztonsági ellenőrzéséhez.
|
||||
Ellenőrzi, hogy normál felhasználó nem fér hozzá admin végponthoz.
|
||||
"""
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
from app.models.identity import User, UserRole
|
||||
from app.api.deps import get_current_user
|
||||
|
||||
|
||||
def test_normal_user_cannot_access_admin_ping():
|
||||
"""
|
||||
Normál felhasználó nem fér hozzá a GET /api/v1/admin/ping végponthoz.
|
||||
Elvárt: 403 Forbidden.
|
||||
"""
|
||||
# Mock a normal user (non-admin)
|
||||
mock_user = User(
|
||||
id=999,
|
||||
email="normal@example.com",
|
||||
role=UserRole.user,
|
||||
is_active=True,
|
||||
is_deleted=False,
|
||||
subscription_plan="FREE",
|
||||
preferred_language="hu",
|
||||
region_code="HU",
|
||||
preferred_currency="HUF",
|
||||
scope_level="individual",
|
||||
custom_permissions={}
|
||||
)
|
||||
|
||||
# Override get_current_user to return normal user
|
||||
async def mock_get_current_user():
|
||||
return mock_user
|
||||
|
||||
app.dependency_overrides[get_current_user] = mock_get_current_user
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/admin/ping")
|
||||
|
||||
# Clean up
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
# Assert
|
||||
assert response.status_code == 403
|
||||
assert "detail" in response.json()
|
||||
print(f"Response detail: {response.json()['detail']}")
|
||||
|
||||
|
||||
def test_admin_user_can_access_admin_ping():
|
||||
"""
|
||||
Admin felhasználóval a ping végpont 200-at ad vissza.
|
||||
"""
|
||||
mock_admin = User(
|
||||
id=1000,
|
||||
email="admin@example.com",
|
||||
role=UserRole.admin,
|
||||
is_active=True,
|
||||
is_deleted=False,
|
||||
subscription_plan="PREMIUM",
|
||||
preferred_language="en",
|
||||
region_code="HU",
|
||||
preferred_currency="EUR",
|
||||
scope_level="global",
|
||||
custom_permissions={}
|
||||
)
|
||||
|
||||
async def mock_get_current_user():
|
||||
return mock_admin
|
||||
|
||||
app.dependency_overrides[get_current_user] = mock_get_current_user
|
||||
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/admin/ping")
|
||||
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["message"] == "Admin felület aktív"
|
||||
assert data["role"] == "admin"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
28
backend/app/tests/e2e/test_analytics_import.py
Normal file
28
backend/app/tests/e2e/test_analytics_import.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick test to verify analytics module imports correctly.
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, '/opt/docker/dev/service_finder/backend')
|
||||
|
||||
try:
|
||||
from app.api.v1.endpoints.analytics import router
|
||||
print("✓ Analytics router imported successfully")
|
||||
print(f"Router prefix: {router.prefix}")
|
||||
print(f"Router tags: {router.tags}")
|
||||
except ImportError as e:
|
||||
print(f"✗ Import error: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"✗ Unexpected error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Try importing schemas
|
||||
try:
|
||||
from app.schemas.analytics import TCOSummaryResponse
|
||||
print("✓ Analytics schemas imported successfully")
|
||||
except ImportError as e:
|
||||
print(f"✗ Schemas import error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
print("All imports passed.")
|
||||
37
backend/app/tests/e2e/test_expense_flow.py
Normal file
37
backend/app/tests/e2e/test_expense_flow.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
End-to-end test for Expense creation flow.
|
||||
Uses the authenticated_client fixture to test POST /api/v1/expenses/add endpoint.
|
||||
"""
|
||||
import pytest
|
||||
import httpx
|
||||
from datetime import date
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_expense_creation(authenticated_client: httpx.AsyncClient, setup_vehicle):
|
||||
"""
|
||||
Test that a user can add an expense (fuel/service) to an asset.
|
||||
Uses the setup_vehicle fixture to get a valid asset_id.
|
||||
"""
|
||||
asset_id = setup_vehicle
|
||||
|
||||
# Now add an expense for this asset
|
||||
expense_payload = {
|
||||
"asset_id": str(asset_id), # must be string
|
||||
"category": "fuel", # or "service", "insurance", etc.
|
||||
"amount": 15000.0,
|
||||
"date": str(date.today()), # YYYY-MM-DD
|
||||
}
|
||||
|
||||
response = await authenticated_client.post(
|
||||
"/api/v1/expenses/add",
|
||||
json=expense_payload
|
||||
)
|
||||
|
||||
# Assert success
|
||||
assert response.status_code == 200, f"Unexpected status: {response.status_code}, response: {response.text}"
|
||||
|
||||
data = response.json()
|
||||
assert data["status"] == "success"
|
||||
|
||||
print(f"✅ Expense added for asset {asset_id}")
|
||||
119
backend/app/tests/e2e/test_hierarchical_params.py
Normal file
119
backend/app/tests/e2e/test_hierarchical_params.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Teszt szkript a hierarchikus System Parameters működésének ellenőrzéséhez.
|
||||
Futtatás: docker exec sf_api python /app/test_hierarchical_params.py
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, '/app')
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.models.system import SystemParameter, ParameterScope
|
||||
from app.services.system_service import system_service
|
||||
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres:postgres@postgres:5432/service_finder")
|
||||
|
||||
async def test_hierarchical():
|
||||
engine = create_async_engine(DATABASE_URL, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as db:
|
||||
# Töröljük a teszt paramétereket, ha vannak
|
||||
await db.execute(
|
||||
SystemParameter.__table__.delete().where(SystemParameter.key == "test.hierarchical")
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
# 1. GLOBAL paraméter létrehozása
|
||||
global_param = SystemParameter(
|
||||
key="test.hierarchical",
|
||||
value={"message": "global value"},
|
||||
scope_level=ParameterScope.GLOBAL,
|
||||
scope_id=None,
|
||||
category="test",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(global_param)
|
||||
|
||||
# 2. COUNTRY paraméter létrehozása (HU)
|
||||
country_param = SystemParameter(
|
||||
key="test.hierarchical",
|
||||
value={"message": "country HU value"},
|
||||
scope_level=ParameterScope.COUNTRY,
|
||||
scope_id="HU",
|
||||
category="test",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(country_param)
|
||||
|
||||
# 3. REGION paraméter létrehozása (budapest)
|
||||
region_param = SystemParameter(
|
||||
key="test.hierarchical",
|
||||
value={"message": "region budapest value"},
|
||||
scope_level=ParameterScope.REGION,
|
||||
scope_id="budapest",
|
||||
category="test",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(region_param)
|
||||
|
||||
# 4. USER paraméter létrehozása (user_123)
|
||||
user_param = SystemParameter(
|
||||
key="test.hierarchical",
|
||||
value={"message": "user user_123 value"},
|
||||
scope_level=ParameterScope.USER,
|
||||
scope_id="user_123",
|
||||
category="test",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(user_param)
|
||||
|
||||
await db.commit()
|
||||
|
||||
# Teszt: csak global scope (nincs user, region, country)
|
||||
value = await system_service.get_scoped_parameter(db, "test.hierarchical", default=None)
|
||||
print(f"Global only: {value}")
|
||||
assert value["message"] == "global value"
|
||||
|
||||
# COUNTRY scope (HU)
|
||||
value = await system_service.get_scoped_parameter(db, "test.hierarchical", country_code="HU", default=None)
|
||||
print(f"Country HU: {value}")
|
||||
assert value["message"] == "country HU value"
|
||||
|
||||
# REGION scope (budapest) – a region a country feletti prioritás? A prioritási sorrend: User > Region > Country > Global
|
||||
# Ha region_id megadva, de country_code is, akkor region elsőbbséget élvez.
|
||||
value = await system_service.get_scoped_parameter(db, "test.hierarchical", region_id="budapest", country_code="HU", default=None)
|
||||
print(f"Region budapest (with country HU): {value}")
|
||||
assert value["message"] == "region budapest value"
|
||||
|
||||
# USER scope (user_123) – legmagasabb prioritás
|
||||
value = await system_service.get_scoped_parameter(db, "test.hierarchical", user_id="user_123", region_id="budapest", country_code="HU", default=None)
|
||||
print(f"User user_123 (with region and country): {value}")
|
||||
assert value["message"] == "user user_123 value"
|
||||
|
||||
# Nem létező user, de létező region
|
||||
value = await system_service.get_scoped_parameter(db, "test.hierarchical", user_id="nonexistent", region_id="budapest", country_code="HU", default=None)
|
||||
print(f"Non-existent user, region budapest: {value}")
|
||||
assert value["message"] == "region budapest value"
|
||||
|
||||
# Nem létező region, de létező country
|
||||
value = await system_service.get_scoped_parameter(db, "test.hierarchical", region_id="nonexistent", country_code="HU", default=None)
|
||||
print(f"Non-existent region, country HU: {value}")
|
||||
assert value["message"] == "country HU value"
|
||||
|
||||
# Semmi specifikus – global
|
||||
value = await system_service.get_scoped_parameter(db, "test.hierarchical", default=None)
|
||||
print(f"Fallback to global: {value}")
|
||||
assert value["message"] == "global value"
|
||||
|
||||
# Törlés
|
||||
await db.execute(
|
||||
SystemParameter.__table__.delete().where(SystemParameter.key == "test.hierarchical")
|
||||
)
|
||||
await db.commit()
|
||||
print("✅ Minden teszt sikeres!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_hierarchical())
|
||||
72
backend/app/tests/e2e/test_marketplace_flow.py
Normal file
72
backend/app/tests/e2e/test_marketplace_flow.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
End-to-end test for Service Hunt (Marketplace) flow.
|
||||
Tests the POST /api/v1/services/hunt endpoint with form data.
|
||||
"""
|
||||
import pytest
|
||||
import httpx
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_hunt(authenticated_client: httpx.AsyncClient):
|
||||
"""
|
||||
Test that a user can submit a service hunt (discovery) with location data.
|
||||
"""
|
||||
# Payload as form data (x-www-form-urlencoded)
|
||||
payload = {
|
||||
"name": "Test Garage",
|
||||
"lat": 47.4979,
|
||||
"lng": 19.0402
|
||||
}
|
||||
|
||||
# Note: httpx sends form data with data=, not json=
|
||||
response = await authenticated_client.post(
|
||||
"/api/v1/services/hunt",
|
||||
data=payload
|
||||
)
|
||||
|
||||
# Assert success
|
||||
assert response.status_code == 200, f"Unexpected status: {response.status_code}, response: {response.text}"
|
||||
|
||||
data = response.json()
|
||||
assert data["status"] == "success"
|
||||
|
||||
print(f"✅ Service hunt submitted successfully: {data}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_validation(authenticated_client: httpx.AsyncClient):
|
||||
"""
|
||||
Test the validation endpoint for staged service records.
|
||||
- Creates a staging record via hunt endpoint.
|
||||
- Attempts to validate own submission (should fail with 400).
|
||||
- (Optional) Successful validation by a different user would require a second user.
|
||||
"""
|
||||
# 1. Create a staging record
|
||||
payload = {
|
||||
"name": "Validation Test Garage",
|
||||
"lat": 47.5000,
|
||||
"lng": 19.0500
|
||||
}
|
||||
response = await authenticated_client.post(
|
||||
"/api/v1/services/hunt",
|
||||
data=payload
|
||||
)
|
||||
assert response.status_code == 200, f"Failed to create staging: {response.text}"
|
||||
hunt_data = response.json()
|
||||
staging_id = hunt_data.get("staging_id")
|
||||
if not staging_id:
|
||||
# If response doesn't contain staging_id, we need to extract it from the message
|
||||
# For now, skip this test if staging_id not present
|
||||
print("⚠️ staging_id not found in response, skipping validation test")
|
||||
return
|
||||
|
||||
# 2. Attempt to validate own submission (should return 400)
|
||||
validate_response = await authenticated_client.post(
|
||||
f"/api/v1/services/hunt/{staging_id}/validate"
|
||||
)
|
||||
# Expect 400 Bad Request because user cannot validate their own submission
|
||||
assert validate_response.status_code == 400, f"Expected 400 for self-validation, got {validate_response.status_code}: {validate_response.text}"
|
||||
|
||||
# 3. (Optional) Successful validation by a different user would require a second authenticated client.
|
||||
# For now, we can at least verify that the endpoint exists and returns proper error.
|
||||
print(f"✅ Self-validation correctly rejected with 400")
|
||||
58
backend/app/tests/e2e/test_organization_flow.py
Normal file
58
backend/app/tests/e2e/test_organization_flow.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
End-to-end test for Organization onboarding flow.
|
||||
Uses the authenticated_client fixture to test the POST /api/v1/organizations/onboard endpoint.
|
||||
"""
|
||||
import pytest
|
||||
import httpx
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_organization_onboard(authenticated_client: httpx.AsyncClient):
|
||||
"""
|
||||
Test that a user can create an organization via the onboard endpoint.
|
||||
"""
|
||||
# Prepare payload according to CorpOnboardIn schema
|
||||
payload = {
|
||||
"full_name": "Test Corporation Kft.",
|
||||
"name": "TestCorp",
|
||||
"display_name": "Test Corporation",
|
||||
"tax_number": "12345678-2-41",
|
||||
"reg_number": "01-09-123456",
|
||||
"country_code": "HU",
|
||||
"language": "hu",
|
||||
"default_currency": "HUF",
|
||||
# Atomic address fields
|
||||
"address_zip": "1234",
|
||||
"address_city": "Budapest",
|
||||
"address_street_name": "Test",
|
||||
"address_street_type": "utca",
|
||||
"address_house_number": "1",
|
||||
"address_stairwell": "A",
|
||||
"address_floor": "2",
|
||||
"address_door": "3",
|
||||
# Optional contacts
|
||||
"contacts": [
|
||||
{
|
||||
"full_name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"phone": "+36123456789",
|
||||
"contact_type": "primary"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
response = await authenticated_client.post(
|
||||
"/api/v1/organizations/onboard",
|
||||
json=payload
|
||||
)
|
||||
|
||||
# Assert success (201 Created or 200 OK)
|
||||
assert response.status_code in (200, 201), f"Unexpected status: {response.status_code}, response: {response.text}"
|
||||
|
||||
# Parse response
|
||||
data = response.json()
|
||||
assert "organization_id" in data
|
||||
assert data["organization_id"] > 0
|
||||
assert data["status"] == "pending_verification"
|
||||
|
||||
print(f"✅ Organization created with ID: {data['organization_id']}")
|
||||
74
backend/app/tests/e2e/test_r0_spider.py
Normal file
74
backend/app/tests/e2e/test_r0_spider.py
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Teszt szkript az R0 spider számára.
|
||||
Csak egy járművet dolgoz fel, majd leáll.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the backend to the path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
|
||||
|
||||
from app.workers.vehicle.ultimatespecs.vehicle_ultimate_r0_spider import UltimateSpecsSpider
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [TEST-R0] %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger("TEST-R0")
|
||||
|
||||
class TestSpider(UltimateSpecsSpider):
|
||||
"""Teszt spider, amely csak egy iterációt fut."""
|
||||
|
||||
async def run_test(self):
|
||||
"""Run a single test iteration."""
|
||||
logger.info("Teszt spider indítása...")
|
||||
|
||||
try:
|
||||
await self.init_browser()
|
||||
|
||||
# Process just one vehicle
|
||||
processed = await self.process_single_vehicle()
|
||||
|
||||
if processed:
|
||||
logger.info("Teszt sikeres - egy jármű feldolgozva")
|
||||
else:
|
||||
logger.info("Teszt sikeres - nincs feldolgozandó jármű")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Teszt hiba: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
finally:
|
||||
await self.close_browser()
|
||||
|
||||
return True
|
||||
|
||||
async def main():
|
||||
"""Main test function."""
|
||||
spider = TestSpider()
|
||||
|
||||
try:
|
||||
success = await spider.run_test()
|
||||
if success:
|
||||
print("\n✅ TESZT SIKERES")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n❌ TESZT SIKERTELEN")
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ Teszt megszakítva")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"\n💥 Váratlan hiba: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
23
backend/app/tests/e2e/test_robot.py
Executable file
23
backend/app/tests/e2e/test_robot.py
Executable file
@@ -0,0 +1,23 @@
|
||||
# Tell pytest to skip this module - it's a standalone script, not a test
|
||||
__test__ = False
|
||||
|
||||
import asyncio
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.services.harvester_robot import VehicleHarvester
|
||||
from app.core.config import settings
|
||||
|
||||
# Adatbázis kapcsolat felépítése a pontos névvel
|
||||
engine = create_async_engine(str(settings.DATABASE_URL))
|
||||
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async def run_test():
|
||||
async with AsyncSessionLocal() as db:
|
||||
harvester = VehicleHarvester()
|
||||
print("🚀 Robot indítása...")
|
||||
# Megpróbáljuk betölteni a katalógust
|
||||
await harvester.harvest_all(db)
|
||||
print("✅ Teszt lefutott.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_test())
|
||||
85
backend/app/tests/e2e/test_trust_endpoint.py
Normal file
85
backend/app/tests/e2e/test_trust_endpoint.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Egyszerű teszt a Gondos Gazda Index API végponthoz.
|
||||
"""
|
||||
|
||||
# Tell pytest to skip this module - it's a standalone script, not a test
|
||||
__test__ = False
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.trust_engine import TrustEngine
|
||||
from app.models.identity import User
|
||||
|
||||
async def test_trust_engine():
|
||||
"""Teszteli a TrustEngine működését."""
|
||||
print("TrustEngine teszt indítása...")
|
||||
|
||||
# Adatbázis kapcsolat
|
||||
engine = create_async_engine(
|
||||
"postgresql+asyncpg://postgres:postgres@localhost:5432/service_finder",
|
||||
echo=False
|
||||
)
|
||||
|
||||
async_session = sessionmaker(
|
||||
engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
async with async_session() as db:
|
||||
# Keressünk egy teszt felhasználót
|
||||
from sqlalchemy import select
|
||||
stmt = select(User).limit(1)
|
||||
result = await db.execute(stmt)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if not user:
|
||||
print("Nincs felhasználó az adatbázisban, teszt felhasználó létrehozása...")
|
||||
# Egyszerűsítés: csak kiírjuk, hogy nincs felhasználó
|
||||
print("Nincs felhasználó, a teszt kihagyva.")
|
||||
return
|
||||
|
||||
print(f"Teszt felhasználó: {user.email} (ID: {user.id})")
|
||||
|
||||
# TrustEngine példányosítás
|
||||
trust_engine = TrustEngine()
|
||||
|
||||
# Trust számítás
|
||||
trust_data = await trust_engine.calculate_user_trust(db, user.id)
|
||||
|
||||
print("\n=== Trust Score Eredmény ===")
|
||||
print(f"Trust Score: {trust_data['trust_score']}/100")
|
||||
print(f"Maintenance Score: {trust_data['maintenance_score']:.2f}")
|
||||
print(f"Quality Score: {trust_data['quality_score']:.2f}")
|
||||
print(f"Preventive Score: {trust_data['preventive_score']:.2f}")
|
||||
print(f"Last Calculated: {trust_data['last_calculated']}")
|
||||
|
||||
if trust_data['weights']:
|
||||
print(f"\nSúlyozások:")
|
||||
for key, value in trust_data['weights'].items():
|
||||
print(f" {key}: {value:.2f}")
|
||||
|
||||
if trust_data['tolerance_km']:
|
||||
print(f"Tolerancia KM: {trust_data['tolerance_km']}")
|
||||
|
||||
# Ellenőrizzük, hogy a UserTrustProfile létrejött-e
|
||||
from sqlalchemy import select
|
||||
from app.models.identity import UserTrustProfile
|
||||
stmt = select(UserTrustProfile).where(UserTrustProfile.user_id == user.id)
|
||||
result = await db.execute(stmt)
|
||||
profile = result.scalar_one_or_none()
|
||||
|
||||
if profile:
|
||||
print(f"\nUserTrustProfile létrehozva:")
|
||||
print(f" Trust Score: {profile.trust_score}")
|
||||
print(f" Last Calculated: {profile.last_calculated}")
|
||||
else:
|
||||
print("\nFIGYELEM: UserTrustProfile nem jött létre!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_trust_engine())
|
||||
101
backend/app/tests/e2e/test_trust_endpoint_simple.py
Normal file
101
backend/app/tests/e2e/test_trust_endpoint_simple.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Egyszerű teszt a Gondos Gazda Index API végponthoz - import hibák elkerülésével.
|
||||
"""
|
||||
|
||||
# Tell pytest to skip this module - it's a standalone script, not a test
|
||||
__test__ = False
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Ideiglenes megoldás: mockoljuk a hiányzó importokat
|
||||
import unittest.mock as mock
|
||||
|
||||
# Mock the missing imports before importing trust_engine
|
||||
sys.modules['app.models.asset'] = mock.Mock()
|
||||
sys.modules['app.models.service'] = mock.Mock()
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.services.trust_engine import TrustEngine
|
||||
from app.models.identity import User
|
||||
|
||||
async def test_trust_engine():
|
||||
"""Teszteli a TrustEngine működését."""
|
||||
print("TrustEngine teszt indítása...")
|
||||
|
||||
# Adatbázis kapcsolat
|
||||
engine = create_async_engine(
|
||||
"postgresql+asyncpg://postgres:postgres@localhost:5432/service_finder",
|
||||
echo=False
|
||||
)
|
||||
|
||||
async_session = sessionmaker(
|
||||
engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
async with async_session() as db:
|
||||
# Keressünk egy teszt felhasználót
|
||||
from sqlalchemy import select
|
||||
stmt = select(User).limit(1)
|
||||
result = await db.execute(stmt)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if not user:
|
||||
print("Nincs felhasználó az adatbázisban, teszt felhasználó létrehozása...")
|
||||
# Egyszerűsítés: csak kiírjuk, hogy nincs felhasználó
|
||||
print("Nincs felhasználó, a teszt kihagyva.")
|
||||
return
|
||||
|
||||
print(f"Teszt felhasználó: {user.email} (ID: {user.id})")
|
||||
|
||||
# TrustEngine példányosítás
|
||||
trust_engine = TrustEngine()
|
||||
|
||||
# Trust számítás (force_recalculate=True, hogy biztosan számoljon)
|
||||
try:
|
||||
trust_data = await trust_engine.calculate_user_trust(db, user.id, force_recalculate=True)
|
||||
|
||||
print("\n=== Trust Score Eredmény ===")
|
||||
print(f"Trust Score: {trust_data['trust_score']}/100")
|
||||
print(f"Maintenance Score: {trust_data['maintenance_score']:.2f}")
|
||||
print(f"Quality Score: {trust_data['quality_score']:.2f}")
|
||||
print(f"Preventive Score: {trust_data['preventive_score']:.2f}")
|
||||
print(f"Last Calculated: {trust_data['last_calculated']}")
|
||||
|
||||
if trust_data['weights']:
|
||||
print(f"\nSúlyozások:")
|
||||
for key, value in trust_data['weights'].items():
|
||||
print(f" {key}: {value:.2f}")
|
||||
|
||||
if trust_data['tolerance_km']:
|
||||
print(f"Tolerancia KM: {trust_data['tolerance_km']}")
|
||||
|
||||
# Ellenőrizzük, hogy a UserTrustProfile létrejött-e
|
||||
from sqlalchemy import select
|
||||
from app.models.identity import UserTrustProfile
|
||||
stmt = select(UserTrustProfile).where(UserTrustProfile.user_id == user.id)
|
||||
result = await db.execute(stmt)
|
||||
profile = result.scalar_one_or_none()
|
||||
|
||||
if profile:
|
||||
print(f"\nUserTrustProfile létrehozva:")
|
||||
print(f" Trust Score: {profile.trust_score}")
|
||||
print(f" Last Calculated: {profile.last_calculated}")
|
||||
else:
|
||||
print("\nFIGYELEM: UserTrustProfile nem jött létre!")
|
||||
|
||||
print("\n✅ TrustEngine sikeresen működik!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Hiba történt: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_trust_engine())
|
||||
256
backend/app/tests/e2e/test_user_registration_flow.py
Normal file
256
backend/app/tests/e2e/test_user_registration_flow.py
Normal file
@@ -0,0 +1,256 @@
|
||||
"""
|
||||
End-to-end test for user registration flow with Mailpit email interception.
|
||||
This test validates the complete user journey:
|
||||
1. Register with a unique email (Lite registration)
|
||||
2. Intercept activation email via Mailpit API
|
||||
3. Extract verification token and call verify-email endpoint
|
||||
4. Login with credentials
|
||||
5. Complete KYC with dummy data
|
||||
6. Verify gamification endpoint returns 200 OK
|
||||
"""
|
||||
import asyncio
|
||||
import httpx
|
||||
import pytest
|
||||
import uuid
|
||||
import re
|
||||
import logging
|
||||
from typing import Dict, Optional
|
||||
from datetime import date
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configuration
|
||||
BASE_URL = "http://sf_api:8000"
|
||||
MAILPIT_URL = "http://sf_mailpit:8025"
|
||||
TEST_EMAIL_DOMAIN = "example.com"
|
||||
|
||||
|
||||
class MailpitClient:
|
||||
"""Client for interacting with Mailpit API."""
|
||||
|
||||
def __init__(self, base_url: str = MAILPIT_URL):
|
||||
self.base_url = base_url
|
||||
self.client = httpx.AsyncClient(timeout=30.0)
|
||||
|
||||
async def get_latest_message(self) -> Optional[Dict]:
|
||||
"""Fetch the latest email message from Mailpit."""
|
||||
try:
|
||||
response = await self.client.get(f"{self.base_url}/api/v1/messages?limit=1")
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if data.get("messages"):
|
||||
return data["messages"][0]
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch latest message: {e}")
|
||||
return None
|
||||
|
||||
async def get_message_content(self, message_id: str) -> Optional[str]:
|
||||
"""Get the full content (HTML and text) of a specific message."""
|
||||
try:
|
||||
response = await self.client.get(f"{self.base_url}/api/v1/message/{message_id}")
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
# Prefer text over HTML
|
||||
return data.get("Text") or data.get("HTML") or ""
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch message content: {e}")
|
||||
return None
|
||||
|
||||
async def cleanup(self):
|
||||
"""Close the HTTP client."""
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
class APIClient:
|
||||
"""Client for interacting with the Service Finder API."""
|
||||
|
||||
def __init__(self, base_url: str = BASE_URL):
|
||||
self.base_url = base_url
|
||||
self.client = httpx.AsyncClient(timeout=30.0)
|
||||
self.token = None
|
||||
|
||||
async def register(self, email: str, password: str = "TestPassword123!") -> httpx.Response:
|
||||
"""Register a new user."""
|
||||
payload = {
|
||||
"email": email,
|
||||
"password": password,
|
||||
"first_name": "Test",
|
||||
"last_name": "User",
|
||||
"region_code": "HU",
|
||||
"lang": "hu"
|
||||
}
|
||||
response = await self.client.post(f"{self.base_url}/api/v1/auth/register", json=payload)
|
||||
return response
|
||||
|
||||
async def login(self, email: str, password: str = "TestPassword123!") -> Optional[str]:
|
||||
"""Login and return JWT token."""
|
||||
payload = {
|
||||
"username": email,
|
||||
"password": password
|
||||
}
|
||||
response = await self.client.post(f"{self.base_url}/api/v1/auth/login", data=payload)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
self.token = data.get("access_token")
|
||||
return self.token
|
||||
return None
|
||||
|
||||
async def verify_email(self, token: str) -> httpx.Response:
|
||||
"""Call verify-email endpoint with token."""
|
||||
payload = {"token": token}
|
||||
response = await self.client.post(f"{self.base_url}/api/v1/auth/verify-email", json=payload)
|
||||
return response
|
||||
|
||||
async def complete_kyc(self, kyc_data: Dict) -> httpx.Response:
|
||||
"""Complete KYC for current user."""
|
||||
headers = {}
|
||||
if self.token:
|
||||
headers["Authorization"] = f"Bearer {self.token}"
|
||||
response = await self.client.post(f"{self.base_url}/api/v1/auth/complete-kyc", json=kyc_data, headers=headers)
|
||||
return response
|
||||
|
||||
async def get_gamification(self) -> httpx.Response:
|
||||
"""Get gamification data for current user."""
|
||||
headers = {}
|
||||
if self.token:
|
||||
headers["Authorization"] = f"Bearer {self.token}"
|
||||
response = await self.client.get(f"{self.base_url}/api/v1/gamification/me", headers=headers)
|
||||
return response
|
||||
|
||||
async def cleanup(self):
|
||||
"""Close the HTTP client."""
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
def extract_verification_token(text: str) -> Optional[str]:
|
||||
"""
|
||||
Extract verification token from email text using regex.
|
||||
Looks for UUID patterns in URLs or plain text.
|
||||
"""
|
||||
# Pattern for UUID (version 4)
|
||||
uuid_pattern = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
||||
match = re.search(uuid_pattern, text, re.IGNORECASE)
|
||||
if match:
|
||||
return match.group(0)
|
||||
|
||||
# Fallback: look for token parameter in URL
|
||||
token_pattern = r'(?:token|code)=([0-9a-f\-]+)'
|
||||
match = re.search(token_pattern, text, re.IGNORECASE)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def create_dummy_kyc_data() -> Dict:
|
||||
"""Create dummy KYC data for testing."""
|
||||
return {
|
||||
"phone_number": "+36123456789",
|
||||
"birth_place": "Budapest",
|
||||
"birth_date": "1990-01-01",
|
||||
"mothers_last_name": "Kovács",
|
||||
"mothers_first_name": "Éva",
|
||||
"address_zip": "1011",
|
||||
"address_city": "Budapest",
|
||||
"address_street_name": "Kossuth",
|
||||
"address_street_type": "utca",
|
||||
"address_house_number": "1",
|
||||
"address_stairwell": "A",
|
||||
"address_floor": "2",
|
||||
"address_door": "3",
|
||||
"address_hrsz": None,
|
||||
"identity_docs": {
|
||||
"ID_CARD": {
|
||||
"number": "123456AB",
|
||||
"expiry_date": "2030-12-31"
|
||||
}
|
||||
},
|
||||
"ice_contact": {
|
||||
"name": "Test Contact",
|
||||
"phone": "+36198765432",
|
||||
"relationship": "parent"
|
||||
},
|
||||
"preferred_language": "hu",
|
||||
"preferred_currency": "HUF"
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_registration_flow():
|
||||
"""Main E2E test for user registration flow."""
|
||||
# Generate unique test email
|
||||
test_email = f"test_{uuid.uuid4().hex[:8]}@{TEST_EMAIL_DOMAIN}"
|
||||
logger.info(f"Using test email: {test_email}")
|
||||
|
||||
# Initialize clients
|
||||
api_client = APIClient()
|
||||
mailpit = MailpitClient()
|
||||
|
||||
try:
|
||||
# 1. Register new user (Lite registration)
|
||||
logger.info("Step 1: Registering user")
|
||||
reg_response = await api_client.register(test_email)
|
||||
assert reg_response.status_code in (200, 201, 202), f"Registration failed: {reg_response.status_code} - {reg_response.text}"
|
||||
logger.info(f"Registration response: {reg_response.status_code}")
|
||||
|
||||
# 2. Wait for email (Mailpit may need a moment)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# 3. Fetch latest email from Mailpit
|
||||
logger.info("Step 2: Fetching email from Mailpit")
|
||||
message = await mailpit.get_latest_message()
|
||||
assert message is not None, "No email found in Mailpit"
|
||||
logger.info(f"Found email with ID: {message.get('ID')}, Subject: {message.get('Subject')}")
|
||||
|
||||
# 4. Get email content and extract verification token
|
||||
content = await mailpit.get_message_content(message["ID"])
|
||||
assert content, "Email content is empty"
|
||||
|
||||
token = extract_verification_token(content)
|
||||
assert token is not None, f"Could not extract verification token from email content: {content[:500]}"
|
||||
logger.info(f"Extracted verification token: {token}")
|
||||
|
||||
# 5. Verify email using the token
|
||||
logger.info("Step 3: Verifying email")
|
||||
verify_response = await api_client.verify_email(token)
|
||||
assert verify_response.status_code in (200, 201, 202), f"Email verification failed: {verify_response.status_code} - {verify_response.text}"
|
||||
logger.info(f"Email verification response: {verify_response.status_code}")
|
||||
|
||||
# 6. Login to get JWT token
|
||||
logger.info("Step 4: Logging in")
|
||||
token = await api_client.login(test_email)
|
||||
assert token is not None, "Login failed - no token received"
|
||||
logger.info("Login successful, token obtained")
|
||||
|
||||
# 7. Complete KYC with dummy data
|
||||
logger.info("Step 5: Completing KYC")
|
||||
kyc_data = create_dummy_kyc_data()
|
||||
kyc_response = await api_client.complete_kyc(kyc_data)
|
||||
assert kyc_response.status_code in (200, 201, 202), f"KYC completion failed: {kyc_response.status_code} - {kyc_response.text}"
|
||||
logger.info(f"KYC completion response: {kyc_response.status_code}")
|
||||
|
||||
# 8. Verify gamification endpoint
|
||||
logger.info("Step 6: Checking gamification endpoint")
|
||||
gamification_response = await api_client.get_gamification()
|
||||
assert gamification_response.status_code == 200, f"Gamification endpoint failed: {gamification_response.status_code} - {gamification_response.text}"
|
||||
logger.info(f"Gamification response: {gamification_response.status_code}")
|
||||
|
||||
# Optional: Validate response structure
|
||||
gamification_data = gamification_response.json()
|
||||
assert "points" in gamification_data or "level" in gamification_data or "achievements" in gamification_data, \
|
||||
"Gamification response missing expected fields"
|
||||
|
||||
logger.info("✅ All steps passed! User registration flow works end-to-end.")
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
await api_client.cleanup()
|
||||
await mailpit.cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# For manual testing
|
||||
import sys
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
asyncio.run(test_user_registration_flow())
|
||||
49
backend/app/tests/e2e/test_vehicle_flow.py
Normal file
49
backend/app/tests/e2e/test_vehicle_flow.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
End-to-end test for Vehicle/Asset creation flow.
|
||||
Uses the authenticated_client fixture to test adding a new vehicle to the user's garage.
|
||||
"""
|
||||
import pytest
|
||||
import httpx
|
||||
import uuid
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vehicle_creation(authenticated_client: httpx.AsyncClient, setup_organization):
|
||||
"""
|
||||
Test that a user can add a new vehicle (asset) to their garage.
|
||||
Uses the new POST /api/v1/assets/vehicles endpoint.
|
||||
"""
|
||||
# Generate unique VIN and license plate
|
||||
unique_suffix = uuid.uuid4().hex[:8]
|
||||
# VIN must be exactly 17 characters
|
||||
vin = f"VIN{unique_suffix}123456" # 3 + 8 + 6 = 17
|
||||
payload = {
|
||||
"vin": vin,
|
||||
"license_plate": f"TEST-{unique_suffix[:6]}",
|
||||
# catalog_id omitted (optional)
|
||||
"organization_id": setup_organization,
|
||||
}
|
||||
# The backend will uppercase the VIN, so we compare case-insensitively
|
||||
expected_vin = vin.upper()
|
||||
|
||||
# POST to the new endpoint
|
||||
response = await authenticated_client.post(
|
||||
"/api/v1/assets/vehicles",
|
||||
json=payload
|
||||
)
|
||||
|
||||
# Assert success (201 Created)
|
||||
assert response.status_code == 201, f"Unexpected status: {response.status_code}, response: {response.text}"
|
||||
|
||||
# Parse response
|
||||
data = response.json()
|
||||
# Expect AssetResponse schema
|
||||
assert "id" in data
|
||||
assert data["vin"] == expected_vin
|
||||
assert data["license_plate"] == payload["license_plate"].upper()
|
||||
|
||||
asset_id = data["id"]
|
||||
print(f"✅ Vehicle/Asset created with ID: {asset_id}")
|
||||
|
||||
# Return the asset_id for potential use in expense test
|
||||
return asset_id
|
||||
358
backend/app/tests/run_admin_audit.py
Normal file
358
backend/app/tests/run_admin_audit.py
Normal file
@@ -0,0 +1,358 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Standalone Admin Audit Script for Service Finder
|
||||
|
||||
Ez a szkript teljesen önálló, nem használ subprocess-t, és közvetlenül hívja a Gitea API-t.
|
||||
A konténeren belül fut, ahol a Docker parancsok nem elérhetők.
|
||||
|
||||
Feladatok:
|
||||
1. Hardcode értékek szkennelése a megadott könyvtárakban
|
||||
2. Gitea mérföldkő létrehozása
|
||||
3. 4 db issue létrehozása a szigorú sablonnal
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
|
||||
# ==================== KONSTANSOK ====================
|
||||
GITEA_URL = "http://gitea:3000/api/v1"
|
||||
TOKEN = "783f58519ee0ca060491dbc07f3dde1d8e48c5dd"
|
||||
HEADERS = {"Authorization": f"token {TOKEN}", "Content-Type": "application/json"}
|
||||
REPO_OWNER = "kincses"
|
||||
REPO_NAME = "service-finder"
|
||||
# A Docker konténer belső útvonalai!
|
||||
SCAN_DIRS = [Path("/app/app/services"), Path("/app/app/api/v1/endpoints")]
|
||||
|
||||
# Hardcode minta regexek - kombinált lista
|
||||
HARDCODE_PATTERNS = [
|
||||
# Általános minták
|
||||
(r'\b\d{1,3}\b', "Mágikus szám (1-3 jegyű)"),
|
||||
(r'\b(50|10|100|1000|5000|10000)\b', "Gyakori mágikus szám (pl. 50, 10)"),
|
||||
(r'"(active|inactive|pending|approved|rejected|blocked)"', "Fix státusz string"),
|
||||
(r"'active'|'inactive'|'pending'|'approved'|'rejected'|'blocked'", "Fix státusz string (aposztróf)"),
|
||||
(r'\b(True|False)\b', "Hardcode boolean"),
|
||||
(r'\b(max|min|limit|threshold|default)\s*=\s*\d+', "Limit/Threshold érték"),
|
||||
|
||||
# Specifikus gamification minták
|
||||
(r'award_points\([^)]*,\s*(\d+)\s*,', "Fix pontszám osztás (Gamification)"),
|
||||
(r'validation_level\s*[+=]\s*(\d+)', "Fix validációs szint növelés/csökkentés"),
|
||||
(r'validation_level\s*[<>]=?\s*(\d+)', "Fix validációs küszöb ellenőrzés"),
|
||||
(r'trust_score\s*[+=]\s*(\d+)', "Fix trust score módosítás"),
|
||||
(r'level_threshold\s*=\s*(\d+)', "Fix szint küszöbérték"),
|
||||
(r'bonus_multiplier\s*=\s*(\d+(?:\.\d+)?)', "Fix bónusz szorzó"),
|
||||
]
|
||||
|
||||
# ==================== GITEA API SEGÉDFÜGGVÉNYEK ====================
|
||||
|
||||
def gitea_request(method: str, endpoint: str, data: Optional[Dict] = None) -> Tuple[bool, Dict]:
|
||||
"""Közvetlen Gitea API hívás."""
|
||||
url = f"{GITEA_URL}/{endpoint}"
|
||||
try:
|
||||
if method.upper() == "GET":
|
||||
response = requests.get(url, headers=HEADERS, timeout=30)
|
||||
elif method.upper() == "POST":
|
||||
response = requests.post(url, headers=HEADERS, json=data, timeout=30)
|
||||
else:
|
||||
return False, {"error": f"Unsupported method: {method}"}
|
||||
|
||||
if response.status_code >= 200 and response.status_code < 300:
|
||||
return True, response.json()
|
||||
else:
|
||||
return False, {"error": f"HTTP {response.status_code}", "details": response.text}
|
||||
except requests.exceptions.RequestException as e:
|
||||
return False, {"error": f"Request failed: {e}"}
|
||||
|
||||
def create_milestone(title: str, description: str) -> Tuple[bool, Optional[int]]:
|
||||
"""Mérföldkő létrehozása a Gitea-ban."""
|
||||
data = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"state": "open"
|
||||
}
|
||||
success, result = gitea_request("POST", f"repos/{REPO_OWNER}/{REPO_NAME}/milestones", data)
|
||||
if success and "id" in result:
|
||||
return True, result["id"]
|
||||
else:
|
||||
print(f"❌ Mérföldkő létrehozása sikertelen: {result}")
|
||||
return False, None
|
||||
|
||||
def create_issue(title: str, body: str, milestone_id: Optional[int] = None, labels: Optional[List[str]] = None) -> Tuple[bool, Optional[int]]:
|
||||
"""Issue létrehozása a Gitea-ban."""
|
||||
data = {
|
||||
"title": title,
|
||||
"body": body,
|
||||
"state": "open"
|
||||
}
|
||||
if milestone_id:
|
||||
data["milestone"] = milestone_id
|
||||
if labels:
|
||||
data["labels"] = labels
|
||||
|
||||
success, result = gitea_request("POST", f"repos/{REPO_OWNER}/{REPO_NAME}/issues", data)
|
||||
if success and "id" in result:
|
||||
return True, result["id"]
|
||||
else:
|
||||
print(f"❌ Issue létrehozása sikertelen: {result}")
|
||||
return False, None
|
||||
|
||||
# ==================== HARDCODE SCANNER ====================
|
||||
|
||||
def find_python_files(directory: Path) -> List[Path]:
|
||||
"""Rekurzívan gyűjti össze az összes .py fájlt a megadott könyvtárban."""
|
||||
python_files = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
python_files.append(Path(root) / file)
|
||||
return python_files
|
||||
|
||||
def scan_file(file_path: Path) -> List[Dict]:
|
||||
"""Egy fájlban keres hardcode értékeket a regex minták alapján."""
|
||||
findings = []
|
||||
try:
|
||||
content = file_path.read_text(encoding='utf-8')
|
||||
lines = content.splitlines()
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
for pattern, description in HARDCODE_PATTERNS:
|
||||
matches = re.finditer(pattern, line)
|
||||
for match in matches:
|
||||
# Csak számértékeket gyűjtsünk a specifikus mintáknál
|
||||
if 'gamification' in description.lower() or 'trust' in description.lower():
|
||||
value = match.group(1) if match.groups() else match.group()
|
||||
else:
|
||||
value = match.group()
|
||||
|
||||
findings.append({
|
||||
'file': str(file_path),
|
||||
'line': line_num,
|
||||
'column': match.start() + 1,
|
||||
'match': value,
|
||||
'description': description,
|
||||
'context': line.strip()[:100]
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"⚠️ Hiba a fájl olvasásakor {file_path}: {e}")
|
||||
|
||||
return findings
|
||||
|
||||
def generate_markdown_report(findings: List[Dict]) -> str:
|
||||
"""Generál egy Markdown formátumú riportot a találatokról."""
|
||||
if not findings:
|
||||
return "## ✅ Nincs hardcode találat\n\nA szkennelés nem talált gyanús hardcode értékeket."
|
||||
|
||||
# Csoportosítás fájl szerint
|
||||
by_file = {}
|
||||
for finding in findings:
|
||||
file = finding['file']
|
||||
if file not in by_file:
|
||||
by_file[file] = []
|
||||
by_file[file].append(finding)
|
||||
|
||||
report_lines = [
|
||||
"# 🔍 Hardcode Audit Részletes Részletek",
|
||||
"",
|
||||
f"**Összes találat:** {len(findings)}",
|
||||
"",
|
||||
"---",
|
||||
]
|
||||
|
||||
for file, file_findings in sorted(by_file.items()):
|
||||
# Csak relatív útvonalat mutassunk
|
||||
rel_path = file
|
||||
try:
|
||||
rel_path = Path(file).relative_to("/app")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
report_lines.append(f"## 📄 {rel_path}")
|
||||
report_lines.append("")
|
||||
|
||||
for finding in file_findings:
|
||||
report_lines.append(f"### L{ finding['line'] }: `{ finding['match'] }`")
|
||||
report_lines.append(f"- **Leírás:** {finding['description']}")
|
||||
report_lines.append(f"- **Kontextus:** `{finding['context']}`")
|
||||
report_lines.append(f"- **Hely:** {finding['file']}:{finding['line']}:{finding['column']}")
|
||||
report_lines.append("")
|
||||
|
||||
return "\n".join(report_lines)
|
||||
|
||||
# ==================== EPIC BUILDER LOGIKA ====================
|
||||
|
||||
def run_hardcode_scan() -> Tuple[List[Dict], str]:
|
||||
"""Végrehajtja a hardcode szkennelést és generálja a jelentést."""
|
||||
print("🔍 Hardcode értékek szkennelése...")
|
||||
all_findings = []
|
||||
|
||||
for scan_dir in SCAN_DIRS:
|
||||
if not scan_dir.exists():
|
||||
print(f"⚠️ A könyvtár nem létezik: {scan_dir}")
|
||||
continue
|
||||
|
||||
print(f" 📁 Könyvtár: {scan_dir}")
|
||||
python_files = find_python_files(scan_dir)
|
||||
print(f" {len(python_files)} Python fájl található")
|
||||
|
||||
for file_path in python_files:
|
||||
findings = scan_file(file_path)
|
||||
all_findings.extend(findings)
|
||||
|
||||
print(f"✅ Szkennelés kész. Összes találat: {len(all_findings)}")
|
||||
markdown_report = generate_markdown_report(all_findings)
|
||||
return all_findings, markdown_report
|
||||
|
||||
def create_epic_project(markdown_report: str):
|
||||
"""Létrehozza a teljes projekt tervet a Gitea-ban."""
|
||||
print("\n🚀 Gitea Projekt Terv Létrehozása...")
|
||||
|
||||
# 1. Mérföldkő létrehozása
|
||||
print("📌 Mérföldkő létrehozása...")
|
||||
milestone_title = "v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig"
|
||||
milestone_desc = "Admin rendszer fejlesztése: RBAC, dinamikus konfiguráció, anomália detektálás"
|
||||
success, milestone_id = create_milestone(milestone_title, milestone_desc)
|
||||
if success:
|
||||
print(f"✅ Mérföldkő sikeresen létrehozva (ID: {milestone_id})")
|
||||
else:
|
||||
print("⚠️ Mérföldkő létrehozása sikertelen, folytatás milestone nélkül")
|
||||
milestone_id = None
|
||||
|
||||
# 2. Issue 1: Hardcode Értékek Dinamikussá Tétele
|
||||
issue1_body = f"""**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Hardcode értékek kiszervezése SystemParameter táblába és ConfigService-be
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Database (system.parameters tábla), ConfigService
|
||||
- **Kimenet (Mik támaszkodnak rá):** GamificationService, NotificationService, SecurityService
|
||||
|
||||
### 📝 Elemzés
|
||||
A hardcode audit {len(markdown_report.splitlines())} sor találatot jelentett. Ezeket az értékeket át kell helyezni a dinamikus konfigurációs rendszerbe.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `SystemParameter` vagy `AdminConfig` adatbázis tábla létrehozása (Key-Value alapú)
|
||||
- [ ] `ConfigService` megírása (Redis/Memória gyorsítótárral)
|
||||
- [ ] A kód átírása, hogy az értékeket a DB-ből olvassa
|
||||
|
||||
### 🔍 Hardcode Találatok (Összefoglaló)
|
||||
{markdown_report[:1500]}...
|
||||
"""
|
||||
|
||||
print("📝 Issue 1 létrehozása...")
|
||||
success, issue1_id = create_issue(
|
||||
title="Phase 1: Hardcode Értékek Dinamikussá Tétele",
|
||||
body=issue1_body,
|
||||
milestone_id=milestone_id,
|
||||
labels=["Scope: Backend", "Type: Refactor", "Status: To Do"]
|
||||
)
|
||||
if success:
|
||||
print(f"✅ Issue 1 létrehozva (ID: {issue1_id})")
|
||||
|
||||
# 3. Issue 2: RBAC és Admin API Router
|
||||
issue2_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Superadmin, Moderator szerepkörök és `/api/v1/admin` végpontok implementálása
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Identity modell (User, Role), Permission tábla
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin UI, Moderátori felület
|
||||
|
||||
### 📝 Elemzés
|
||||
Létre kell hozni a Role-Based Access Control (RBAC) rendszert, amely támogatja a Superadmin, Moderator, és Auditor szerepköröket.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `User` modell bővítése: `role` oszlop bevezetése (user, moderator, superadmin)
|
||||
- [ ] FastAPI Dependency (`get_current_admin`) megírása, ami blokkolja a normál usereket
|
||||
- [ ] Az `/api/v1/admin` router regisztrálása a main.py-ban
|
||||
"""
|
||||
|
||||
print("📝 Issue 2 létrehozása...")
|
||||
success, issue2_id = create_issue(
|
||||
title="Phase 2: RBAC és Admin API Router",
|
||||
body=issue2_body,
|
||||
milestone_id=milestone_id,
|
||||
labels=["Scope: Backend", "Type: Feature", "Role: Admin", "Status: To Do"]
|
||||
)
|
||||
if success:
|
||||
print(f"✅ Issue 2 létrehozva (ID: {issue2_id})")
|
||||
|
||||
# 4. Issue 3: Core Felügyeleti Végpontok
|
||||
issue3_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** User (KYC), Jármű, Szerviz felügyelet (Tiltás/Jóváhagyás) végpontok
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** UserService, VehicleService, ServiceRegistry
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin dashboard, Moderátori munkafolyamatok
|
||||
|
||||
### 📝 Elemzés
|
||||
Külön végpontok kellenek a felhasználók KYC (Know Your Customer) jóváhagyásához, járművek tiltásához/engedélyezéséhez, és szervizek moderálásához.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `GET /admin/users` - Felhasználók listázása (szűréssel, pl. pending KYC)
|
||||
- [ ] `POST /admin/users/{id}/ban` - Fiók tiltása/felfüggesztése
|
||||
- [ ] `POST /admin/marketplace/services/{id}/approve` - Szerviz manuális 100%-ra validálása (Kék pipa)
|
||||
- [ ] `GET /admin/assets/flagged` - Gyanús járművek listája
|
||||
"""
|
||||
|
||||
print("📝 Issue 3 létrehozása...")
|
||||
success, issue3_id = create_issue(
|
||||
title="Phase 3: Core Felügyeleti Végpontok",
|
||||
body=issue3_body,
|
||||
milestone_id=milestone_id,
|
||||
labels=["Scope: API", "Type: Feature", "Role: Admin", "Status: To Do"]
|
||||
)
|
||||
if success:
|
||||
print(f"✅ Issue 3 létrehozva (ID: {issue3_id})")
|
||||
|
||||
# 5. Issue 4: Anomália Detektálás (Anti-Cheat)
|
||||
issue4_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Robot felügyelő a gyanús aktivitásokhoz (pl. túl gyors pontgyűjtés, sok sikertelen bejelentkezés)
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Audit log, Gamification events, Security events
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin értesítések, Automatikus tiltások
|
||||
|
||||
### 📝 Elemzés
|
||||
Anomália detektáló algoritmus készítése, amely gyanús mintákat keres a felhasználói aktivitásban.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] Automatikus detektálás gyanúsan sok validációra rövid időn belül
|
||||
- [ ] Sebesség/Távolság ellenőrzés (Nem lehet 1 perc alatt 50km-re lévő szervizeket rögzíteni)
|
||||
- [ ] Riasztások küldése a `/admin/alerts` végpontra a Moderátoroknak
|
||||
"""
|
||||
|
||||
print("📝 Issue 4 létrehozása...")
|
||||
success, issue4_id = create_issue(
|
||||
title="Phase 4: Anomália Detektálás (Anti-Cheat)",
|
||||
body=issue4_body,
|
||||
milestone_id=milestone_id,
|
||||
labels=["Scope: Backend", "Type: Feature", "Role: Admin", "Status: To Do"]
|
||||
)
|
||||
if success:
|
||||
print(f"✅ Issue 4 létrehozva (ID: {issue4_id})")
|
||||
|
||||
print("\n🎉 Gitea projekt terv sikeresen létrehozva!")
|
||||
print(f" Mérföldkő ID: {milestone_id}")
|
||||
print(f" Issue-k: {issue1_id}, {issue2_id}, {issue3_id}, {issue4_id}")
|
||||
|
||||
# ==================== FŐ FÜGGVÉNY ====================
|
||||
|
||||
def main():
|
||||
"""A szkript fő végrehajtási logikája."""
|
||||
print("=" * 60)
|
||||
print("🚀 Service Finder Admin Audit & Gitea Epic Builder")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. Hardcode szkennelés
|
||||
findings, markdown_report = run_hardcode_scan()
|
||||
|
||||
# 2. Gitea projekt létrehozása
|
||||
create_epic_project(markdown_report)
|
||||
|
||||
print("\n✅ A szkript sikeresen lefutott!")
|
||||
print(" A Gitea-ban megjelentek a kártyák a 'v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig' mérföldkő alatt.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
262
backend/app/tests/test_admin_audit_gitea.py
Normal file
262
backend/app/tests/test_admin_audit_gitea.py
Normal file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Hardcode Audit Teszt és Gitea Integráció
|
||||
|
||||
Ez a szkript:
|
||||
1. Szkennel a backend/app/services/ és backend/app/api/ mappákban hardcode értékeket
|
||||
2. Generál egy Markdown riportot a találatokról
|
||||
3. Létrehoz egy mérföldkövet és 4 issue-t a Gitea-ban az admin rendszer fejlesztéséhez
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
# ==================== KONFIGURÁCIÓ ====================
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent # /opt/docker/dev/service_finder
|
||||
GITEA_SCRIPT = PROJECT_ROOT / ".roo" / "scripts" / "gitea_manager.py"
|
||||
|
||||
SCAN_DIRS = [
|
||||
PROJECT_ROOT / "backend" / "app" / "services",
|
||||
PROJECT_ROOT / "backend" / "app" / "api",
|
||||
]
|
||||
|
||||
# Hardcode minta regexek
|
||||
HARDCODE_PATTERNS = [
|
||||
(r'\b\d{1,3}\b', "Mágikus szám (1-3 jegyű)"),
|
||||
(r'\b(50|10|100|1000|5000|10000)\b', "Gyakori mágikus szám (pl. 50, 10)"),
|
||||
(r'"(active|inactive|pending|approved|rejected|blocked)"', "Fix státusz string"),
|
||||
(r"'active'|'inactive'|'pending'|'approved'|'rejected'|'blocked'", "Fix státusz string (aposztróf)"),
|
||||
(r'\b(True|False)\b', "Hardcode boolean"),
|
||||
(r'\b(max|min|limit|threshold|default)\s*=\s*\d+', "Limit/Threshold érték"),
|
||||
]
|
||||
|
||||
# ==================== SEGÉDFÜGGVÉNYEK ====================
|
||||
|
||||
def find_python_files(directory: Path) -> List[Path]:
|
||||
"""Rekurzívan gyűjti össze az összes .py fájlt a megadott könyvtárban."""
|
||||
python_files = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
python_files.append(Path(root) / file)
|
||||
return python_files
|
||||
|
||||
def scan_file(file_path: Path) -> List[Dict]:
|
||||
"""Egy fájlban keres hardcode értékeket a regex minták alapján."""
|
||||
findings = []
|
||||
try:
|
||||
content = file_path.read_text(encoding='utf-8')
|
||||
lines = content.splitlines()
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
for pattern, description in HARDCODE_PATTERNS:
|
||||
matches = re.finditer(pattern, line)
|
||||
for match in matches:
|
||||
findings.append({
|
||||
'file': str(file_path.relative_to(PROJECT_ROOT)),
|
||||
'line': line_num,
|
||||
'column': match.start() + 1,
|
||||
'match': match.group(),
|
||||
'description': description,
|
||||
'context': line.strip()[:100]
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"⚠️ Hiba a fájl olvasásakor {file_path}: {e}")
|
||||
|
||||
return findings
|
||||
|
||||
def generate_markdown_report(findings: List[Dict]) -> str:
|
||||
"""Generál egy Markdown formátumú riportot a találatokról."""
|
||||
if not findings:
|
||||
return "## ✅ Nincs hardcode találat\n\nA szkennelés nem talált gyanús hardcode értékeket."
|
||||
|
||||
# Csoportosítás fájl szerint
|
||||
by_file = {}
|
||||
for finding in findings:
|
||||
file = finding['file']
|
||||
if file not in by_file:
|
||||
by_file[file] = []
|
||||
by_file[file].append(finding)
|
||||
|
||||
report_lines = [
|
||||
"# 🔍 Hardcode Audit Részletes Részletek",
|
||||
"",
|
||||
f"**Összes találat:** {len(findings)}",
|
||||
"",
|
||||
"---",
|
||||
]
|
||||
|
||||
for file, file_findings in sorted(by_file.items()):
|
||||
report_lines.append(f"## 📄 {file}")
|
||||
report_lines.append("")
|
||||
|
||||
for finding in file_findings:
|
||||
report_lines.append(f"### L{ finding['line'] }: `{ finding['match'] }`")
|
||||
report_lines.append(f"- **Leírás:** {finding['description']}")
|
||||
report_lines.append(f"- **Kontextus:** `{finding['context']}`")
|
||||
report_lines.append(f"- **Hely:** {finding['file']}:{finding['line']}:{finding['column']}")
|
||||
report_lines.append("")
|
||||
|
||||
return "\n".join(report_lines)
|
||||
|
||||
def run_gitea_command(args: List[str]) -> Tuple[bool, str]:
|
||||
"""Futtat egy Gitea manager parancsot."""
|
||||
cmd = ["docker", "exec", "roo-helper", "python3", "/scripts/gitea_manager.py"] + args
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
return result.returncode == 0, result.stdout + "\n" + result.stderr
|
||||
except subprocess.TimeoutExpired:
|
||||
return False, "Időtúllépés a parancs futtatásakor"
|
||||
except Exception as e:
|
||||
return False, f"Hiba: {e}"
|
||||
|
||||
def create_milestone() -> bool:
|
||||
"""Létrehozza a 'v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig' mérföldkövet."""
|
||||
print("📌 Mérföldkő létrehozása...")
|
||||
success, output = run_gitea_command([
|
||||
"ms", "create", "v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Admin rendszer fejlesztése: RBAC, dinamikus konfiguráció, anomália detektálás"
|
||||
])
|
||||
if success:
|
||||
print("✅ Mérföldkő sikeresen létrehozva")
|
||||
else:
|
||||
print(f"⚠️ Figyelmeztetés: {output}")
|
||||
return success
|
||||
|
||||
def create_issue(title: str, body: str, labels: List[str]) -> bool:
|
||||
"""Létrehoz egy issue-t a Gitea-ban."""
|
||||
print(f"📝 Issue létrehozása: {title}")
|
||||
|
||||
# Build the command
|
||||
cmd = ["create", f'"{title}"', f'"{body}"', "v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig"]
|
||||
cmd.extend(labels)
|
||||
|
||||
success, output = run_gitea_command(cmd)
|
||||
if success:
|
||||
print(f"✅ Issue sikeresen létrehozva: {title}")
|
||||
else:
|
||||
print(f"⚠️ Hiba az issue létrehozásakor: {output}")
|
||||
return success
|
||||
|
||||
def create_gitea_issues(markdown_report: str):
|
||||
"""Létrehozza a 4 issue-t a Gitea-ban a megadott sablonnal."""
|
||||
|
||||
# Issue 1: Hardcode értékek dinamikussá tétele
|
||||
issue1_body = f"""**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Hardcode értékek kiszervezése SystemParameter táblába és ConfigService-be
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Database (system.parameters tábla), ConfigService
|
||||
- **Kimenet (Mik támaszkodnak rá):** GamificationService, NotificationService, SecurityService
|
||||
|
||||
### 📝 Elemzés
|
||||
A hardcode audit {len(markdown_report.splitlines())} sor találatot jelentett. Ezeket az értékeket át kell helyezni a dinamikus konfigurációs rendszerbe.
|
||||
|
||||
### 🔍 Hardcode Találatok (Összefoglaló)
|
||||
{markdown_report[:2000]}...
|
||||
"""
|
||||
|
||||
# Issue 2: RBAC és Admin API Router
|
||||
issue2_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Superadmin, Moderator szerepkörök és `/api/v1/admin` végpontok implementálása
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Identity modell (User, Role), Permission tábla
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin UI, Moderátori felület
|
||||
|
||||
### 📝 Elemzés
|
||||
Létre kell hozni a Role-Based Access Control (RBAC) rendszert, amely támogatja a Superadmin, Moderator, és Auditor szerepköröket. Az admin végpontoknak külön routerben kell lenniük, és JWT token alapú autorizációt kell használniuk.
|
||||
"""
|
||||
|
||||
# Issue 3: Core Felügyeleti Végpontok
|
||||
issue3_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** User (KYC), Jármű, Szerviz felügyelet (Tiltás/Jóváhagyás) végpontok
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** UserService, VehicleService, ServiceRegistry
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin dashboard, Moderátori munkafolyamatok
|
||||
|
||||
### 📝 Elemzés
|
||||
Külön végpontok kellenek a felhasználók KYC (Know Your Customer) jóváhagyásához, járművek tiltásához/engedélyezéséhez, és szervizek moderálásához. Minden művelet naplózandó az audit logba.
|
||||
"""
|
||||
|
||||
# Issue 4: Anomália Detektálás (Anti-Cheat)
|
||||
issue4_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Robot felügyelő a gyanús aktivitásokhoz (pl. túl gyors pontgyűjtés, sok sikertelen bejelentkezés)
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Audit log, Gamification events, Security events
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin értesítések, Automatikus tiltások
|
||||
|
||||
### 📝 Elemzés
|
||||
Anomália detektáló algoritmus készítése, amely gyanús mintákat keres a felhasználói aktivitásban. A rendszer automatikusan jelzést küld és/vagy ideiglenesen tiltja a gyanús fiókokat.
|
||||
"""
|
||||
|
||||
# Issue létrehozások
|
||||
issues = [
|
||||
("Phase 1: Hardcode Értékek Dinamikussá Tétele", issue1_body, ["Scope: Backend", "Type: Refactor"]),
|
||||
("Phase 2: RBAC és Admin API Router", issue2_body, ["Scope: Backend", "Type: Feature"]),
|
||||
("Phase 3: Core Felügyeleti Végpontok", issue3_body, ["Scope: API", "Type: Feature"]),
|
||||
("Phase 4: Anomália Detektálás (Anti-Cheat)", issue4_body, ["Scope: Core", "Type: Feature"]),
|
||||
]
|
||||
|
||||
for title, body, labels in issues:
|
||||
create_issue(title, body, labels)
|
||||
|
||||
# ==================== FŐPROGRAM ====================
|
||||
|
||||
def main():
|
||||
print("🔍 Hardcode Audit Szkennelés indítása...")
|
||||
|
||||
# 1. Python fájlok gyűjtése
|
||||
all_files = []
|
||||
for scan_dir in SCAN_DIRS:
|
||||
if scan_dir.exists():
|
||||
all_files.extend(find_python_files(scan_dir))
|
||||
else:
|
||||
print(f"⚠️ A könyvtár nem létezik: {scan_dir}")
|
||||
|
||||
print(f"📁 Összesen {len(all_files)} fájl található a szkenneléshez")
|
||||
|
||||
# 2. Hardcode értékek keresése
|
||||
all_findings = []
|
||||
for file in all_files:
|
||||
findings = scan_file(file)
|
||||
all_findings.extend(findings)
|
||||
|
||||
print(f"🔎 {len(all_findings)} hardcode találat")
|
||||
|
||||
# 3. Markdown riport generálása
|
||||
markdown_report = generate_markdown_report(all_findings)
|
||||
|
||||
# 4. Riport mentése fájlba (opcionális)
|
||||
report_path = PROJECT_ROOT / "hardcode_audit_report.md"
|
||||
report_path.write_text(markdown_report, encoding='utf-8')
|
||||
print(f"📄 Részletes riport mentve: {report_path}")
|
||||
|
||||
# 5. Gitea integráció
|
||||
print("\n🚀 Gitea Integráció indítása...")
|
||||
|
||||
# Ellenőrizzük, hogy a Gitea script létezik-e
|
||||
if not GITEA_SCRIPT.exists():
|
||||
print(f"❌ A Gitea manager script nem található: {GITEA_SCRIPT}")
|
||||
print("A szkript csak a riportot generálta, Gitea műveletek kihagyva.")
|
||||
return
|
||||
|
||||
# Mérföldkő létrehozása
|
||||
create_milestone()
|
||||
|
||||
# Issue-ok létrehozása
|
||||
create_gitea_issues(markdown_report)
|
||||
|
||||
print("\n✅ Audit szkript sikeresen lefutott!")
|
||||
print(f" - Találatok: {len(all_findings)}")
|
||||
print(f" - Riport: {report_path}")
|
||||
print(" - Gitea issue-k létrehozva a 'v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig' mérföldkő alatt")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
317
backend/app/tests/test_admin_audit_gitea_combined.py
Normal file
317
backend/app/tests/test_admin_audit_gitea_combined.py
Normal file
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enterprise Admin & Gamification Audit - Kombinált Verzió
|
||||
|
||||
Ez a szkript a két legjobb megközelítést egyesíti:
|
||||
1. Részletes hardcode szkennelés általános és specifikus mintákkal
|
||||
2. Gitea integráció importálással (fallback subprocess-szel)
|
||||
3. Teljes projekt terv létrehozása a Gitea-ban a szigorú sablonnal
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
|
||||
# ==================== KONFIGURÁCIÓ ====================
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent # /opt/docker/dev/service_finder
|
||||
GITEA_SCRIPT = PROJECT_ROOT / ".roo" / "scripts" / "gitea_manager.py"
|
||||
|
||||
SCAN_DIRS = [
|
||||
PROJECT_ROOT / "backend" / "app" / "services",
|
||||
PROJECT_ROOT / "backend" / "app" / "api",
|
||||
]
|
||||
|
||||
# Hardcode minta regexek - kombinált lista
|
||||
HARDCODE_PATTERNS = [
|
||||
# Általános minták (1. kódból)
|
||||
(r'\b\d{1,3}\b', "Mágikus szám (1-3 jegyű)"),
|
||||
(r'\b(50|10|100|1000|5000|10000)\b', "Gyakori mágikus szám (pl. 50, 10)"),
|
||||
(r'"(active|inactive|pending|approved|rejected|blocked)"', "Fix státusz string"),
|
||||
(r"'active'|'inactive'|'pending'|'approved'|'rejected'|'blocked'", "Fix státusz string (aposztróf)"),
|
||||
(r'\b(True|False)\b', "Hardcode boolean"),
|
||||
(r'\b(max|min|limit|threshold|default)\s*=\s*\d+', "Limit/Threshold érték"),
|
||||
|
||||
# Specifikus gamification minták (2. kódból)
|
||||
(r'award_points\([^)]*,\s*(\d+)\s*,', "Fix pontszám osztás (Gamification)"),
|
||||
(r'validation_level\s*[+=]\s*(\d+)', "Fix validációs szint növelés/csökkentés"),
|
||||
(r'validation_level\s*[<>]=?\s*(\d+)', "Fix validációs küszöb ellenőrzés"),
|
||||
(r'trust_score\s*[+=]\s*(\d+)', "Fix trust score módosítás"),
|
||||
(r'level_threshold\s*=\s*(\d+)', "Fix szint küszöbérték"),
|
||||
(r'bonus_multiplier\s*=\s*(\d+(?:\.\d+)?)', "Fix bónusz szorzó"),
|
||||
]
|
||||
|
||||
# ==================== GITEA INTEGRÁCIÓ ====================
|
||||
|
||||
class GiteaManager:
|
||||
"""Gitea integráció kezelése - importálás vagy subprocess fallback"""
|
||||
|
||||
def __init__(self):
|
||||
self.use_import = False
|
||||
self.gitea_module = None
|
||||
|
||||
# Próbáljuk meg importálni a gitea_manager-t
|
||||
try:
|
||||
gitea_manager_path = PROJECT_ROOT / ".roo" / "scripts"
|
||||
sys.path.append(str(gitea_manager_path))
|
||||
import gitea_manager as gm
|
||||
self.gitea_module = gm
|
||||
self.use_import = True
|
||||
print("✅ Gitea manager importálva")
|
||||
except ImportError as e:
|
||||
print(f"⚠️ Gitea manager importálási hiba: {e}")
|
||||
print(" Subprocess fallback használata")
|
||||
|
||||
def run_command(self, args: List[str]) -> Tuple[bool, str]:
|
||||
"""Futtat egy Gitea parancsot importálással vagy subprocess-szel"""
|
||||
if self.use_import and self.gitea_module:
|
||||
return self._run_via_import(args)
|
||||
else:
|
||||
return self._run_via_subprocess(args)
|
||||
|
||||
def _run_via_import(self, args: List[str]) -> Tuple[bool, str]:
|
||||
"""Importált modul használata"""
|
||||
try:
|
||||
action = args[0].lower() if args else ""
|
||||
|
||||
if action == "ms" and len(args) > 1 and args[1].lower() == "create":
|
||||
# Mérföldkő létrehozása
|
||||
title = args[2]
|
||||
description = args[3] if len(args) > 3 else ""
|
||||
ms_id = self.gitea_module.create_milestone(title, description)
|
||||
return True, f"Mérföldkő létrehozva: {ms_id}"
|
||||
|
||||
elif action == "create" and len(args) > 2:
|
||||
# Issue létrehozása
|
||||
title = args[1].strip('"')
|
||||
body = args[2].strip('"')
|
||||
milestone_ref = "v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig"
|
||||
categories = []
|
||||
|
||||
if len(args) > 3:
|
||||
# Címkék kinyerése
|
||||
for arg in args[3:]:
|
||||
if any(arg.startswith(prefix) for prefix in ["Status:", "Scope:", "Type:", "Role:"]):
|
||||
categories.append(arg)
|
||||
|
||||
success = self.gitea_module.create_issue(
|
||||
title=title,
|
||||
body=body,
|
||||
categories=categories,
|
||||
milestone_ref=milestone_ref
|
||||
)
|
||||
return success, "Issue létrehozva" if success else "Hiba az issue létrehozásakor"
|
||||
|
||||
else:
|
||||
return False, f"Ismeretlen parancs: {action}"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Import hiba: {e}"
|
||||
|
||||
def _run_via_subprocess(self, args: List[str]) -> Tuple[bool, str]:
|
||||
"""Subprocess használata"""
|
||||
cmd = ["docker", "exec", "roo-helper", "python3", "/scripts/gitea_manager.py"] + args
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
return result.returncode == 0, result.stdout + "\n" + result.stderr
|
||||
except subprocess.TimeoutExpired:
|
||||
return False, "Időtúllépés a parancs futtatásakor"
|
||||
except Exception as e:
|
||||
return False, f"Hiba: {e}"
|
||||
|
||||
# ==================== SEGÉDFÜGGVÉNYEK ====================
|
||||
|
||||
def find_python_files(directory: Path) -> List[Path]:
|
||||
"""Rekurzívan gyűjti össze az összes .py fájlt a megadott könyvtárban."""
|
||||
python_files = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
python_files.append(Path(root) / file)
|
||||
return python_files
|
||||
|
||||
def scan_file(file_path: Path) -> List[Dict]:
|
||||
"""Egy fájlban keres hardcode értékeket a regex minták alapján."""
|
||||
findings = []
|
||||
try:
|
||||
content = file_path.read_text(encoding='utf-8')
|
||||
lines = content.splitlines()
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
for pattern, description in HARDCODE_PATTERNS:
|
||||
matches = re.finditer(pattern, line)
|
||||
for match in matches:
|
||||
# Csak számértékeket gyűjtsünk a specifikus mintáknál
|
||||
if 'gamification' in description.lower() or 'trust' in description.lower():
|
||||
value = match.group(1) if match.groups() else match.group()
|
||||
else:
|
||||
value = match.group()
|
||||
|
||||
findings.append({
|
||||
'file': str(file_path.relative_to(PROJECT_ROOT)),
|
||||
'line': line_num,
|
||||
'column': match.start() + 1,
|
||||
'match': value,
|
||||
'description': description,
|
||||
'context': line.strip()[:100]
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"⚠️ Hiba a fájl olvasásakor {file_path}: {e}")
|
||||
|
||||
return findings
|
||||
|
||||
def generate_markdown_report(findings: List[Dict]) -> str:
|
||||
"""Generál egy Markdown formátumú riportot a találatokról."""
|
||||
if not findings:
|
||||
return "## ✅ Nincs hardcode találat\n\nA szkennelés nem talált gyanús hardcode értékeket."
|
||||
|
||||
# Csoportosítás fájl szerint
|
||||
by_file = {}
|
||||
for finding in findings:
|
||||
file = finding['file']
|
||||
if file not in by_file:
|
||||
by_file[file] = []
|
||||
by_file[file].append(finding)
|
||||
|
||||
report_lines = [
|
||||
"# 🔍 Hardcode Audit Részletes Részletek",
|
||||
"",
|
||||
f"**Összes találat:** {len(findings)}",
|
||||
"",
|
||||
"---",
|
||||
]
|
||||
|
||||
for file, file_findings in sorted(by_file.items()):
|
||||
report_lines.append(f"## 📄 {file}")
|
||||
report_lines.append("")
|
||||
|
||||
for finding in file_findings:
|
||||
report_lines.append(f"### L{ finding['line'] }: `{ finding['match'] }`")
|
||||
report_lines.append(f"- **Leírás:** {finding['description']}")
|
||||
report_lines.append(f"- **Kontextus:** `{finding['context']}`")
|
||||
report_lines.append(f"- **Hely:** {finding['file']}:{finding['line']}:{finding['column']}")
|
||||
report_lines.append("")
|
||||
|
||||
return "\n".join(report_lines)
|
||||
|
||||
# ==================== GITEA PROJEKT LÉTREHOZÁS ====================
|
||||
|
||||
def create_gitea_project(gitea: GiteaManager, markdown_report: str):
|
||||
"""Létrehozza a teljes projekt tervet a Gitea-ban."""
|
||||
|
||||
print("\n🚀 Gitea Projekt Terv Létrehozása...")
|
||||
|
||||
# 1. Mérföldkő létrehozása
|
||||
print("📌 Mérföldkő létrehozása...")
|
||||
success, msg = gitea.run_command([
|
||||
"ms", "create", "v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Admin rendszer fejlesztése: RBAC, dinamikus konfiguráció, anomália detektálás"
|
||||
])
|
||||
if success:
|
||||
print("✅ Mérföldkő sikeresen létrehozva")
|
||||
else:
|
||||
print(f"⚠️ Figyelmeztetés: {msg}")
|
||||
|
||||
# 2. Issue 1: Hardcode Értékek Dinamikussá Tétele
|
||||
issue1_body = f"""**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Hardcode értékek kiszervezése SystemParameter táblába és ConfigService-be
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Database (system.parameters tábla), ConfigService
|
||||
- **Kimenet (Mik támaszkodnak rá):** GamificationService, NotificationService, SecurityService
|
||||
|
||||
### 📝 Elemzés
|
||||
A hardcode audit {len(markdown_report.splitlines())} sor találatot jelentett. Ezeket az értékeket át kell helyezni a dinamikus konfigurációs rendszerbe.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `SystemParameter` vagy `AdminConfig` adatbázis tábla létrehozása (Key-Value alapú)
|
||||
- [ ] `ConfigService` megírása (Redis/Memória gyorsítótárral)
|
||||
- [ ] A kód átírása, hogy az értékeket a DB-ből olvassa
|
||||
|
||||
### 🔍 Hardcode Találatok (Összefoglaló)
|
||||
{markdown_report[:1500]}...
|
||||
"""
|
||||
|
||||
print("📝 Issue 1 létrehozása...")
|
||||
gitea.run_command([
|
||||
"create", "Phase 1: Hardcode Értékek Dinamikussá Tétele",
|
||||
issue1_body,
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: Backend", "Type: Refactor", "Status: To Do"
|
||||
])
|
||||
|
||||
# 3. Issue 2: RBAC és Admin API Router
|
||||
issue2_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Superadmin, Moderator szerepkörök és `/api/v1/admin` végpontok implementálása
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Identity modell (User, Role), Permission tábla
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin UI, Moderátori felület
|
||||
|
||||
### 📝 Elemzés
|
||||
Létre kell hozni a Role-Based Access Control (RBAC) rendszert, amely támogatja a Superadmin, Moderator, és Auditor szerepköröket.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `User` modell bővítése: `role` oszlop bevezetése (user, moderator, superadmin)
|
||||
- [ ] FastAPI Dependency (`get_current_admin`) megírása, ami blokkolja a normál usereket
|
||||
- [ ] Az `/api/v1/admin` router regisztrálása a main.py-ban
|
||||
"""
|
||||
|
||||
print("📝 Issue 2 létrehozása...")
|
||||
gitea.run_command([
|
||||
"create", "Phase 2: RBAC és Admin API Router",
|
||||
issue2_body,
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: Backend", "Type: Feature", "Role: Admin", "Status: To Do"
|
||||
])
|
||||
|
||||
# 4. Issue 3: Core Felügyeleti Végpontok
|
||||
issue3_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** User (KYC), Jármű, Szerviz felügyelet (Tiltás/Jóváhagyás) végpontok
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** UserService, VehicleService, ServiceRegistry
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin dashboard, Moderátori munkafolyamatok
|
||||
|
||||
### 📝 Elemzés
|
||||
Külön végpontok kellenek a felhasználók KYC (Know Your Customer) jóváhagyásához, járművek tiltásához/engedélyezéséhez, és szervizek moderálásához.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `GET /admin/users` - Felhasználók listázása (szűréssel, pl. pending KYC)
|
||||
- [ ] `POST /admin/users/{id}/ban` - Fiók tiltása/felfüggesztése
|
||||
- [ ] `POST /admin/marketplace/services/{id}/approve` - Szerviz manuális 100%-ra validálása (Kék pipa)
|
||||
- [ ] `GET /admin/assets/flagged` - Gyanús járművek listája
|
||||
"""
|
||||
|
||||
print("📝 Issue 3 létrehozása...")
|
||||
gitea.run_command([
|
||||
"create", "Phase 3: Core Felügyeleti Végpontok",
|
||||
issue3_body,
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: API", "Type: Feature", "Role: Admin", "Status: To Do"
|
||||
])
|
||||
|
||||
# 5. Issue 4: Anomália Detektálás (Anti-Cheat)
|
||||
issue4_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Robot felügyelő a gyanús aktivitásokhoz (pl. túl gyors pontgyűjtés, sok sikertelen bejelentkezés)
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Audit log, Gamification events, Security events
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin értesítések, Automatikus tiltások
|
||||
|
||||
### 📝 Elemzés
|
||||
Anomália detektáló algoritmus készítése, amely gyanús mintákat keres a felhasználói aktivitásban.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] Automatikus detektálás gyanúsan sok validációra rövid időn belül
|
||||
- [ ] Sebesség/Távolság ellenőrzés (Nem lehet 1 perc alatt 50km-re lévő szervizeket rögzíteni)
|
||||
- [ ] Riasztások küldése a `/admin/alerts` végpontra a Moderátoroknak
|
||||
"""
|
||||
|
||||
print("📝 Issue 4 létrehozása...")
|
||||
gitea.run_command([
|
||||
"create", "Phase 4: Anomália Detektálás (Anti-Cheat)",
|
||||
issue4_body,
|
||||
"v2.0 - Enterprise
|
||||
318
backend/app/tests/test_admin_audit_gitea_final.py
Normal file
318
backend/app/tests/test_admin_audit_gitea_final.py
Normal file
@@ -0,0 +1,318 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/tests/test_admin_audit_gitea_final.py
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enterprise Admin & Gamification Audit - Kombinált Verzió
|
||||
|
||||
Ez a szkript a két legjobb megközelítést egyesíti:
|
||||
1. Részletes hardcode szkennelés általános és specifikus mintákkal
|
||||
2. Gitea integráció importálással (fallback subprocess-szel)
|
||||
3. Teljes projekt terv létrehozása a Gitea-ban a szigorú sablonnal
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
|
||||
# ==================== KONFIGURÁCIÓ ====================
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent # /opt/docker/dev/service_finder
|
||||
GITEA_SCRIPT = PROJECT_ROOT / ".roo" / "scripts" / "gitea_manager.py"
|
||||
|
||||
SCAN_DIRS = [
|
||||
PROJECT_ROOT / "backend" / "app" / "services",
|
||||
PROJECT_ROOT / "backend" / "app" / "api",
|
||||
]
|
||||
|
||||
# Hardcode minta regexek - kombinált lista
|
||||
HARDCODE_PATTERNS = [
|
||||
# Általános minták (1. kódból)
|
||||
(r'\b\d{1,3}\b', "Mágikus szám (1-3 jegyű)"),
|
||||
(r'\b(50|10|100|1000|5000|10000)\b', "Gyakori mágikus szám (pl. 50, 10)"),
|
||||
(r'"(active|inactive|pending|approved|rejected|blocked)"', "Fix státusz string"),
|
||||
(r"'active'|'inactive'|'pending'|'approved'|'rejected'|'blocked'", "Fix státusz string (aposztróf)"),
|
||||
(r'\b(True|False)\b', "Hardcode boolean"),
|
||||
(r'\b(max|min|limit|threshold|default)\s*=\s*\d+', "Limit/Threshold érték"),
|
||||
|
||||
# Specifikus gamification minták (2. kódból)
|
||||
(r'award_points\([^)]*,\s*(\d+)\s*,', "Fix pontszám osztás (Gamification)"),
|
||||
(r'validation_level\s*[+=]\s*(\d+)', "Fix validációs szint növelés/csökkentés"),
|
||||
(r'validation_level\s*[<>]=?\s*(\d+)', "Fix validációs küszöb ellenőrzés"),
|
||||
(r'trust_score\s*[+=]\s*(\d+)', "Fix trust score módosítás"),
|
||||
(r'level_threshold\s*=\s*(\d+)', "Fix szint küszöbérték"),
|
||||
(r'bonus_multiplier\s*=\s*(\d+(?:\.\d+)?)', "Fix bónusz szorzó"),
|
||||
]
|
||||
|
||||
# ==================== GITEA INTEGRÁCIÓ ====================
|
||||
|
||||
class GiteaManager:
|
||||
"""Gitea integráció kezelése - importálás vagy subprocess fallback"""
|
||||
|
||||
def __init__(self):
|
||||
self.use_import = False
|
||||
self.gitea_module = None
|
||||
|
||||
# Próbáljuk meg importálni a gitea_manager-t
|
||||
try:
|
||||
gitea_manager_path = PROJECT_ROOT / ".roo" / "scripts"
|
||||
sys.path.append(str(gitea_manager_path))
|
||||
import gitea_manager as gm
|
||||
self.gitea_module = gm
|
||||
self.use_import = True
|
||||
print("✅ Gitea manager importálva")
|
||||
except ImportError as e:
|
||||
print(f"⚠️ Gitea manager importálási hiba: {e}")
|
||||
print(" Subprocess fallback használata")
|
||||
|
||||
def run_command(self, args: List[str]) -> Tuple[bool, str]:
|
||||
"""Futtat egy Gitea parancsot importálással vagy subprocess-szel"""
|
||||
if self.use_import and self.gitea_module:
|
||||
return self._run_via_import(args)
|
||||
else:
|
||||
return self._run_via_subprocess(args)
|
||||
|
||||
def _run_via_import(self, args: List[str]) -> Tuple[bool, str]:
|
||||
"""Importált modul használata"""
|
||||
try:
|
||||
action = args[0].lower() if args else ""
|
||||
|
||||
if action == "ms" and len(args) > 1 and args[1].lower() == "create":
|
||||
# Mérföldkő létrehozása
|
||||
title = args[2]
|
||||
description = args[3] if len(args) > 3 else ""
|
||||
ms_id = self.gitea_module.create_milestone(title, description)
|
||||
return True, f"Mérföldkő létrehozva: {ms_id}"
|
||||
|
||||
elif action == "create" and len(args) > 2:
|
||||
# Issue létrehozása
|
||||
title = args[1].strip('"')
|
||||
body = args[2].strip('"')
|
||||
milestone_ref = "v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig"
|
||||
categories = []
|
||||
|
||||
if len(args) > 3:
|
||||
# Címkék kinyerése
|
||||
for arg in args[3:]:
|
||||
if any(arg.startswith(prefix) for prefix in ["Status:", "Scope:", "Type:", "Role:"]):
|
||||
categories.append(arg)
|
||||
|
||||
success = self.gitea_module.create_issue(
|
||||
title=title,
|
||||
body=body,
|
||||
categories=categories,
|
||||
milestone_ref=milestone_ref
|
||||
)
|
||||
return success, "Issue létrehozva" if success else "Hiba az issue létrehozásakor"
|
||||
|
||||
else:
|
||||
return False, f"Ismeretlen parancs: {action}"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Import hiba: {e}"
|
||||
|
||||
def _run_via_subprocess(self, args: List[str]) -> Tuple[bool, str]:
|
||||
"""Subprocess használata"""
|
||||
cmd = ["docker", "exec", "roo-helper", "python3", "/scripts/gitea_manager.py"] + args
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
return result.returncode == 0, result.stdout + "\n" + result.stderr
|
||||
except subprocess.TimeoutExpired:
|
||||
return False, "Időtúllépés a parancs futtatásakor"
|
||||
except Exception as e:
|
||||
return False, f"Hiba: {e}"
|
||||
|
||||
# ==================== SEGÉDFÜGGVÉNYEK ====================
|
||||
|
||||
def find_python_files(directory: Path) -> List[Path]:
|
||||
"""Rekurzívan gyűjti össze az összes .py fájlt a megadott könyvtárban."""
|
||||
python_files = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
python_files.append(Path(root) / file)
|
||||
return python_files
|
||||
|
||||
def scan_file(file_path: Path) -> List[Dict]:
|
||||
"""Egy fájlban keres hardcode értékeket a regex minták alapján."""
|
||||
findings = []
|
||||
try:
|
||||
content = file_path.read_text(encoding='utf-8')
|
||||
lines = content.splitlines()
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
for pattern, description in HARDCODE_PATTERNS:
|
||||
matches = re.finditer(pattern, line)
|
||||
for match in matches:
|
||||
# Csak számértékeket gyűjtsünk a specifikus mintáknál
|
||||
if 'gamification' in description.lower() or 'trust' in description.lower():
|
||||
value = match.group(1) if match.groups() else match.group()
|
||||
else:
|
||||
value = match.group()
|
||||
|
||||
findings.append({
|
||||
'file': str(file_path.relative_to(PROJECT_ROOT)),
|
||||
'line': line_num,
|
||||
'column': match.start() + 1,
|
||||
'match': value,
|
||||
'description': description,
|
||||
'context': line.strip()[:100]
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"⚠️ Hiba a fájl olvasásakor {file_path}: {e}")
|
||||
|
||||
return findings
|
||||
|
||||
def generate_markdown_report(findings: List[Dict]) -> str:
|
||||
"""Generál egy Markdown formátumú riportot a találatokról."""
|
||||
if not findings:
|
||||
return "## ✅ Nincs hardcode találat\n\nA szkennelés nem talált gyanús hardcode értékeket."
|
||||
|
||||
# Csoportosítás fájl szerint
|
||||
by_file = {}
|
||||
for finding in findings:
|
||||
file = finding['file']
|
||||
if file not in by_file:
|
||||
by_file[file] = []
|
||||
by_file[file].append(finding)
|
||||
|
||||
report_lines = [
|
||||
"# 🔍 Hardcode Audit Részletes Részletek",
|
||||
"",
|
||||
f"**Összes találat:** {len(findings)}",
|
||||
"",
|
||||
"---",
|
||||
]
|
||||
|
||||
for file, file_findings in sorted(by_file.items()):
|
||||
report_lines.append(f"## 📄 {file}")
|
||||
report_lines.append("")
|
||||
|
||||
for finding in file_findings:
|
||||
report_lines.append(f"### L{ finding['line'] }: `{ finding['match'] }`")
|
||||
report_lines.append(f"- **Leírás:** {finding['description']}")
|
||||
report_lines.append(f"- **Kontextus:** `{finding['context']}`")
|
||||
report_lines.append(f"- **Hely:** {finding['file']}:{finding['line']}:{finding['column']}")
|
||||
report_lines.append("")
|
||||
|
||||
return "\n".join(report_lines)
|
||||
|
||||
# ==================== GITEA PROJEKT LÉTREHOZÁS ====================
|
||||
|
||||
def create_gitea_project(gitea: GiteaManager, markdown_report: str):
|
||||
"""Létrehozza a teljes projekt tervet a Gitea-ban."""
|
||||
|
||||
print("\n🚀 Gitea Projekt Terv Létrehozása...")
|
||||
|
||||
# 1. Mérföldkő létrehozása
|
||||
print("📌 Mérföldkő létrehozása...")
|
||||
success, msg = gitea.run_command([
|
||||
"ms", "create", "v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Admin rendszer fejlesztése: RBAC, dinamikus konfiguráció, anomália detektálás"
|
||||
])
|
||||
if success:
|
||||
print("✅ Mérföldkő sikeresen létrehozva")
|
||||
else:
|
||||
print(f"⚠️ Figyelmeztetés: {msg}")
|
||||
|
||||
# 2. Issue 1: Hardcode Értékek Dinamikussá Tétele
|
||||
issue1_body = f"""**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Hardcode értékek kiszervezése SystemParameter táblába és ConfigService-be
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Database (system.parameters tábla), ConfigService
|
||||
- **Kimenet (Mik támaszkodnak rá):** GamificationService, NotificationService, SecurityService
|
||||
|
||||
### 📝 Elemzés
|
||||
A hardcode audit {len(markdown_report.splitlines())} sor találatot jelentett. Ezeket az értékeket át kell helyezni a dinamikus konfigurációs rendszerbe.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `SystemParameter` vagy `AdminConfig` adatbázis tábla létrehozása (Key-Value alapú)
|
||||
- [ ] `ConfigService` megírása (Redis/Memória gyorsítótárral)
|
||||
- [ ] A kód átírása, hogy az értékeket a DB-ből olvassa
|
||||
|
||||
### 🔍 Hardcode Találatok (Összefoglaló)
|
||||
{markdown_report[:1500]}...
|
||||
"""
|
||||
|
||||
print("📝 Issue 1 létrehozása...")
|
||||
gitea.run_command([
|
||||
"create", "Phase 1: Hardcode Értékek Dinamikussá Tétele",
|
||||
issue1_body,
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: Backend", "Type: Refactor", "Status: To Do"
|
||||
])
|
||||
|
||||
# 3. Issue 2: RBAC és Admin API Router
|
||||
issue2_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Superadmin, Moderator szerepkörök és `/api/v1/admin` végpontok implementálása
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Identity modell (User, Role), Permission tábla
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin UI, Moderátori felület
|
||||
|
||||
### 📝 Elemzés
|
||||
Létre kell hozni a Role-Based Access Control (RBAC) rendszert, amely támogatja a Superadmin, Moderator, és Auditor szerepköröket.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `User` modell bővítése: `role` oszlop bevezetése (user, moderator, superadmin)
|
||||
- [ ] FastAPI Dependency (`get_current_admin`) megírása, ami blokkolja a normál usereket
|
||||
- [ ] Az `/api/v1/admin` router regisztrálása a main.py-ban
|
||||
"""
|
||||
|
||||
print("📝 Issue 2 létrehozása...")
|
||||
gitea.run_command([
|
||||
"create", "Phase 2: RBAC és Admin API Router",
|
||||
issue2_body,
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: Backend", "Type: Feature", "Role: Admin", "Status: To Do"
|
||||
])
|
||||
|
||||
# 4. Issue 3: Core Felügyeleti Végpontok
|
||||
issue3_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** User (KYC), Jármű, Szerviz felügyelet (Tiltás/Jóváhagyás) végpontok
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** UserService, VehicleService, ServiceRegistry
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin dashboard, Moderátori munkafolyamatok
|
||||
|
||||
### 📝 Elemzés
|
||||
Külön végpontok kellenek a felhasználók KYC (Know Your Customer) jóváhagyásához, járművek tiltásához/engedélyezéséhez, és szervizek moderálásához.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `GET /admin/users` - Felhasználók listázása (szűréssel, pl. pending KYC)
|
||||
- [ ] `POST /admin/users/{id}/ban` - Fiók tiltása/felfüggesztése
|
||||
- [ ] `POST /admin/marketplace/services/{id}/approve` - Szerviz manuális 100%-ra validálása (Kék pipa)
|
||||
- [ ] `GET /admin/assets/flagged` - Gyanús járművek listája
|
||||
"""
|
||||
|
||||
print("📝 Issue 3 létrehozása...")
|
||||
gitea.run_command([
|
||||
"create", "Phase 3: Core Felügyeleti Végpontok",
|
||||
issue3_body,
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: API", "Type: Feature", "Role: Admin", "Status: To Do"
|
||||
])
|
||||
|
||||
# 5. Issue 4: Anomália Detektálás (Anti-Cheat)
|
||||
issue4_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Robot felügyelő a gyanús aktivitásokhoz (pl. túl gyors pontgyűjtés, sok sikertelen bejelentkezés)
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Audit log, Gamification events, Security events
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin értesítések, Automatikus tiltások
|
||||
|
||||
### 📝 Elemzés
|
||||
Anomália detektáló algoritmus készítése, amely gyanús mintákat keres a felhasználói aktivitásban.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] Automatikus detektálás gyanúsan sok validációra rövid időn belül
|
||||
- [ ] Sebesség/Távolság ellenőrzés (Nem lehet 1 perc alatt 50km-re lévő szervizeket rögzíteni)
|
||||
- [ ] Riasztások küldése a `/admin/alerts` végpontra a Moderátoroknak
|
||||
"""
|
||||
|
||||
print("📝 Issue 4 létrehozása...")
|
||||
gitea.run_command([
|
||||
"create", "Phase 4: Anomália Detektálás (Anti-Cheat)",
|
||||
issue4_body,
|
||||
"v2.0 - Enterprise Admin Rendszer
|
||||
295
backend/app/tests/test_admin_audit_gitea_final_complete.py
Normal file
295
backend/app/tests/test_admin_audit_gitea_final_complete.py
Normal file
@@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enterprise Admin & Gamification Audit - Kombinált Verzió
|
||||
|
||||
Ez a szkript a két legjobb megközelítést egyesíti:
|
||||
1. Részletes hardcode szkennelés általános és specifikus mintákkal
|
||||
2. Gitea integráció importálással (fallback subprocess-szel)
|
||||
3. Teljes projekt terv létrehozása a Gitea-ban a szigorú sablonnal
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
# ==================== KONFIGURÁCIÓ ====================
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent # /opt/docker/dev/service_finder
|
||||
|
||||
SCAN_DIRS = [
|
||||
PROJECT_ROOT / "backend" / "app" / "services",
|
||||
PROJECT_ROOT / "backend" / "app" / "api",
|
||||
]
|
||||
|
||||
# Hardcode minta regexek - kombinált lista
|
||||
HARDCODE_PATTERNS = [
|
||||
# Általános minták
|
||||
(r'\b\d{1,3}\b', "Mágikus szám (1-3 jegyű)"),
|
||||
(r'\b(50|10|100|1000|5000|10000)\b', "Gyakori mágikus szám (pl. 50, 10)"),
|
||||
(r'"(active|inactive|pending|approved|rejected|blocked)"', "Fix státusz string"),
|
||||
(r"'active'|'inactive'|'pending'|'approved'|'rejected'|'blocked'", "Fix státusz string (aposztróf)"),
|
||||
(r'\b(True|False)\b', "Hardcode boolean"),
|
||||
(r'\b(max|min|limit|threshold|default)\s*=\s*\d+', "Limit/Threshold érték"),
|
||||
|
||||
# Specifikus gamification minták
|
||||
(r'award_points\([^)]*,\s*(\d+)\s*,', "Fix pontszám osztás (Gamification)"),
|
||||
(r'validation_level\s*[+=]\s*(\d+)', "Fix validációs szint növelés/csökkentés"),
|
||||
(r'validation_level\s*[<>]=?\s*(\d+)', "Fix validációs küszöb ellenőrzés"),
|
||||
(r'trust_score\s*[+=]\s*(\d+)', "Fix trust score módosítás"),
|
||||
(r'level_threshold\s*=\s*(\d+)', "Fix szint küszöbérték"),
|
||||
(r'bonus_multiplier\s*=\s*(\d+(?:\.\d+)?)', "Fix bónusz szorzó"),
|
||||
]
|
||||
|
||||
# ==================== GITEA INTEGRÁCIÓ ====================
|
||||
|
||||
def run_gitea_command(args: List[str]) -> Tuple[bool, str]:
|
||||
"""Futtat egy Gitea parancsot subprocess-szel."""
|
||||
cmd = ["docker", "exec", "roo-helper", "python3", "/scripts/gitea_manager.py"] + args
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
return result.returncode == 0, result.stdout + "\n" + result.stderr
|
||||
except subprocess.TimeoutExpired:
|
||||
return False, "Időtúllépés a parancs futtatásakor"
|
||||
except Exception as e:
|
||||
return False, f"Hiba: {e}"
|
||||
|
||||
# ==================== SEGÉDFÜGGVÉNYEK ====================
|
||||
|
||||
def find_python_files(directory: Path) -> List[Path]:
|
||||
"""Rekurzívan gyűjti össze az összes .py fájlt a megadott könyvtárban."""
|
||||
python_files = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
python_files.append(Path(root) / file)
|
||||
return python_files
|
||||
|
||||
def scan_file(file_path: Path) -> List[Dict]:
|
||||
"""Egy fájlban keres hardcode értékeket a regex minták alapján."""
|
||||
findings = []
|
||||
try:
|
||||
content = file_path.read_text(encoding='utf-8')
|
||||
lines = content.splitlines()
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
for pattern, description in HARDCODE_PATTERNS:
|
||||
matches = re.finditer(pattern, line)
|
||||
for match in matches:
|
||||
# Csak számértékeket gyűjtsünk a specifikus mintáknál
|
||||
if 'gamification' in description.lower() or 'trust' in description.lower():
|
||||
value = match.group(1) if match.groups() else match.group()
|
||||
else:
|
||||
value = match.group()
|
||||
|
||||
findings.append({
|
||||
'file': str(file_path.relative_to(PROJECT_ROOT)),
|
||||
'line': line_num,
|
||||
'column': match.start() + 1,
|
||||
'match': value,
|
||||
'description': description,
|
||||
'context': line.strip()[:100]
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"⚠️ Hiba a fájl olvasásakor {file_path}: {e}")
|
||||
|
||||
return findings
|
||||
|
||||
def generate_markdown_report(findings: List[Dict]) -> str:
|
||||
"""Generál egy Markdown formátumú riportot a találatokról."""
|
||||
if not findings:
|
||||
return "## ✅ Nincs hardcode találat\n\nA szkennelés nem talált gyanús hardcode értékeket."
|
||||
|
||||
# Csoportosítás fájl szerint
|
||||
by_file = {}
|
||||
for finding in findings:
|
||||
file = finding['file']
|
||||
if file not in by_file:
|
||||
by_file[file] = []
|
||||
by_file[file].append(finding)
|
||||
|
||||
report_lines = [
|
||||
"# 🔍 Hardcode Audit Részletes Részletek",
|
||||
"",
|
||||
f"**Összes találat:** {len(findings)}",
|
||||
"",
|
||||
"---",
|
||||
]
|
||||
|
||||
for file, file_findings in sorted(by_file.items()):
|
||||
report_lines.append(f"## 📄 {file}")
|
||||
report_lines.append("")
|
||||
|
||||
for finding in file_findings:
|
||||
report_lines.append(f"### L{ finding['line'] }: `{ finding['match'] }`")
|
||||
report_lines.append(f"- **Leírás:** {finding['description']}")
|
||||
report_lines.append(f"- **Kontextus:** `{finding['context']}`")
|
||||
report_lines.append(f"- **Hely:** {finding['file']}:{finding['line']}:{finding['column']}")
|
||||
report_lines.append("")
|
||||
|
||||
return "\n".join(report_lines)
|
||||
|
||||
# ==================== GITEA PROJEKT LÉTREHOZÁS ====================
|
||||
|
||||
def create_gitea_project(markdown_report: str):
|
||||
"""Létrehozza a teljes projekt tervet a Gitea-ban."""
|
||||
|
||||
print("\n🚀 Gitea Projekt Terv Létrehozása...")
|
||||
|
||||
# 1. Mérföldkő létrehozása
|
||||
print("📌 Mérföldkő létrehozása...")
|
||||
success, msg = run_gitea_command([
|
||||
"ms", "create", "v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Admin rendszer fejlesztése: RBAC, dinamikus konfiguráció, anomália detektálás"
|
||||
])
|
||||
if success:
|
||||
print("✅ Mérföldkő sikeresen létrehozva")
|
||||
else:
|
||||
print(f"⚠️ Figyelmeztetés: {msg}")
|
||||
|
||||
# 2. Issue 1: Hardcode Értékek Dinamikussá Tétele
|
||||
issue1_body = f"""**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Hardcode értékek kiszervezése SystemParameter táblába és ConfigService-be
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Database (system.parameters tábla), ConfigService
|
||||
- **Kimenet (Mik támaszkodnak rá):** GamificationService, NotificationService, SecurityService
|
||||
|
||||
### 📝 Elemzés
|
||||
A hardcode audit {len(markdown_report.splitlines())} sor találatot jelentett. Ezeket az értékeket át kell helyezni a dinamikus konfigurációs rendszerbe.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `SystemParameter` vagy `AdminConfig` adatbázis tábla létrehozása (Key-Value alapú)
|
||||
- [ ] `ConfigService` megírása (Redis/Memória gyorsítótárral)
|
||||
- [ ] A kód átírása, hogy az értékeket a DB-ből olvassa
|
||||
|
||||
### 🔍 Hardcode Találatok (Összefoglaló)
|
||||
{markdown_report[:1500]}...
|
||||
"""
|
||||
|
||||
print("📝 Issue 1 létrehozása...")
|
||||
run_gitea_command([
|
||||
"create", "Phase 1: Hardcode Értékek Dinamikussá Tétele",
|
||||
f'"{issue1_body}"',
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: Backend", "Type: Refactor", "Status: To Do"
|
||||
])
|
||||
|
||||
# 3. Issue 2: RBAC és Admin API Router
|
||||
issue2_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Superadmin, Moderator szerepkörök és `/api/v1/admin` végpontok implementálása
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Identity modell (User, Role), Permission tábla
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin UI, Moderátori felület
|
||||
|
||||
### 📝 Elemzés
|
||||
Létre kell hozni a Role-Based Access Control (RBAC) rendszert, amely támogatja a Superadmin, Moderator, és Auditor szerepköröket.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `User` modell bővítése: `role` oszlop bevezetése (user, moderator, superadmin)
|
||||
- [ ] FastAPI Dependency (`get_current_admin`) megírása, ami blokkolja a normál usereket
|
||||
- [ ] Az `/api/v1/admin` router regisztrálása a main.py-ban
|
||||
"""
|
||||
|
||||
print("📝 Issue 2 létrehozása...")
|
||||
run_gitea_command([
|
||||
"create", "Phase 2: RBAC és Admin API Router",
|
||||
f'"{issue2_body}"',
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: Backend", "Type: Feature", "Role: Admin", "Status: To Do"
|
||||
])
|
||||
|
||||
# 4. Issue 3: Core Felügyeleti Végpontok
|
||||
issue3_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** User (KYC), Jármű, Szerviz felügyelet (Tiltás/Jóváhagyás) végpontok
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** UserService, VehicleService, ServiceRegistry
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin dashboard, Moderátori munkafolyamatok
|
||||
|
||||
### 📝 Elemzés
|
||||
Külön végpontok kellenek a felhasználók KYC (Know Your Customer) jóváhagyásához, járművek tiltásához/engedélyezéséhez, és szervizek moderálásához.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] `GET /admin/users` - Felhasználók listázása (szűréssel, pl. pending KYC)
|
||||
- [ ] `POST /admin/users/{id}/ban` - Fiók tiltása/felfüggesztése
|
||||
- [ ] `POST /admin/marketplace/services/{id}/approve` - Szerviz manuális 100%-ra validálása (Kék pipa)
|
||||
- [ ] `GET /admin/assets/flagged` - Gyanús járművek listája
|
||||
"""
|
||||
|
||||
print("📝 Issue 3 létrehozása...")
|
||||
run_gitea_command([
|
||||
"create", "Phase 3: Core Felügyeleti Végpontok",
|
||||
f'"{issue3_body}"',
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: API", "Type: Feature", "Role: Admin", "Status: To Do"
|
||||
])
|
||||
|
||||
# 5. Issue 4: Anomália Detektálás (Anti-Cheat)
|
||||
issue4_body = """**Mérföldkő:** v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig
|
||||
**Cél:** Robot felügyelő a gyanús aktivitásokhoz (pl. túl gyors pontgyűjtés, sok sikertelen bejelentkezés)
|
||||
|
||||
### 🔗 Függőségek (Dependencies)
|
||||
- **Bemenet (Mikre támaszkodik):** Audit log, Gamification events, Security events
|
||||
- **Kimenet (Mik támaszkodnak rá):** Admin értesítések, Automatikus tiltások
|
||||
|
||||
### 📝 Elemzés
|
||||
Anomália detektáló algoritmus készítése, amely gyanús mintákat keres a felhasználói aktivitásban.
|
||||
|
||||
### ✅ To-Do Lista
|
||||
- [ ] Automatikus detektálás gyanúsan sok validációra rövid időn belül
|
||||
- [ ] Sebesség/Távolság ellenőrzés (Nem lehet 1 perc alatt 50km-re lévő szervizeket rögzíteni)
|
||||
- [ ] Riasztások küldése a `/admin/alerts` végpontra a Moderátoroknak
|
||||
"""
|
||||
|
||||
print("📝 Issue 4 létrehozása...")
|
||||
run_gitea_command([
|
||||
"create", "Phase 4: Anomália Detektálás (Anti-Cheat)",
|
||||
f'"{issue4_body}"',
|
||||
"v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig",
|
||||
"Scope: Core", "Type: Feature", "Role: Admin", "Status: To Do"
|
||||
])
|
||||
|
||||
# ==================== FŐPROGRAM ====================
|
||||
|
||||
def main():
|
||||
print("🔍 Hardcode Audit Szkennelés indítása...")
|
||||
|
||||
# 1. Python fájlok gyűjtése
|
||||
all_files = []
|
||||
for scan_dir in SCAN_DIRS:
|
||||
if scan_dir.exists():
|
||||
all_files.extend(find_python_files(scan_dir))
|
||||
else:
|
||||
print(f"⚠️ A könyvtár nem létezik: {scan_dir}")
|
||||
|
||||
print(f"📁 Összesen {len(all_files)} fájl található a szkenneléshez")
|
||||
|
||||
# 2. Hardcode értékek keresése
|
||||
all_findings = []
|
||||
for file in all_files:
|
||||
findings = scan_file(file)
|
||||
all_findings.extend(findings)
|
||||
|
||||
print(f"🔎 {len(all_findings)} hardcode találat")
|
||||
|
||||
# 3. Markdown riport generálása
|
||||
markdown_report = generate_markdown_report(all_findings)
|
||||
|
||||
# 4. Riport mentése fájlba
|
||||
report_path = PROJECT_ROOT / "hardcode_audit_report.md"
|
||||
report_path.write_text(markdown_report, encoding='utf-8')
|
||||
print(f"📄 Részletes riport mentve: {report_path}")
|
||||
|
||||
# 5. Gitea integráció
|
||||
create_gitea_project(markdown_report)
|
||||
|
||||
print("\n✅ Audit szkript sikeresen lefutott!")
|
||||
print(f" - Találatok: {len(all_findings)}")
|
||||
print(f" - Riport: {report_path}")
|
||||
print(" - Gitea issue-k létrehozva a 'v2.0 - Enterprise Admin Rendszer & Dinamikus Konfig' mérföldkő alatt")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user