This commit is contained in:
2025-09-21 13:00:10 +02:00
parent a26f8086f8
commit c2a1056db7
56 changed files with 339 additions and 104 deletions

View File

@@ -10,7 +10,7 @@ from main import app
# Import all models to ensure they're registered with Base metadata
from models.database.marketplace import MarketplaceImportJob
from models.database.product import Product
from models.database.shop import Shop,ShopProduct
from models.database.shop import Shop, ShopProduct
from models.database.stock import Stock
from models.database.user import User
@@ -87,7 +87,7 @@ def cleanup():
# Import fixtures from fixture modules
pytest_plugins = [
"tests.fixtures.auth_fixtures",
"tests.fixtures.product_fixtures",
"tests.fixtures.product_fixtures",
"tests.fixtures.shop_fixtures",
"tests.fixtures.marketplace_fixtures",
]
]

View File

@@ -1,3 +1,2 @@
# tests/fixtures/__init__.py
"""Test fixtures for the FastAPI application test suite."""

View File

@@ -3,7 +3,7 @@ import uuid
import pytest
from models.database.shop import Shop,ShopProduct
from models.database.shop import Shop, ShopProduct
from models.database.stock import Stock

View File

@@ -1,3 +1,2 @@
# tests/integration/__init__.py
"""Integration tests - multiple components working together."""

View File

@@ -1,3 +1,2 @@
# tests/integration/api/__init__.py
"""API integration tests."""

View File

@@ -1,3 +1,2 @@
# tests/integration/api/v1/__init__.py
"""API v1 endpoint integration tests."""

View File

