refactor: modernize code quality tooling with Ruff

- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter)
- Add comprehensive pyproject.toml configuration
- Simplify Makefile code quality targets
- Configure exclusions for venv/.venv in pyproject.toml
- Auto-fix 1,359 linting issues across codebase

Benefits:
- Much faster builds (Ruff is written in Rust)
- Single tool replaces multiple tools
- More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q)
- All configuration centralized in pyproject.toml
- Better import sorting and formatting consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 19:37:38 +01:00
parent 21c13ca39b
commit 238c1ec9b8
169 changed files with 2183 additions and 1784 deletions

View File

@@ -7,13 +7,8 @@ from sqlalchemy.pool import StaticPool
from app.core.database import Base, get_db
from main import app
from models.database.inventory import Inventory
# Import all models to ensure they're registered with Base metadata
from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.marketplace_product import MarketplaceProduct
from models.database.product import Product
from models.database.user import User
from models.database.vendor import Vendor
# Use in-memory SQLite database for tests
SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///:memory:"

View File

@@ -1,5 +1,5 @@
# tests/integration/api/v1/test_auth_endpoints.py
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
import pytest
from jose import jwt
@@ -196,8 +196,8 @@ class TestAuthenticationAPI:
"username": test_user.username,
"email": test_user.email,
"role": test_user.role,
"exp": datetime.now(timezone.utc) - timedelta(hours=1),
"iat": datetime.now(timezone.utc) - timedelta(hours=2),
"exp": datetime.now(UTC) - timedelta(hours=1),
"iat": datetime.now(UTC) - timedelta(hours=2),
}
expired_token = jwt.encode(

View File

@@ -9,7 +9,6 @@ from models.database.marketplace_product import MarketplaceProduct
@pytest.mark.products
@pytest.mark.marketplace
class TestFiltering:
def test_product_brand_filter_success(self, client, auth_headers, db):
"""Test filtering products by brand successfully"""
# Create products with different brands using unique IDs
@@ -131,7 +130,7 @@ class TestFiltering:
# Search for "Apple"
response = client.get(
f"/api/v1/marketplace/product?search=Apple", headers=auth_headers
"/api/v1/marketplace/product?search=Apple", headers=auth_headers
)
assert response.status_code == 200
data = response.json()
@@ -139,7 +138,7 @@ class TestFiltering:
# Search for "phone"
response = client.get(
f"/api/v1/marketplace/product?search=phone", headers=auth_headers
"/api/v1/marketplace/product?search=phone", headers=auth_headers
)
assert response.status_code == 200
data = response.json()

View File

@@ -8,7 +8,6 @@ from models.database.inventory import Inventory
@pytest.mark.api
@pytest.mark.inventory
class TestInventoryAPI:
def test_set_inventory_new_success(self, client, auth_headers):
"""Test setting inventory for new GTIN successfully"""
inventory_data = {
@@ -445,7 +444,7 @@ class TestInventoryAPI:
"""Test invalid inventory operations return InvalidInventoryOperationException"""
# This would test business logic validation
# The exact scenario depends on your business rules
pass # Implementation depends on specific business rules
# Implementation depends on specific business rules
def test_get_inventory_without_auth_returns_invalid_token(self, client):
"""Test that inventory endpoints require authentication returns InvalidTokenException"""

View File

@@ -1,5 +1,4 @@
# tests/integration/api/v1/test_marketplace_import_job_endpoints.py
from unittest.mock import AsyncMock, patch
import pytest

View File

@@ -12,7 +12,6 @@ from models.database.marketplace_product import MarketplaceProduct
@pytest.mark.api
@pytest.mark.performance # for the performance test
class TestExportFunctionality:
def test_csv_export_basic_success(
self, client, auth_headers, test_marketplace_product
):
@@ -198,9 +197,9 @@ class TestExportFunctionality:
assert response.status_code == 200
assert response.headers["content-type"] == "text/csv; charset=utf-8"
assert (
execution_time < 10.0
), f"Export took {execution_time:.2f} seconds, should be under 10s"
assert execution_time < 10.0, (
f"Export took {execution_time:.2f} seconds, should be under 10s"
)
# Verify content contains our test data
csv_content = response.content.decode("utf-8")

View File

@@ -6,7 +6,6 @@ import pytest
@pytest.mark.api
@pytest.mark.products
class TestMarketplaceProductsAPI:
def test_get_products_empty(self, client, auth_headers):
"""Test getting products when none exist"""
response = client.get("/api/v1/marketplace/product", headers=auth_headers)

View File

@@ -10,7 +10,6 @@ from models.database.vendor import Vendor
@pytest.mark.database
@pytest.mark.products
class TestPagination:
def test_product_pagination_success(self, client, auth_headers, db):
"""Test pagination for product listing successfully"""
import uuid
@@ -224,7 +223,6 @@ class TestPagination:
unique_suffix = str(uuid.uuid4())[:8]
# Create multiple vendors for pagination testing
from models.database.vendor import Vendor
vendors = []
for i in range(15):

View File

@@ -6,7 +6,6 @@ import pytest
@pytest.mark.api
@pytest.mark.vendors
class TestVendorsAPI:
def test_create_vendor_success(self, client, auth_headers):
"""Test creating a new vendor successfully"""
vendor_data = {
@@ -91,7 +90,7 @@ class TestVendorsAPI:
# Assuming max vendors is enforced at service level
# This test validates the expected response structure
pass # Implementation depends on your max_vendors business logic
# Implementation depends on your max_vendors business logic
def test_get_vendors_success(self, client, auth_headers, test_vendor):
"""Test getting vendors list successfully"""

View File

@@ -10,7 +10,7 @@ These tests verify that:
5. Vendor context middleware works correctly with API authentication
"""
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
import pytest
from jose import jwt
@@ -88,8 +88,8 @@ class TestVendorAPIAuthentication:
"username": test_vendor_user.username,
"email": test_vendor_user.email,
"role": test_vendor_user.role,
"exp": datetime.now(timezone.utc) - timedelta(hours=1),
"iat": datetime.now(timezone.utc) - timedelta(hours=2),
"exp": datetime.now(UTC) - timedelta(hours=1),
"iat": datetime.now(UTC) - timedelta(hours=2),
}
expired_token = jwt.encode(
@@ -184,9 +184,9 @@ class TestVendorAPIAuthentication:
response = client.get(endpoint)
# All should fail with 401 (header required)
assert (
response.status_code == 401
), f"Endpoint {endpoint} should reject cookie-only auth"
assert response.status_code == 401, (
f"Endpoint {endpoint} should reject cookie-only auth"
)
# ========================================================================
# Role-Based Access Control Tests
@@ -205,15 +205,15 @@ class TestVendorAPIAuthentication:
for endpoint in endpoints:
# Test with regular user token
response = client.get(endpoint, headers=auth_headers)
assert (
response.status_code == 403
), f"Endpoint {endpoint} should reject regular users"
assert response.status_code == 403, (
f"Endpoint {endpoint} should reject regular users"
)
# Test with admin token
response = client.get(endpoint, headers=admin_headers)
assert (
response.status_code == 403
), f"Endpoint {endpoint} should reject admin users"
assert response.status_code == 403, (
f"Endpoint {endpoint} should reject admin users"
)
def test_vendor_api_accepts_only_vendor_role(
self, client, vendor_user_headers, test_vendor_user
@@ -228,7 +228,9 @@ class TestVendorAPIAuthentication:
assert response.status_code in [
200,
404,
], f"Endpoint {endpoint} should accept vendor users (got {response.status_code})"
], (
f"Endpoint {endpoint} should accept vendor users (got {response.status_code})"
)
# ========================================================================
# Token Validation Tests
@@ -246,9 +248,9 @@ class TestVendorAPIAuthentication:
for headers in malformed_headers:
response = client.get("/api/v1/vendor/auth/me", headers=headers)
assert (
response.status_code == 401
), f"Should reject malformed header: {headers}"
assert response.status_code == 401, (
f"Should reject malformed header: {headers}"
)
def test_token_with_missing_claims(self, client, auth_manager):
"""Test token missing required claims"""
@@ -256,7 +258,7 @@ class TestVendorAPIAuthentication:
invalid_payload = {
"sub": "123",
"username": "test",
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
"exp": datetime.now(UTC) + timedelta(hours=1),
}
invalid_token = jwt.encode(
@@ -348,9 +350,9 @@ class TestVendorAPIConsistency:
response = client.post(endpoint, json={})
# All should reject cookie-only auth with 401
assert (
response.status_code == 401
), f"Endpoint {endpoint} should require Authorization header (got {response.status_code})"
assert response.status_code == 401, (
f"Endpoint {endpoint} should require Authorization header (got {response.status_code})"
)
def test_vendor_endpoints_accept_vendor_token(
self, client, vendor_user_headers, test_vendor_with_vendor_user
@@ -371,4 +373,6 @@ class TestVendorAPIConsistency:
assert response.status_code not in [
401,
403,
], f"Endpoint {endpoint} should accept vendor token (got {response.status_code}: {response.text})"
], (
f"Endpoint {endpoint} should accept vendor token (got {response.status_code}: {response.text})"
)

View File

@@ -1,5 +1,5 @@
# tests/integration/conftest.py
"""Integration test specific fixtures."""
import pytest
# Add any integration-specific fixtures here if needed

View File

@@ -2,6 +2,7 @@
"""
Fixtures specific to middleware integration tests.
"""
import pytest
from models.database.vendor import Vendor

View File

@@ -5,6 +5,7 @@ Integration tests for request context detection end-to-end flow.
These tests verify that context type (API, ADMIN, VENDOR_DASHBOARD, SHOP, FALLBACK)
is correctly detected through real HTTP requests.
"""
from unittest.mock import patch
import pytest

View File

@@ -5,12 +5,11 @@ Integration tests for the complete middleware stack.
These tests verify that all middleware components work together correctly
through real HTTP requests, ensuring proper execution order and state injection.
"""
from unittest.mock import patch
import pytest
from middleware.context import RequestContext
@pytest.mark.integration
@pytest.mark.middleware

View File

@@ -5,6 +5,7 @@ Integration tests for theme loading end-to-end flow.
These tests verify that vendor themes are correctly loaded and injected
into request.state through real HTTP requests.
"""
from unittest.mock import patch
import pytest

View File

@@ -5,6 +5,7 @@ Integration tests for vendor context detection end-to-end flow.
These tests verify that vendor detection works correctly through real HTTP requests
for all routing modes: subdomain, custom domain, and path-based.
"""
from unittest.mock import patch
import pytest

View File

@@ -1,5 +1,4 @@
# tests/test_background_tasks.py
from datetime import datetime
from unittest.mock import AsyncMock, patch
import pytest
@@ -32,8 +31,9 @@ class TestBackgroundTasks:
job_id = job.id
# Mock CSV processor and prevent session from closing
with patch("app.tasks.background_tasks.CSVProcessor") as mock_processor, patch(
"app.tasks.background_tasks.SessionLocal", return_value=db
with (
patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
patch("app.tasks.background_tasks.SessionLocal", return_value=db),
):
mock_instance = mock_processor.return_value
mock_instance.process_marketplace_csv_from_url = AsyncMock(
@@ -86,10 +86,10 @@ class TestBackgroundTasks:
job_id = job.id
# Mock CSV processor to raise exception
with patch("app.tasks.background_tasks.CSVProcessor") as mock_processor, patch(
"app.tasks.background_tasks.SessionLocal", return_value=db
with (
patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
patch("app.tasks.background_tasks.SessionLocal", return_value=db),
):
mock_instance = mock_processor.return_value
mock_instance.process_marketplace_csv_from_url = AsyncMock(
side_effect=Exception("Import failed")
@@ -124,8 +124,9 @@ class TestBackgroundTasks:
@pytest.mark.asyncio
async def test_marketplace_import_job_not_found(self, db):
"""Test handling when import job doesn't exist"""
with patch("app.tasks.background_tasks.CSVProcessor") as mock_processor, patch(
"app.tasks.background_tasks.SessionLocal", return_value=db
with (
patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
patch("app.tasks.background_tasks.SessionLocal", return_value=db),
):
mock_instance = mock_processor.return_value
mock_instance.process_marketplace_csv_from_url = AsyncMock(
@@ -170,8 +171,9 @@ class TestBackgroundTasks:
job_id = job.id
# Mock CSV processor with some errors
with patch("app.tasks.background_tasks.CSVProcessor") as mock_processor, patch(
"app.tasks.background_tasks.SessionLocal", return_value=db
with (
patch("app.tasks.background_tasks.CSVProcessor") as mock_processor,
patch("app.tasks.background_tasks.SessionLocal", return_value=db),
):
mock_instance = mock_processor.return_value
mock_instance.process_marketplace_csv_from_url = AsyncMock(

View File

@@ -96,7 +96,7 @@ class TestIntegrationFlows:
# 4. Get vendor details
response = client.get(
f"/api/v1/vendor/{vendor ['vendor_code']}", headers=auth_headers
f"/api/v1/vendor/{vendor['vendor_code']}", headers=auth_headers
)
assert response.status_code == 200

View File

@@ -1,5 +1,6 @@
# tests/performance/conftest.py
"""Performance test specific fixtures."""
import pytest

View File

@@ -1,5 +1,5 @@
# tests/system/conftest.py
"""System test specific fixtures."""
import pytest
# Add any system-specific fixtures here if needed

View File

@@ -5,7 +5,7 @@ System tests for error handling across the LetzVendor API.
Tests the complete error handling flow from FastAPI through custom exception handlers
to ensure proper HTTP status codes, error structures, and client-friendly responses.
"""
import json
import pytest
@@ -377,9 +377,9 @@ class TestErrorHandling:
# All error responses should have these fields
required_fields = ["error_code", "message", "status_code"]
for field in required_fields:
assert (
field in data
), f"Missing {field} in error response for {endpoint}"
assert field in data, (
f"Missing {field} in error response for {endpoint}"
)
# Details field should be present (can be empty dict)
assert "details" in data

View File

@@ -1,5 +1,5 @@
# tests/unit/conftest.py
"""Unit test specific fixtures."""
import pytest
# Add any unit-specific fixtures here if needed

View File

@@ -12,17 +12,21 @@ Tests cover:
- Error handling and edge cases
"""
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, Mock, patch
from datetime import UTC, datetime, timedelta
from unittest.mock import Mock, patch
import pytest
from fastapi import HTTPException
from jose import jwt
from app.exceptions import (AdminRequiredException,
InsufficientPermissionsException,
InvalidCredentialsException, InvalidTokenException,
TokenExpiredException, UserNotActiveException)
from app.exceptions import (
AdminRequiredException,
InsufficientPermissionsException,
InvalidCredentialsException,
InvalidTokenException,
TokenExpiredException,
UserNotActiveException,
)
from middleware.auth import AuthManager
from models.database.user import User
@@ -316,7 +320,7 @@ class TestJWTTokenVerification:
# Create token without 'sub' field
payload = {
"username": "testuser",
"exp": datetime.now(timezone.utc) + timedelta(minutes=30),
"exp": datetime.now(UTC) + timedelta(minutes=30),
}
token = jwt.encode(
payload, auth_manager.secret_key, algorithm=auth_manager.algorithm
@@ -349,7 +353,7 @@ class TestJWTTokenVerification:
payload = {
"sub": "1",
"username": "testuser",
"exp": datetime.now(timezone.utc) + timedelta(minutes=30),
"exp": datetime.now(UTC) + timedelta(minutes=30),
}
# Create token with different algorithm
token = jwt.encode(payload, auth_manager.secret_key, algorithm="HS512")
@@ -362,7 +366,7 @@ class TestJWTTokenVerification:
auth_manager = AuthManager()
# Create a token with expiration in the past
past_time = datetime.now(timezone.utc) - timedelta(minutes=1)
past_time = datetime.now(UTC) - timedelta(minutes=1)
payload = {"sub": "1", "username": "testuser", "exp": past_time.timestamp()}
token = jwt.encode(
payload, auth_manager.secret_key, algorithm=auth_manager.algorithm
@@ -661,8 +665,8 @@ class TestEdgeCases:
payload = {
"sub": "1",
"username": "testuser",
"iat": datetime.now(timezone.utc) + timedelta(hours=1), # Future time
"exp": datetime.now(timezone.utc) + timedelta(hours=2),
"iat": datetime.now(UTC) + timedelta(hours=1), # Future time
"exp": datetime.now(UTC) + timedelta(hours=2),
}
token = jwt.encode(
payload, auth_manager.secret_key, algorithm=auth_manager.algorithm

View File

@@ -10,13 +10,17 @@ Tests cover:
- Edge cases and error handling
"""
from unittest.mock import AsyncMock, Mock, patch
from unittest.mock import AsyncMock, Mock
import pytest
from fastapi import Request
from middleware.context import (ContextManager, ContextMiddleware,
RequestContext, get_request_context)
from middleware.context import (
ContextManager,
ContextMiddleware,
RequestContext,
get_request_context,
)
@pytest.mark.unit

View File

@@ -12,7 +12,6 @@ Tests cover:
- Edge cases and isolation
"""
from unittest.mock import Mock
import pytest

View File

@@ -134,8 +134,9 @@ class TestLoggingMiddleware:
call_next = AsyncMock(side_effect=Exception("Test error"))
with patch("middleware.logging.logger") as mock_logger, pytest.raises(
Exception
with (
patch("middleware.logging.logger") as mock_logger,
pytest.raises(Exception),
):
await middleware.dispatch(request, call_next)

View File

@@ -12,8 +12,7 @@ Tests cover:
"""
from collections import deque
from datetime import datetime, timedelta, timezone
from unittest.mock import Mock, patch
from datetime import UTC, datetime, timedelta
import pytest
@@ -53,7 +52,7 @@ class TestRateLimiterBasic:
# Make 10 requests (at the limit)
for i in range(max_requests):
result = limiter.allow_request(client_id, max_requests, 3600)
assert result is True, f"Request {i+1} should be allowed"
assert result is True, f"Request {i + 1} should be allowed"
assert len(limiter.clients[client_id]) == max_requests
@@ -109,7 +108,7 @@ class TestRateLimiterSlidingWindow:
window_seconds = 10
# Manually add old requests
old_time = datetime.now(timezone.utc) - timedelta(seconds=15)
old_time = datetime.now(UTC) - timedelta(seconds=15)
limiter.clients[client_id].append(old_time)
limiter.clients[client_id].append(old_time)
@@ -127,7 +126,7 @@ class TestRateLimiterSlidingWindow:
window_seconds = 60
# Add recent requests
recent_time = datetime.now(timezone.utc) - timedelta(seconds=30)
recent_time = datetime.now(UTC) - timedelta(seconds=30)
limiter.clients[client_id].append(recent_time)
limiter.clients[client_id].append(recent_time)
@@ -147,12 +146,12 @@ class TestRateLimiterSlidingWindow:
window_seconds = 30
# Add old requests (outside window)
old_time = datetime.now(timezone.utc) - timedelta(seconds=60)
old_time = datetime.now(UTC) - timedelta(seconds=60)
limiter.clients[client_id].append(old_time)
limiter.clients[client_id].append(old_time)
# Add recent request (within window)
recent_time = datetime.now(timezone.utc) - timedelta(seconds=10)
recent_time = datetime.now(UTC) - timedelta(seconds=10)
limiter.clients[client_id].append(recent_time)
# Old requests removed, only 1 recent request, so 2 more allowed
@@ -170,7 +169,7 @@ class TestRateLimiterSlidingWindow:
window_seconds = 1 # 1 second window
# Add old request
old_time = datetime.now(timezone.utc) - timedelta(seconds=2)
old_time = datetime.now(UTC) - timedelta(seconds=2)
limiter.clients[client_id].append(old_time)
# Should allow request because old one is outside 1-second window
@@ -188,12 +187,12 @@ class TestRateLimiterCleanup:
limiter = RateLimiter()
# Add clients with old requests
old_time = datetime.now(timezone.utc) - timedelta(hours=25)
old_time = datetime.now(UTC) - timedelta(hours=25)
limiter.clients["old_client_1"].append(old_time)
limiter.clients["old_client_2"].append(old_time)
# Add client with recent requests
recent_time = datetime.now(timezone.utc) - timedelta(hours=1)
recent_time = datetime.now(UTC) - timedelta(hours=1)
limiter.clients["recent_client"].append(recent_time)
# Run cleanup
@@ -215,7 +214,7 @@ class TestRateLimiterCleanup:
limiter.clients["empty_client_2"] = deque()
# Add client with requests
limiter.clients["active_client"].append(datetime.now(timezone.utc))
limiter.clients["active_client"].append(datetime.now(UTC))
# Run cleanup
limiter._cleanup_old_entries()
@@ -233,12 +232,12 @@ class TestRateLimiterCleanup:
client_id = "mixed_client"
# Add old requests
old_time = datetime.now(timezone.utc) - timedelta(hours=30)
old_time = datetime.now(UTC) - timedelta(hours=30)
limiter.clients[client_id].append(old_time)
limiter.clients[client_id].append(old_time)
# Add recent requests
recent_time = datetime.now(timezone.utc) - timedelta(hours=1)
recent_time = datetime.now(UTC) - timedelta(hours=1)
limiter.clients[client_id].append(recent_time)
limiter.clients[client_id].append(recent_time)
@@ -255,10 +254,10 @@ class TestRateLimiterCleanup:
limiter.cleanup_interval = 0 # Force immediate cleanup
# Set last_cleanup to past
limiter.last_cleanup = datetime.now(timezone.utc) - timedelta(hours=2)
limiter.last_cleanup = datetime.now(UTC) - timedelta(hours=2)
# Add old client
old_time = datetime.now(timezone.utc) - timedelta(hours=25)
old_time = datetime.now(UTC) - timedelta(hours=25)
limiter.clients["old_client"].append(old_time)
# Make request (should trigger cleanup)
@@ -272,7 +271,7 @@ class TestRateLimiterCleanup:
limiter = RateLimiter()
# Add multiple active clients
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
for i in range(5):
limiter.clients[f"client_{i}"].append(now - timedelta(hours=i))
@@ -305,7 +304,7 @@ class TestRateLimiterStatistics:
client_id = "active_client"
# Add requests at different times
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
limiter.clients[client_id].append(now - timedelta(minutes=30)) # Within hour
limiter.clients[client_id].append(now - timedelta(hours=2)) # Within day
limiter.clients[client_id].append(now - timedelta(hours=12)) # Within day
@@ -322,7 +321,7 @@ class TestRateLimiterStatistics:
client_id = "old_requests_client"
# Add very old requests
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
limiter.clients[client_id].append(now - timedelta(days=2))
limiter.clients[client_id].append(now - timedelta(days=3))
@@ -347,7 +346,7 @@ class TestRateLimiterStatistics:
limiter = RateLimiter()
client_id = "boundary_client"
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
# Exactly 1 hour ago (should be included)
limiter.clients[client_id].append(now - timedelta(hours=1, seconds=1))
@@ -485,7 +484,7 @@ class TestRateLimiterEdgeCases:
limiter = RateLimiter()
# Set last_cleanup to past to ensure cleanup triggers
old_cleanup_time = datetime.now(timezone.utc) - timedelta(hours=2)
old_cleanup_time = datetime.now(UTC) - timedelta(hours=2)
limiter.last_cleanup = old_cleanup_time
limiter.cleanup_interval = 0 # Force cleanup on next request
@@ -523,7 +522,7 @@ class TestRateLimiterMemoryManagement:
client_id = "efficiency_test"
# Add many old requests
old_time = datetime.now(timezone.utc) - timedelta(hours=2)
old_time = datetime.now(UTC) - timedelta(hours=2)
for _ in range(1000):
limiter.clients[client_id].append(old_time)

View File

@@ -16,9 +16,11 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from fastapi import Request
from middleware.theme_context import (ThemeContextManager,
ThemeContextMiddleware,
get_current_theme)
from middleware.theme_context import (
ThemeContextManager,
ThemeContextMiddleware,
get_current_theme,
)
@pytest.mark.unit
@@ -144,12 +146,12 @@ class TestThemeContextMiddleware:
mock_db = MagicMock()
mock_theme = {"theme_name": "test_theme"}
with patch(
"middleware.theme_context.get_db", return_value=iter([mock_db])
), patch.object(
ThemeContextManager, "get_vendor_theme", return_value=mock_theme
with (
patch("middleware.theme_context.get_db", return_value=iter([mock_db])),
patch.object(
ThemeContextManager, "get_vendor_theme", return_value=mock_theme
),
):
await middleware.dispatch(request, call_next)
assert request.state.theme == mock_theme
@@ -184,12 +186,14 @@ class TestThemeContextMiddleware:
mock_db = MagicMock()
with patch(
"middleware.theme_context.get_db", return_value=iter([mock_db])
), patch.object(
ThemeContextManager, "get_vendor_theme", side_effect=Exception("DB Error")
with (
patch("middleware.theme_context.get_db", return_value=iter([mock_db])),
patch.object(
ThemeContextManager,
"get_vendor_theme",
side_effect=Exception("DB Error"),
),
):
await middleware.dispatch(request, call_next)
# Should fallback to default theme

View File

@@ -17,10 +17,12 @@ import pytest
from fastapi import HTTPException, Request
from sqlalchemy.orm import Session
from middleware.vendor_context import (VendorContextManager,
VendorContextMiddleware,
get_current_vendor,
require_vendor_context)
from middleware.vendor_context import (
VendorContextManager,
VendorContextMiddleware,
get_current_vendor,
require_vendor_context,
)
@pytest.mark.unit
@@ -197,9 +199,7 @@ class TestVendorContextManager:
mock_vendor.is_active = True
mock_vendor_domain.vendor = mock_vendor
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = (
mock_vendor_domain
)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor_domain
context = {"detection_method": "custom_domain", "domain": "customdomain1.com"}
@@ -216,9 +216,7 @@ class TestVendorContextManager:
mock_vendor.is_active = False
mock_vendor_domain.vendor = mock_vendor
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = (
mock_vendor_domain
)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor_domain
context = {"detection_method": "custom_domain", "domain": "customdomain1.com"}
@@ -229,9 +227,7 @@ class TestVendorContextManager:
def test_get_vendor_from_custom_domain_not_found(self):
"""Test custom domain not found in database."""
mock_db = Mock(spec=Session)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = (
None
)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = None
context = {"detection_method": "custom_domain", "domain": "nonexistent.com"}
@@ -245,9 +241,7 @@ class TestVendorContextManager:
mock_vendor = Mock()
mock_vendor.is_active = True
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = (
mock_vendor
)
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor
context = {"detection_method": "subdomain", "subdomain": "vendor1"}
@@ -261,9 +255,7 @@ class TestVendorContextManager:
mock_vendor = Mock()
mock_vendor.is_active = True
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = (
mock_vendor
)
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor
context = {"detection_method": "path", "subdomain": "vendor1"}
@@ -285,9 +277,7 @@ class TestVendorContextManager:
mock_vendor = Mock()
mock_vendor.is_active = True
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = (
mock_vendor
)
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor
context = {"detection_method": "subdomain", "subdomain": "VENDOR1"} # Uppercase
@@ -533,16 +523,24 @@ class TestVendorContextMiddleware:
mock_db = MagicMock()
with patch.object(
VendorContextManager, "detect_vendor_context", return_value=vendor_context
), patch.object(
VendorContextManager, "get_vendor_from_context", return_value=mock_vendor
), patch.object(
VendorContextManager, "extract_clean_path", return_value="/shop/products"
), patch(
"middleware.vendor_context.get_db", return_value=iter([mock_db])
with (
patch.object(
VendorContextManager,
"detect_vendor_context",
return_value=vendor_context,
),
patch.object(
VendorContextManager,
"get_vendor_from_context",
return_value=mock_vendor,
),
patch.object(
VendorContextManager,
"extract_clean_path",
return_value="/shop/products",
),
patch("middleware.vendor_context.get_db", return_value=iter([mock_db])),
):
await middleware.dispatch(request, call_next)
assert request.state.vendor is mock_vendor
@@ -566,14 +564,17 @@ class TestVendorContextMiddleware:
mock_db = MagicMock()
with patch.object(
VendorContextManager, "detect_vendor_context", return_value=vendor_context
), patch.object(
VendorContextManager, "get_vendor_from_context", return_value=None
), patch(
"middleware.vendor_context.get_db", return_value=iter([mock_db])
with (
patch.object(
VendorContextManager,
"detect_vendor_context",
return_value=vendor_context,
),
patch.object(
VendorContextManager, "get_vendor_from_context", return_value=None
),
patch("middleware.vendor_context.get_db", return_value=iter([mock_db])),
):
await middleware.dispatch(request, call_next)
assert request.state.vendor is None

View File

@@ -1,5 +1,4 @@
# tests/unit/models/test_database_models.py
from datetime import datetime, timezone
import pytest
from sqlalchemy.exc import IntegrityError

View File

@@ -1,14 +1,15 @@
# tests/unit/services/test_admin_service.py
import pytest
from app.exceptions import (AdminOperationException, CannotModifySelfException,
UserNotFoundException, UserStatusChangeException,
VendorNotFoundException,
VendorVerificationException)
from app.exceptions import (
AdminOperationException,
CannotModifySelfException,
UserNotFoundException,
UserStatusChangeException,
VendorNotFoundException,
)
from app.services.admin_service import AdminService
from app.services.stats_service import stats_service
from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.vendor import Vendor
@pytest.mark.unit

View File

@@ -1,9 +1,11 @@
# tests/test_auth_service.py
import pytest
from app.exceptions.auth import (InvalidCredentialsException,
UserAlreadyExistsException,
UserNotActiveException)
from app.exceptions.auth import (
InvalidCredentialsException,
UserAlreadyExistsException,
UserNotActiveException,
)
from app.exceptions.base import ValidationException
from app.services.auth_service import AuthService
from models.schema.auth import UserLogin, UserRegister

View File

@@ -3,17 +3,16 @@ import uuid
import pytest
from app.exceptions import (InsufficientInventoryException,
InvalidInventoryOperationException,
InvalidQuantityException,
InventoryNotFoundException,
InventoryValidationException,
NegativeInventoryException, ValidationException)
from app.exceptions import (
InsufficientInventoryException,
InvalidQuantityException,
InventoryNotFoundException,
InventoryValidationException,
)
from app.services.inventory_service import InventoryService
from models.database.inventory import Inventory
from models.database.marketplace_product import MarketplaceProduct
from models.schema.inventory import (InventoryAdd, InventoryCreate,
InventoryUpdate)
from models.schema.inventory import InventoryAdd, InventoryCreate, InventoryUpdate
@pytest.mark.unit

View File

@@ -1,20 +1,21 @@
# tests/test_marketplace_service.py
import uuid
from datetime import datetime
import pytest
from app.exceptions.base import ValidationException
from app.exceptions.marketplace_import_job import (
ImportJobCannotBeCancelledException, ImportJobCannotBeDeletedException,
ImportJobNotFoundException, ImportJobNotOwnedException)
from app.exceptions.vendor import (UnauthorizedVendorAccessException,
VendorNotFoundException)
from app.services.marketplace_import_job_service import \
MarketplaceImportJobService
ImportJobCannotBeCancelledException,
ImportJobCannotBeDeletedException,
ImportJobNotFoundException,
ImportJobNotOwnedException,
)
from app.exceptions.vendor import (
UnauthorizedVendorAccessException,
VendorNotFoundException,
)
from app.services.marketplace_import_job_service import MarketplaceImportJobService
from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.user import User
from models.database.vendor import Vendor
from models.schema.marketplace_import_job import MarketplaceImportJobRequest

View File

@@ -1,15 +1,17 @@
# tests/test_product_service.py
import pytest
from app.exceptions import (InvalidMarketplaceProductDataException,
MarketplaceProductAlreadyExistsException,
MarketplaceProductNotFoundException,
MarketplaceProductValidationException,
ValidationException)
from app.exceptions import (
InvalidMarketplaceProductDataException,
MarketplaceProductAlreadyExistsException,
MarketplaceProductNotFoundException,
MarketplaceProductValidationException,
)
from app.services.marketplace_product_service import MarketplaceProductService
from models.database.marketplace_product import MarketplaceProduct
from models.schema.marketplace_product import (MarketplaceProductCreate,
MarketplaceProductUpdate)
from models.schema.marketplace_product import (
MarketplaceProductCreate,
MarketplaceProductUpdate,
)
@pytest.mark.unit

View File

@@ -1,13 +1,16 @@
# tests/test_vendor_service.py (updated to use custom exceptions)
import pytest
from app.exceptions import (InvalidVendorDataException,
MarketplaceProductNotFoundException,
MaxVendorsReachedException,
ProductAlreadyExistsException,
UnauthorizedVendorAccessException,
ValidationException, VendorAlreadyExistsException,
VendorNotFoundException)
from app.exceptions import (
InvalidVendorDataException,
MarketplaceProductNotFoundException,
MaxVendorsReachedException,
ProductAlreadyExistsException,
UnauthorizedVendorAccessException,
ValidationException,
VendorAlreadyExistsException,
VendorNotFoundException,
)
from app.services.vendor_service import VendorService
from models.schema.product import ProductCreate
from models.schema.vendor import VendorCreate

View File

@@ -105,9 +105,10 @@ TEST002,Test MarketplaceProduct 2,15.99,TestMarket"""
@pytest.mark.asyncio
async def test_process_marketplace_csv_from_url(self, db):
"""Test complete marketplace CSV processing"""
with patch.object(
self.processor, "download_csv"
) as mock_download, patch.object(self.processor, "parse_csv") as mock_parse:
with (
patch.object(self.processor, "download_csv") as mock_download,
patch.object(self.processor, "parse_csv") as mock_parse,
):
# Mock successful download and parsing
mock_download.return_value = "csv_content"
mock_df = pd.DataFrame(

View File

@@ -45,4 +45,4 @@ class TestDataValidation:
"""Test input sanitization"""
# These tests would verify that inputs are properly sanitized
# to prevent SQL injection, XSS, etc.
pass # Implementation would depend on your sanitization logic
# Implementation would depend on your sanitization logic