feat: add Letzshop CSV product export for admin and vendor
- Create letzshop_export_service.py with CSV generation logic
- Add admin endpoint: GET /api/v1/admin/vendors/{vendor}/export/letzshop
- Add vendor endpoint: GET /api/v1/vendor/letzshop/export
- Support language selection (en, fr, de) and include_inactive filter
- Generate Google Shopping compatible tab-delimited CSV format
- Add comprehensive integration tests for both endpoints
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -359,3 +359,197 @@ class TestAdminLetzshopAccessControl:
|
||||
response = client.get("/api/v1/admin/letzshop/vendors")
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.admin
|
||||
@pytest.mark.letzshop
|
||||
class TestAdminLetzshopExportAPI:
|
||||
"""Test admin Letzshop product export endpoints."""
|
||||
|
||||
def test_export_vendor_products_empty(
|
||||
self, client, admin_headers, test_vendor
|
||||
):
|
||||
"""Test exporting products when vendor has no products."""
|
||||
response = client.get(
|
||||
f"/api/v1/admin/vendors/{test_vendor.id}/export/letzshop",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/csv; charset=utf-8"
|
||||
# Should have header row at minimum
|
||||
content = response.text
|
||||
assert "id\ttitle\tdescription" in content
|
||||
|
||||
def test_export_vendor_products_with_data(
|
||||
self, client, db, admin_headers, test_vendor
|
||||
):
|
||||
"""Test exporting products with actual data."""
|
||||
from models.database.product import Product
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.marketplace_product_translation import (
|
||||
MarketplaceProductTranslation,
|
||||
)
|
||||
|
||||
# Create marketplace product
|
||||
mp = MarketplaceProduct(
|
||||
marketplace_product_id="EXPORT-TEST-001",
|
||||
price="29.99",
|
||||
price_numeric=29.99,
|
||||
currency="EUR",
|
||||
brand="TestBrand",
|
||||
availability="in stock",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(mp)
|
||||
db.flush()
|
||||
|
||||
# Add translation
|
||||
translation = MarketplaceProductTranslation(
|
||||
marketplace_product_id=mp.id,
|
||||
language="en",
|
||||
title="Export Test Product",
|
||||
description="A product for testing exports",
|
||||
)
|
||||
db.add(translation)
|
||||
db.flush()
|
||||
|
||||
# Create product linked to vendor
|
||||
product = Product(
|
||||
vendor_id=test_vendor.id,
|
||||
vendor_sku="EXP-001",
|
||||
marketplace_product_id=mp.id,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1/admin/vendors/{test_vendor.id}/export/letzshop",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
content = response.text
|
||||
assert "EXP-001" in content
|
||||
assert "Export Test Product" in content
|
||||
assert "29.99 EUR" in content
|
||||
|
||||
def test_export_vendor_products_french(
|
||||
self, client, db, admin_headers, test_vendor
|
||||
):
|
||||
"""Test exporting products in French."""
|
||||
from models.database.product import Product
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.marketplace_product_translation import (
|
||||
MarketplaceProductTranslation,
|
||||
)
|
||||
|
||||
mp = MarketplaceProduct(
|
||||
marketplace_product_id="EXPORT-FR-001",
|
||||
price="19.99",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(mp)
|
||||
db.flush()
|
||||
|
||||
# Add French translation
|
||||
translation_fr = MarketplaceProductTranslation(
|
||||
marketplace_product_id=mp.id,
|
||||
language="fr",
|
||||
title="Produit Test Export",
|
||||
description="Un produit pour tester les exportations",
|
||||
)
|
||||
db.add(translation_fr)
|
||||
db.flush()
|
||||
|
||||
product = Product(
|
||||
vendor_id=test_vendor.id,
|
||||
vendor_sku="EXP-FR-001",
|
||||
marketplace_product_id=mp.id,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1/admin/vendors/{test_vendor.id}/export/letzshop?language=fr",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
content = response.text
|
||||
assert "Produit Test Export" in content
|
||||
|
||||
def test_export_vendor_products_include_inactive(
|
||||
self, client, db, admin_headers, test_vendor
|
||||
):
|
||||
"""Test exporting including inactive products."""
|
||||
from models.database.product import Product
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.marketplace_product_translation import (
|
||||
MarketplaceProductTranslation,
|
||||
)
|
||||
|
||||
# Create inactive product
|
||||
mp = MarketplaceProduct(
|
||||
marketplace_product_id="EXPORT-INACTIVE-001",
|
||||
price="5.99",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(mp)
|
||||
db.flush()
|
||||
|
||||
translation = MarketplaceProductTranslation(
|
||||
marketplace_product_id=mp.id,
|
||||
language="en",
|
||||
title="Inactive Product",
|
||||
)
|
||||
db.add(translation)
|
||||
db.flush()
|
||||
|
||||
product = Product(
|
||||
vendor_id=test_vendor.id,
|
||||
vendor_sku="INACTIVE-001",
|
||||
marketplace_product_id=mp.id,
|
||||
is_active=False, # Inactive
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
# Without include_inactive
|
||||
response = client.get(
|
||||
f"/api/v1/admin/vendors/{test_vendor.id}/export/letzshop",
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert "INACTIVE-001" not in response.text
|
||||
|
||||
# With include_inactive
|
||||
response = client.get(
|
||||
f"/api/v1/admin/vendors/{test_vendor.id}/export/letzshop?include_inactive=true",
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert "INACTIVE-001" in response.text
|
||||
|
||||
def test_export_vendor_products_by_vendor_code(
|
||||
self, client, admin_headers, test_vendor
|
||||
):
|
||||
"""Test exporting products using vendor_code identifier."""
|
||||
response = client.get(
|
||||
f"/api/v1/admin/vendors/{test_vendor.vendor_code}/export/letzshop",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/csv; charset=utf-8"
|
||||
|
||||
def test_export_vendor_not_found(self, client, admin_headers):
|
||||
"""Test exporting for non-existent vendor."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendors/999999/export/letzshop",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
Reference in New Issue
Block a user