@@ -24,8 +24,8 @@ class TestAdminAPI:
assert response.status_code == 403
assert (
"Access denied" in response.json()["detail"]
or "admin" in response.json()["detail"].lower()
"Access denied" in response.json()["detail"]
or "admin" in response.json()["detail"].lower()
)
def test_toggle_user_status_admin(self, client, admin_headers, test_user):
@@ -48,7 +48,7 @@ class TestAdminAPI:
assert "User not found" in response.json()["detail"]
def test_toggle_user_status_cannot_deactivate_self(
self, client, admin_headers, test_admin
self, client, admin_headers, test_admin
):
"""Test that admin cannot deactivate their own account"""
response = client.put(
@@ -79,8 +79,8 @@ class TestAdminAPI:
assert response.status_code == 403
assert (
"Access denied" in response.json()["detail"]
or "admin" in response.json()["detail"].lower()
"Access denied" in response.json()["detail"]
or "admin" in response.json()["detail"].lower()
)
def test_verify_shop_admin(self, client, admin_headers, test_shop):
@@ -120,7 +120,7 @@ class TestAdminAPI:
assert "Shop not found" in response.json()["detail"]
def test_get_marketplace_import_jobs_admin(
self, client, admin_headers, test_marketplace_job
self, client, admin_headers, test_marketplace_job
):
"""Test admin getting marketplace import jobs"""
response = client.get(
@@ -136,7 +136,7 @@ class TestAdminAPI:
assert test_marketplace_job.id in job_ids
def test_get_marketplace_import_jobs_with_filters(
self, client, admin_headers, test_marketplace_job
self, client, admin_headers, test_marketplace_job
):
"""Test admin getting marketplace import jobs with filters"""
response = client.get(
@@ -160,8 +160,8 @@ class TestAdminAPI:
assert response.status_code == 403
assert (
"Access denied" in response.json()["detail"]
or "admin" in response.json()["detail"].lower()
"Access denied" in response.json()["detail"]
or "admin" in response.json()["detail"].lower()
)
def test_admin_pagination_users(self, client, admin_headers, test_user, test_admin):

View File

@@ -79,7 +79,9 @@ class TestPagination:
db.commit()
# Test first page
response = client.get("/api/v1/admin/shops?limit=5&skip=0", headers=admin_headers)
response = client.get(
"/api/v1/admin/shops?limit=5&skip=0", headers=admin_headers
)
assert response.status_code == 200
data = response.json()
assert len(data["shops"]) == 5

View File

@@ -3,4 +3,3 @@
import pytest
# Add any integration-specific fixtures here if needed

View File

@@ -1,3 +1,2 @@
# tests/integration/security/__init__.py
"""Security integration tests."""

View File

@@ -48,7 +48,9 @@ class TestAuthentication:
print(f"Admin endpoint - Raw: {response.content}")
# Test 2: Try a regular endpoint that uses get_current_user
response2 = client.get("/api/v1/product") # or any endpoint with get_current_user
response2 = client.get(
"/api/v1/product"
) # or any endpoint with get_current_user
print(f"Regular endpoint - Status: {response2.status_code}")
try:
print(f"Regular endpoint - Response: {response2.json()}")

View File

@@ -36,11 +36,15 @@ class TestAuthorization:
response = client.get(endpoint, headers=auth_headers)
assert response.status_code == 200 # Regular user should have access
def test_shop_owner_access_control(self, client, auth_headers, test_shop, other_user):
def test_shop_owner_access_control(
self, client, auth_headers, test_shop, other_user
):
"""Test that users can only access their own shops"""
# Test accessing own shop (should work)
response = client.get(f"/api/v1/shop/{test_shop.shop_code}", headers=auth_headers)
response = client.get(
f"/api/v1/shop/{test_shop.shop_code}", headers=auth_headers
)
# Response depends on your implementation - could be 200 or 404 if shop doesn't belong to user
# The exact assertion depends on your shop access control implementation
assert response.status_code in [200, 403, 404]

View File

@@ -50,9 +50,7 @@ class TestInputValidation:
"""Test JSON validation for POST requests"""
# Test invalid JSON structure
response = client.post(
"/api/v1/product",
headers=auth_headers,
content="invalid json content"
"/api/v1/product", headers=auth_headers, content="invalid json content"
)
assert response.status_code == 422 # JSON decode error
@@ -60,6 +58,6 @@ class TestInputValidation:
response = client.post(
"/api/v1/product",
headers=auth_headers,
json={"title": "Test Product"} # Missing required product_id
json={"title": "Test Product"}, # Missing required product_id
)
assert response.status_code == 422 # Validation error

View File

@@ -7,6 +7,7 @@ import pytest
from app.tasks.background_tasks import process_marketplace_import
from models.database.marketplace import MarketplaceImportJob
@pytest.mark.integration
@pytest.mark.database
@pytest.mark.marketplace

View File

@@ -1,6 +1,7 @@
# tests/test_integration.py
import pytest
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.e2e

View File

@@ -1,3 +1,2 @@
# tests/performance/__init__.py
"""Performance and load tests."""

View File

@@ -2,9 +2,9 @@
"""Performance test specific fixtures."""
import pytest
@pytest.fixture
def performance_db_session(db):
"""Database session optimized for performance testing"""
# You can add performance-specific DB configurations here
return db

View File

@@ -67,7 +67,7 @@ class TestPerformance:
products = []
brands = ["Brand1", "Brand2", "Brand3"]
marketplaces = ["Market1", "Market2"]
for i in range(200):
product = Product(
product_id=f"COMPLEX{i:03d}",
@@ -85,13 +85,15 @@ class TestPerformance:
# Test complex filtering performance
start_time = time.time()
response = client.get(
"/api/v1/product?brand=Brand1&marketplace=Market1&limit=50",
headers=auth_headers
"/api/v1/product?brand=Brand1&marketplace=Market1&limit=50",
headers=auth_headers,
)
end_time = time.time()
assert response.status_code == 200
assert end_time - start_time < 1.5 # Complex query should still be reasonably fast
assert (
end_time - start_time < 1.5
) # Complex query should still be reasonably fast
def test_pagination_performance_large_dataset(self, client, auth_headers, db):
"""Test pagination performance with large dataset"""
@@ -113,11 +115,12 @@ class TestPerformance:
for offset in offsets:
start_time = time.time()
response = client.get(
f"/api/v1/product?skip={offset}&limit=20",
headers=auth_headers
f"/api/v1/product?skip={offset}&limit=20", headers=auth_headers
)
end_time = time.time()
assert response.status_code == 200
assert len(response.json()["products"]) == 20
assert end_time - start_time < 1.0 # Pagination should be fast regardless of offset
assert (
end_time - start_time < 1.0
) # Pagination should be fast regardless of offset

View File

@@ -1,3 +1,2 @@
# tests/system/__init__.py
"""System-level tests - full application behavior."""

View File

@@ -3,4 +3,3 @@
import pytest
# Add any system-specific fixtures here if needed

View File

@@ -72,13 +72,17 @@ class TestErrorHandling:
"""Test handling of various malformed requests"""
# Test extremely long URLs
long_search = "x" * 10000
response = client.get(f"/api/v1/product?search={long_search}", headers=auth_headers)
response = client.get(
f"/api/v1/product?search={long_search}", headers=auth_headers
)
# Should handle gracefully, either 200 with no results or 422 for too long
assert response.status_code in [200, 422]
# Test special characters in parameters
special_chars = "!@#$%^&*(){}[]|\\:;\"'<>,.?/~`"
response = client.get(f"/api/v1/product?search={special_chars}", headers=auth_headers)
response = client.get(
f"/api/v1/product?search={special_chars}", headers=auth_headers
)
# Should handle gracefully
assert response.status_code in [200, 422]
@@ -95,9 +99,13 @@ class TestErrorHandling:
response = client.post(
"/api/v1/product",
headers={**auth_headers, "Content-Type": "application/xml"},
content="<xml>not json</xml>"
content="<xml>not json</xml>",
)
assert response.status_code in [400, 422, 415] # Bad request or unsupported media type
assert response.status_code in [
400,
422,
415,
] # Bad request or unsupported media type
def test_large_payload_handling(self, client, auth_headers):
"""Test handling of unusually large payloads"""
@@ -105,9 +113,9 @@ class TestErrorHandling:
large_data = {
"product_id": "LARGE_TEST",
"title": "Large Test Product",
"description": "x" * 50000 # Very long description
"description": "x" * 50000, # Very long description
}
response = client.post("/api/v1/product", headers=auth_headers, json=large_data)
# Should either accept it or reject with 422 (too large)
assert response.status_code in [200, 201, 422, 413]
assert response.status_code in [200, 201, 422, 413]

View File

@@ -1,3 +1,2 @@
# tests/unit/__init__.py
"""Unit tests - fast, isolated component tests."""

View File

@@ -3,4 +3,3 @@
import pytest
# Add any unit-specific fixtures here if needed

View File

@@ -6,6 +6,7 @@ import pytest
from middleware.auth import AuthManager
from middleware.rate_limiter import RateLimiter
@pytest.mark.unit
@pytest.mark.auth # for auth manager tests
class TestRateLimiter:

View File

@@ -1,3 +1,2 @@
# tests/unit/models/__init__.py
"""Database and API model unit tests."""

View File

@@ -1,3 +1,2 @@
# tests/unit/services/__init__.py
"""Service layer unit tests."""

View File

@@ -114,4 +114,4 @@ class TestAdminService:
assert test_job is not None
assert test_job.marketplace == test_marketplace_job.marketplace
assert test_job.shop_name == test_marketplace_job.shop_name
assert test_job.status == test_marketplace_job.status
assert test_job.status == test_marketplace_job.status

View File

@@ -6,6 +6,7 @@ from app.services.auth_service import AuthService
from models.api.auth import UserLogin, UserRegister
from models.database.user import User
@pytest.mark.unit
@pytest.mark.auth
class TestAuthService:

View File

@@ -10,6 +10,7 @@ from models.database.marketplace import MarketplaceImportJob
from models.database.shop import Shop
from models.database.user import User
@pytest.mark.unit
@pytest.mark.marketplace
class TestMarketplaceService:

View File

@@ -5,6 +5,7 @@ from app.services.product_service import ProductService
from models.api.product import ProductCreate
from models.database.product import Product
@pytest.mark.unit
@pytest.mark.products
class TestProductService:

View File

@@ -5,6 +5,7 @@ from fastapi import HTTPException
from app.services.shop_service import ShopService
from models.api.shop import ShopCreate, ShopProductCreate
@pytest.mark.unit
@pytest.mark.shops
class TestShopService:

View File

@@ -5,6 +5,7 @@ from app.services.stats_service import StatsService
from models.database.product import Product
from models.database.stock import Stock
@pytest.mark.unit
@pytest.mark.stats
class TestStatsService:

View File

@@ -8,6 +8,7 @@ from models.api.stock import StockAdd, StockCreate, StockUpdate
from models.database.product import Product
from models.database.stock import Stock
@pytest.mark.unit
@pytest.mark.stock
class TestStockService:

View File

@@ -1,3 +1,2 @@
# tests/unit/utils/__init__.py
"""Utility function unit tests."""

View File

@@ -8,6 +8,7 @@ import requests.exceptions
from utils.csv_processor import CSVProcessor
@pytest.mark.unit
class TestCSVProcessor:
def setup_method(self):
@@ -80,7 +81,9 @@ class TestCSVProcessor:
# Mock failed HTTP response - need to make raise_for_status() raise an exception
mock_response = Mock()
mock_response.status_code = 404
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404 Not Found")
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError(
"404 Not Found"
)
mock_get.return_value = mock_response
with pytest.raises(requests.exceptions.HTTPError):

View File

@@ -3,6 +3,7 @@ import pytest
from utils.data_processing import GTINProcessor, PriceProcessor
@pytest.mark.unit
class TestDataValidation:
def test_gtin_normalization_edge_cases(self):