From 366093bbc6567bcd0ee5d5b3620bb3f57fe89c65 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Fri, 19 Sep 2025 21:23:57 +0200 Subject: [PATCH] Tests restructuring --- app/core/config.py | 38 +- {tests => backup}/test_background_tasks.py | 0 {tests => backup}/test_export.py | 0 {tests => backup}/test_filtering.py | 0 {tests => backup}/test_integration.py | 0 {tests => backup}/test_middleware.py | 0 comprehensive_readme.md | 6 +- enhanced_pytest_config.txt | 80 ---- init_files.py | 60 --- main_conftest.py | 90 ---- powershell_migration_script.ps1 | 113 ----- pytest.ini | 65 +++ tests/.coverage | Bin 0 -> 53248 bytes tests/conftest.py | 404 +----------------- tests/ecommerce.db | 0 tests/fixtures/__init__.py | 3 + .../fixtures/auth_fixtures.py | 0 .../fixtures/marketplace_fixtures.py | 0 .../fixtures/product_fixtures.py | 0 .../fixtures/shop_fixtures.py | 0 tests/integration/__init__.py | 3 + tests/integration/api/__init__.py | 3 + tests/integration/api/v1/__init__.py | 3 + .../api/v1/test_admin_endpoints.py | 18 +- .../api/v1/test_authentication_endpoints.py} | 2 +- .../api/v1/test_marketplace_endpoints.py} | 2 +- .../integration/api/v1/test_pagination.py | 3 +- .../api/v1/test_product_endpoints.py} | 2 +- .../api/v1/test_shop_endpoints.py} | 2 +- .../api/v1/test_stats_endpoints.py} | 2 +- .../api/v1/test_stock_endpoints.py} | 2 +- tests/integration/conftest.py | 6 + tests/integration/security/__init__.py | 3 + .../security/test_authentication.py | 0 .../security/test_authorization.py | 0 .../security/test_input_validation.py | 0 tests/performance/__init__.py | 3 + tests/performance/conftest.py | 10 + .../performance/test_api_performance.py | 0 tests/pytest.ini | 21 - tests/requirements-test.txt | 1 + tests/system/__init__.py | 3 + tests/system/conftest.py | 6 + .../system/test_error_handling.py | 0 tests/test_admin.py | 192 --------- tests/test_admin_service.py | 388 ----------------- tests/test_data/csv/sample_products.csv | 4 + tests/test_database.py | 95 ---- tests/test_error_handling.py | 48 --- tests/test_pagination.py | 57 --- tests/test_performance.py | 59 --- tests/test_security.py | 119 ------ tests/test_utils.py | 119 ------ tests/unit/__init__.py | 3 + tests/unit/conftest.py | 6 + tests/unit/models/__init__.py | 3 + .../unit/models/test_database_models.py | 0 tests/unit/services/__init__.py | 3 + .../unit/services/test_admin_service.py | 0 .../{ => unit/services}/test_auth_service.py | 3 +- .../services}/test_marketplace_service.py | 3 +- .../services}/test_product_service.py | 3 +- .../{ => unit/services}/test_shop_service.py | 3 +- .../{ => unit/services}/test_stats_service.py | 3 +- .../{ => unit/services}/test_stock_service.py | 3 +- tests/unit/utils/__init__.py | 3 + tests/{ => unit/utils}/test_csv_processor.py | 2 +- .../unit/utils/test_data_processing.py | 2 +- .../{ => unit/utils}/test_data_validation.py | 2 +- windows_migration_script.txt | 92 ---- 70 files changed, 212 insertions(+), 1957 deletions(-) rename {tests => backup}/test_background_tasks.py (100%) rename {tests => backup}/test_export.py (100%) rename {tests => backup}/test_filtering.py (100%) rename {tests => backup}/test_integration.py (100%) rename {tests => backup}/test_middleware.py (100%) delete mode 100644 enhanced_pytest_config.txt delete mode 100644 init_files.py delete mode 100644 main_conftest.py delete mode 100644 powershell_migration_script.ps1 create mode 100644 pytest.ini create mode 100644 tests/.coverage create mode 100644 tests/ecommerce.db create mode 100644 tests/fixtures/__init__.py rename auth_fixtures.py => tests/fixtures/auth_fixtures.py (100%) rename marketplace_fixtures.py => tests/fixtures/marketplace_fixtures.py (100%) rename product_fixtures.py => tests/fixtures/product_fixtures.py (100%) rename shop_fixtures.py => tests/fixtures/shop_fixtures.py (100%) create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/api/__init__.py create mode 100644 tests/integration/api/v1/__init__.py rename integration_admin_endpoints.py => tests/integration/api/v1/test_admin_endpoints.py (92%) rename tests/{test_auth.py => integration/api/v1/test_authentication_endpoints.py} (99%) rename tests/{test_marketplace.py => integration/api/v1/test_marketplace_endpoints.py} (98%) rename integration_pagination.py => tests/integration/api/v1/test_pagination.py (97%) rename tests/{test_product.py => integration/api/v1/test_product_endpoints.py} (99%) rename tests/{test_shop.py => integration/api/v1/test_shop_endpoints.py} (98%) rename tests/{test_stats.py => integration/api/v1/test_stats_endpoints.py} (97%) rename tests/{test_stock.py => integration/api/v1/test_stock_endpoints.py} (99%) create mode 100644 tests/integration/conftest.py create mode 100644 tests/integration/security/__init__.py rename integration_authentication.py => tests/integration/security/test_authentication.py (100%) rename integration_authorization.py => tests/integration/security/test_authorization.py (100%) rename integration_input_validation.py => tests/integration/security/test_input_validation.py (100%) create mode 100644 tests/performance/__init__.py create mode 100644 tests/performance/conftest.py rename performance_tests.py => tests/performance/test_api_performance.py (100%) delete mode 100644 tests/pytest.ini create mode 100644 tests/system/__init__.py create mode 100644 tests/system/conftest.py rename system_error_handling.py => tests/system/test_error_handling.py (100%) delete mode 100644 tests/test_admin.py delete mode 100644 tests/test_admin_service.py create mode 100644 tests/test_data/csv/sample_products.csv delete mode 100644 tests/test_database.py delete mode 100644 tests/test_error_handling.py delete mode 100644 tests/test_pagination.py delete mode 100644 tests/test_performance.py delete mode 100644 tests/test_security.py delete mode 100644 tests/test_utils.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/conftest.py create mode 100644 tests/unit/models/__init__.py rename unit_database_models.py => tests/unit/models/test_database_models.py (100%) create mode 100644 tests/unit/services/__init__.py rename unit_admin_service.py => tests/unit/services/test_admin_service.py (100%) rename tests/{ => unit/services}/test_auth_service.py (99%) rename tests/{ => unit/services}/test_marketplace_service.py (99%) rename tests/{ => unit/services}/test_product_service.py (97%) rename tests/{ => unit/services}/test_shop_service.py (99%) rename tests/{ => unit/services}/test_stats_service.py (99%) rename tests/{ => unit/services}/test_stock_service.py (99%) create mode 100644 tests/unit/utils/__init__.py rename tests/{ => unit/utils}/test_csv_processor.py (99%) rename unit_data_processing.py => tests/unit/utils/test_data_processing.py (99%) rename tests/{ => unit/utils}/test_data_validation.py (98%) delete mode 100644 windows_migration_script.txt diff --git a/app/core/config.py b/app/core/config.py index ae6b9138..24876c20 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -18,7 +18,43 @@ class Settings(BaseSettings): # Project information project_name: str = "Ecommerce Backend API with Marketplace Support" - description: str = "Advanced product management system with JWT authentication" + description: str = """ + ## ๐Ÿš€ Letzshop Import API + + Complete marketplace product import and management system built with FastAPI. + + ### ๐Ÿ“š Documentation Links + + - **[Complete Documentation Site](#)** - Full project documentation + - **[Getting Started Guide](#)** - Installation and setup + - **[User Guides](#)** - How-to guides and tutorials + - **[API Authentication Guide](#)** - Security and authentication + - **[Testing Documentation](#)** - Test suite and conventions + + ### ๐Ÿ”— Quick Links + + - **[Alternative API Docs](/redoc)** - ReDoc interface + - **[Health Check](/health)** - System status + - **[OpenAPI Spec](/openapi.json)** - Machine-readable API spec + + ### ๐Ÿ“– Key Features + + - **Product Management** - Complete CRUD operations with validation + - **Multi-Shop Support** - Independent shop configurations + - **CSV Import System** - Bulk import from various marketplace formats + - **Stock Management** - Inventory tracking across locations + - **User Management** - Role-based access control + - **Marketplace Integration** - Import from multiple platforms + + ### ๐Ÿ—๏ธ Architecture + + Built with modern Python stack: + - **FastAPI** - High-performance async API framework + - **SQLAlchemy** - Powerful ORM with PostgreSQL + - **Pydantic** - Data validation and serialization + - **JWT Authentication** - Secure token-based auth + - **pytest** - Comprehensive test suite + """ version: str = "2.2.0" # Database diff --git a/tests/test_background_tasks.py b/backup/test_background_tasks.py similarity index 100% rename from tests/test_background_tasks.py rename to backup/test_background_tasks.py diff --git a/tests/test_export.py b/backup/test_export.py similarity index 100% rename from tests/test_export.py rename to backup/test_export.py diff --git a/tests/test_filtering.py b/backup/test_filtering.py similarity index 100% rename from tests/test_filtering.py rename to backup/test_filtering.py diff --git a/tests/test_integration.py b/backup/test_integration.py similarity index 100% rename from tests/test_integration.py rename to backup/test_integration.py diff --git a/tests/test_middleware.py b/backup/test_middleware.py similarity index 100% rename from tests/test_middleware.py rename to backup/test_middleware.py diff --git a/comprehensive_readme.md b/comprehensive_readme.md index 3a4531e7..2a27c680 100644 --- a/comprehensive_readme.md +++ b/comprehensive_readme.md @@ -318,9 +318,9 @@ pytest tests/ -m integration -v # Integration tests only pytest tests/ -m "not slow" -v # Fast tests only # Run specific test files -pytest tests/test_auth.py -v # Authentication tests -pytest tests/test_product.py -v # Product tests -pytest tests/test_stock.py -v # Stock management tests +pytest tests/test_authentication_endpoints.py -v # Authentication tests +pytest tests/test_product_endpoints.py -v # Product tests +pytest tests/test_stock_endpoints.py -v # Stock management tests ``` ### Test Coverage diff --git a/enhanced_pytest_config.txt b/enhanced_pytest_config.txt deleted file mode 100644 index 4baa5db2..00000000 --- a/enhanced_pytest_config.txt +++ /dev/null @@ -1,80 +0,0 @@ -# pytest.ini - Enhanced configuration for your FastAPI test suite -[tool:pytest] -testpaths = tests -python_files = test_*.py -python_classes = Test* -python_functions = test_* - -# Enhanced addopts for better development experience -addopts = - -v - --tb=short - --strict-markers - --strict-config - --color=yes - --durations=10 - --showlocals - -ra - --cov=app - --cov=models - --cov=utils - --cov=middleware - --cov-report=term-missing - --cov-report=html:htmlcov - --cov-fail-under=80 - -# Test discovery and execution settings -minversion = 6.0 -testmon = true -python_paths = . - -# Markers for your specific test organization -markers = - # Test Types (for your new structure) - unit: Unit tests - fast, isolated components - integration: Integration tests - multiple components working together - system: System tests - full application behavior - e2e: End-to-end tests - complete user workflows - - # Performance and Speed - slow: Slow running tests (deselect with '-m "not slow"') - performance: Performance and load tests - - # Domain-specific markers (matching your application structure) - auth: Authentication and authorization tests - products: Product management functionality - stock: Stock and inventory management - shops: Shop management functionality - admin: Admin functionality and permissions - marketplace: Marketplace import functionality - stats: Statistics and reporting - - # Infrastructure markers - database: Tests that require database operations - external: Tests that require external services - api: API endpoint tests - security: Security-related tests - - # Test environment markers - ci: Tests that should only run in CI - dev: Development-specific tests - -# Test filtering shortcuts -filterwarnings = - ignore::UserWarning - ignore::DeprecationWarning - ignore::PendingDeprecationWarning - ignore::sqlalchemy.exc.SAWarning - -# Timeout settings -timeout = 300 -timeout_method = thread - -# Parallel execution settings (uncomment if using pytest-xdist) -# addopts = -n auto - -# Additional logging configuration -log_cli = true -log_cli_level = INFO -log_cli_format = %(asctime)s [%(levelname)8s] %(name)s: %(message)s -log_cli_date_format = %Y-%m-%d %H:%M:%S \ No newline at end of file diff --git a/init_files.py b/init_files.py deleted file mode 100644 index d5fffe2a..00000000 --- a/init_files.py +++ /dev/null @@ -1,60 +0,0 @@ -# tests/fixtures/__init__.py -"""Test fixtures for the FastAPI application test suite.""" - -# tests/unit/__init__.py -"""Unit tests - fast, isolated component tests.""" - -# tests/unit/models/__init__.py -"""Database and API model unit tests.""" - -# tests/unit/utils/__init__.py -"""Utility function unit tests.""" - -# tests/unit/services/__init__.py -"""Service layer unit tests.""" - -# tests/integration/__init__.py -"""Integration tests - multiple components working together.""" - -# tests/integration/api/__init__.py -"""API integration tests.""" - -# tests/integration/api/v1/__init__.py -"""API v1 endpoint integration tests.""" - -# tests/integration/security/__init__.py -"""Security integration tests.""" - -# tests/performance/__init__.py -"""Performance and load tests.""" - -# tests/system/__init__.py -"""System-level tests - full application behavior.""" - -# tests/integration/conftest.py -"""Integration test specific fixtures.""" -import pytest - -# Add any integration-specific fixtures here if needed - -# tests/unit/conftest.py -"""Unit test specific fixtures.""" -import pytest - -# Add any unit-specific fixtures here if needed - -# tests/performance/conftest.py -"""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 - -# tests/system/conftest.py -"""System test specific fixtures.""" -import pytest - -# Add any system-specific fixtures here if needed \ No newline at end of file diff --git a/main_conftest.py b/main_conftest.py deleted file mode 100644 index 91c1242d..00000000 --- a/main_conftest.py +++ /dev/null @@ -1,90 +0,0 @@ -# tests/conftest.py - Updated main conftest with core fixtures only -import pytest -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import StaticPool - -from app.core.database import Base, get_db -from main import app -# Import all models to ensure they're registered with Base metadata -from models.database_models import (MarketplaceImportJob, Product, Shop, - ShopProduct, Stock, User) - -# Use in-memory SQLite database for tests -SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///:memory:" - - -@pytest.fixture(scope="session") -def engine(): - """Create test database engine""" - return create_engine( - SQLALCHEMY_TEST_DATABASE_URL, - connect_args={"check_same_thread": False}, - poolclass=StaticPool, - echo=False, # Set to True for SQL debugging - ) - - -@pytest.fixture(scope="session") -def testing_session_local(engine): - """Create session factory for tests""" - return sessionmaker(autocommit=False, autoflush=False, bind=engine) - - -@pytest.fixture(scope="function") -def db(engine, testing_session_local): - """Create a database session for direct database operations""" - # Create all tables - Base.metadata.create_all(bind=engine) - - # Create session - db_session = testing_session_local() - - try: - yield db_session - finally: - db_session.close() - # Clean up all data after each test - Base.metadata.drop_all(bind=engine) - Base.metadata.create_all(bind=engine) - - -@pytest.fixture(scope="function") -def client(db): - """Create a test client with database dependency override""" - - # Override the dependency to use our test database - def override_get_db(): - try: - yield db - finally: - pass # Don't close here, the db fixture handles it - - app.dependency_overrides[get_db] = override_get_db - - try: - client = TestClient(app) - yield client - finally: - # Clean up the dependency override - if get_db in app.dependency_overrides: - del app.dependency_overrides[get_db] - - -# Cleanup fixture to ensure clean state -@pytest.fixture(autouse=True) -def cleanup(): - """Automatically clean up after each test""" - yield - # Clear any remaining dependency overrides - app.dependency_overrides.clear() - - -# Import fixtures from fixture modules -pytest_plugins = [ - "tests.fixtures.auth_fixtures", - "tests.fixtures.product_fixtures", - "tests.fixtures.shop_fixtures", - "tests.fixtures.marketplace_fixtures", -] \ No newline at end of file diff --git a/powershell_migration_script.ps1 b/powershell_migration_script.ps1 deleted file mode 100644 index 8b57a865..00000000 --- a/powershell_migration_script.ps1 +++ /dev/null @@ -1,113 +0,0 @@ -# migrate_tests.ps1 - PowerShell script to migrate your test structure - -Write-Host "๐Ÿš€ Starting test structure migration..." -ForegroundColor Cyan - -# Create new directory structure -Write-Host "๐Ÿ“ Creating directory structure..." -ForegroundColor Yellow -$directories = @( - "tests\fixtures", - "tests\unit", "tests\unit\models", "tests\unit\utils", "tests\unit\services", - "tests\integration", "tests\integration\api", "tests\integration\api\v1", "tests\integration\security", - "tests\performance", - "tests\system", - "tests\test_data", "tests\test_data\csv" -) - -foreach ($dir in $directories) { - New-Item -Path $dir -ItemType Directory -Force | Out-Null - Write-Host " Created: $dir" -ForegroundColor Gray -} - -# Create __init__.py files -Write-Host "๐Ÿ“„ Creating __init__.py files..." -ForegroundColor Yellow -$initFiles = @( - "tests\fixtures\__init__.py", - "tests\unit\__init__.py", "tests\unit\models\__init__.py", "tests\unit\utils\__init__.py", "tests\unit\services\__init__.py", - "tests\integration\__init__.py", "tests\integration\api\__init__.py", "tests\integration\api\v1\__init__.py", "tests\integration\security\__init__.py", - "tests\performance\__init__.py", - "tests\system\__init__.py" -) - -foreach ($file in $initFiles) { - New-Item -Path $file -ItemType File -Force | Out-Null - Write-Host " Created: $file" -ForegroundColor Gray -} - -# Create conftest.py files for each test category -Write-Host "โš™๏ธ Creating conftest.py files..." -ForegroundColor Yellow -$conftestFiles = @( - "tests\unit\conftest.py", - "tests\integration\conftest.py", - "tests\performance\conftest.py", - "tests\system\conftest.py" -) - -foreach ($file in $conftestFiles) { - New-Item -Path $file -ItemType File -Force | Out-Null - Write-Host " Created: $file" -ForegroundColor Gray -} - -# Backup original files -Write-Host "๐Ÿ’พ Backing up original files..." -ForegroundColor Yellow -New-Item -Path "tests\backup" -ItemType Directory -Force | Out-Null - -if (Test-Path "tests\conftest.py") { - Copy-Item "tests\conftest.py" "tests\backup\" -Force - Write-Host " Backed up: conftest.py" -ForegroundColor Gray -} - -Get-ChildItem "tests\test_*.py" -ErrorAction SilentlyContinue | ForEach-Object { - Copy-Item $_.FullName "tests\backup\" -Force - Write-Host " Backed up: $($_.Name)" -ForegroundColor Gray -} - -# Create sample test data file -Write-Host "๐Ÿ“Š Creating sample test data..." -ForegroundColor Yellow -$csvContent = @" -product_id,title,price,currency,brand,marketplace -TEST001,Sample Product 1,19.99,EUR,TestBrand,TestMarket -TEST002,Sample Product 2,29.99,EUR,TestBrand,TestMarket -TEST003,Sample Product 3,39.99,USD,AnotherBrand,TestMarket -"@ - -$csvContent | Out-File -FilePath "tests\test_data\csv\sample_products.csv" -Encoding UTF8 -Write-Host " Created: sample_products.csv" -ForegroundColor Gray - -Write-Host "โœ… Directory structure created!" -ForegroundColor Green -Write-Host "" -Write-Host "๐Ÿ“‹ Next steps:" -ForegroundColor Cyan -Write-Host "1. Copy the fixture files I provided to tests\fixtures\" -ForegroundColor White -Write-Host "2. Update tests\conftest.py with the new version" -ForegroundColor White -Write-Host "3. Move test files to their new locations:" -ForegroundColor White -Write-Host " - test_database.py โ†’ tests\unit\models\test_database_models.py" -ForegroundColor Gray -Write-Host " - test_utils.py โ†’ tests\unit\utils\test_data_processing.py" -ForegroundColor Gray -Write-Host " - test_admin_service.py โ†’ tests\unit\services\" -ForegroundColor Gray -Write-Host " - test_admin.py โ†’ tests\integration\api\v1\test_admin_endpoints.py" -ForegroundColor Gray -Write-Host " - test_pagination.py โ†’ tests\integration\api\v1\" -ForegroundColor Gray -Write-Host " - test_performance.py โ†’ tests\performance\test_api_performance.py" -ForegroundColor Gray -Write-Host " - test_error_handling.py โ†’ tests\system\" -ForegroundColor Gray -Write-Host " - Split test_security.py into security subdirectory" -ForegroundColor Gray -Write-Host "" -Write-Host "4. Update imports in moved test files" -ForegroundColor White -Write-Host "5. Add pytest markers to test classes" -ForegroundColor White -Write-Host "6. Update pytest.ini with the enhanced configuration" -ForegroundColor White -Write-Host "" -Write-Host "๐Ÿงช Test the migration with:" -ForegroundColor Cyan -Write-Host "pytest tests\unit -v" -ForegroundColor Yellow -Write-Host "pytest tests\integration -v" -ForegroundColor Yellow -Write-Host "pytest -m unit" -ForegroundColor Yellow -Write-Host "" -Write-Host "๐Ÿ”ง Quick test commands after migration:" -ForegroundColor Cyan -Write-Host "pytest -m unit # Fast unit tests" -ForegroundColor White -Write-Host "pytest -m integration # Integration tests" -ForegroundColor White -Write-Host "pytest -m `"not slow`" # Skip slow tests" -ForegroundColor White -Write-Host "pytest tests\unit\models\ # Model tests only" -ForegroundColor White -Write-Host "pytest --cov=app --cov-report=html # Coverage report" -ForegroundColor White -Write-Host "" -Write-Host "โœจ Migration structure ready! Follow the steps above to complete the migration." -ForegroundColor Green -Write-Host "๐Ÿ“š All your original files are backed up in tests\backup\" -ForegroundColor Green - -# Optional: Pause to let user read the output -Write-Host "" -Write-Host "Press any key to continue..." -ForegroundColor DarkGray -$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..a3864354 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,65 @@ +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* + +# Enhanced addopts for better development experience +addopts = + -v + --tb=short + --strict-markers + --strict-config + --color=yes + --durations=10 + --showlocals + -ra + --cov=app + --cov=models + --cov=utils + --cov=middleware + --cov-report=term-missing + --cov-report=html:htmlcov + --cov-fail-under=80 + +# Test discovery and execution settings +minversion = 6.0 + +# Markers for your specific test organization +markers = + unit: marks tests as unit tests - fast, isolated components + integration: marks tests as integration tests - multiple components working together + system: marks tests as system tests - full application behavior + e2e: marks tests as end-to-end tests - complete user workflows + slow: marks tests as slow running tests (deselect with '-m "not slow"') + performance: marks tests as performance and load tests + auth: marks tests as authentication and authorization tests + products: marks tests as product management functionality + stock: marks tests as stock and inventory management + shops: marks tests as shop management functionality + admin: marks tests as admin functionality and permissions + marketplace: marks tests as marketplace import functionality + stats: marks tests as statistics and reporting + database: marks tests as tests that require database operations + external: marks tests as tests that require external services + api: marks tests as API endpoint tests + security: marks tests as security-related tests + ci: marks tests as tests that should only run in CI + dev: marks tests as development-specific tests + +# Test filtering shortcuts +filterwarnings = + ignore::UserWarning + ignore::DeprecationWarning + ignore::PendingDeprecationWarning + ignore::sqlalchemy.exc.SAWarning + +# Timeout settings +timeout = 300 +timeout_method = thread + +# Additional logging configuration +log_cli = true +log_cli_level = INFO +log_cli_format = %(asctime)s [%(levelname)8s] %(name)s: %(message)s +log_cli_date_format = %Y-%m-%d %H:%M:%S diff --git a/tests/.coverage b/tests/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..7451e62260496862ebfd23ac4b20d9e679034aa2 GIT binary patch literal 53248 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.11.0 httpx>=0.24.0 faker>=19.0.0 pytest-repeat>=0.9.4 +pytest-timeout>=2.1.0 diff --git a/tests/system/__init__.py b/tests/system/__init__.py new file mode 100644 index 00000000..ac25623d --- /dev/null +++ b/tests/system/__init__.py @@ -0,0 +1,3 @@ +# tests/system/__init__.py +"""System-level tests - full application behavior.""" + diff --git a/tests/system/conftest.py b/tests/system/conftest.py new file mode 100644 index 00000000..8c6bc72a --- /dev/null +++ b/tests/system/conftest.py @@ -0,0 +1,6 @@ +# tests/system/conftest.py +"""System test specific fixtures.""" +import pytest + +# Add any system-specific fixtures here if needed + diff --git a/system_error_handling.py b/tests/system/test_error_handling.py similarity index 100% rename from system_error_handling.py rename to tests/system/test_error_handling.py diff --git a/tests/test_admin.py b/tests/test_admin.py deleted file mode 100644 index 0bf46621..00000000 --- a/tests/test_admin.py +++ /dev/null @@ -1,192 +0,0 @@ -# tests/test_admin.py -import pytest - - -class TestAdminAPI: - def test_get_all_users_admin(self, client, admin_headers, test_user): - """Test admin getting all users""" - response = client.get("/api/v1/admin/users", headers=admin_headers) - - assert response.status_code == 200 - data = response.json() - assert len(data) >= 2 # test_user + admin user - - # Check that test_user is in the response - user_ids = [user["id"] for user in data if "id" in user] - assert test_user.id in user_ids - - def test_get_all_users_non_admin(self, client, auth_headers): - """Test non-admin trying to access admin endpoint""" - response = client.get("/api/v1/admin/users", headers=auth_headers) - - assert response.status_code == 403 - assert ( - "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): - """Test admin toggling user status""" - response = client.put( - f"/api/v1/admin/users/{test_user.id}/status", headers=admin_headers - ) - - assert response.status_code == 200 - message = response.json()["message"] - assert "deactivated" in message or "activated" in message - # Verify the username is in the message - assert test_user.username in message - - def test_toggle_user_status_user_not_found(self, client, admin_headers): - """Test admin toggling status for non-existent user""" - response = client.put("/api/v1/admin/users/99999/status", headers=admin_headers) - - assert response.status_code == 404 - assert "User not found" in response.json()["detail"] - - def test_toggle_user_status_cannot_deactivate_self( - self, client, admin_headers, test_admin - ): - """Test that admin cannot deactivate their own account""" - response = client.put( - f"/api/v1/admin/users/{test_admin.id}/status", headers=admin_headers - ) - - assert response.status_code == 400 - assert "Cannot deactivate your own account" in response.json()["detail"] - - def test_get_all_shops_admin(self, client, admin_headers, test_shop): - """Test admin getting all shops""" - response = client.get("/api/v1/admin/shops", headers=admin_headers) - - assert response.status_code == 200 - data = response.json() - assert data["total"] >= 1 - assert len(data["shops"]) >= 1 - - # Check that test_shop is in the response - shop_codes = [ - shop["shop_code"] for shop in data["shops"] if "shop_code" in shop - ] - assert test_shop.shop_code in shop_codes - - def test_get_all_shops_non_admin(self, client, auth_headers): - """Test non-admin trying to access admin shop endpoint""" - response = client.get("/api/v1/admin/shops", headers=auth_headers) - - assert response.status_code == 403 - assert ( - "Access denied" in response.json()["detail"] - or "admin" in response.json()["detail"].lower() - ) - - def test_verify_shop_admin(self, client, admin_headers, test_shop): - """Test admin verifying/unverifying shop""" - response = client.put( - f"/api/v1/admin/shops/{test_shop.id}/verify", headers=admin_headers - ) - - assert response.status_code == 200 - message = response.json()["message"] - assert "verified" in message or "unverified" in message - assert test_shop.shop_code in message - - def test_verify_shop_not_found(self, client, admin_headers): - """Test admin verifying non-existent shop""" - response = client.put("/api/v1/admin/shops/99999/verify", headers=admin_headers) - - assert response.status_code == 404 - assert "Shop not found" in response.json()["detail"] - - def test_toggle_shop_status_admin(self, client, admin_headers, test_shop): - """Test admin toggling shop status""" - response = client.put( - f"/api/v1/admin/shops/{test_shop.id}/status", headers=admin_headers - ) - - assert response.status_code == 200 - message = response.json()["message"] - assert "activated" in message or "deactivated" in message - assert test_shop.shop_code in message - - def test_toggle_shop_status_not_found(self, client, admin_headers): - """Test admin toggling status for non-existent shop""" - response = client.put("/api/v1/admin/shops/99999/status", headers=admin_headers) - - assert response.status_code == 404 - assert "Shop not found" in response.json()["detail"] - - def test_get_marketplace_import_jobs_admin( - self, client, admin_headers, test_marketplace_job - ): - """Test admin getting marketplace import jobs""" - response = client.get( - "/api/v1/admin/marketplace-import-jobs", headers=admin_headers - ) - - assert response.status_code == 200 - data = response.json() - assert len(data) >= 1 - - # Check that test_marketplace_job is in the response - job_ids = [job["job_id"] for job in data if "job_id" in job] - assert test_marketplace_job.id in job_ids - - def test_get_marketplace_import_jobs_with_filters( - self, client, admin_headers, test_marketplace_job - ): - """Test admin getting marketplace import jobs with filters""" - response = client.get( - "/api/v1/admin/marketplace-import-jobs", - params={"marketplace": test_marketplace_job.marketplace}, - headers=admin_headers, - ) - - assert response.status_code == 200 - data = response.json() - assert len(data) >= 1 - assert all( - job["marketplace"] == test_marketplace_job.marketplace for job in data - ) - - def test_get_marketplace_import_jobs_non_admin(self, client, auth_headers): - """Test non-admin trying to access marketplace import jobs""" - response = client.get( - "/api/v1/admin/marketplace-import-jobs", headers=auth_headers - ) - - assert response.status_code == 403 - assert ( - "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): - """Test user pagination works correctly""" - # Test first page - response = client.get( - "/api/v1/admin/users?skip=0&limit=1", headers=admin_headers - ) - assert response.status_code == 200 - data = response.json() - assert len(data) == 1 - - # Test second page - response = client.get( - "/api/v1/admin/users?skip=1&limit=1", headers=admin_headers - ) - assert response.status_code == 200 - data = response.json() - assert len(data) >= 0 # Could be 1 or 0 depending on total users - - def test_admin_pagination_shops(self, client, admin_headers, test_shop): - """Test shop pagination works correctly""" - response = client.get( - "/api/v1/admin/shops?skip=0&limit=1", headers=admin_headers - ) - assert response.status_code == 200 - data = response.json() - assert data["total"] >= 1 - assert len(data["shops"]) >= 0 - assert "skip" in data - assert "limit" in data diff --git a/tests/test_admin_service.py b/tests/test_admin_service.py deleted file mode 100644 index f5574f21..00000000 --- a/tests/test_admin_service.py +++ /dev/null @@ -1,388 +0,0 @@ -# tests/test_admin_service.py -from datetime import datetime - -import pytest -from fastapi import HTTPException - -from app.services.admin_service import AdminService -from models.database_models import MarketplaceImportJob, Shop, User - - -class TestAdminService: - """Test suite for AdminService following the application's testing patterns""" - - def setup_method(self): - """Setup method following the same pattern as product service tests""" - self.service = AdminService() - - def test_get_all_users(self, db, test_user, test_admin): - """Test getting all users with pagination""" - users = self.service.get_all_users(db, skip=0, limit=10) - - assert len(users) >= 2 # test_user + test_admin - user_ids = [user.id for user in users] - assert test_user.id in user_ids - assert test_admin.id in user_ids - - def test_get_all_users_with_pagination(self, db, test_user, test_admin): - """Test user pagination works correctly""" - users = self.service.get_all_users(db, skip=0, limit=1) - - assert len(users) == 1 - - users_second_page = self.service.get_all_users(db, skip=1, limit=1) - assert len(users_second_page) == 1 - assert users[0].id != users_second_page[0].id - - def test_toggle_user_status_deactivate(self, db, test_user, test_admin): - """Test deactivating a user""" - assert test_user.is_active is True - - user, message = self.service.toggle_user_status(db, test_user.id, test_admin.id) - - assert user.id == test_user.id - assert user.is_active is False - assert f"{user.username} has been deactivated" in message - - def test_toggle_user_status_activate(self, db, test_user, test_admin): - """Test activating a user""" - # First deactivate the user - test_user.is_active = False - db.commit() - - user, message = self.service.toggle_user_status(db, test_user.id, test_admin.id) - - assert user.id == test_user.id - assert user.is_active is True - assert f"{user.username} has been activated" in message - - def test_toggle_user_status_user_not_found(self, db, test_admin): - """Test toggle user status when user not found""" - with pytest.raises(HTTPException) as exc_info: - self.service.toggle_user_status(db, 99999, test_admin.id) - - assert exc_info.value.status_code == 404 - assert "User not found" in str(exc_info.value.detail) - - def test_toggle_user_status_cannot_deactivate_self(self, db, test_admin): - """Test that admin cannot deactivate their own account""" - with pytest.raises(HTTPException) as exc_info: - self.service.toggle_user_status(db, test_admin.id, test_admin.id) - - assert exc_info.value.status_code == 400 - assert "Cannot deactivate your own account" in str(exc_info.value.detail) - - def test_get_all_shops(self, db, test_shop): - """Test getting all shops with total count""" - shops, total = self.service.get_all_shops(db, skip=0, limit=10) - - assert total >= 1 - assert len(shops) >= 1 - shop_codes = [shop.shop_code for shop in shops] - assert test_shop.shop_code in shop_codes - - def test_get_all_shops_with_pagination(self, db, test_shop): - """Test shop pagination works correctly""" - # Create additional shop for pagination test using the helper function - # from conftest import create_test_import_job # If you added the helper function - - # Or create directly with proper fields - additional_shop = Shop( - shop_code=f"{test_shop.shop_code}_2", - shop_name="Test Shop 2", - owner_id=test_shop.owner_id, - is_active=True, - is_verified=False, - ) - db.add(additional_shop) - db.commit() - - shops_page_1 = self.service.get_all_shops(db, skip=0, limit=1) - assert len(shops_page_1[0]) == 1 - - shops_page_2 = self.service.get_all_shops(db, skip=1, limit=1) - assert len(shops_page_2[0]) == 1 - - # Ensure different shops on different pages - assert shops_page_1[0][0].id != shops_page_2[0][0].id - - def test_verify_shop_mark_verified(self, db, test_shop): - """Test marking shop as verified""" - # Ensure shop starts unverified - test_shop.is_verified = False - db.commit() - - shop, message = self.service.verify_shop(db, test_shop.id) - - assert shop.id == test_shop.id - assert shop.is_verified is True - assert f"{shop.shop_code} has been verified" in message - - def test_verify_shop_mark_unverified(self, db, test_shop): - """Test marking shop as unverified""" - # Ensure shop starts verified - test_shop.is_verified = True - db.commit() - - shop, message = self.service.verify_shop(db, test_shop.id) - - assert shop.id == test_shop.id - assert shop.is_verified is False - assert f"{shop.shop_code} has been unverified" in message - - def test_verify_shop_not_found(self, db): - """Test verify shop when shop not found""" - with pytest.raises(HTTPException) as exc_info: - self.service.verify_shop(db, 99999) - - assert exc_info.value.status_code == 404 - assert "Shop not found" in str(exc_info.value.detail) - - def test_toggle_shop_status_deactivate(self, db, test_shop): - """Test deactivating a shop""" - assert test_shop.is_active is True - - shop, message = self.service.toggle_shop_status(db, test_shop.id) - - assert shop.id == test_shop.id - assert shop.is_active is False - assert f"{shop.shop_code} has been deactivated" in message - - def test_toggle_shop_status_activate(self, db, test_shop): - """Test activating a shop""" - # First deactivate the shop - test_shop.is_active = False - db.commit() - - shop, message = self.service.toggle_shop_status(db, test_shop.id) - - assert shop.id == test_shop.id - assert shop.is_active is True - assert f"{shop.shop_code} has been activated" in message - - def test_toggle_shop_status_not_found(self, db): - """Test toggle shop status when shop not found""" - with pytest.raises(HTTPException) as exc_info: - self.service.toggle_shop_status(db, 99999) - - assert exc_info.value.status_code == 404 - assert "Shop not found" in str(exc_info.value.detail) - - def test_get_marketplace_import_jobs_no_filters(self, db, test_marketplace_job): - """Test getting marketplace import jobs without filters using fixture""" - result = self.service.get_marketplace_import_jobs(db, skip=0, limit=10) - - assert len(result) >= 1 - # Find our test job in the results - test_job = next( - (job for job in result if job.job_id == test_marketplace_job.id), None - ) - 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 - - def test_get_marketplace_import_jobs_with_marketplace_filter( - self, db, test_marketplace_job, test_user, test_shop - ): - """Test getting marketplace import jobs filtered by marketplace""" - # Create additional job with different marketplace - other_job = MarketplaceImportJob( - marketplace="ebay", - shop_name="eBay Shop", - status="completed", - source_url="https://ebay.example.com/import", - shop_id=test_shop.id, - user_id=test_user.id, # Fixed: Added missing user_id - ) - db.add(other_job) - db.commit() - - # Filter by the test marketplace job's marketplace - result = self.service.get_marketplace_import_jobs( - db, marketplace=test_marketplace_job.marketplace - ) - - assert len(result) >= 1 - # All results should match the marketplace filter - for job in result: - assert test_marketplace_job.marketplace.lower() in job.marketplace.lower() - - def test_get_marketplace_import_jobs_with_shop_filter( - self, db, test_marketplace_job, test_user, test_shop - ): - """Test getting marketplace import jobs filtered by shop name""" - # Create additional job with different shop name - other_job = MarketplaceImportJob( - marketplace="amazon", - shop_name="Different Shop Name", - status="completed", - source_url="https://different.example.com/import", - shop_id=test_shop.id, - user_id=test_user.id, # Fixed: Added missing user_id - ) - db.add(other_job) - db.commit() - - # Filter by the test marketplace job's shop name - result = self.service.get_marketplace_import_jobs( - db, shop_name=test_marketplace_job.shop_name - ) - - assert len(result) >= 1 - # All results should match the shop name filter - for job in result: - assert test_marketplace_job.shop_name.lower() in job.shop_name.lower() - - def test_get_marketplace_import_jobs_with_status_filter( - self, db, test_marketplace_job, test_user, test_shop - ): - """Test getting marketplace import jobs filtered by status""" - # Create additional job with different status - other_job = MarketplaceImportJob( - marketplace="amazon", - shop_name="Test Shop", - status="pending", - source_url="https://pending.example.com/import", - shop_id=test_shop.id, - user_id=test_user.id, # Fixed: Added missing user_id - ) - db.add(other_job) - db.commit() - - # Filter by the test marketplace job's status - result = self.service.get_marketplace_import_jobs( - db, status=test_marketplace_job.status - ) - - assert len(result) >= 1 - # All results should match the status filter - for job in result: - assert job.status == test_marketplace_job.status - - def test_get_marketplace_import_jobs_with_multiple_filters( - self, db, test_marketplace_job, test_shop, test_user - ): - """Test getting marketplace import jobs with multiple filters""" - # Create jobs that don't match all filters - non_matching_job1 = MarketplaceImportJob( - marketplace="ebay", # Different marketplace - shop_name=test_marketplace_job.shop_name, - status=test_marketplace_job.status, - source_url="https://non-matching1.example.com/import", - shop_id=test_shop.id, - user_id=test_user.id, # Fixed: Added missing user_id - ) - non_matching_job2 = MarketplaceImportJob( - marketplace=test_marketplace_job.marketplace, - shop_name="Different Shop", # Different shop name - status=test_marketplace_job.status, - source_url="https://non-matching2.example.com/import", - shop_id=test_shop.id, - user_id=test_user.id, # Fixed: Added missing user_id - ) - db.add_all([non_matching_job1, non_matching_job2]) - db.commit() - - # Apply all three filters matching the test job - result = self.service.get_marketplace_import_jobs( - db, - marketplace=test_marketplace_job.marketplace, - shop_name=test_marketplace_job.shop_name, - status=test_marketplace_job.status, - ) - - assert len(result) >= 1 - # Find our test job in the results - test_job = next( - (job for job in result if job.job_id == test_marketplace_job.id), None - ) - 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 - - def test_get_marketplace_import_jobs_null_values(self, db, test_user, test_shop): - """Test that marketplace import jobs handle null values correctly""" - # Create job with null values but required fields - job = MarketplaceImportJob( - shop_id=test_shop.id, - user_id=test_user.id, # Fixed: Added missing user_id - marketplace="test", - shop_name="Test Shop", - status="pending", - source_url="https://test.example.com/import", - imported_count=None, - updated_count=None, - total_processed=None, - error_count=None, - error_message=None, - ) - db.add(job) - db.commit() - - result = self.service.get_marketplace_import_jobs(db) - - assert len(result) >= 1 - # Find the job with null values - null_job = next((j for j in result if j.job_id == job.id), None) - assert null_job is not None - assert null_job.imported == 0 # None converted to 0 - assert null_job.updated == 0 - assert null_job.total_processed == 0 - assert null_job.error_count == 0 - assert null_job.error_message is None - - def test_get_user_by_id(self, db, test_user): - """Test getting user by ID using fixture""" - user = self.service.get_user_by_id(db, test_user.id) - - assert user is not None - assert user.id == test_user.id - assert user.email == test_user.email - assert user.username == test_user.username - - def test_get_user_by_id_not_found(self, db): - """Test getting user by ID when user doesn't exist""" - user = self.service.get_user_by_id(db, 99999) - - assert user is None - - def test_get_shop_by_id(self, db, test_shop): - """Test getting shop by ID using fixture""" - shop = self.service.get_shop_by_id(db, test_shop.id) - - assert shop is not None - assert shop.id == test_shop.id - assert shop.shop_code == test_shop.shop_code - assert shop.shop_name == test_shop.shop_name - - def test_get_shop_by_id_not_found(self, db): - """Test getting shop by ID when shop doesn't exist""" - shop = self.service.get_shop_by_id(db, 99999) - - assert shop is None - - def test_user_exists_true(self, db, test_user): - """Test user_exists returns True when user exists""" - exists = self.service.user_exists(db, test_user.id) - - assert exists is True - - def test_user_exists_false(self, db): - """Test user_exists returns False when user doesn't exist""" - exists = self.service.user_exists(db, 99999) - - assert exists is False - - def test_shop_exists_true(self, db, test_shop): - """Test shop_exists returns True when shop exists""" - exists = self.service.shop_exists(db, test_shop.id) - - assert exists is True - - def test_shop_exists_false(self, db): - """Test shop_exists returns False when shop doesn't exist""" - exists = self.service.shop_exists(db, 99999) - - assert exists is False diff --git a/tests/test_data/csv/sample_products.csv b/tests/test_data/csv/sample_products.csv new file mode 100644 index 00000000..bd8d6733 --- /dev/null +++ b/tests/test_data/csv/sample_products.csv @@ -0,0 +1,4 @@ +product_id,title,price,currency,brand,marketplace +TEST001,Sample Product 1,19.99,EUR,TestBrand,TestMarket +TEST002,Sample Product 2,29.99,EUR,TestBrand,TestMarket +TEST003,Sample Product 3,39.99,USD,AnotherBrand,TestMarket diff --git a/tests/test_database.py b/tests/test_database.py deleted file mode 100644 index f2894a77..00000000 --- a/tests/test_database.py +++ /dev/null @@ -1,95 +0,0 @@ -# tests/test_database.py -import pytest -from sqlalchemy import text - -from models.database_models import Product, Shop, Stock, User - - -class TestDatabaseModels: - def test_user_model(self, db): - """Test User model creation and relationships""" - user = User( - email="db_test@example.com", - username="dbtest", - hashed_password="hashed_password_123", - role="user", - is_active=True, - ) - - db.add(user) - db.commit() - db.refresh(user) - - assert user.id is not None - assert user.email == "db_test@example.com" - assert user.created_at is not None - assert user.updated_at is not None - - def test_product_model(self, db): - """Test Product model creation""" - product = Product( - product_id="DB_TEST_001", - title="Database Test Product", - description="Testing product model", - price="25.99", - currency="USD", - brand="DBTest", - gtin="1234567890123", - availability="in stock", - marketplace="TestDB", - shop_name="DBTestShop", - ) - - db.add(product) - db.commit() - db.refresh(product) - - assert product.id is not None - assert product.product_id == "DB_TEST_001" - assert product.created_at is not None - - def test_stock_model(self, db): - """Test Stock model creation""" - stock = Stock(gtin="1234567890123", location="DB_WAREHOUSE", quantity=150) - - db.add(stock) - db.commit() - db.refresh(stock) - - assert stock.id is not None - assert stock.gtin == "1234567890123" - assert stock.location == "DB_WAREHOUSE" - assert stock.quantity == 150 - - def test_shop_model_with_owner(self, db, test_user): - """Test Shop model with owner relationship""" - shop = Shop( - shop_code="DBTEST", - shop_name="Database Test Shop", - description="Testing shop model", - owner_id=test_user.id, - is_active=True, - is_verified=False, - ) - - db.add(shop) - db.commit() - db.refresh(shop) - - assert shop.id is not None - assert shop.shop_code == "DBTEST" - assert shop.owner_id == test_user.id - assert shop.owner.username == test_user.username - - def test_database_constraints(self, db): - """Test database constraints and unique indexes""" - # Test unique product_id constraint - product1 = Product(product_id="UNIQUE_001", title="Product 1") - db.add(product1) - db.commit() - - # This should raise an integrity error - with pytest.raises(Exception): # Could be IntegrityError or similar - product2 = Product(product_id="UNIQUE_001", title="Product 2") - db.add(product2) - db.commit() diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py deleted file mode 100644 index 66951aa0..00000000 --- a/tests/test_error_handling.py +++ /dev/null @@ -1,48 +0,0 @@ -# tests/test_error_handling.py -import pytest - - -class TestErrorHandling: - def test_invalid_json(self, client, auth_headers): - """Test handling of invalid JSON""" - response = client.post( - "/api/v1/product", headers=auth_headers, content="invalid json" - ) - - assert response.status_code == 422 # Validation error - - def test_missing_required_fields(self, client, auth_headers): - """Test handling of missing required fields""" - response = client.post( - "/api/v1/product", headers=auth_headers, json={"title": "Test"} - ) # Missing product_id - - assert response.status_code == 422 - - def test_invalid_authentication(self, client): - """Test handling of invalid authentication""" - response = client.get( - "/api/v1/product", headers={"Authorization": "Bearer invalid_token"} - ) - - assert response.status_code == 401 # Token is not valid - - def test_nonexistent_resource(self, client, auth_headers): - """Test handling of nonexistent resource access""" - response = client.get("/api/v1/product/NONEXISTENT", headers=auth_headers) - assert response.status_code == 404 - - response = client.get("/api/v1/shop/NONEXISTENT", headers=auth_headers) - assert response.status_code == 404 - - def test_duplicate_resource_creation(self, client, auth_headers, test_product): - """Test handling of duplicate resource creation""" - product_data = { - "product_id": test_product.product_id, # Duplicate ID - "title": "Another Product", - } - - response = client.post( - "/api/v1/product", headers=auth_headers, json=product_data - ) - assert response.status_code == 400 diff --git a/tests/test_pagination.py b/tests/test_pagination.py deleted file mode 100644 index d2d28eda..00000000 --- a/tests/test_pagination.py +++ /dev/null @@ -1,57 +0,0 @@ -# tests/test_pagination.py -import pytest - -from models.database_models import Product - - -class TestPagination: - def test_product_pagination(self, client, auth_headers, db): - """Test pagination for product listing""" - # Create multiple products - products = [] - for i in range(25): - product = Product( - product_id=f"PAGE{i:03d}", - title=f"Pagination Test Product {i}", - marketplace="PaginationTest", - ) - products.append(product) - - db.add_all(products) - db.commit() - - # Test first page - response = client.get("/api/v1/product?limit=10&skip=0", headers=auth_headers) - assert response.status_code == 200 - data = response.json() - assert len(data["products"]) == 10 - assert data["total"] == 25 - assert data["skip"] == 0 - assert data["limit"] == 10 - - # Test second page - response = client.get("/api/v1/product?limit=10&skip=10", headers=auth_headers) - assert response.status_code == 200 - data = response.json() - assert len(data["products"]) == 10 - assert data["skip"] == 10 - - # Test last page - response = client.get("/api/v1/product?limit=10&skip=20", headers=auth_headers) - assert response.status_code == 200 - data = response.json() - assert len(data["products"]) == 5 # Only 5 remaining - - def test_pagination_boundaries(self, client, auth_headers): - """Test pagination boundary conditions""" - # Test negative skip - response = client.get("/api/v1/product?skip=-1", headers=auth_headers) - assert response.status_code == 422 # Validation error - - # Test zero limit - response = client.get("/api/v1/product?limit=0", headers=auth_headers) - assert response.status_code == 422 # Validation error - - # Test excessive limit - response = client.get("/api/v1/product?limit=10000", headers=auth_headers) - assert response.status_code == 422 # Should be limited diff --git a/tests/test_performance.py b/tests/test_performance.py deleted file mode 100644 index f71eade4..00000000 --- a/tests/test_performance.py +++ /dev/null @@ -1,59 +0,0 @@ -# tests/test_performance.py -import time - -import pytest - -from models.database_models import Product - - -class TestPerformance: - def test_product_list_performance(self, client, auth_headers, db): - """Test performance of product listing with many products""" - # Create multiple products - products = [] - for i in range(100): - product = Product( - product_id=f"PERF{i:03d}", - title=f"Performance Test Product {i}", - price=f"{i}.99", - marketplace="Performance", - ) - products.append(product) - - db.add_all(products) - db.commit() - - # Time the request - start_time = time.time() - response = client.get("/api/v1/product?limit=100", headers=auth_headers) - end_time = time.time() - - assert response.status_code == 200 - assert len(response.json()["products"]) == 100 - assert end_time - start_time < 2.0 # Should complete within 2 seconds - - def test_search_performance(self, client, auth_headers, db): - """Test search performance""" - # Create products with searchable content - products = [] - for i in range(50): - product = Product( - product_id=f"SEARCH{i:03d}", - title=f"Searchable Product {i}", - description=f"This is a searchable product number {i}", - brand="SearchBrand", - marketplace="SearchMarket", - ) - products.append(product) - - db.add_all(products) - db.commit() - - # Time search request - start_time = time.time() - response = client.get("/api/v1/product?search=Searchable", headers=auth_headers) - end_time = time.time() - - assert response.status_code == 200 - assert response.json()["total"] == 50 - assert end_time - start_time < 1.0 # Search should be fast diff --git a/tests/test_security.py b/tests/test_security.py deleted file mode 100644 index dc1ac162..00000000 --- a/tests/test_security.py +++ /dev/null @@ -1,119 +0,0 @@ -# tests/test_security.py -from unittest.mock import patch - -import pytest -from fastapi import HTTPException - - -class TestSecurity: - def test_debug_direct_bearer(self, client): - """Test HTTPBearer directly""" - - response = client.get("/api/v1/debug-bearer") - print(f"Direct Bearer - Status: {response.status_code}") - print( - f"Direct Bearer - Response: {response.json() if response.content else 'No content'}" - ) - - def test_debug_dependencies(self, client): - """Debug the dependency chain step by step""" - - # Test 1: Direct endpoint with no auth - response = client.get("/api/v1/admin/users") - print(f"Admin endpoint - Status: {response.status_code}") - try: - print(f"Admin endpoint - Response: {response.json()}") - except: - 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 - print(f"Regular endpoint - Status: {response2.status_code}") - try: - print(f"Regular endpoint - Response: {response2.json()}") - except: - print(f"Regular endpoint - Raw: {response2.content}") - - def test_debug_available_routes(self, client): - """Debug test to see all available routes""" - print("\n=== All Available Routes ===") - for route in client.app.routes: - if hasattr(route, "path") and hasattr(route, "methods"): - print(f"{list(route.methods)} {route.path}") - - print("\n=== Testing Product Endpoint Variations ===") - variations = [ - "/api/v1/product", # Your current attempt - "/api/v1/product/", # With trailing slash - "/api/v1/product/list", # With list endpoint - "/api/v1/product/all", # With all endpoint - ] - - for path in variations: - response = client.get(path) - print(f"{path}: Status {response.status_code}") - - def test_protected_endpoint_without_auth(self, client): - """Test that protected endpoints reject unauthenticated requests""" - protected_endpoints = [ - "/api/v1/admin/users", - "/api/v1/admin/shops", - "/api/v1/marketplace/import-jobs", - "/api/v1/product", - "/api/v1/shop", - "/api/v1/stats", - "/api/v1/stock", - ] - - for endpoint in protected_endpoints: - response = client.get(endpoint) - assert response.status_code == 401 # Authentication missing - - def test_protected_endpoint_with_invalid_token(self, client): - """Test protected endpoints with invalid token""" - headers = {"Authorization": "Bearer invalid_token_here"} - - response = client.get("/api/v1/product", headers=headers) - assert response.status_code == 401 # Token is not valid - - def test_admin_endpoint_requires_admin_role(self, client, auth_headers): - """Test that admin endpoints require admin role""" - response = client.get("/api/v1/admin/users", headers=auth_headers) - assert ( - response.status_code == 403 - ) # Token is valid but user does not have access. - # Regular user should be denied - - def test_sql_injection_prevention(self, client, auth_headers): - """Test SQL injection prevention in search parameters""" - # Try SQL injection in search parameter - malicious_search = "'; DROP TABLE products; --" - - response = client.get( - f"/api/v1/product?search={malicious_search}", headers=auth_headers - ) - - # Should not crash and should return normal response - assert response.status_code == 200 - # Database should still be intact (no products dropped) - - # def test_input_validation(self, client, auth_headers): - # # TODO: implement sanitization - # """Test input validation and sanitization""" - # # Test XSS attempt in product creation - # xss_payload = "" - # - # product_data = { - # "product_id": "XSS_TEST", - # "title": xss_payload, - # "description": xss_payload, - # } - # - # response = client.post("/api/v1/product", headers=auth_headers, json=product_data) - # - # assert response.status_code == 200 - # data = response.json() - # assert "