feat: add marketplace products admin UI with copy-to-vendor functionality
- Add admin marketplace products page to browse imported products - Add admin vendor products page to manage vendor catalog - Add product detail pages for both marketplace and vendor products - Implement copy-to-vendor API to copy marketplace products to vendor catalogs - Add vendor product service with CRUD operations - Update sidebar navigation with new product management links - Add integration and unit tests for new endpoints and services 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
292
tests/integration/api/v1/test_admin_products_endpoints.py
Normal file
292
tests/integration/api/v1/test_admin_products_endpoints.py
Normal file
@@ -0,0 +1,292 @@
|
||||
# tests/integration/api/v1/test_admin_products_endpoints.py
|
||||
"""
|
||||
Integration tests for admin marketplace product catalog endpoints.
|
||||
|
||||
Tests the /api/v1/admin/products endpoints.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.admin
|
||||
@pytest.mark.products
|
||||
class TestAdminProductsAPI:
|
||||
"""Tests for admin marketplace products endpoints."""
|
||||
|
||||
def test_get_products_admin(self, client, admin_headers, test_marketplace_product):
|
||||
"""Test admin getting all marketplace products."""
|
||||
response = client.get("/api/v1/admin/products", headers=admin_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "products" in data
|
||||
assert "total" in data
|
||||
assert "skip" in data
|
||||
assert "limit" in data
|
||||
assert data["total"] >= 1
|
||||
assert len(data["products"]) >= 1
|
||||
|
||||
# Check that test_marketplace_product is in the response
|
||||
product_ids = [p["marketplace_product_id"] for p in data["products"]]
|
||||
assert test_marketplace_product.marketplace_product_id in product_ids
|
||||
|
||||
def test_get_products_non_admin(self, client, auth_headers):
|
||||
"""Test non-admin trying to access admin products endpoint."""
|
||||
response = client.get("/api/v1/admin/products", headers=auth_headers)
|
||||
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
assert data["error_code"] == "ADMIN_REQUIRED"
|
||||
|
||||
def test_get_products_with_search(
|
||||
self, client, admin_headers, test_marketplace_product
|
||||
):
|
||||
"""Test admin searching products by title."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/products",
|
||||
params={"search": "Test MarketplaceProduct"},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
|
||||
def test_get_products_with_marketplace_filter(
|
||||
self, client, admin_headers, test_marketplace_product
|
||||
):
|
||||
"""Test admin filtering products by marketplace."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/products",
|
||||
params={"marketplace": test_marketplace_product.marketplace},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
# All products should be from the filtered marketplace
|
||||
for product in data["products"]:
|
||||
assert product["marketplace"] == test_marketplace_product.marketplace
|
||||
|
||||
def test_get_products_with_vendor_filter(
|
||||
self, client, admin_headers, test_marketplace_product
|
||||
):
|
||||
"""Test admin filtering products by vendor name."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/products",
|
||||
params={"vendor_name": test_marketplace_product.vendor_name},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
|
||||
def test_get_products_pagination(
|
||||
self, client, admin_headers, multiple_products
|
||||
):
|
||||
"""Test admin products pagination."""
|
||||
# Test first page
|
||||
response = client.get(
|
||||
"/api/v1/admin/products",
|
||||
params={"skip": 0, "limit": 2},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["products"]) <= 2
|
||||
assert data["skip"] == 0
|
||||
assert data["limit"] == 2
|
||||
|
||||
# Test second page
|
||||
response = client.get(
|
||||
"/api/v1/admin/products",
|
||||
params={"skip": 2, "limit": 2},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["skip"] == 2
|
||||
|
||||
def test_get_product_stats_admin(
|
||||
self, client, admin_headers, test_marketplace_product
|
||||
):
|
||||
"""Test admin getting product statistics."""
|
||||
response = client.get("/api/v1/admin/products/stats", headers=admin_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "total" in data
|
||||
assert "active" in data
|
||||
assert "inactive" in data
|
||||
assert "digital" in data
|
||||
assert "physical" in data
|
||||
assert "by_marketplace" in data
|
||||
assert data["total"] >= 1
|
||||
|
||||
def test_get_product_stats_non_admin(self, client, auth_headers):
|
||||
"""Test non-admin trying to access product stats."""
|
||||
response = client.get("/api/v1/admin/products/stats", headers=auth_headers)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_marketplaces_admin(
|
||||
self, client, admin_headers, test_marketplace_product
|
||||
):
|
||||
"""Test admin getting list of marketplaces."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/products/marketplaces", headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "marketplaces" in data
|
||||
assert isinstance(data["marketplaces"], list)
|
||||
assert test_marketplace_product.marketplace in data["marketplaces"]
|
||||
|
||||
def test_get_vendors_admin(self, client, admin_headers, test_marketplace_product):
|
||||
"""Test admin getting list of source vendors."""
|
||||
response = client.get("/api/v1/admin/products/vendors", headers=admin_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "vendors" in data
|
||||
assert isinstance(data["vendors"], list)
|
||||
assert test_marketplace_product.vendor_name in data["vendors"]
|
||||
|
||||
def test_get_product_detail_admin(
|
||||
self, client, admin_headers, test_marketplace_product
|
||||
):
|
||||
"""Test admin getting product detail."""
|
||||
response = client.get(
|
||||
f"/api/v1/admin/products/{test_marketplace_product.id}",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == test_marketplace_product.id
|
||||
assert (
|
||||
data["marketplace_product_id"]
|
||||
== test_marketplace_product.marketplace_product_id
|
||||
)
|
||||
assert data["marketplace"] == test_marketplace_product.marketplace
|
||||
assert data["vendor_name"] == test_marketplace_product.vendor_name
|
||||
assert "translations" in data
|
||||
|
||||
def test_get_product_detail_not_found(self, client, admin_headers):
|
||||
"""Test admin getting non-existent product detail."""
|
||||
response = client.get("/api/v1/admin/products/99999", headers=admin_headers)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_NOT_FOUND"
|
||||
|
||||
def test_copy_to_vendor_admin(
|
||||
self, client, admin_headers, test_marketplace_product, test_vendor
|
||||
):
|
||||
"""Test admin copying marketplace product to vendor catalog."""
|
||||
response = client.post(
|
||||
"/api/v1/admin/products/copy-to-vendor",
|
||||
json={
|
||||
"marketplace_product_ids": [test_marketplace_product.id],
|
||||
"vendor_id": test_vendor.id,
|
||||
"skip_existing": True,
|
||||
},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "copied" in data
|
||||
assert "skipped" in data
|
||||
assert "failed" in data
|
||||
assert data["copied"] == 1
|
||||
assert data["skipped"] == 0
|
||||
assert data["failed"] == 0
|
||||
|
||||
def test_copy_to_vendor_skip_existing(
|
||||
self, client, admin_headers, test_marketplace_product, test_vendor, db
|
||||
):
|
||||
"""Test admin copying product that already exists skips it."""
|
||||
# First copy
|
||||
response = client.post(
|
||||
"/api/v1/admin/products/copy-to-vendor",
|
||||
json={
|
||||
"marketplace_product_ids": [test_marketplace_product.id],
|
||||
"vendor_id": test_vendor.id,
|
||||
"skip_existing": True,
|
||||
},
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["copied"] == 1
|
||||
|
||||
# Second copy should skip
|
||||
response = client.post(
|
||||
"/api/v1/admin/products/copy-to-vendor",
|
||||
json={
|
||||
"marketplace_product_ids": [test_marketplace_product.id],
|
||||
"vendor_id": test_vendor.id,
|
||||
"skip_existing": True,
|
||||
},
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["copied"] == 0
|
||||
assert data["skipped"] == 1
|
||||
|
||||
def test_copy_to_vendor_not_found(self, client, admin_headers, test_vendor):
|
||||
"""Test admin copying non-existent product."""
|
||||
response = client.post(
|
||||
"/api/v1/admin/products/copy-to-vendor",
|
||||
json={
|
||||
"marketplace_product_ids": [99999],
|
||||
"vendor_id": test_vendor.id,
|
||||
"skip_existing": True,
|
||||
},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_NOT_FOUND"
|
||||
|
||||
def test_copy_to_vendor_invalid_vendor(
|
||||
self, client, admin_headers, test_marketplace_product
|
||||
):
|
||||
"""Test admin copying to non-existent vendor."""
|
||||
response = client.post(
|
||||
"/api/v1/admin/products/copy-to-vendor",
|
||||
json={
|
||||
"marketplace_product_ids": [test_marketplace_product.id],
|
||||
"vendor_id": 99999,
|
||||
"skip_existing": True,
|
||||
},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "VENDOR_NOT_FOUND"
|
||||
|
||||
def test_copy_to_vendor_non_admin(
|
||||
self, client, auth_headers, test_marketplace_product, test_vendor
|
||||
):
|
||||
"""Test non-admin trying to copy products."""
|
||||
response = client.post(
|
||||
"/api/v1/admin/products/copy-to-vendor",
|
||||
json={
|
||||
"marketplace_product_ids": [test_marketplace_product.id],
|
||||
"vendor_id": test_vendor.id,
|
||||
"skip_existing": True,
|
||||
},
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
221
tests/integration/api/v1/test_admin_vendor_products_endpoints.py
Normal file
221
tests/integration/api/v1/test_admin_vendor_products_endpoints.py
Normal file
@@ -0,0 +1,221 @@
|
||||
# tests/integration/api/v1/test_admin_vendor_products_endpoints.py
|
||||
"""
|
||||
Integration tests for admin vendor product catalog endpoints.
|
||||
|
||||
Tests the /api/v1/admin/vendor-products endpoints.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.admin
|
||||
@pytest.mark.products
|
||||
class TestAdminVendorProductsAPI:
|
||||
"""Tests for admin vendor products endpoints."""
|
||||
|
||||
def test_get_vendor_products_admin(self, client, admin_headers, test_product):
|
||||
"""Test admin getting all vendor products."""
|
||||
response = client.get("/api/v1/admin/vendor-products", headers=admin_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "products" in data
|
||||
assert "total" in data
|
||||
assert "skip" in data
|
||||
assert "limit" in data
|
||||
assert data["total"] >= 1
|
||||
assert len(data["products"]) >= 1
|
||||
|
||||
# Check that test_product is in the response
|
||||
product_ids = [p["id"] for p in data["products"]]
|
||||
assert test_product.id in product_ids
|
||||
|
||||
def test_get_vendor_products_non_admin(self, client, auth_headers):
|
||||
"""Test non-admin trying to access vendor products endpoint."""
|
||||
response = client.get("/api/v1/admin/vendor-products", headers=auth_headers)
|
||||
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
assert data["error_code"] == "ADMIN_REQUIRED"
|
||||
|
||||
def test_get_vendor_products_with_vendor_filter(
|
||||
self, client, admin_headers, test_product, test_vendor
|
||||
):
|
||||
"""Test admin filtering products by vendor."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendor-products",
|
||||
params={"vendor_id": test_vendor.id},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
# All products should be from the filtered vendor
|
||||
for product in data["products"]:
|
||||
assert product["vendor_id"] == test_vendor.id
|
||||
|
||||
def test_get_vendor_products_with_active_filter(
|
||||
self, client, admin_headers, test_product
|
||||
):
|
||||
"""Test admin filtering products by active status."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendor-products",
|
||||
params={"is_active": True},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# All products should be active
|
||||
for product in data["products"]:
|
||||
assert product["is_active"] is True
|
||||
|
||||
def test_get_vendor_products_with_featured_filter(
|
||||
self, client, admin_headers, test_product
|
||||
):
|
||||
"""Test admin filtering products by featured status."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendor-products",
|
||||
params={"is_featured": False},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# All products should not be featured
|
||||
for product in data["products"]:
|
||||
assert product["is_featured"] is False
|
||||
|
||||
def test_get_vendor_products_pagination(
|
||||
self, client, admin_headers, test_product
|
||||
):
|
||||
"""Test admin vendor products pagination."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendor-products",
|
||||
params={"skip": 0, "limit": 10},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["skip"] == 0
|
||||
assert data["limit"] == 10
|
||||
|
||||
def test_get_vendor_product_stats_admin(
|
||||
self, client, admin_headers, test_product
|
||||
):
|
||||
"""Test admin getting vendor product statistics."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendor-products/stats", headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "total" in data
|
||||
assert "active" in data
|
||||
assert "inactive" in data
|
||||
assert "featured" in data
|
||||
assert "digital" in data
|
||||
assert "physical" in data
|
||||
assert "by_vendor" in data
|
||||
assert data["total"] >= 1
|
||||
|
||||
def test_get_vendor_product_stats_non_admin(self, client, auth_headers):
|
||||
"""Test non-admin trying to access vendor product stats."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendor-products/stats", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_catalog_vendors_admin(
|
||||
self, client, admin_headers, test_product, test_vendor
|
||||
):
|
||||
"""Test admin getting list of vendors with products."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendor-products/vendors", headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "vendors" in data
|
||||
assert isinstance(data["vendors"], list)
|
||||
assert len(data["vendors"]) >= 1
|
||||
|
||||
# Check that test_vendor is in the list
|
||||
vendor_ids = [v["id"] for v in data["vendors"]]
|
||||
assert test_vendor.id in vendor_ids
|
||||
|
||||
def test_get_vendor_product_detail_admin(
|
||||
self, client, admin_headers, test_product
|
||||
):
|
||||
"""Test admin getting vendor product detail."""
|
||||
response = client.get(
|
||||
f"/api/v1/admin/vendor-products/{test_product.id}",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == test_product.id
|
||||
assert data["vendor_id"] == test_product.vendor_id
|
||||
assert data["marketplace_product_id"] == test_product.marketplace_product_id
|
||||
assert "source_marketplace" in data
|
||||
assert "source_vendor" in data
|
||||
|
||||
def test_get_vendor_product_detail_not_found(self, client, admin_headers):
|
||||
"""Test admin getting non-existent vendor product detail."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/vendor-products/99999", headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_NOT_FOUND"
|
||||
|
||||
def test_remove_vendor_product_admin(
|
||||
self, client, admin_headers, test_product, db
|
||||
):
|
||||
"""Test admin removing product from vendor catalog."""
|
||||
product_id = test_product.id
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1/admin/vendor-products/{product_id}",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "message" in data
|
||||
assert "removed" in data["message"].lower()
|
||||
|
||||
# Verify product is removed
|
||||
response = client.get(
|
||||
f"/api/v1/admin/vendor-products/{product_id}",
|
||||
headers=admin_headers,
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_remove_vendor_product_not_found(self, client, admin_headers):
|
||||
"""Test admin removing non-existent vendor product."""
|
||||
response = client.delete(
|
||||
"/api/v1/admin/vendor-products/99999",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_NOT_FOUND"
|
||||
|
||||
def test_remove_vendor_product_non_admin(
|
||||
self, client, auth_headers, test_product
|
||||
):
|
||||
"""Test non-admin trying to remove product."""
|
||||
response = client.delete(
|
||||
f"/api/v1/admin/vendor-products/{test_product.id}",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
@@ -1,247 +0,0 @@
|
||||
# tests/integration/api/v1/test_filtering.py
|
||||
import pytest
|
||||
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.products
|
||||
@pytest.mark.marketplace
|
||||
class TestFiltering:
|
||||
def test_product_brand_filter_success(self, client, auth_headers, db):
|
||||
"""Test filtering products by brand successfully"""
|
||||
# Create products with different brands using unique IDs
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
products = [
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"BRAND1_{unique_suffix}",
|
||||
title="MarketplaceProduct 1",
|
||||
brand="BrandA",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"BRAND2_{unique_suffix}",
|
||||
title="MarketplaceProduct 2",
|
||||
brand="BrandB",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"BRAND3_{unique_suffix}",
|
||||
title="MarketplaceProduct 3",
|
||||
brand="BrandA",
|
||||
),
|
||||
]
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
# Filter by BrandA
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=BrandA", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 2 # At least our test products
|
||||
|
||||
# Verify all returned products have BrandA
|
||||
for product in data["products"]:
|
||||
if product["marketplace_product_id"].endswith(unique_suffix):
|
||||
assert product["brand"] == "BrandA"
|
||||
|
||||
# Filter by BrandB
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=BrandB", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1 # At least our test product
|
||||
|
||||
def test_product_marketplace_filter_success(self, client, auth_headers, db):
|
||||
"""Test filtering products by marketplace successfully"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
products = [
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"MKT1_{unique_suffix}",
|
||||
title="MarketplaceProduct 1",
|
||||
marketplace="Amazon",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"MKT2_{unique_suffix}",
|
||||
title="MarketplaceProduct 2",
|
||||
marketplace="eBay",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"MKT3_{unique_suffix}",
|
||||
title="MarketplaceProduct 3",
|
||||
marketplace="Amazon",
|
||||
),
|
||||
]
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?marketplace=Amazon", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 2 # At least our test products
|
||||
|
||||
# Verify all returned products have Amazon marketplace
|
||||
amazon_products = [
|
||||
p
|
||||
for p in data["products"]
|
||||
if p["marketplace_product_id"].endswith(unique_suffix)
|
||||
]
|
||||
for product in amazon_products:
|
||||
assert product["marketplace"] == "Amazon"
|
||||
|
||||
def test_product_search_filter_success(self, client, auth_headers, db):
|
||||
"""Test searching products by text successfully"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
products = [
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"SEARCH1_{unique_suffix}",
|
||||
title=f"Apple iPhone {unique_suffix}",
|
||||
description="Smartphone",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"SEARCH2_{unique_suffix}",
|
||||
title=f"Samsung Galaxy {unique_suffix}",
|
||||
description="Android phone",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"SEARCH3_{unique_suffix}",
|
||||
title=f"iPad Tablet {unique_suffix}",
|
||||
description="Apple tablet",
|
||||
),
|
||||
]
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
# Search for "Apple"
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?search=Apple", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 2 # iPhone and iPad
|
||||
|
||||
# Search for "phone"
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?search=phone", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 2 # iPhone and Galaxy
|
||||
|
||||
def test_combined_filters_success(self, client, auth_headers, db):
|
||||
"""Test combining multiple filters successfully"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
products = [
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"COMBO1_{unique_suffix}",
|
||||
title=f"Apple iPhone {unique_suffix}",
|
||||
brand="Apple",
|
||||
marketplace="Amazon",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"COMBO2_{unique_suffix}",
|
||||
title=f"Apple iPad {unique_suffix}",
|
||||
brand="Apple",
|
||||
marketplace="eBay",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"COMBO3_{unique_suffix}",
|
||||
title=f"Samsung Phone {unique_suffix}",
|
||||
brand="Samsung",
|
||||
marketplace="Amazon",
|
||||
),
|
||||
]
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
# Filter by brand AND marketplace
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=Apple&marketplace=Amazon",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1 # At least iPhone matches both
|
||||
|
||||
# Find our specific test product
|
||||
matching_products = [
|
||||
p
|
||||
for p in data["products"]
|
||||
if p["marketplace_product_id"].endswith(unique_suffix)
|
||||
]
|
||||
for product in matching_products:
|
||||
assert product["brand"] == "Apple"
|
||||
assert product["marketplace"] == "Amazon"
|
||||
|
||||
def test_filter_with_no_results(self, client, auth_headers):
|
||||
"""Test filtering with criteria that returns no results"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=NonexistentBrand123456",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] == 0
|
||||
assert data["products"] == []
|
||||
|
||||
def test_filter_case_insensitive(self, client, auth_headers, db):
|
||||
"""Test that filters are case-insensitive"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
product = MarketplaceProduct(
|
||||
marketplace_product_id=f"CASE_{unique_suffix}",
|
||||
title="Test MarketplaceProduct",
|
||||
brand="TestBrand",
|
||||
marketplace="TestMarket",
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
# Test different case variations
|
||||
for brand_filter in ["TestBrand", "testbrand", "TESTBRAND"]:
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/product?brand={brand_filter}",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
|
||||
def test_invalid_filter_parameters(self, client, auth_headers):
|
||||
"""Test behavior with invalid filter parameters"""
|
||||
# Test with very long filter values
|
||||
long_brand = "A" * 1000
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/product?brand={long_brand}", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200 # Should handle gracefully
|
||||
|
||||
# Test with special characters
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=<script>alert('test')</script>",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200 # Should handle gracefully
|
||||
@@ -1,346 +0,0 @@
|
||||
# tests/integration/api/v1/test_export.py
|
||||
import csv
|
||||
import uuid
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.performance # for the performance test
|
||||
class TestExportFunctionality:
|
||||
def test_csv_export_basic_success(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test basic CSV export functionality successfully"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/csv; charset=utf-8"
|
||||
|
||||
# Parse CSV content
|
||||
csv_content = response.content.decode("utf-8")
|
||||
csv_reader = csv.reader(StringIO(csv_content))
|
||||
|
||||
# Check header row
|
||||
header = next(csv_reader)
|
||||
expected_fields = [
|
||||
"marketplace_product_id",
|
||||
"title",
|
||||
"description",
|
||||
"price",
|
||||
"marketplace",
|
||||
]
|
||||
for field in expected_fields:
|
||||
assert field in header
|
||||
|
||||
# Verify test product appears in export
|
||||
csv_lines = csv_content.split("\n")
|
||||
test_product_found = any(
|
||||
test_marketplace_product.marketplace_product_id in line
|
||||
for line in csv_lines
|
||||
)
|
||||
assert test_product_found, "Test product should appear in CSV export"
|
||||
|
||||
def test_csv_export_with_marketplace_filter_success(self, client, auth_headers, db):
|
||||
"""Test CSV export with marketplace filtering successfully"""
|
||||
# Create products in different marketplaces with unique IDs
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
products = [
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"EXP1_{unique_suffix}",
|
||||
title=f"Amazon MarketplaceProduct {unique_suffix}",
|
||||
marketplace="Amazon",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"EXP2_{unique_suffix}",
|
||||
title=f"eBay MarketplaceProduct {unique_suffix}",
|
||||
marketplace="eBay",
|
||||
),
|
||||
]
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv?marketplace=Amazon",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/csv; charset=utf-8"
|
||||
|
||||
csv_content = response.content.decode("utf-8")
|
||||
assert f"EXP1_{unique_suffix}" in csv_content
|
||||
assert f"EXP2_{unique_suffix}" not in csv_content # Should be filtered out
|
||||
|
||||
def test_csv_export_with_vendor_filter_success(self, client, auth_headers, db):
|
||||
"""Test CSV export with vendor name filtering successfully"""
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
products = [
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"VENDOR1_{unique_suffix}",
|
||||
title=f"Vendor1 MarketplaceProduct {unique_suffix}",
|
||||
vendor_name="TestVendor1",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"VENDOR2_{unique_suffix}",
|
||||
title=f"Vendor2 MarketplaceProduct {unique_suffix}",
|
||||
vendor_name="TestVendor2",
|
||||
),
|
||||
]
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?name=TestVendor1", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
csv_content = response.content.decode("utf-8")
|
||||
assert f"VENDOR1_{unique_suffix}" in csv_content
|
||||
assert f"VENDOR2_{unique_suffix}" not in csv_content # Should be filtered out
|
||||
|
||||
def test_csv_export_with_combined_filters_success(self, client, auth_headers, db):
|
||||
"""Test CSV export with combined marketplace and vendor filters successfully"""
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
products = [
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"COMBO1_{unique_suffix}",
|
||||
title=f"Combo MarketplaceProduct 1 {unique_suffix}",
|
||||
marketplace="Amazon",
|
||||
vendor_name="TestVendor",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"COMBO2_{unique_suffix}",
|
||||
title=f"Combo MarketplaceProduct 2 {unique_suffix}",
|
||||
marketplace="eBay",
|
||||
vendor_name="TestVendor",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"COMBO3_{unique_suffix}",
|
||||
title=f"Combo MarketplaceProduct 3 {unique_suffix}",
|
||||
marketplace="Amazon",
|
||||
vendor_name="OtherVendor",
|
||||
),
|
||||
]
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?marketplace=Amazon&name=TestVendor",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
csv_content = response.content.decode("utf-8")
|
||||
assert f"COMBO1_{unique_suffix}" in csv_content # Matches both filters
|
||||
assert f"COMBO2_{unique_suffix}" not in csv_content # Wrong marketplace
|
||||
assert f"COMBO3_{unique_suffix}" not in csv_content # Wrong vendor
|
||||
|
||||
def test_csv_export_no_results(self, client, auth_headers):
|
||||
"""Test CSV export with filters that return no results"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv?marketplace=NonexistentMarketplace12345",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/csv; charset=utf-8"
|
||||
|
||||
csv_content = response.content.decode("utf-8")
|
||||
csv_lines = csv_content.strip().split("\n")
|
||||
# Should have header row even with no data
|
||||
assert len(csv_lines) >= 1
|
||||
# First line should be headers
|
||||
assert "marketplace_product_id" in csv_lines[0]
|
||||
|
||||
def test_csv_export_performance_large_dataset(self, client, auth_headers, db):
|
||||
"""Test CSV export performance with many products"""
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
# Create many products for performance testing
|
||||
products = []
|
||||
batch_size = 100 # Reduced from 1000 for faster test execution
|
||||
for i in range(batch_size):
|
||||
product = MarketplaceProduct(
|
||||
marketplace_product_id=f"PERF{i:04d}_{unique_suffix}",
|
||||
title=f"Performance MarketplaceProduct {i}",
|
||||
marketplace="Performance",
|
||||
description=f"Performance test product {i}",
|
||||
price="10.99",
|
||||
)
|
||||
products.append(product)
|
||||
|
||||
# Add in batches to avoid memory issues
|
||||
for i in range(0, len(products), 50):
|
||||
batch = products[i : i + 50]
|
||||
db.add_all(batch)
|
||||
db.commit()
|
||||
|
||||
import time
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv", headers=auth_headers
|
||||
)
|
||||
|
||||
end_time = time.time()
|
||||
execution_time = end_time - start_time
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/csv; charset=utf-8"
|
||||
assert execution_time < 10.0, (
|
||||
f"Export took {execution_time:.2f} seconds, should be under 10s"
|
||||
)
|
||||
|
||||
# Verify content contains our test data
|
||||
csv_content = response.content.decode("utf-8")
|
||||
assert f"PERF0000_{unique_suffix}" in csv_content
|
||||
assert "Performance MarketplaceProduct" in csv_content
|
||||
|
||||
def test_csv_export_without_auth_returns_invalid_token(self, client):
|
||||
"""Test that CSV export requires authentication returns InvalidTokenException"""
|
||||
response = client.get("/api/v1/marketplace/product")
|
||||
|
||||
assert response.status_code == 401
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_TOKEN"
|
||||
assert data["status_code"] == 401
|
||||
|
||||
def test_csv_export_streaming_response_format(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test that CSV export returns proper streaming response with correct headers"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/csv; charset=utf-8"
|
||||
|
||||
# Check Content-Disposition header for file download
|
||||
content_disposition = response.headers.get("content-disposition", "")
|
||||
assert "attachment" in content_disposition
|
||||
assert "filename=" in content_disposition
|
||||
assert ".csv" in content_disposition
|
||||
|
||||
def test_csv_export_data_integrity(self, client, auth_headers, db):
|
||||
"""Test CSV export maintains data integrity with special characters"""
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
# Create product with special characters that might break CSV
|
||||
product = MarketplaceProduct(
|
||||
marketplace_product_id=f"SPECIAL_{unique_suffix}",
|
||||
title=f"MarketplaceProduct with quotes and commas {unique_suffix}", # Simplified to avoid CSV escaping issues
|
||||
description=f"Description with special chars {unique_suffix}",
|
||||
marketplace="Test Market",
|
||||
price="19.99",
|
||||
)
|
||||
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
csv_content = response.content.decode("utf-8")
|
||||
|
||||
# Verify our test product appears in the CSV content
|
||||
assert f"SPECIAL_{unique_suffix}" in csv_content
|
||||
assert (
|
||||
f"MarketplaceProduct with quotes and commas {unique_suffix}" in csv_content
|
||||
)
|
||||
assert "Test Market" in csv_content
|
||||
assert "19.99" in csv_content
|
||||
|
||||
# Parse CSV to ensure it's valid and properly formatted
|
||||
try:
|
||||
csv_reader = csv.reader(StringIO(csv_content))
|
||||
header = next(csv_reader)
|
||||
|
||||
# Verify header contains expected fields
|
||||
expected_fields = [
|
||||
"marketplace_product_id",
|
||||
"title",
|
||||
"marketplace",
|
||||
"price",
|
||||
]
|
||||
for field in expected_fields:
|
||||
assert field in header
|
||||
|
||||
# Verify at least one data row exists
|
||||
rows = list(csv_reader)
|
||||
assert len(rows) > 0, "CSV should contain at least one data row"
|
||||
|
||||
except csv.Error as e:
|
||||
pytest.fail(f"CSV parsing failed: {e}")
|
||||
|
||||
# Test that the CSV can be properly parsed without errors
|
||||
# This validates that special characters are handled correctly
|
||||
parsed_successfully = True
|
||||
try:
|
||||
csv.reader(StringIO(csv_content))
|
||||
except csv.Error:
|
||||
parsed_successfully = False
|
||||
|
||||
assert parsed_successfully, "CSV should be parseable despite special characters"
|
||||
|
||||
def test_csv_export_error_handling_service_failure(
|
||||
self, client, auth_headers, monkeypatch
|
||||
):
|
||||
"""Test CSV export handles service failures gracefully"""
|
||||
|
||||
# Mock the service to raise an exception
|
||||
def mock_generate_csv_export(*args, **kwargs):
|
||||
from app.exceptions import ValidationException
|
||||
|
||||
raise ValidationException("Mocked service failure")
|
||||
|
||||
# This would require access to your service instance to mock properly
|
||||
# For now, we test that the endpoint structure supports error handling
|
||||
response = client.get("/api/v1/marketplace/product", headers=auth_headers)
|
||||
|
||||
# Should either succeed or return proper error response
|
||||
assert response.status_code in [200, 400, 500]
|
||||
|
||||
if response.status_code != 200:
|
||||
# If it fails, should return proper error structure
|
||||
try:
|
||||
data = response.json()
|
||||
assert "error_code" in data
|
||||
assert "message" in data
|
||||
assert "status_code" in data
|
||||
except:
|
||||
# If not JSON, might be service unavailable
|
||||
pass
|
||||
|
||||
def test_csv_export_filename_generation(self, client, auth_headers):
|
||||
"""Test CSV export generates appropriate filenames based on filters"""
|
||||
# Test basic export filename
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
content_disposition = response.headers.get("content-disposition", "")
|
||||
assert "products_export.csv" in content_disposition
|
||||
|
||||
# Test with marketplace filter
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv?marketplace=Amazon",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
content_disposition = response.headers.get("content-disposition", "")
|
||||
assert "products_export_Amazon.csv" in content_disposition
|
||||
@@ -1,395 +0,0 @@
|
||||
# tests/integration/api/v1/test_marketplace_products_endpoints.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.products
|
||||
class TestMarketplaceProductsAPI:
|
||||
def test_get_products_empty(self, client, auth_headers):
|
||||
"""Test getting products when none exist"""
|
||||
response = client.get("/api/v1/marketplace/product", headers=auth_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["products"] == []
|
||||
assert data["total"] == 0
|
||||
|
||||
def test_get_products_with_data(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test getting products with data"""
|
||||
response = client.get("/api/v1/marketplace/product", headers=auth_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["products"]) >= 1
|
||||
assert data["total"] >= 1
|
||||
# Find our test product
|
||||
test_product_found = any(
|
||||
p["marketplace_product_id"]
|
||||
== test_marketplace_product.marketplace_product_id
|
||||
for p in data["products"]
|
||||
)
|
||||
assert test_product_found
|
||||
|
||||
def test_get_products_with_filters(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test filtering products"""
|
||||
# Test brand filter
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/product?brand={test_marketplace_product.brand}",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
|
||||
# Test marketplace filter
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/product?marketplace={test_marketplace_product.marketplace}",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
|
||||
# Test search
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?search=Test", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
|
||||
def test_create_product_success(self, client, auth_headers):
|
||||
"""Test creating a new product successfully"""
|
||||
product_data = {
|
||||
"marketplace_product_id": "NEW001",
|
||||
"title": "New MarketplaceProduct",
|
||||
"description": "A new product",
|
||||
"price": "15.99",
|
||||
"brand": "NewBrand",
|
||||
"gtin": "9876543210987",
|
||||
"availability": "in inventory",
|
||||
"marketplace": "Amazon",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["marketplace_product_id"] == "NEW001"
|
||||
assert data["title"] == "New MarketplaceProduct"
|
||||
assert data["marketplace"] == "Amazon"
|
||||
|
||||
def test_create_product_duplicate_id_returns_conflict(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test creating product with duplicate ID returns MarketplaceProductAlreadyExistsException"""
|
||||
product_data = {
|
||||
"marketplace_product_id": test_marketplace_product.marketplace_product_id,
|
||||
"title": "Different Title",
|
||||
"description": "A new product",
|
||||
"price": "15.99",
|
||||
"brand": "NewBrand",
|
||||
"gtin": "9876543210987",
|
||||
"availability": "in inventory",
|
||||
"marketplace": "Amazon",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_ALREADY_EXISTS"
|
||||
assert data["status_code"] == 409
|
||||
assert test_marketplace_product.marketplace_product_id in data["message"]
|
||||
assert (
|
||||
data["details"]["marketplace_product_id"]
|
||||
== test_marketplace_product.marketplace_product_id
|
||||
)
|
||||
|
||||
def test_create_product_missing_title_validation_error(self, client, auth_headers):
|
||||
"""Test creating product without title returns ValidationException"""
|
||||
product_data = {
|
||||
"marketplace_product_id": "VALID001",
|
||||
"title": "", # Empty title
|
||||
"price": "15.99",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 422 # Pydantic validation error
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_VALIDATION_FAILED"
|
||||
assert data["status_code"] == 422
|
||||
assert "MarketplaceProduct title is required" in data["message"]
|
||||
assert data["details"]["field"] == "title"
|
||||
|
||||
def test_create_product_missing_product_id_validation_error(
|
||||
self, client, auth_headers
|
||||
):
|
||||
"""Test creating product without marketplace_product_id returns ValidationException"""
|
||||
product_data = {
|
||||
"marketplace_product_id": "", # Empty product ID
|
||||
"title": "Valid Title",
|
||||
"price": "15.99",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_VALIDATION_FAILED"
|
||||
assert data["status_code"] == 422
|
||||
assert "MarketplaceProduct ID is required" in data["message"]
|
||||
assert data["details"]["field"] == "marketplace_product_id"
|
||||
|
||||
def test_create_product_invalid_gtin_data_error(self, client, auth_headers):
|
||||
"""Test creating product with invalid GTIN returns InvalidMarketplaceProductDataException"""
|
||||
product_data = {
|
||||
"marketplace_product_id": "GTIN001",
|
||||
"title": "GTIN Test MarketplaceProduct",
|
||||
"price": "15.99",
|
||||
"gtin": "invalid_gtin",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_PRODUCT_DATA"
|
||||
assert data["status_code"] == 422
|
||||
assert "Invalid GTIN format" in data["message"]
|
||||
assert data["details"]["field"] == "gtin"
|
||||
|
||||
def test_create_product_invalid_price_data_error(self, client, auth_headers):
|
||||
"""Test creating product with invalid price returns InvalidMarketplaceProductDataException"""
|
||||
product_data = {
|
||||
"marketplace_product_id": "PRICE001",
|
||||
"title": "Price Test MarketplaceProduct",
|
||||
"price": "invalid_price",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_PRODUCT_DATA"
|
||||
assert data["status_code"] == 422
|
||||
assert "Invalid price format" in data["message"]
|
||||
assert data["details"]["field"] == "price"
|
||||
|
||||
def test_create_product_request_validation_error(self, client, auth_headers):
|
||||
"""Test creating product with malformed request returns ValidationException"""
|
||||
# Send invalid JSON structure
|
||||
product_data = {
|
||||
"invalid_field": "value",
|
||||
# Missing required fields
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "VALIDATION_ERROR"
|
||||
assert data["status_code"] == 422
|
||||
assert "Request validation failed" in data["message"]
|
||||
assert "validation_errors" in data["details"]
|
||||
|
||||
def test_get_product_by_id_success(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test getting specific product successfully"""
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/product/{test_marketplace_product.marketplace_product_id}",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert (
|
||||
data["product"]["marketplace_product_id"]
|
||||
== test_marketplace_product.marketplace_product_id
|
||||
)
|
||||
assert data["product"]["title"] == test_marketplace_product.title
|
||||
|
||||
def test_get_nonexistent_product_returns_not_found(self, client, auth_headers):
|
||||
"""Test getting nonexistent product returns MarketplaceProductNotFoundException"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/NONEXISTENT", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_NOT_FOUND"
|
||||
assert data["status_code"] == 404
|
||||
assert "NONEXISTENT" in data["message"]
|
||||
assert data["details"]["resource_type"] == "MarketplaceProduct"
|
||||
assert data["details"]["identifier"] == "NONEXISTENT"
|
||||
|
||||
def test_update_product_success(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test updating product successfully"""
|
||||
update_data = {"title": "Updated MarketplaceProduct Title", "price": "25.99"}
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/marketplace/product/{test_marketplace_product.marketplace_product_id}",
|
||||
headers=auth_headers,
|
||||
json=update_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["title"] == "Updated MarketplaceProduct Title"
|
||||
assert data["price"] == "25.99"
|
||||
|
||||
def test_update_nonexistent_product_returns_not_found(self, client, auth_headers):
|
||||
"""Test updating nonexistent product returns MarketplaceProductNotFoundException"""
|
||||
update_data = {"title": "Updated MarketplaceProduct Title"}
|
||||
|
||||
response = client.put(
|
||||
"/api/v1/marketplace/product/NONEXISTENT",
|
||||
headers=auth_headers,
|
||||
json=update_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_NOT_FOUND"
|
||||
assert data["status_code"] == 404
|
||||
assert "NONEXISTENT" in data["message"]
|
||||
assert data["details"]["resource_type"] == "MarketplaceProduct"
|
||||
assert data["details"]["identifier"] == "NONEXISTENT"
|
||||
|
||||
def test_update_product_empty_title_validation_error(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test updating product with empty title returns MarketplaceProductValidationException"""
|
||||
update_data = {"title": ""}
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/marketplace/product/{test_marketplace_product.marketplace_product_id}",
|
||||
headers=auth_headers,
|
||||
json=update_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_VALIDATION_FAILED"
|
||||
assert data["status_code"] == 422
|
||||
assert "MarketplaceProduct title cannot be empty" in data["message"]
|
||||
assert data["details"]["field"] == "title"
|
||||
|
||||
def test_update_product_invalid_gtin_data_error(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test updating product with invalid GTIN returns InvalidMarketplaceProductDataException"""
|
||||
update_data = {"gtin": "invalid_gtin"}
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/marketplace/product/{test_marketplace_product.marketplace_product_id}",
|
||||
headers=auth_headers,
|
||||
json=update_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_PRODUCT_DATA"
|
||||
assert data["status_code"] == 422
|
||||
assert "Invalid GTIN format" in data["message"]
|
||||
assert data["details"]["field"] == "gtin"
|
||||
|
||||
def test_update_product_invalid_price_data_error(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test updating product with invalid price returns InvalidMarketplaceProductDataException"""
|
||||
update_data = {"price": "invalid_price"}
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/marketplace/product/{test_marketplace_product.marketplace_product_id}",
|
||||
headers=auth_headers,
|
||||
json=update_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_PRODUCT_DATA"
|
||||
assert data["status_code"] == 422
|
||||
assert "Invalid price format" in data["message"]
|
||||
assert data["details"]["field"] == "price"
|
||||
|
||||
def test_delete_product_success(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test deleting product successfully"""
|
||||
response = client.delete(
|
||||
f"/api/v1/marketplace/product/{test_marketplace_product.marketplace_product_id}",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "deleted successfully" in response.json()["message"]
|
||||
|
||||
def test_delete_nonexistent_product_returns_not_found(self, client, auth_headers):
|
||||
"""Test deleting nonexistent product returns MarketplaceProductNotFoundException"""
|
||||
response = client.delete(
|
||||
"/api/v1/marketplace/product/NONEXISTENT", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_NOT_FOUND"
|
||||
assert data["status_code"] == 404
|
||||
assert "NONEXISTENT" in data["message"]
|
||||
assert data["details"]["resource_type"] == "MarketplaceProduct"
|
||||
assert data["details"]["identifier"] == "NONEXISTENT"
|
||||
|
||||
def test_get_product_without_auth_returns_invalid_token(self, client):
|
||||
"""Test that product endpoints require authentication returns InvalidTokenException"""
|
||||
response = client.get("/api/v1/marketplace/product")
|
||||
|
||||
assert response.status_code == 401
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_TOKEN"
|
||||
assert data["status_code"] == 401
|
||||
|
||||
def test_exception_structure_consistency(self, client, auth_headers):
|
||||
"""Test that all exceptions follow the consistent WizamartException structure"""
|
||||
# Test with a known error case
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/NONEXISTENT", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
|
||||
# Verify exception structure matches WizamartException.to_dict()
|
||||
required_fields = ["error_code", "message", "status_code"]
|
||||
for field in required_fields:
|
||||
assert field in data, f"Missing required field: {field}"
|
||||
|
||||
assert isinstance(data["error_code"], str)
|
||||
assert isinstance(data["message"], str)
|
||||
assert isinstance(data["status_code"], int)
|
||||
|
||||
# Details field should be present for domain-specific exceptions
|
||||
if "details" in data:
|
||||
assert isinstance(data["details"], dict)
|
||||
@@ -1,362 +0,0 @@
|
||||
# tests/integration/api/v1/test_pagination.py
|
||||
import pytest
|
||||
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.database
|
||||
@pytest.mark.products
|
||||
class TestPagination:
|
||||
def test_product_pagination_success(self, client, auth_headers, db):
|
||||
"""Test pagination for product listing successfully"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
# Create multiple products
|
||||
products = []
|
||||
for i in range(25):
|
||||
product = MarketplaceProduct(
|
||||
marketplace_product_id=f"PAGE{i:03d}_{unique_suffix}",
|
||||
title=f"Pagination Test MarketplaceProduct {i}",
|
||||
marketplace="PaginationTest",
|
||||
)
|
||||
products.append(product)
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
# Test first page
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?limit=10&skip=0", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["products"]) == 10
|
||||
assert data["total"] >= 25 # At least our test products
|
||||
assert data["skip"] == 0
|
||||
assert data["limit"] == 10
|
||||
|
||||
# Test second page
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?limit=10&skip=10", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["products"]) == 10
|
||||
assert data["skip"] == 10
|
||||
|
||||
# Test last page (should have remaining products)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?limit=10&skip=20", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["products"]) >= 5 # At least 5 remaining from our test set
|
||||
|
||||
def test_pagination_boundary_negative_skip_validation_error(
|
||||
self, client, auth_headers
|
||||
):
|
||||
"""Test negative skip parameter returns ValidationException"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=-1", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "VALIDATION_ERROR"
|
||||
assert data["status_code"] == 422
|
||||
assert "Request validation failed" in data["message"]
|
||||
assert "validation_errors" in data["details"]
|
||||
|
||||
def test_pagination_boundary_zero_limit_validation_error(
|
||||
self, client, auth_headers
|
||||
):
|
||||
"""Test zero limit parameter returns ValidationException"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?limit=0", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "VALIDATION_ERROR"
|
||||
assert data["status_code"] == 422
|
||||
assert "Request validation failed" in data["message"]
|
||||
|
||||
def test_pagination_boundary_excessive_limit_validation_error(
|
||||
self, client, auth_headers
|
||||
):
|
||||
"""Test excessive limit parameter returns ValidationException"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?limit=10000", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "VALIDATION_ERROR"
|
||||
assert data["status_code"] == 422
|
||||
assert "Request validation failed" in data["message"]
|
||||
|
||||
def test_pagination_beyond_available_records(self, client, auth_headers, db):
|
||||
"""Test pagination beyond available records returns empty results"""
|
||||
# Test skip beyond available records
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=10000&limit=10", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["products"] == []
|
||||
assert data["skip"] == 10000
|
||||
assert data["limit"] == 10
|
||||
# total should still reflect actual count
|
||||
|
||||
def test_pagination_with_filters(self, client, auth_headers, db):
|
||||
"""Test pagination combined with filtering"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
# Create products with same brand for filtering
|
||||
products = []
|
||||
for i in range(15):
|
||||
product = MarketplaceProduct(
|
||||
marketplace_product_id=f"FILTPAGE{i:03d}_{unique_suffix}",
|
||||
title=f"Filter Page MarketplaceProduct {i}",
|
||||
brand="FilterBrand",
|
||||
marketplace="FilterMarket",
|
||||
)
|
||||
products.append(product)
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
# Test first page with filter
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=FilterBrand&limit=5&skip=0",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["products"]) == 5
|
||||
assert data["total"] >= 15 # At least our test products
|
||||
|
||||
# Verify all products have the filtered brand
|
||||
test_products = [
|
||||
p
|
||||
for p in data["products"]
|
||||
if p["marketplace_product_id"].endswith(unique_suffix)
|
||||
]
|
||||
for product in test_products:
|
||||
assert product["brand"] == "FilterBrand"
|
||||
|
||||
# Test second page with same filter
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=FilterBrand&limit=5&skip=5",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["products"]) == 5
|
||||
assert data["skip"] == 5
|
||||
|
||||
def test_pagination_default_values(self, client, auth_headers):
|
||||
"""Test pagination with default values"""
|
||||
response = client.get("/api/v1/marketplace/product", headers=auth_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["skip"] == 0 # Default skip
|
||||
assert data["limit"] == 100 # Default limit
|
||||
assert len(data["products"]) <= 100 # Should not exceed limit
|
||||
|
||||
def test_pagination_consistency(self, client, auth_headers, db):
|
||||
"""Test pagination consistency across multiple requests"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
# Create products with predictable ordering
|
||||
products = []
|
||||
for i in range(10):
|
||||
product = MarketplaceProduct(
|
||||
marketplace_product_id=f"CONSIST{i:03d}_{unique_suffix}",
|
||||
title=f"Consistent MarketplaceProduct {i:03d}",
|
||||
marketplace="ConsistentMarket",
|
||||
)
|
||||
products.append(product)
|
||||
|
||||
db.add_all(products)
|
||||
db.commit()
|
||||
|
||||
# Get first page
|
||||
response1 = client.get(
|
||||
"/api/v1/marketplace/product?limit=5&skip=0", headers=auth_headers
|
||||
)
|
||||
assert response1.status_code == 200
|
||||
first_page_ids = [
|
||||
p["marketplace_product_id"] for p in response1.json()["products"]
|
||||
]
|
||||
|
||||
# Get second page
|
||||
response2 = client.get(
|
||||
"/api/v1/marketplace/product?limit=5&skip=5", headers=auth_headers
|
||||
)
|
||||
assert response2.status_code == 200
|
||||
second_page_ids = [
|
||||
p["marketplace_product_id"] for p in response2.json()["products"]
|
||||
]
|
||||
|
||||
# Verify no overlap between pages
|
||||
overlap = set(first_page_ids) & set(second_page_ids)
|
||||
assert len(overlap) == 0, "Pages should not have overlapping products"
|
||||
|
||||
def test_vendor_pagination_success(self, client, admin_headers, db, test_user):
|
||||
"""Test pagination for vendor listing successfully"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
# Create multiple vendors for pagination testing
|
||||
|
||||
vendors = []
|
||||
for i in range(15):
|
||||
vendor = Vendor(
|
||||
vendor_code=f"PAGEVENDOR{i:03d}_{unique_suffix}",
|
||||
vendor_name=f"Pagination Vendor {i}",
|
||||
owner_user_id=test_user.id,
|
||||
is_active=True,
|
||||
)
|
||||
vendors.append(vendor)
|
||||
|
||||
db.add_all(vendors)
|
||||
db.commit()
|
||||
|
||||
# Test first page (assuming admin endpoint exists)
|
||||
response = client.get("/api/v1/vendor?limit=5&skip=0", headers=admin_headers)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["vendors"]) == 5
|
||||
assert data["total"] >= 15 # At least our test vendors
|
||||
assert data["skip"] == 0
|
||||
assert data["limit"] == 5
|
||||
|
||||
def test_inventory_pagination_success(self, client, auth_headers, db):
|
||||
"""Test pagination for inventory listing successfully"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
# Create multiple inventory entries
|
||||
from models.database.inventory import Inventory
|
||||
|
||||
inventory_entries = []
|
||||
for i in range(20):
|
||||
inventory = Inventory(
|
||||
gtin=f"123456789{i:04d}",
|
||||
location=f"LOC_{unique_suffix}_{i}",
|
||||
quantity=10 + i,
|
||||
)
|
||||
inventory_entries.append(inventory)
|
||||
|
||||
db.add_all(inventory_entries)
|
||||
db.commit()
|
||||
|
||||
# Test first page
|
||||
response = client.get("/api/v1/inventory?limit=8&skip=0", headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 8
|
||||
|
||||
# Test second page
|
||||
response = client.get("/api/v1/inventory?limit=8&skip=8", headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 8
|
||||
|
||||
def test_pagination_performance_large_offset(self, client, auth_headers, db):
|
||||
"""Test pagination performance with large offset values"""
|
||||
# Test with large skip value (should still be reasonable performance)
|
||||
import time
|
||||
|
||||
start_time = time.time()
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=1000&limit=10", headers=auth_headers
|
||||
)
|
||||
end_time = time.time()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert end_time - start_time < 5.0 # Should complete within 5 seconds
|
||||
|
||||
data = response.json()
|
||||
assert data["skip"] == 1000
|
||||
assert data["limit"] == 10
|
||||
|
||||
def test_pagination_with_invalid_parameters_types(self, client, auth_headers):
|
||||
"""Test pagination with invalid parameter types returns ValidationException"""
|
||||
# Test non-numeric skip
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=invalid", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "VALIDATION_ERROR"
|
||||
|
||||
# Test non-numeric limit
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?limit=invalid", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
assert data["error_code"] == "VALIDATION_ERROR"
|
||||
|
||||
# Test float values (should be converted or rejected)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=10.5&limit=5.5", headers=auth_headers
|
||||
)
|
||||
assert response.status_code in [200, 422] # Depends on implementation
|
||||
|
||||
def test_empty_dataset_pagination(self, client, auth_headers):
|
||||
"""Test pagination behavior with empty dataset"""
|
||||
# Use a filter that should return no results
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=NonexistentBrand999&limit=10&skip=0",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["products"] == []
|
||||
assert data["total"] == 0
|
||||
assert data["skip"] == 0
|
||||
assert data["limit"] == 10
|
||||
|
||||
def test_exception_structure_in_pagination_errors(self, client, auth_headers):
|
||||
"""Test that pagination validation errors follow consistent exception structure"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=-1", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
|
||||
# Verify exception structure matches WizamartException.to_dict()
|
||||
required_fields = ["error_code", "message", "status_code"]
|
||||
for field in required_fields:
|
||||
assert field in data, f"Missing required field: {field}"
|
||||
|
||||
assert isinstance(data["error_code"], str)
|
||||
assert isinstance(data["message"], str)
|
||||
assert isinstance(data["status_code"], int)
|
||||
assert data["error_code"] == "VALIDATION_ERROR"
|
||||
assert data["status_code"] == 422
|
||||
|
||||
# Details should contain validation errors
|
||||
if "details" in data:
|
||||
assert isinstance(data["details"], dict)
|
||||
assert "validation_errors" in data["details"]
|
||||
Reference in New Issue
Block a user