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:
@@ -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:"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# tests/integration/api/v1/test_marketplace_import_job_endpoints.py
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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})"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# tests/integration/conftest.py
|
||||
"""Integration test specific fixtures."""
|
||||
import pytest
|
||||
|
||||
|
||||
# Add any integration-specific fixtures here if needed
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""
|
||||
Fixtures specific to middleware integration tests.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# tests/performance/conftest.py
|
||||
"""Performance test specific fixtures."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# tests/system/conftest.py
|
||||
"""System test specific fixtures."""
|
||||
import pytest
|
||||
|
||||
|
||||
# Add any system-specific fixtures here if needed
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# tests/unit/conftest.py
|
||||
"""Unit test specific fixtures."""
|
||||
import pytest
|
||||
|
||||
|
||||
# Add any unit-specific fixtures here if needed
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,6 @@ Tests cover:
|
||||
- Edge cases and isolation
|
||||
"""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# tests/unit/models/test_database_models.py
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user