fix: correct tojson|safe usage in templates and update validator

- Remove |safe from |tojson in HTML attributes (x-data) - quotes must
  become " for browsers to parse correctly
- Update LANG-002 and LANG-003 architecture rules to document correct
  |tojson usage patterns:
  - HTML attributes: |tojson (no |safe)
  - Script blocks: |tojson|safe
- Fix validator to warn when |tojson|safe is used in x-data (breaks
  HTML attribute parsing)
- Improve code quality across services, APIs, and tests

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-13 22:59:51 +01:00
parent 94d268f330
commit 9920430b9e
123 changed files with 1408 additions and 840 deletions

View File

@@ -790,10 +790,11 @@ class TestVendorContextMiddleware:
call_next = AsyncMock(return_value=Mock())
with patch.object(
VendorContextManager, "is_admin_request", return_value=False
), patch.object(
VendorContextManager, "is_static_file_request", return_value=False
with (
patch.object(VendorContextManager, "is_admin_request", return_value=False),
patch.object(
VendorContextManager, "is_static_file_request", return_value=False
),
):
await middleware.dispatch(request, call_next)
@@ -836,9 +837,7 @@ class TestVendorContextMiddleware:
mock_db = MagicMock()
with (
patch.object(
VendorContextManager, "is_admin_request", return_value=False
),
patch.object(VendorContextManager, "is_admin_request", return_value=False),
patch.object(
VendorContextManager, "is_static_file_request", return_value=False
),
@@ -889,9 +888,7 @@ class TestVendorContextMiddleware:
mock_db = MagicMock()
with (
patch.object(
VendorContextManager, "is_admin_request", return_value=False
),
patch.object(VendorContextManager, "is_admin_request", return_value=False),
patch.object(
VendorContextManager, "is_static_file_request", return_value=False
),
@@ -928,9 +925,7 @@ class TestVendorContextMiddleware:
call_next = AsyncMock(return_value=Mock())
with (
patch.object(
VendorContextManager, "is_admin_request", return_value=False
),
patch.object(VendorContextManager, "is_admin_request", return_value=False),
patch.object(
VendorContextManager, "is_static_file_request", return_value=False
),

View File

@@ -1,5 +1,6 @@
# tests/unit/models/database/test_customer.py
"""Unit tests for Customer and CustomerAddress database models."""
import pytest
from models.database.customer import Customer, CustomerAddress

View File

@@ -1,5 +1,6 @@
# tests/unit/models/database/test_inventory.py
"""Unit tests for Inventory database model."""
import pytest
from sqlalchemy.exc import IntegrityError
@@ -56,7 +57,9 @@ class TestInventoryModel:
db.add(inventory2)
db.commit()
def test_inventory_same_product_different_location(self, db, test_vendor, test_product):
def test_inventory_same_product_different_location(
self, db, test_vendor, test_product
):
"""Test same product can have inventory in different locations."""
inventory1 = Inventory(
product_id=test_product.id,

View File

@@ -1,5 +1,6 @@
# tests/unit/models/database/test_marketplace_import_job.py
"""Unit tests for MarketplaceImportJob database model."""
import pytest
from models.database.marketplace_import_job import MarketplaceImportJob
@@ -58,7 +59,13 @@ class TestMarketplaceImportJobModel:
def test_import_job_status_values(self, db, test_user, test_vendor):
"""Test MarketplaceImportJob with different status values."""
statuses = ["pending", "processing", "completed", "failed", "completed_with_errors"]
statuses = [
"pending",
"processing",
"completed",
"failed",
"completed_with_errors",
]
for i, status in enumerate(statuses):
import_job = MarketplaceImportJob(

View File

@@ -1,10 +1,13 @@
# tests/unit/models/database/test_marketplace_product.py
"""Unit tests for MarketplaceProduct database model."""
import pytest
from sqlalchemy.exc import IntegrityError
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import MarketplaceProductTranslation
from models.database.marketplace_product_translation import (
MarketplaceProductTranslation,
)
def _create_with_translation(db, marketplace_product_id, title, **kwargs):

View File

@@ -1,5 +1,6 @@
# tests/unit/models/database/test_order.py
"""Unit tests for Order and OrderItem database models."""
import pytest
from sqlalchemy.exc import IntegrityError
@@ -74,7 +75,14 @@ class TestOrderModel:
self, db, test_vendor, test_customer, test_customer_address
):
"""Test Order with different status values."""
statuses = ["pending", "confirmed", "processing", "shipped", "delivered", "cancelled"]
statuses = [
"pending",
"confirmed",
"processing",
"shipped",
"delivered",
"cancelled",
]
for i, status in enumerate(statuses):
order = Order(
@@ -93,9 +101,7 @@ class TestOrderModel:
assert order.status == status
def test_order_amounts(
self, db, test_vendor, test_customer, test_customer_address
):
def test_order_amounts(self, db, test_vendor, test_customer, test_customer_address):
"""Test Order amount fields."""
order = Order(
vendor_id=test_vendor.id,

View File

@@ -1,5 +1,6 @@
# tests/unit/models/database/test_product.py
"""Unit tests for Product (vendor catalog) database model."""
import pytest
from sqlalchemy.exc import IntegrityError
@@ -35,10 +36,9 @@ class TestProductModel:
assert product.is_featured is True
assert product.vendor.vendor_code == test_vendor.vendor_code
# Use get_title() method instead of .title attribute
assert (
product.marketplace_product.get_title("en")
== test_marketplace_product.get_title("en")
)
assert product.marketplace_product.get_title(
"en"
) == test_marketplace_product.get_title("en")
def test_product_unique_per_vendor(self, db, test_vendor, test_marketplace_product):
"""Test that same marketplace product can't be added twice to vendor catalog."""
@@ -75,7 +75,9 @@ class TestProductModel:
assert product.min_quantity == 1 # Default
assert product.display_order == 0 # Default
def test_product_vendor_override_fields(self, db, test_vendor, test_marketplace_product):
def test_product_vendor_override_fields(
self, db, test_vendor, test_marketplace_product
):
"""Test Product model vendor-specific override fields."""
product = Product(
vendor_id=test_vendor.id,
@@ -97,7 +99,9 @@ class TestProductModel:
assert product.currency == "USD"
assert product.availability == "limited"
def test_product_inventory_settings(self, db, test_vendor, test_marketplace_product):
def test_product_inventory_settings(
self, db, test_vendor, test_marketplace_product
):
"""Test Product model inventory settings."""
product = Product(
vendor_id=test_vendor.id,
@@ -126,7 +130,9 @@ class TestProductModel:
assert product.marketplace_product is not None
assert product.inventory_entries == [] # No inventory yet
def test_product_effective_properties(self, db, test_vendor, test_marketplace_product):
def test_product_effective_properties(
self, db, test_vendor, test_marketplace_product
):
"""Test Product effective properties with override pattern."""
# First, set some values on the marketplace product
test_marketplace_product.price_numeric = 100.00

View File

@@ -1,5 +1,6 @@
# tests/unit/models/database/test_team.py
"""Unit tests for VendorUser and Role database models."""
import pytest
from models.database.vendor import Role, Vendor, VendorUser
@@ -87,7 +88,9 @@ class TestVendorUserModel:
assert vendor_user.role.name == "Manager"
assert "products.create" in vendor_user.role.permissions
def test_vendor_user_multiple_vendors(self, db, test_vendor, test_user, other_company):
def test_vendor_user_multiple_vendors(
self, db, test_vendor, test_user, other_company
):
"""Test same user can be added to multiple vendors."""
# Create another vendor
other_vendor = Vendor(

View File

@@ -1,5 +1,6 @@
# tests/unit/models/database/test_user.py
"""Unit tests for User database model."""
import pytest
from sqlalchemy.exc import IntegrityError

View File

@@ -1,5 +1,6 @@
# tests/unit/models/database/test_vendor.py
"""Unit tests for Vendor database model."""
import pytest
from sqlalchemy.exc import IntegrityError

View File

@@ -1,5 +1,6 @@
# tests/unit/models/schema/test_auth.py
"""Unit tests for auth Pydantic schemas."""
import pytest
from pydantic import ValidationError

View File

@@ -1,16 +1,17 @@
# tests/unit/models/schema/test_customer.py
"""Unit tests for customer Pydantic schemas."""
import pytest
from pydantic import ValidationError
from models.schema.customer import (
CustomerRegister,
CustomerUpdate,
CustomerResponse,
CustomerAddressCreate,
CustomerAddressUpdate,
CustomerAddressResponse,
CustomerAddressUpdate,
CustomerPreferencesUpdate,
CustomerRegister,
CustomerResponse,
CustomerUpdate,
)

View File

@@ -1,16 +1,16 @@
# tests/unit/models/schema/test_inventory.py
"""Unit tests for inventory Pydantic schemas."""
import pytest
from pydantic import ValidationError
from models.schema.inventory import (
InventoryBase,
InventoryCreate,
InventoryAdjust,
InventoryUpdate,
InventoryCreate,
InventoryLocationResponse,
InventoryReserve,
InventoryResponse,
InventoryLocationResponse,
InventoryUpdate,
ProductInventorySummary,
)

View File

@@ -1,12 +1,13 @@
# tests/unit/models/schema/test_marketplace_import_job.py
"""Unit tests for marketplace import job Pydantic schemas."""
import pytest
from pydantic import ValidationError
from models.schema.marketplace_import_job import (
MarketplaceImportJobListResponse,
MarketplaceImportJobRequest,
MarketplaceImportJobResponse,
MarketplaceImportJobListResponse,
MarketplaceImportJobStatusUpdate,
)

View File

@@ -1,18 +1,17 @@
# tests/unit/models/schema/test_order.py
"""Unit tests for order Pydantic schemas."""
import pytest
from pydantic import ValidationError
from models.schema.order import (
OrderAddressCreate,
OrderCreate,
OrderItemCreate,
OrderItemResponse,
OrderAddressCreate,
OrderAddressResponse,
OrderCreate,
OrderUpdate,
OrderResponse,
OrderDetailResponse,
OrderListResponse,
OrderResponse,
OrderUpdate,
)
@@ -253,7 +252,14 @@ class TestOrderUpdateSchema:
def test_valid_status_values(self):
"""Test all valid status values."""
valid_statuses = ["pending", "processing", "shipped", "delivered", "cancelled", "refunded"]
valid_statuses = [
"pending",
"processing",
"shipped",
"delivered",
"cancelled",
"refunded",
]
for status in valid_statuses:
update = OrderUpdate(status=status)
assert update.status == status

View File

@@ -1,14 +1,14 @@
# tests/unit/models/schema/test_product.py
"""Unit tests for product Pydantic schemas."""
import pytest
from pydantic import ValidationError
from models.schema.product import (
ProductCreate,
ProductUpdate,
ProductResponse,
ProductDetailResponse,
ProductListResponse,
ProductResponse,
ProductUpdate,
)

View File

@@ -1,15 +1,16 @@
# tests/unit/models/schema/test_vendor.py
"""Unit tests for vendor Pydantic schemas."""
import pytest
from pydantic import ValidationError
from models.schema.vendor import (
VendorCreate,
VendorUpdate,
VendorResponse,
VendorDetailResponse,
VendorListResponse,
VendorResponse,
VendorSummary,
VendorUpdate,
)

View File

@@ -122,7 +122,9 @@ class TestAdminService:
from models.database.vendor import Vendor
# Re-query vendor to get fresh instance
vendor_to_unverify = db.query(Vendor).filter(Vendor.id == test_vendor.id).first()
vendor_to_unverify = (
db.query(Vendor).filter(Vendor.id == test_vendor.id).first()
)
vendor_to_unverify.is_verified = False
db.commit()
@@ -186,7 +188,10 @@ class TestAdminService:
None,
)
assert test_job is not None
assert test_job.marketplace.lower() == test_marketplace_import_job.marketplace.lower()
assert (
test_job.marketplace.lower()
== test_marketplace_import_job.marketplace.lower()
)
assert test_job.status == test_marketplace_import_job.status
def test_get_marketplace_import_jobs_with_marketplace_filter(

View File

@@ -1,5 +1,6 @@
# tests/unit/services/test_auth_service.py
"""Unit tests for AuthService - login and password hashing."""
import pytest
from app.exceptions.auth import (

View File

@@ -1,5 +1,6 @@
# tests/unit/services/test_inventory_service.py
"""Unit tests for InventoryService."""
import uuid
import pytest
@@ -352,23 +353,21 @@ class TestInventoryService:
self, db, test_inventory, test_product, test_vendor
):
"""Test getting product inventory summary."""
result = self.service.get_product_inventory(
db, test_vendor.id, test_product.id
)
result = self.service.get_product_inventory(db, test_vendor.id, test_product.id)
assert result.product_id == test_product.id
assert result.vendor_id == test_vendor.id
assert result.total_quantity >= test_inventory.quantity
assert len(result.locations) >= 1
def test_get_product_inventory_no_inventory(
self, db, test_product, test_vendor
):
def test_get_product_inventory_no_inventory(self, db, test_product, test_vendor):
"""Test getting inventory for product with no inventory entries."""
# Create a new product without inventory
from models.database.product import Product
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import MarketplaceProductTranslation
from models.database.marketplace_product_translation import (
MarketplaceProductTranslation,
)
from models.database.product import Product
unique_id = str(uuid.uuid4())[:8]
mp = MarketplaceProduct(
@@ -412,9 +411,7 @@ class TestInventoryService:
# ==================== Get Vendor Inventory Tests ====================
def test_get_vendor_inventory_success(
self, db, test_inventory, test_vendor
):
def test_get_vendor_inventory_success(self, db, test_inventory, test_vendor):
"""Test getting all vendor inventory."""
result = self.service.get_vendor_inventory(db, test_vendor.id)
@@ -433,9 +430,7 @@ class TestInventoryService:
for inv in result:
assert test_inventory.location[:10].upper() in inv.location.upper()
def test_get_vendor_inventory_with_low_stock_filter(
self, db, test_vendor
):
def test_get_vendor_inventory_with_low_stock_filter(self, db, test_vendor):
"""Test getting vendor inventory filtered by low stock threshold."""
result = self.service.get_vendor_inventory(
db, test_vendor.id, low_stock_threshold=5
@@ -446,17 +441,13 @@ class TestInventoryService:
def test_get_vendor_inventory_pagination(self, db, test_vendor):
"""Test vendor inventory pagination."""
result = self.service.get_vendor_inventory(
db, test_vendor.id, skip=0, limit=10
)
result = self.service.get_vendor_inventory(db, test_vendor.id, skip=0, limit=10)
assert len(result) <= 10
# ==================== Update Inventory Tests ====================
def test_update_inventory_quantity(
self, db, test_inventory, test_vendor
):
def test_update_inventory_quantity(self, db, test_inventory, test_vendor):
"""Test updating inventory quantity."""
inventory_update = InventoryUpdate(quantity=500)
@@ -466,9 +457,7 @@ class TestInventoryService:
assert result.quantity == 500
def test_update_inventory_reserved_quantity(
self, db, test_inventory, test_vendor
):
def test_update_inventory_reserved_quantity(self, db, test_inventory, test_vendor):
"""Test updating inventory reserved quantity."""
inventory_update = InventoryUpdate(reserved_quantity=20)
@@ -478,9 +467,7 @@ class TestInventoryService:
assert result.reserved_quantity == 20
def test_update_inventory_location(
self, db, test_inventory, test_vendor
):
def test_update_inventory_location(self, db, test_inventory, test_vendor):
"""Test updating inventory location."""
unique_id = str(uuid.uuid4())[:8].upper()
new_location = f"NEW_LOCATION_{unique_id}"
@@ -499,9 +486,7 @@ class TestInventoryService:
with pytest.raises(InventoryNotFoundException):
self.service.update_inventory(db, test_vendor.id, 99999, inventory_update)
def test_update_inventory_wrong_vendor(
self, db, test_inventory, other_company
):
def test_update_inventory_wrong_vendor(self, db, test_inventory, other_company):
"""Test updating inventory from wrong vendor raises InventoryNotFoundException."""
from models.database.vendor import Vendor
@@ -525,9 +510,7 @@ class TestInventoryService:
# ==================== Delete Inventory Tests ====================
def test_delete_inventory_success(
self, db, test_inventory, test_vendor
):
def test_delete_inventory_success(self, db, test_inventory, test_vendor):
"""Test deleting inventory entry."""
inventory_id = test_inventory.id
@@ -544,9 +527,7 @@ class TestInventoryService:
with pytest.raises(InventoryNotFoundException):
self.service.delete_inventory(db, test_vendor.id, 99999)
def test_delete_inventory_wrong_vendor(
self, db, test_inventory, other_company
):
def test_delete_inventory_wrong_vendor(self, db, test_inventory, other_company):
"""Test deleting inventory from wrong vendor raises InventoryNotFoundException."""
from models.database.vendor import Vendor
@@ -563,4 +544,3 @@ class TestInventoryService:
with pytest.raises(InventoryNotFoundException):
self.service.delete_inventory(db, other_vendor.id, test_inventory.id)

View File

@@ -8,26 +8,22 @@ Tests cover:
- GraphQL client (mocked)
"""
import pytest
from unittest.mock import MagicMock, patch
import pytest
from app.services.letzshop import (
LetzshopClient,
LetzshopClientError,
LetzshopAuthError,
LetzshopAPIError,
LetzshopCredentialsService,
CredentialsNotFoundError,
LetzshopAPIError,
LetzshopClient,
LetzshopCredentialsService,
)
from app.utils.encryption import (
EncryptionService,
EncryptionError,
encrypt_value,
decrypt_value,
EncryptionService,
mask_api_key,
)
# ============================================================================
# Encryption Tests
# ============================================================================
@@ -339,9 +335,7 @@ class TestLetzshopClient:
"""Test successful connection test."""
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"data": {"__typename": "Query"}
}
mock_response.json.return_value = {"data": {"__typename": "Query"}}
mock_post.return_value = mock_response
client = LetzshopClient(api_key="test-key")
@@ -398,7 +392,7 @@ class TestLetzshopClient:
"inventoryUnits": [
{"id": "unit_1", "state": "confirmed"},
],
"errors": []
"errors": [],
}
}
}
@@ -420,12 +414,9 @@ class TestLetzshopClient:
"setShipmentTracking": {
"shipment": {
"id": "ship_1",
"tracking": {
"code": "1Z999AA1",
"provider": "ups"
}
"tracking": {"code": "1Z999AA1", "provider": "ups"},
},
"errors": []
"errors": [],
}
}
}
@@ -446,9 +437,7 @@ class TestLetzshopClient:
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"errors": [
{"message": "Invalid shipment ID"}
]
"errors": [{"message": "Invalid shipment ID"}]
}
mock_post.return_value = mock_response

View File

@@ -1,9 +1,9 @@
# tests/unit/services/test_marketplace_service.py
"""Unit tests for MarketplaceImportJobService."""
import uuid
import pytest
from unittest.mock import patch
from app.exceptions.base import ValidationException
from app.exceptions.marketplace_import_job import (
@@ -52,7 +52,9 @@ class TestMarketplaceImportJobService:
assert result.marketplace == "Letzshop" # Default
def test_create_import_job_database_error(self, db, test_vendor, test_user, monkeypatch):
def test_create_import_job_database_error(
self, db, test_vendor, test_user, monkeypatch
):
"""Test import job creation handles database errors."""
request = MarketplaceImportJobRequest(
source_url="https://example.com/products.csv",
@@ -119,6 +121,7 @@ class TestMarketplaceImportJobService:
def test_get_import_job_by_id_database_error(self, db, test_user, monkeypatch):
"""Test get import job handles database errors."""
def mock_query(*args):
raise Exception("Database query failed")
@@ -200,7 +203,10 @@ class TestMarketplaceImportJobService:
):
"""Test getting import jobs with marketplace filter."""
jobs = self.service.get_import_jobs(
db, test_vendor, test_user, marketplace=test_marketplace_import_job.marketplace
db,
test_vendor,
test_user,
marketplace=test_marketplace_import_job.marketplace,
)
assert len(jobs) >= 1
@@ -254,8 +260,11 @@ class TestMarketplaceImportJobService:
assert len(jobs) == 0
def test_get_import_jobs_database_error(self, db, test_vendor, test_user, monkeypatch):
def test_get_import_jobs_database_error(
self, db, test_vendor, test_user, monkeypatch
):
"""Test get import jobs handles database errors."""
def mock_query(*args):
raise Exception("Database query failed")
@@ -270,7 +279,9 @@ class TestMarketplaceImportJobService:
# ==================== convert_to_response_model Tests ====================
def test_convert_to_response_model(self, db, test_marketplace_import_job, test_vendor):
def test_convert_to_response_model(
self, db, test_marketplace_import_job, test_vendor
):
"""Test converting database model to response model."""
from models.database.marketplace_import_job import MarketplaceImportJob as MIJ

View File

@@ -108,8 +108,7 @@ class TestProductService:
assert existing_product_id in str(exc_info.value)
assert exc_info.value.status_code == 409
assert (
exc_info.value.details.get("marketplace_product_id")
== existing_product_id
exc_info.value.details.get("marketplace_product_id") == existing_product_id
)
def test_create_product_invalid_price(self, db):
@@ -224,7 +223,9 @@ class TestProductService:
assert "Invalid GTIN format" in str(exc_info.value)
assert exc_info.value.details.get("field") == "gtin"
def test_update_product_empty_title_preserves_existing(self, db, test_marketplace_product):
def test_update_product_empty_title_preserves_existing(
self, db, test_marketplace_product
):
"""Test updating product with empty title preserves existing title in translation"""
original_title = test_marketplace_product.get_title()
update_data = MarketplaceProductUpdate(title="")
@@ -275,7 +276,9 @@ class TestProductService:
self, db, test_marketplace_product_with_inventory
):
"""Test getting inventory info for product with inventory."""
marketplace_product = test_marketplace_product_with_inventory["marketplace_product"]
marketplace_product = test_marketplace_product_with_inventory[
"marketplace_product"
]
inventory = test_marketplace_product_with_inventory["inventory"]
inventory_info = self.service.get_inventory_info(db, marketplace_product.gtin)
@@ -352,7 +355,10 @@ class TestMarketplaceProductServiceAdmin:
# Find our test product in results
found = False
for p in products:
if p["marketplace_product_id"] == test_marketplace_product.marketplace_product_id:
if (
p["marketplace_product_id"]
== test_marketplace_product.marketplace_product_id
):
found = True
assert p["id"] == test_marketplace_product.id
assert p["marketplace"] == test_marketplace_product.marketplace
@@ -429,9 +435,7 @@ class TestMarketplaceProductServiceAdmin:
def test_get_admin_product_detail(self, db, test_marketplace_product):
"""Test getting admin product detail by ID."""
product = self.service.get_admin_product_detail(
db, test_marketplace_product.id
)
product = self.service.get_admin_product_detail(db, test_marketplace_product.id)
assert product["id"] == test_marketplace_product.id
assert (

View File

@@ -1,19 +1,19 @@
# tests/unit/services/test_stats_service.py
"""Unit tests for StatsService following the application's testing patterns."""
import uuid
from unittest.mock import patch
import pytest
from sqlalchemy.exc import SQLAlchemyError
from unittest.mock import patch, MagicMock
from app.exceptions import AdminOperationException, VendorNotFoundException
from app.services.stats_service import StatsService
from models.database.inventory import Inventory
from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import MarketplaceProductTranslation
from models.database.product import Product
from models.database.vendor import Vendor
from models.database.marketplace_product_translation import (
MarketplaceProductTranslation,
)
def create_marketplace_product_with_translation(
@@ -21,8 +21,7 @@ def create_marketplace_product_with_translation(
):
"""Helper to create a MarketplaceProduct with its translation."""
product = MarketplaceProduct(
marketplace_product_id=marketplace_product_id,
**kwargs
marketplace_product_id=marketplace_product_id, **kwargs
)
db.add(product)
db.flush() # Get the product ID
@@ -275,7 +274,10 @@ class TestStatsService:
with pytest.raises(AdminOperationException) as exc_info:
self.service.get_marketplace_breakdown_stats(db)
assert exc_info.value.details.get("operation") == "get_marketplace_breakdown_stats"
assert (
exc_info.value.details.get("operation")
== "get_marketplace_breakdown_stats"
)
# ==================== get_vendor_stats Tests ====================
@@ -371,9 +373,7 @@ class TestStatsService:
stats = self.service.get_vendor_statistics(db)
if stats["total_vendors"] > 0:
expected_rate = (
stats["verified_vendors"] / stats["total_vendors"] * 100
)
expected_rate = stats["verified_vendors"] / stats["total_vendors"] * 100
assert abs(stats["verification_rate"] - expected_rate) < 0.01
def test_get_vendor_statistics_database_error(self, db):
@@ -438,9 +438,7 @@ class TestStatsService:
stats = self.service.get_import_statistics(db)
if stats["total_imports"] > 0:
expected_rate = (
stats["completed_imports"] / stats["total_imports"] * 100
)
expected_rate = stats["completed_imports"] / stats["total_imports"] * 100
assert abs(stats["success_rate"] - expected_rate) < 0.01
else:
assert stats["success_rate"] == 0

View File

@@ -4,6 +4,7 @@ Unit tests for VendorProductService.
Tests the vendor product catalog service operations.
"""
import pytest
from app.exceptions import ProductNotFoundException
@@ -31,7 +32,9 @@ class TestVendorProductService:
if p["id"] == test_product.id:
found = True
assert p["vendor_id"] == test_product.vendor_id
assert p["marketplace_product_id"] == test_product.marketplace_product_id
assert (
p["marketplace_product_id"] == test_product.marketplace_product_id
)
break
assert found, "Test product not found in results"

View File

@@ -1,5 +1,6 @@
# tests/unit/services/test_vendor_service.py
"""Unit tests for VendorService following the application's exception patterns."""
import uuid
import pytest
@@ -85,7 +86,9 @@ class TestVendorService:
assert vendor.is_verified is True # Admin creates verified vendor
def test_create_vendor_duplicate_code(self, db, test_user, test_company, test_vendor):
def test_create_vendor_duplicate_code(
self, db, test_user, test_company, test_vendor
):
"""Test vendor creation fails with duplicate vendor code."""
vendor_data = VendorCreate(
company_id=test_company.id,
@@ -359,9 +362,11 @@ class TestVendorService:
# Re-query objects to avoid session issues
vendor = db.query(Vendor).filter(Vendor.id == test_vendor.id).first()
mp = db.query(MarketplaceProduct).filter(
MarketplaceProduct.id == unique_product.id
).first()
mp = (
db.query(MarketplaceProduct)
.filter(MarketplaceProduct.id == unique_product.id)
.first()
)
product_data = ProductCreate(
marketplace_product_id=mp.id,
@@ -394,6 +399,7 @@ class TestVendorService:
"""Test adding product that's already in vendor fails."""
# Re-query to get fresh instances
from models.database.product import Product
vendor = db.query(Vendor).filter(Vendor.id == test_vendor.id).first()
product = db.query(Product).filter(Product.id == test_product.id).first()
@@ -469,7 +475,9 @@ class TestVendorServiceExceptionDetails:
def setup_method(self):
self.service = VendorService()
def test_exception_to_dict_structure(self, db, test_user, test_vendor, test_company):
def test_exception_to_dict_structure(
self, db, test_user, test_vendor, test_company
):
"""Test that exceptions can be properly serialized to dict for API responses."""
vendor_data = VendorCreate(
company_id=test_company.id,

View File

@@ -10,7 +10,6 @@ import requests.exceptions
from app.utils.csv_processor import CSVProcessor
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import MarketplaceProductTranslation
@pytest.mark.unit