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:
2025-12-13 18:04:04 +01:00
parent 0f99130b3d
commit d21cd366dc
5 changed files with 812 additions and 0 deletions

View File

@@ -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