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:
@@ -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
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# tests/unit/models/database/test_user.py
|
||||
"""Unit tests for User database model."""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# tests/unit/models/database/test_vendor.py
|
||||
"""Unit tests for Vendor database model."""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# tests/unit/models/schema/test_auth.py
|
||||
"""Unit tests for auth Pydantic schemas."""
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user