test: update tests for multi-language translation support

- Update marketplace_product_fixtures to create translations
- Update test_marketplace_product.py for translation-based titles
- Update test_product.py for effective property tests
- Update test_order.py to use get_title() method
- Add comprehensive CSV processor tests for translations
- Update stats service tests for new flat response structure
- Fix product schema tests with required marketplace_product_id field
- Add helper function create_marketplace_product_with_translation()

🤖 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-11 17:29:50 +01:00
parent 22c4937779
commit 2ecc2a9785
8 changed files with 790 additions and 201 deletions

View File

@@ -2,7 +2,10 @@
"""
Marketplace product test fixtures.
Note: Fixtures should NOT use db.expunge() as it breaks lazy loading.
Note: Since title/description are now in translations table,
we create the translation alongside the marketplace product.
Fixtures should NOT use db.expunge() as it breaks lazy loading.
See tests/conftest.py for details on fixture best practices.
"""
import uuid
@@ -10,12 +13,42 @@ import uuid
import pytest
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import MarketplaceProductTranslation
def _create_marketplace_product_with_translation(
db,
marketplace_product_id: str,
title: str,
description: str | None = None,
**kwargs,
) -> MarketplaceProduct:
"""Helper to create a MarketplaceProduct with its English translation."""
marketplace_product = MarketplaceProduct(
marketplace_product_id=marketplace_product_id,
**kwargs,
)
db.add(marketplace_product)
db.flush() # Get the ID
# Create the English translation
translation = MarketplaceProductTranslation(
marketplace_product_id=marketplace_product.id,
language="en",
title=title,
description=description,
)
db.add(translation)
db.commit()
db.refresh(marketplace_product)
return marketplace_product
@pytest.fixture
def test_marketplace_product(db):
"""Create a test product."""
marketplace_product = MarketplaceProduct(
"""Create a test product with translation."""
return _create_marketplace_product_with_translation(
db,
marketplace_product_id="TEST001",
title="Test MarketplaceProduct",
description="A test product",
@@ -27,17 +60,14 @@ def test_marketplace_product(db):
marketplace="Letzshop",
vendor_name="TestVendor",
)
db.add(marketplace_product)
db.commit()
db.refresh(marketplace_product)
return marketplace_product
@pytest.fixture
def unique_product(db):
"""Create a unique product for tests that need isolated product data."""
unique_id = str(uuid.uuid4())[:8]
marketplace_product = MarketplaceProduct(
return _create_marketplace_product_with_translation(
db,
marketplace_product_id=f"UNIQUE_{unique_id}",
title=f"Unique MarketplaceProduct {unique_id}",
description=f"A unique test product {unique_id}",
@@ -50,10 +80,6 @@ def unique_product(db):
vendor_name=f"UniqueVendor_{unique_id}",
google_product_category=f"UniqueCategory_{unique_id}",
)
db.add(marketplace_product)
db.commit()
db.refresh(marketplace_product)
return marketplace_product
@pytest.fixture
@@ -63,7 +89,8 @@ def multiple_products(db):
marketplace_products = []
for i in range(5):
marketplace_product = MarketplaceProduct(
marketplace_product = _create_marketplace_product_with_translation(
db,
marketplace_product_id=f"MULTI_{unique_id}_{i}",
title=f"Multi MarketplaceProduct {i} {unique_id}",
description=f"Multi test product {i}",
@@ -77,33 +104,30 @@ def multiple_products(db):
)
marketplace_products.append(marketplace_product)
db.add_all(marketplace_products)
db.commit()
for product in marketplace_products:
db.refresh(product)
return marketplace_products
def create_unique_marketplace_product_factory():
"""Factory function to create unique products in tests."""
def _marketplace_create_product(db, **kwargs):
def _marketplace_create_product(db, title: str | None = None, **kwargs):
unique_id = str(uuid.uuid4())[:8]
defaults = {
"marketplace_product_id": f"FACTORY_{unique_id}",
"title": f"Factory MarketplaceProduct {unique_id}",
"price": "15.99",
"currency": "EUR",
"marketplace": "TestMarket",
"name": "TestVendor",
"vendor_name": "TestVendor",
}
defaults.update(kwargs)
marketplace_product = MarketplaceProduct(**defaults)
db.add(marketplace_product)
db.commit()
db.refresh(marketplace_product)
return marketplace_product
title = title or f"Factory MarketplaceProduct {unique_id}"
return _create_marketplace_product_with_translation(
db,
title=title,
**defaults,
)
return _marketplace_create_product

View File

@@ -4,6 +4,24 @@ import pytest
from sqlalchemy.exc import IntegrityError
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace_product_translation import MarketplaceProductTranslation
def _create_with_translation(db, marketplace_product_id, title, **kwargs):
"""Helper to create MarketplaceProduct with translation."""
mp = MarketplaceProduct(marketplace_product_id=marketplace_product_id, **kwargs)
db.add(mp)
db.flush()
translation = MarketplaceProductTranslation(
marketplace_product_id=mp.id,
language="en",
title=title,
)
db.add(translation)
db.commit()
db.refresh(mp)
return mp
@pytest.mark.unit
@@ -13,10 +31,10 @@ class TestMarketplaceProductModel:
def test_marketplace_product_creation(self, db):
"""Test MarketplaceProduct model creation."""
marketplace_product = MarketplaceProduct(
marketplace_product = _create_with_translation(
db,
marketplace_product_id="DB_TEST_001",
title="Database Test Product",
description="Testing product model",
price="25.99",
currency="USD",
brand="DBTest",
@@ -26,42 +44,36 @@ class TestMarketplaceProductModel:
vendor_name="Test Vendor",
)
db.add(marketplace_product)
db.commit()
db.refresh(marketplace_product)
assert marketplace_product.id is not None
assert marketplace_product.marketplace_product_id == "DB_TEST_001"
assert marketplace_product.title == "Database Test Product"
assert marketplace_product.get_title("en") == "Database Test Product"
assert marketplace_product.marketplace == "Letzshop"
assert marketplace_product.created_at is not None
def test_marketplace_product_id_uniqueness(self, db):
"""Test unique marketplace_product_id constraint."""
product1 = MarketplaceProduct(
_create_with_translation(
db,
marketplace_product_id="UNIQUE_001",
title="Product 1",
marketplace="Letzshop",
)
db.add(product1)
db.commit()
# Duplicate marketplace_product_id should raise error
with pytest.raises(IntegrityError):
product2 = MarketplaceProduct(
_create_with_translation(
db,
marketplace_product_id="UNIQUE_001",
title="Product 2",
marketplace="Letzshop",
)
db.add(product2)
db.commit()
def test_marketplace_product_all_fields(self, db):
"""Test MarketplaceProduct with all optional fields."""
marketplace_product = MarketplaceProduct(
marketplace_product = _create_with_translation(
db,
marketplace_product_id="FULL_001",
title="Full Product",
description="Complete product description",
link="https://example.com/product",
image_link="https://example.com/image.jpg",
availability="in stock",
@@ -78,16 +90,12 @@ class TestMarketplaceProductModel:
pattern="solid",
size="M",
google_product_category="Apparel & Accessories",
product_type="Clothing",
product_type_raw="Clothing",
currency="EUR",
marketplace="Letzshop",
vendor_name="Full Vendor",
)
db.add(marketplace_product)
db.commit()
db.refresh(marketplace_product)
assert marketplace_product.brand == "TestBrand"
assert marketplace_product.gtin == "9876543210123"
assert marketplace_product.color == "blue"
@@ -95,7 +103,8 @@ class TestMarketplaceProductModel:
def test_marketplace_product_custom_labels(self, db):
"""Test MarketplaceProduct with custom labels."""
marketplace_product = MarketplaceProduct(
marketplace_product = _create_with_translation(
db,
marketplace_product_id="LABELS_001",
title="Labeled Product",
marketplace="Letzshop",
@@ -106,26 +115,127 @@ class TestMarketplaceProductModel:
custom_label_4="Label4",
)
db.add(marketplace_product)
db.commit()
db.refresh(marketplace_product)
assert marketplace_product.custom_label_0 == "Label0"
assert marketplace_product.custom_label_4 == "Label4"
def test_marketplace_product_minimal_fields(self, db):
"""Test MarketplaceProduct with only required fields."""
marketplace_product = MarketplaceProduct(
marketplace_product = _create_with_translation(
db,
marketplace_product_id="MINIMAL_001",
title="Minimal Product",
)
db.add(marketplace_product)
db.commit()
db.refresh(marketplace_product)
assert marketplace_product.id is not None
assert marketplace_product.marketplace_product_id == "MINIMAL_001"
assert marketplace_product.title == "Minimal Product"
assert marketplace_product.description is None
assert marketplace_product.get_title("en") == "Minimal Product"
assert marketplace_product.get_description("en") is None
assert marketplace_product.price is None
def test_marketplace_product_digital_fields(self, db):
"""Test MarketplaceProduct with digital product fields."""
marketplace_product = _create_with_translation(
db,
marketplace_product_id="DIGITAL_001",
title="Digital Product",
product_type_enum="digital",
is_digital=True,
digital_delivery_method="license_key",
platform="steam",
region_restrictions=["EU", "US"],
license_type="single_use",
)
assert marketplace_product.product_type_enum == "digital"
assert marketplace_product.is_digital is True
assert marketplace_product.digital_delivery_method == "license_key"
assert marketplace_product.platform == "steam"
assert marketplace_product.region_restrictions == ["EU", "US"]
assert marketplace_product.license_type == "single_use"
def test_marketplace_product_translation_methods(self, db):
"""Test translation helper methods."""
mp = MarketplaceProduct(marketplace_product_id="TRANS_001")
db.add(mp)
db.flush()
# Add English translation
en_trans = MarketplaceProductTranslation(
marketplace_product_id=mp.id,
language="en",
title="English Title",
description="English Description",
)
db.add(en_trans)
# Add French translation
fr_trans = MarketplaceProductTranslation(
marketplace_product_id=mp.id,
language="fr",
title="Titre Français",
description="Description Française",
)
db.add(fr_trans)
db.commit()
db.refresh(mp)
assert mp.get_title("en") == "English Title"
assert mp.get_title("fr") == "Titre Français"
assert mp.get_description("en") == "English Description"
assert mp.get_description("fr") == "Description Française"
# Test fallback to English for unknown language
assert mp.get_title("de") == "English Title" # Falls back to 'en'
assert mp.get_description("de") == "English Description"
def test_marketplace_product_numeric_prices(self, db):
"""Test numeric price fields."""
marketplace_product = _create_with_translation(
db,
marketplace_product_id="PRICES_001",
title="Priced Product",
price="99.99 EUR",
price_numeric=99.99,
sale_price="79.99 EUR",
sale_price_numeric=79.99,
)
assert marketplace_product.price == "99.99 EUR"
assert marketplace_product.price_numeric == 99.99
assert marketplace_product.sale_price_numeric == 79.99
assert marketplace_product.effective_price == 99.99
assert marketplace_product.effective_sale_price == 79.99
def test_marketplace_product_attributes_json(self, db):
"""Test flexible attributes JSON field."""
marketplace_product = _create_with_translation(
db,
marketplace_product_id="ATTRS_001",
title="Product with Attributes",
attributes={
"custom_field": "custom_value",
"nested": {"key": "value"},
},
)
assert marketplace_product.attributes["custom_field"] == "custom_value"
assert marketplace_product.attributes["nested"]["key"] == "value"
def test_marketplace_product_all_images_property(self, db):
"""Test all_images property."""
marketplace_product = _create_with_translation(
db,
marketplace_product_id="IMAGES_001",
title="Product with Images",
image_link="https://example.com/main.jpg",
additional_images=[
"https://example.com/img1.jpg",
"https://example.com/img2.jpg",
],
)
images = marketplace_product.all_images
assert len(images) == 3
assert images[0] == "https://example.com/main.jpg"
assert "https://example.com/img1.jpg" in images
assert "https://example.com/img2.jpg" in images

View File

@@ -152,11 +152,14 @@ class TestOrderItemModel:
def test_order_item_creation(self, db, test_order, test_product):
"""Test OrderItem model."""
# Get title from translation
product_title = test_product.marketplace_product.get_title("en")
order_item = OrderItem(
order_id=test_order.id,
product_id=test_product.id,
product_name=test_product.marketplace_product.title,
product_sku=test_product.product_id or "SKU001",
product_name=product_title,
product_sku=test_product.vendor_sku or "SKU001",
quantity=2,
unit_price=49.99,
total_price=99.98,

View File

@@ -16,7 +16,7 @@ class TestProductModel:
product = Product(
vendor_id=test_vendor.id,
marketplace_product_id=test_marketplace_product.id,
product_id="VENDOR_PROD_001",
vendor_sku="VENDOR_PROD_001",
price=89.99,
currency="EUR",
availability="in stock",
@@ -34,7 +34,11 @@ class TestProductModel:
assert product.price == 89.99
assert product.is_featured is True
assert product.vendor.vendor_code == test_vendor.vendor_code
assert product.marketplace_product.title == test_marketplace_product.title
# Use get_title() method instead of .title attribute
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."""
@@ -76,7 +80,7 @@ class TestProductModel:
product = Product(
vendor_id=test_vendor.id,
marketplace_product_id=test_marketplace_product.id,
product_id="CUSTOM_SKU_001",
vendor_sku="CUSTOM_SKU_001",
price=49.99,
sale_price=39.99,
currency="USD",
@@ -87,7 +91,7 @@ class TestProductModel:
db.commit()
db.refresh(product)
assert product.product_id == "CUSTOM_SKU_001"
assert product.vendor_sku == "CUSTOM_SKU_001"
assert product.price == 49.99
assert product.sale_price == 39.99
assert product.currency == "USD"
@@ -121,3 +125,99 @@ class TestProductModel:
assert product.vendor is not None
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):
"""Test Product effective properties with override pattern."""
# First, set some values on the marketplace product
test_marketplace_product.price_numeric = 100.00
test_marketplace_product.brand = "SourceBrand"
db.commit()
db.refresh(test_marketplace_product)
# Create product without overrides
product = Product(
vendor_id=test_vendor.id,
marketplace_product_id=test_marketplace_product.id,
)
db.add(product)
db.commit()
db.refresh(product)
# Should inherit from marketplace product
assert product.effective_price == 100.00
assert product.effective_brand == "SourceBrand"
# Now override the price
product.price = 89.99
db.commit()
db.refresh(product)
# Should use the override
assert product.effective_price == 89.99
# Brand still inherited
assert product.effective_brand == "SourceBrand"
def test_product_reset_to_source(self, db, test_vendor, test_marketplace_product):
"""Test reset_to_source methods."""
# Set up marketplace product values
test_marketplace_product.price_numeric = 100.00
test_marketplace_product.brand = "SourceBrand"
db.commit()
# Create product with overrides
product = Product(
vendor_id=test_vendor.id,
marketplace_product_id=test_marketplace_product.id,
price=89.99,
brand="OverrideBrand",
)
db.add(product)
db.commit()
db.refresh(product)
assert product.effective_price == 89.99
assert product.effective_brand == "OverrideBrand"
# Reset price to source
product.reset_field_to_source("price")
db.commit()
db.refresh(product)
assert product.price is None
assert product.effective_price == 100.00 # Now inherits
# Reset all fields
product.reset_all_to_source()
db.commit()
db.refresh(product)
assert product.brand is None
assert product.effective_brand == "SourceBrand" # Now inherits
def test_product_get_override_info(self, db, test_vendor, test_marketplace_product):
"""Test get_override_info method."""
test_marketplace_product.price_numeric = 100.00
test_marketplace_product.brand = "SourceBrand"
db.commit()
product = Product(
vendor_id=test_vendor.id,
marketplace_product_id=test_marketplace_product.id,
price=89.99, # Override
# brand not set - will inherit
)
db.add(product)
db.commit()
db.refresh(product)
info = product.get_override_info()
# Price is overridden
assert info["price"] == 89.99
assert info["price_overridden"] is True
assert info["price_source"] == 100.00
# Brand is inherited
assert info["brand"] == "SourceBrand"
assert info["brand_overridden"] is False
assert info["brand_source"] == "SourceBrand"

View File

@@ -157,12 +157,13 @@ class TestProductResponseSchema:
"vendor_id": 1,
"marketplace_product": {
"id": 1,
"marketplace_product_id": "TEST001", # Required field
"gtin": "1234567890123",
"title": "Test Product",
"description": "A test product",
"brand": "Test Brand",
"category": "Electronics",
"image_url": "https://example.com/image.jpg",
"google_product_category": "Electronics",
"image_link": "https://example.com/image.jpg",
"created_at": datetime.now(),
"updated_at": datetime.now(),
},
@@ -194,12 +195,13 @@ class TestProductResponseSchema:
"vendor_id": 1,
"marketplace_product": {
"id": 1,
"marketplace_product_id": "TEST002", # Required field
"gtin": "1234567890123",
"title": "Test Product",
"description": None,
"brand": None,
"category": None,
"image_url": None,
"google_product_category": None,
"image_link": None,
"created_at": datetime.now(),
"updated_at": datetime.now(),
},

View File

@@ -11,10 +11,31 @@ 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
def create_marketplace_product_with_translation(
db, marketplace_product_id, title, language="en", **kwargs
):
"""Helper to create a MarketplaceProduct with its translation."""
product = MarketplaceProduct(
marketplace_product_id=marketplace_product_id,
**kwargs
)
db.add(product)
db.flush() # Get the product ID
translation = MarketplaceProductTranslation(
marketplace_product_id=product.id,
language=language,
title=title,
)
db.add(translation)
return product
@pytest.mark.unit
@pytest.mark.stats
class TestStatsService:
@@ -58,29 +79,28 @@ class TestStatsService:
):
"""Test comprehensive stats with multiple marketplaces."""
unique_id = str(uuid.uuid4())[:8]
additional_products = [
MarketplaceProduct(
marketplace_product_id=f"PROD002_{unique_id}",
title="MarketplaceProduct 2",
brand="DifferentBrand",
google_product_category="Different Category",
marketplace="Amazon",
vendor_name="AmazonVendor",
price="15.99",
currency="EUR",
),
MarketplaceProduct(
marketplace_product_id=f"PROD003_{unique_id}",
title="MarketplaceProduct 3",
brand="ThirdBrand",
google_product_category="Third Category",
marketplace="eBay",
vendor_name="eBayVendor",
price="25.99",
currency="USD",
),
]
db.add_all(additional_products)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"PROD002_{unique_id}",
title="MarketplaceProduct 2",
brand="DifferentBrand",
google_product_category="Different Category",
marketplace="Amazon",
vendor_name="AmazonVendor",
price="15.99",
currency="EUR",
)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"PROD003_{unique_id}",
title="MarketplaceProduct 3",
brand="ThirdBrand",
google_product_category="Third Category",
marketplace="eBay",
vendor_name="eBayVendor",
price="25.99",
currency="USD",
)
db.commit()
stats = self.service.get_comprehensive_stats(db)
@@ -91,29 +111,28 @@ class TestStatsService:
def test_get_comprehensive_stats_handles_nulls(self, db):
"""Test comprehensive stats handles null/empty values correctly."""
unique_id = str(uuid.uuid4())[:8]
products_with_nulls = [
MarketplaceProduct(
marketplace_product_id=f"NULL001_{unique_id}",
title="MarketplaceProduct with Nulls",
brand=None,
google_product_category=None,
marketplace=None,
vendor_name=None,
price="10.00",
currency="EUR",
),
MarketplaceProduct(
marketplace_product_id=f"EMPTY001_{unique_id}",
title="MarketplaceProduct with Empty Values",
brand="",
google_product_category="",
marketplace="",
vendor_name="",
price="15.00",
currency="EUR",
),
]
db.add_all(products_with_nulls)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"NULL001_{unique_id}",
title="MarketplaceProduct with Nulls",
brand=None,
google_product_category=None,
marketplace=None,
vendor_name=None,
price="10.00",
currency="EUR",
)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"EMPTY001_{unique_id}",
title="MarketplaceProduct with Empty Values",
brand="",
google_product_category="",
marketplace="",
vendor_name="",
price="15.00",
currency="EUR",
)
db.commit()
stats = self.service.get_comprehensive_stats(db)
@@ -170,36 +189,36 @@ class TestStatsService:
):
"""Test marketplace breakdown with multiple marketplaces."""
unique_id = str(uuid.uuid4())[:8]
marketplace_products = [
MarketplaceProduct(
marketplace_product_id=f"AMAZON001_{unique_id}",
title="Amazon MarketplaceProduct 1",
brand="AmazonBrand1",
marketplace="Amazon",
vendor_name="AmazonVendor1",
price="20.00",
currency="EUR",
),
MarketplaceProduct(
marketplace_product_id=f"AMAZON002_{unique_id}",
title="Amazon MarketplaceProduct 2",
brand="AmazonBrand2",
marketplace="Amazon",
vendor_name="AmazonVendor2",
price="25.00",
currency="EUR",
),
MarketplaceProduct(
marketplace_product_id=f"EBAY001_{unique_id}",
title="eBay MarketplaceProduct",
brand="eBayBrand",
marketplace="eBay",
vendor_name="eBayVendor",
price="30.00",
currency="USD",
),
]
db.add_all(marketplace_products)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"AMAZON001_{unique_id}",
title="Amazon MarketplaceProduct 1",
brand="AmazonBrand1",
marketplace="Amazon",
vendor_name="AmazonVendor1",
price="20.00",
currency="EUR",
)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"AMAZON002_{unique_id}",
title="Amazon MarketplaceProduct 2",
brand="AmazonBrand2",
marketplace="Amazon",
vendor_name="AmazonVendor2",
price="25.00",
currency="EUR",
)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"EBAY001_{unique_id}",
title="eBay MarketplaceProduct",
brand="eBayBrand",
marketplace="eBay",
vendor_name="eBayVendor",
price="30.00",
currency="USD",
)
db.commit()
stats = self.service.get_marketplace_breakdown_stats(db)
@@ -223,7 +242,8 @@ class TestStatsService:
def test_get_marketplace_breakdown_stats_excludes_nulls(self, db):
"""Test marketplace breakdown excludes products with null marketplaces."""
unique_id = str(uuid.uuid4())[:8]
null_marketplace_product = MarketplaceProduct(
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"NULLMARKET001_{unique_id}",
title="MarketplaceProduct without marketplace",
marketplace=None,
@@ -232,7 +252,6 @@ class TestStatsService:
price="10.00",
currency="EUR",
)
db.add(null_marketplace_product)
db.commit()
stats = self.service.get_marketplace_breakdown_stats(db)
@@ -264,15 +283,16 @@ class TestStatsService:
"""Test getting vendor statistics successfully."""
stats = self.service.get_vendor_stats(db, test_vendor.id)
assert "catalog" in stats
assert "staging" in stats
assert "inventory" in stats
assert "imports" in stats
assert "orders" in stats
assert "customers" in stats
# New flat structure compatible with VendorDashboardStatsResponse
assert "total_products" in stats
assert "active_products" in stats
assert "total_orders" in stats
assert "total_customers" in stats
assert "total_revenue" in stats
assert "total_inventory_quantity" in stats
assert stats["catalog"]["total_products"] >= 0
assert stats["inventory"]["total_quantity"] >= 0
assert stats["total_products"] >= 0
assert stats["total_inventory_quantity"] >= 0
def test_get_vendor_stats_vendor_not_found(self, db):
"""Test vendor stats with non-existent vendor."""
@@ -285,8 +305,8 @@ class TestStatsService:
"""Test vendor stats includes inventory data."""
stats = self.service.get_vendor_stats(db, test_vendor.id)
assert stats["inventory"]["total_quantity"] >= test_inventory.quantity
assert stats["inventory"]["reserved_quantity"] >= 0
assert stats["total_inventory_quantity"] >= test_inventory.quantity
assert stats["reserved_inventory_quantity"] >= 0
def test_get_vendor_stats_database_error(self, db, test_vendor):
"""Test vendor stats handles database errors after vendor check."""
@@ -453,27 +473,26 @@ class TestStatsService:
def test_get_unique_brands_count(self, db, test_marketplace_product):
"""Test getting unique brands count."""
unique_id = str(uuid.uuid4())[:8]
brand_products = [
MarketplaceProduct(
marketplace_product_id=f"BRAND001_{unique_id}",
title="Brand MarketplaceProduct 1",
brand="BrandA",
marketplace="Test",
vendor_name="TestVendor",
price="10.00",
currency="EUR",
),
MarketplaceProduct(
marketplace_product_id=f"BRAND002_{unique_id}",
title="Brand MarketplaceProduct 2",
brand="BrandB",
marketplace="Test",
vendor_name="TestVendor",
price="15.00",
currency="EUR",
),
]
db.add_all(brand_products)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"BRAND001_{unique_id}",
title="Brand MarketplaceProduct 1",
brand="BrandA",
marketplace="Test",
vendor_name="TestVendor",
price="10.00",
currency="EUR",
)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"BRAND002_{unique_id}",
title="Brand MarketplaceProduct 2",
brand="BrandB",
marketplace="Test",
vendor_name="TestVendor",
price="15.00",
currency="EUR",
)
db.commit()
count = self.service._get_unique_brands_count(db)
@@ -484,27 +503,26 @@ class TestStatsService:
def test_get_unique_categories_count(self, db, test_marketplace_product):
"""Test getting unique categories count."""
unique_id = str(uuid.uuid4())[:8]
category_products = [
MarketplaceProduct(
marketplace_product_id=f"CAT001_{unique_id}",
title="Category MarketplaceProduct 1",
google_product_category="Electronics",
marketplace="Test",
vendor_name="TestVendor",
price="10.00",
currency="EUR",
),
MarketplaceProduct(
marketplace_product_id=f"CAT002_{unique_id}",
title="Category MarketplaceProduct 2",
google_product_category="Books",
marketplace="Test",
vendor_name="TestVendor",
price="15.00",
currency="EUR",
),
]
db.add_all(category_products)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"CAT001_{unique_id}",
title="Category MarketplaceProduct 1",
google_product_category="Electronics",
marketplace="Test",
vendor_name="TestVendor",
price="10.00",
currency="EUR",
)
create_marketplace_product_with_translation(
db,
marketplace_product_id=f"CAT002_{unique_id}",
title="Category MarketplaceProduct 2",
google_product_category="Books",
marketplace="Test",
vendor_name="TestVendor",
price="15.00",
currency="EUR",
)
db.commit()
count = self.service._get_unique_categories_count(db)

View File

@@ -1,4 +1,6 @@
# tests/test_csv_processor.py
# tests/unit/utils/test_csv_processor.py
"""Unit tests for CSV processor with translation support."""
from unittest.mock import Mock, patch
import pandas as pd
@@ -7,6 +9,8 @@ import requests
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
@@ -102,6 +106,74 @@ TEST002,Test MarketplaceProduct 2,15.99,TestMarket"""
assert df.iloc[0]["marketplace_product_id"] == "TEST001"
assert df.iloc[1]["price"] == 15.99
def test_normalize_columns_google_shopping(self):
"""Test column normalization for Google Shopping feed format"""
csv_content = """g:id,g:title,g:description,g:price,g:brand,g:product_type
TEST001,Product 1,Description 1,19.99 EUR,Brand1,Category1"""
df = self.processor.parse_csv(csv_content)
df = self.processor.normalize_columns(df)
assert "marketplace_product_id" in df.columns
assert "title" in df.columns
assert "description" in df.columns
assert "product_type_raw" in df.columns # Renamed from product_type
assert df.iloc[0]["marketplace_product_id"] == "TEST001"
assert df.iloc[0]["title"] == "Product 1"
def test_extract_translation_data(self):
"""Test extraction of translation fields from product data"""
product_data = {
"marketplace_product_id": "TEST001",
"title": "Test Product",
"description": "Test Description",
"short_description": "Short desc",
"price": "19.99",
"brand": "TestBrand",
}
translation_data = self.processor._extract_translation_data(product_data)
# Translation fields should be extracted
assert translation_data["title"] == "Test Product"
assert translation_data["description"] == "Test Description"
assert translation_data["short_description"] == "Short desc"
# Product data should no longer have translation fields
assert "title" not in product_data
assert "description" not in product_data
assert "short_description" not in product_data
# Non-translation fields should remain
assert product_data["marketplace_product_id"] == "TEST001"
assert product_data["price"] == "19.99"
assert product_data["brand"] == "TestBrand"
def test_parse_price_to_numeric(self):
"""Test price string to numeric conversion"""
assert self.processor._parse_price_to_numeric("19.99 EUR") == 19.99
assert self.processor._parse_price_to_numeric("19,99 EUR") == 19.99
assert self.processor._parse_price_to_numeric("$29.99") == 29.99
assert self.processor._parse_price_to_numeric("100") == 100.0
assert self.processor._parse_price_to_numeric(None) is None
assert self.processor._parse_price_to_numeric("") is None
def test_clean_row_data_with_prices(self):
"""Test row data cleaning with price parsing"""
row_data = {
"marketplace_product_id": "TEST001",
"title": "Test Product",
"price": "19.99 EUR",
"sale_price": "14.99 EUR",
"gtin": "1234567890123",
}
cleaned = self.processor._clean_row_data(row_data)
assert cleaned["price_numeric"] == 19.99
assert cleaned["sale_price_numeric"] == 14.99
assert cleaned["currency"] == "EUR"
@pytest.mark.asyncio
async def test_process_marketplace_csv_from_url(self, db):
"""Test complete marketplace CSV processing"""
@@ -117,15 +189,275 @@ TEST002,Test MarketplaceProduct 2,15.99,TestMarket"""
"title": ["MarketplaceProduct 1", "MarketplaceProduct 2"],
"price": ["10.99", "15.99"],
"marketplace": ["TestMarket", "TestMarket"],
"name": ["TestVendor", "TestVendor"],
"vendor_name": ["TestVendor", "TestVendor"],
}
)
mock_parse.return_value = mock_df
result = await self.processor.process_marketplace_csv_from_url(
"http://example.com/test.csv", "TestMarket", "TestVendor", 1000, db
"http://example.com/test.csv",
"TestMarket",
"TestVendor",
1000,
db,
language="en",
)
assert "imported" in result
assert "updated" in result
assert "total_processed" in result
assert "language" in result
assert result["language"] == "en"
@pytest.mark.asyncio
async def test_process_batch_creates_translations(self, db):
"""Test that batch processing creates translation records"""
# Clean up any existing test data
existing = (
db.query(MarketplaceProduct)
.filter(
MarketplaceProduct.marketplace_product_id.in_(
["TRANS_TEST_001", "TRANS_TEST_002"]
)
)
.all()
)
for p in existing:
db.delete(p)
db.commit()
# Create test DataFrame
batch_df = pd.DataFrame(
{
"marketplace_product_id": ["TRANS_TEST_001", "TRANS_TEST_002"],
"title": ["Product One", "Product Two"],
"description": ["Description One", "Description Two"],
"price": ["19.99 EUR", "29.99 EUR"],
"brand": ["Brand1", "Brand2"],
}
)
result = await self.processor._process_marketplace_batch(
batch_df,
"TestMarket",
"TestVendor",
db,
batch_num=1,
language="en",
source_file="test.csv",
)
assert result["imported"] == 2
assert result["errors"] == 0
# Verify products were created
products = (
db.query(MarketplaceProduct)
.filter(
MarketplaceProduct.marketplace_product_id.in_(
["TRANS_TEST_001", "TRANS_TEST_002"]
)
)
.all()
)
assert len(products) == 2
# Verify translations were created
for product in products:
assert len(product.translations) == 1
translation = product.translations[0]
assert translation.language == "en"
assert translation.title is not None
assert translation.source_file == "test.csv"
# Verify get_title method works
product1 = next(
p for p in products if p.marketplace_product_id == "TRANS_TEST_001"
)
assert product1.get_title("en") == "Product One"
assert product1.get_description("en") == "Description One"
# Clean up
for p in products:
db.delete(p)
db.commit()
@pytest.mark.asyncio
async def test_process_batch_updates_existing_translations(self, db):
"""Test that batch processing updates existing translation records"""
# Clean up any existing test data
existing = (
db.query(MarketplaceProduct)
.filter(MarketplaceProduct.marketplace_product_id == "UPDATE_TEST_001")
.first()
)
if existing:
db.delete(existing)
db.commit()
# Create initial product with translation
batch_df = pd.DataFrame(
{
"marketplace_product_id": ["UPDATE_TEST_001"],
"title": ["Original Title"],
"description": ["Original Description"],
"price": ["19.99 EUR"],
}
)
await self.processor._process_marketplace_batch(
batch_df, "TestMarket", "TestVendor", db, 1, language="en"
)
# Update with new data
update_df = pd.DataFrame(
{
"marketplace_product_id": ["UPDATE_TEST_001"],
"title": ["Updated Title"],
"description": ["Updated Description"],
"price": ["24.99 EUR"],
}
)
result = await self.processor._process_marketplace_batch(
update_df, "TestMarket", "TestVendor", db, 1, language="en"
)
assert result["updated"] == 1
assert result["imported"] == 0
# Verify translation was updated
product = (
db.query(MarketplaceProduct)
.filter(MarketplaceProduct.marketplace_product_id == "UPDATE_TEST_001")
.first()
)
assert product.get_title("en") == "Updated Title"
assert product.get_description("en") == "Updated Description"
# Clean up
db.delete(product)
db.commit()
@pytest.mark.asyncio
async def test_process_batch_multi_language(self, db):
"""Test importing same product in multiple languages"""
# Clean up
existing = (
db.query(MarketplaceProduct)
.filter(MarketplaceProduct.marketplace_product_id == "MULTI_LANG_001")
.first()
)
if existing:
db.delete(existing)
db.commit()
# Import English version
en_df = pd.DataFrame(
{
"marketplace_product_id": ["MULTI_LANG_001"],
"title": ["English Title"],
"description": ["English Description"],
"price": ["19.99 EUR"],
"brand": ["TestBrand"],
}
)
await self.processor._process_marketplace_batch(
en_df, "TestMarket", "TestVendor", db, 1, language="en"
)
# Import French version (same product, different language)
fr_df = pd.DataFrame(
{
"marketplace_product_id": ["MULTI_LANG_001"],
"title": ["Titre Français"],
"description": ["Description Française"],
"price": ["19.99 EUR"],
"brand": ["TestBrand"],
}
)
result = await self.processor._process_marketplace_batch(
fr_df, "TestMarket", "TestVendor", db, 1, language="fr"
)
assert result["updated"] == 1 # Product existed, so it's an update
# Verify both translations exist
product = (
db.query(MarketplaceProduct)
.filter(MarketplaceProduct.marketplace_product_id == "MULTI_LANG_001")
.first()
)
assert len(product.translations) == 2
# Verify each language
assert product.get_title("en") == "English Title"
assert product.get_title("fr") == "Titre Français"
assert product.get_description("en") == "English Description"
assert product.get_description("fr") == "Description Française"
# Test fallback to English for unknown language
assert product.get_title("de") == "English Title"
# Clean up
db.delete(product)
db.commit()
@pytest.mark.asyncio
async def test_process_batch_skips_missing_title(self, db):
"""Test that rows without title are skipped"""
batch_df = pd.DataFrame(
{
"marketplace_product_id": ["NO_TITLE_001", "HAS_TITLE_001"],
"title": [None, "Has Title"],
"price": ["19.99", "29.99"],
}
)
result = await self.processor._process_marketplace_batch(
batch_df, "TestMarket", "TestVendor", db, 1, language="en"
)
assert result["imported"] == 1
assert result["errors"] == 1 # Missing title is an error
# Clean up
product = (
db.query(MarketplaceProduct)
.filter(MarketplaceProduct.marketplace_product_id == "HAS_TITLE_001")
.first()
)
if product:
db.delete(product)
db.commit()
@pytest.mark.asyncio
async def test_process_batch_skips_missing_product_id(self, db):
"""Test that rows without marketplace_product_id are skipped"""
batch_df = pd.DataFrame(
{
"marketplace_product_id": [None, "HAS_ID_001"],
"title": ["No ID Product", "Has ID Product"],
"price": ["19.99", "29.99"],
}
)
result = await self.processor._process_marketplace_batch(
batch_df, "TestMarket", "TestVendor", db, 1, language="en"
)
assert result["imported"] == 1
assert result["errors"] == 1 # Missing ID is an error
# Clean up
product = (
db.query(MarketplaceProduct)
.filter(MarketplaceProduct.marketplace_product_id == "HAS_ID_001")
.first()
)
if product:
db.delete(product)
db.commit()

View File

@@ -1,4 +1,4 @@
# tests/test_data_validation.py
# tests/unit/utils/test_data_validation.py
import pytest
from app.utils.data_processing import GTINProcessor, PriceProcessor