style: apply black and isort formatting across entire codebase
- Standardize quote style (single to double quotes) - Reorder and group imports alphabetically - Fix line breaks and indentation for consistency - Apply PEP 8 formatting standards Also updated Makefile to exclude both venv and .venv from code quality checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -59,7 +59,9 @@ class TestAdminAPI:
|
||||
assert response.status_code == 400 # Business logic error
|
||||
data = response.json()
|
||||
assert data["error_code"] == "CANNOT_MODIFY_SELF"
|
||||
assert "Cannot perform 'deactivate account' on your own account" in data["message"]
|
||||
assert (
|
||||
"Cannot perform 'deactivate account' on your own account" in data["message"]
|
||||
)
|
||||
|
||||
def test_toggle_user_status_cannot_modify_admin(
|
||||
self, client, admin_headers, test_admin, another_admin
|
||||
@@ -85,7 +87,9 @@ class TestAdminAPI:
|
||||
|
||||
# Check that test_vendor is in the response
|
||||
vendor_codes = [
|
||||
vendor ["vendor_code"] for vendor in data["vendors"] if "vendor_code" in vendor
|
||||
vendor["vendor_code"]
|
||||
for vendor in data["vendors"]
|
||||
if "vendor_code" in vendor
|
||||
]
|
||||
assert test_vendor.vendor_code in vendor_codes
|
||||
|
||||
@@ -98,7 +102,7 @@ class TestAdminAPI:
|
||||
assert data["error_code"] == "ADMIN_REQUIRED"
|
||||
|
||||
def test_verify_vendor_admin(self, client, admin_headers, test_vendor):
|
||||
"""Test admin verifying/unverifying vendor """
|
||||
"""Test admin verifying/unverifying vendor"""
|
||||
response = client.put(
|
||||
f"/api/v1/admin/vendors/{test_vendor.id}/verify", headers=admin_headers
|
||||
)
|
||||
@@ -109,8 +113,10 @@ class TestAdminAPI:
|
||||
assert test_vendor.vendor_code in message
|
||||
|
||||
def test_verify_vendor_not_found(self, client, admin_headers):
|
||||
"""Test admin verifying non-existent vendor """
|
||||
response = client.put("/api/v1/admin/vendors/99999/verify", headers=admin_headers)
|
||||
"""Test admin verifying non-existent vendor"""
|
||||
response = client.put(
|
||||
"/api/v1/admin/vendors/99999/verify", headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
@@ -129,8 +135,10 @@ class TestAdminAPI:
|
||||
assert test_vendor.vendor_code in message
|
||||
|
||||
def test_toggle_vendor_status_not_found(self, client, admin_headers):
|
||||
"""Test admin toggling status for non-existent vendor """
|
||||
response = client.put("/api/v1/admin/vendors/99999/status", headers=admin_headers)
|
||||
"""Test admin toggling status for non-existent vendor"""
|
||||
response = client.put(
|
||||
"/api/v1/admin/vendors/99999/status", headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
@@ -166,7 +174,8 @@ class TestAdminAPI:
|
||||
data = response.json()
|
||||
assert len(data) >= 1
|
||||
assert all(
|
||||
job["marketplace"] == test_marketplace_import_job.marketplace for job in data
|
||||
job["marketplace"] == test_marketplace_import_job.marketplace
|
||||
for job in data
|
||||
)
|
||||
|
||||
def test_get_marketplace_import_jobs_non_admin(self, client, auth_headers):
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# tests/integration/api/v1/test_auth_endpoints.py
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from jose import jwt
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@@ -178,8 +179,7 @@ class TestAuthenticationAPI:
|
||||
def test_get_current_user_invalid_token(self, client):
|
||||
"""Test getting current user with invalid token"""
|
||||
response = client.get(
|
||||
"/api/v1/auth/me",
|
||||
headers={"Authorization": "Bearer invalid_token_here"}
|
||||
"/api/v1/auth/me", headers={"Authorization": "Bearer invalid_token_here"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
@@ -201,14 +201,11 @@ class TestAuthenticationAPI:
|
||||
}
|
||||
|
||||
expired_token = jwt.encode(
|
||||
expired_payload,
|
||||
auth_manager.secret_key,
|
||||
algorithm=auth_manager.algorithm
|
||||
expired_payload, auth_manager.secret_key, algorithm=auth_manager.algorithm
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/auth/me",
|
||||
headers={"Authorization": f"Bearer {expired_token}"}
|
||||
"/api/v1/auth/me", headers={"Authorization": f"Bearer {expired_token}"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
@@ -321,4 +318,3 @@ class TestAuthManager:
|
||||
user = auth_manager.authenticate_user(db, "nonexistent", "password")
|
||||
|
||||
assert user is None
|
||||
|
||||
|
||||
@@ -14,19 +14,34 @@ class TestFiltering:
|
||||
"""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"),
|
||||
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)
|
||||
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
|
||||
@@ -37,7 +52,9 @@ class TestFiltering:
|
||||
assert product["brand"] == "BrandA"
|
||||
|
||||
# Filter by BrandB
|
||||
response = client.get("/api/v1/marketplace/product?brand=BrandB", headers=auth_headers)
|
||||
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
|
||||
@@ -45,37 +62,57 @@ class TestFiltering:
|
||||
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"),
|
||||
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)
|
||||
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)]
|
||||
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"
|
||||
description="Smartphone",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"SEARCH2_{unique_suffix}",
|
||||
@@ -85,7 +122,7 @@ class TestFiltering:
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"SEARCH3_{unique_suffix}",
|
||||
title=f"iPad Tablet {unique_suffix}",
|
||||
description="Apple tablet"
|
||||
description="Apple tablet",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -93,13 +130,17 @@ class TestFiltering:
|
||||
db.commit()
|
||||
|
||||
# Search for "Apple"
|
||||
response = client.get(f"/api/v1/marketplace/product?search=Apple", headers=auth_headers)
|
||||
response = client.get(
|
||||
f"/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(f"/api/v1/marketplace/product?search=phone", headers=auth_headers)
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/product?search=phone", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 2 # iPhone and Galaxy
|
||||
@@ -107,6 +148,7 @@ class TestFiltering:
|
||||
def test_combined_filters_success(self, client, auth_headers, db):
|
||||
"""Test combining multiple filters successfully"""
|
||||
import uuid
|
||||
|
||||
unique_suffix = str(uuid.uuid4())[:8]
|
||||
|
||||
products = [
|
||||
@@ -135,14 +177,19 @@ class TestFiltering:
|
||||
|
||||
# Filter by brand AND marketplace
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=Apple&marketplace=Amazon", headers=auth_headers
|
||||
"/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)]
|
||||
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"
|
||||
@@ -150,7 +197,8 @@ class TestFiltering:
|
||||
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
|
||||
"/api/v1/marketplace/product?brand=NonexistentBrand123456",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -161,6 +209,7 @@ class TestFiltering:
|
||||
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(
|
||||
@@ -174,7 +223,10 @@ class TestFiltering:
|
||||
|
||||
# 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)
|
||||
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
|
||||
@@ -183,9 +235,14 @@ class TestFiltering:
|
||||
"""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)
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=<script>alert('test')</script>",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200 # Should handle gracefully
|
||||
|
||||
@@ -17,7 +17,9 @@ class TestInventoryAPI:
|
||||
"quantity": 100,
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/inventory", headers=auth_headers, json=inventory_data)
|
||||
response = client.post(
|
||||
"/api/v1/inventory", headers=auth_headers, json=inventory_data
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -38,7 +40,9 @@ class TestInventoryAPI:
|
||||
"quantity": 75,
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/inventory", headers=auth_headers, json=inventory_data)
|
||||
response = client.post(
|
||||
"/api/v1/inventory", headers=auth_headers, json=inventory_data
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -52,7 +56,9 @@ class TestInventoryAPI:
|
||||
"quantity": 100,
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/inventory", headers=auth_headers, json=inventory_data)
|
||||
response = client.post(
|
||||
"/api/v1/inventory", headers=auth_headers, json=inventory_data
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
@@ -60,7 +66,9 @@ class TestInventoryAPI:
|
||||
assert data["status_code"] == 422
|
||||
assert "GTIN is required" in data["message"]
|
||||
|
||||
def test_set_inventory_invalid_quantity_validation_error(self, client, auth_headers):
|
||||
def test_set_inventory_invalid_quantity_validation_error(
|
||||
self, client, auth_headers
|
||||
):
|
||||
"""Test setting inventory with invalid quantity returns InvalidQuantityException"""
|
||||
inventory_data = {
|
||||
"gtin": "1234567890123",
|
||||
@@ -68,7 +76,9 @@ class TestInventoryAPI:
|
||||
"quantity": -10, # Negative quantity
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/inventory", headers=auth_headers, json=inventory_data)
|
||||
response = client.post(
|
||||
"/api/v1/inventory", headers=auth_headers, json=inventory_data
|
||||
)
|
||||
|
||||
assert response.status_code in [400, 422]
|
||||
data = response.json()
|
||||
@@ -138,7 +148,9 @@ class TestInventoryAPI:
|
||||
data = response.json()
|
||||
assert data["quantity"] == 35 # 50 - 15
|
||||
|
||||
def test_remove_inventory_insufficient_returns_business_logic_error(self, client, auth_headers, db):
|
||||
def test_remove_inventory_insufficient_returns_business_logic_error(
|
||||
self, client, auth_headers, db
|
||||
):
|
||||
"""Test removing more inventory than available returns InsufficientInventoryException"""
|
||||
# Create initial inventory
|
||||
inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=10)
|
||||
@@ -184,7 +196,9 @@ class TestInventoryAPI:
|
||||
assert data["error_code"] == "INVENTORY_NOT_FOUND"
|
||||
assert data["status_code"] == 404
|
||||
|
||||
def test_negative_inventory_not_allowed_business_logic_error(self, client, auth_headers, db):
|
||||
def test_negative_inventory_not_allowed_business_logic_error(
|
||||
self, client, auth_headers, db
|
||||
):
|
||||
"""Test operations resulting in negative inventory returns NegativeInventoryException"""
|
||||
# Create initial inventory
|
||||
inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=5)
|
||||
@@ -204,14 +218,21 @@ class TestInventoryAPI:
|
||||
assert response.status_code == 400
|
||||
data = response.json()
|
||||
# This might be caught as INSUFFICIENT_INVENTORY or NEGATIVE_INVENTORY_NOT_ALLOWED
|
||||
assert data["error_code"] in ["INSUFFICIENT_INVENTORY", "NEGATIVE_INVENTORY_NOT_ALLOWED"]
|
||||
assert data["error_code"] in [
|
||||
"INSUFFICIENT_INVENTORY",
|
||||
"NEGATIVE_INVENTORY_NOT_ALLOWED",
|
||||
]
|
||||
assert data["status_code"] == 400
|
||||
|
||||
def test_get_inventory_by_gtin_success(self, client, auth_headers, db):
|
||||
"""Test getting inventory summary for GTIN successfully"""
|
||||
# Create inventory in multiple locations
|
||||
inventory1 = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
|
||||
inventory2 = Inventory(gtin="1234567890123", location="WAREHOUSE_B", quantity=25)
|
||||
inventory1 = Inventory(
|
||||
gtin="1234567890123", location="WAREHOUSE_A", quantity=50
|
||||
)
|
||||
inventory2 = Inventory(
|
||||
gtin="1234567890123", location="WAREHOUSE_B", quantity=25
|
||||
)
|
||||
db.add_all([inventory1, inventory2])
|
||||
db.commit()
|
||||
|
||||
@@ -238,12 +259,18 @@ class TestInventoryAPI:
|
||||
def test_get_total_inventory_success(self, client, auth_headers, db):
|
||||
"""Test getting total inventory for GTIN successfully"""
|
||||
# Create inventory in multiple locations
|
||||
inventory1 = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
|
||||
inventory2 = Inventory(gtin="1234567890123", location="WAREHOUSE_B", quantity=25)
|
||||
inventory1 = Inventory(
|
||||
gtin="1234567890123", location="WAREHOUSE_A", quantity=50
|
||||
)
|
||||
inventory2 = Inventory(
|
||||
gtin="1234567890123", location="WAREHOUSE_B", quantity=25
|
||||
)
|
||||
db.add_all([inventory1, inventory2])
|
||||
db.commit()
|
||||
|
||||
response = client.get("/api/v1/inventory/1234567890123/total", headers=auth_headers)
|
||||
response = client.get(
|
||||
"/api/v1/inventory/1234567890123/total", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -253,7 +280,9 @@ class TestInventoryAPI:
|
||||
|
||||
def test_get_total_inventory_not_found(self, client, auth_headers):
|
||||
"""Test getting total inventory for nonexistent GTIN returns InventoryNotFoundException"""
|
||||
response = client.get("/api/v1/inventory/9999999999999/total", headers=auth_headers)
|
||||
response = client.get(
|
||||
"/api/v1/inventory/9999999999999/total", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
@@ -263,8 +292,12 @@ class TestInventoryAPI:
|
||||
def test_get_all_inventory_success(self, client, auth_headers, db):
|
||||
"""Test getting all inventory entries successfully"""
|
||||
# Create some inventory entries
|
||||
inventory1 = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
|
||||
inventory2 = Inventory(gtin="9876543210987", location="WAREHOUSE_B", quantity=25)
|
||||
inventory1 = Inventory(
|
||||
gtin="1234567890123", location="WAREHOUSE_A", quantity=50
|
||||
)
|
||||
inventory2 = Inventory(
|
||||
gtin="9876543210987", location="WAREHOUSE_B", quantity=25
|
||||
)
|
||||
db.add_all([inventory1, inventory2])
|
||||
db.commit()
|
||||
|
||||
@@ -277,20 +310,28 @@ class TestInventoryAPI:
|
||||
def test_get_all_inventory_with_filters(self, client, auth_headers, db):
|
||||
"""Test getting inventory entries with filtering"""
|
||||
# Create inventory entries
|
||||
inventory1 = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
|
||||
inventory2 = Inventory(gtin="9876543210987", location="WAREHOUSE_B", quantity=25)
|
||||
inventory1 = Inventory(
|
||||
gtin="1234567890123", location="WAREHOUSE_A", quantity=50
|
||||
)
|
||||
inventory2 = Inventory(
|
||||
gtin="9876543210987", location="WAREHOUSE_B", quantity=25
|
||||
)
|
||||
db.add_all([inventory1, inventory2])
|
||||
db.commit()
|
||||
|
||||
# Filter by location
|
||||
response = client.get("/api/v1/inventory?location=WAREHOUSE_A", headers=auth_headers)
|
||||
response = client.get(
|
||||
"/api/v1/inventory?location=WAREHOUSE_A", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
for inventory in data:
|
||||
assert inventory["location"] == "WAREHOUSE_A"
|
||||
|
||||
# Filter by GTIN
|
||||
response = client.get("/api/v1/inventory?gtin=1234567890123", headers=auth_headers)
|
||||
response = client.get(
|
||||
"/api/v1/inventory?gtin=1234567890123", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
for inventory in data:
|
||||
@@ -390,7 +431,9 @@ class TestInventoryAPI:
|
||||
"quantity": 100,
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/inventory", headers=auth_headers, json=inventory_data)
|
||||
response = client.post(
|
||||
"/api/v1/inventory", headers=auth_headers, json=inventory_data
|
||||
)
|
||||
|
||||
# This depends on whether your service validates locations
|
||||
if response.status_code == 404:
|
||||
|
||||
@@ -8,9 +8,11 @@ import pytest
|
||||
@pytest.mark.api
|
||||
@pytest.mark.marketplace
|
||||
class TestMarketplaceImportJobAPI:
|
||||
def test_import_from_marketplace(self, client, auth_headers, test_vendor, test_user):
|
||||
def test_import_from_marketplace(
|
||||
self, client, auth_headers, test_vendor, test_user
|
||||
):
|
||||
"""Test marketplace import endpoint - just test job creation"""
|
||||
# Ensure user owns the vendor
|
||||
# Ensure user owns the vendor
|
||||
test_vendor.owner_user_id = test_user.id
|
||||
|
||||
import_data = {
|
||||
@@ -32,7 +34,7 @@ class TestMarketplaceImportJobAPI:
|
||||
assert data["vendor_id"] == test_vendor.id
|
||||
|
||||
def test_import_from_marketplace_invalid_vendor(self, client, auth_headers):
|
||||
"""Test marketplace import with invalid vendor """
|
||||
"""Test marketplace import with invalid vendor"""
|
||||
import_data = {
|
||||
"url": "https://example.com/products.csv",
|
||||
"marketplace": "TestMarket",
|
||||
@@ -48,7 +50,9 @@ class TestMarketplaceImportJobAPI:
|
||||
assert data["error_code"] == "VENDOR_NOT_FOUND"
|
||||
assert "NONEXISTENT" in data["message"]
|
||||
|
||||
def test_import_from_marketplace_unauthorized_vendor(self, client, auth_headers, test_vendor, other_user):
|
||||
def test_import_from_marketplace_unauthorized_vendor(
|
||||
self, client, auth_headers, test_vendor, other_user
|
||||
):
|
||||
"""Test marketplace import with unauthorized vendor access"""
|
||||
# Set vendor owner to different user
|
||||
test_vendor.owner_user_id = other_user.id
|
||||
@@ -85,8 +89,10 @@ class TestMarketplaceImportJobAPI:
|
||||
assert data["error_code"] == "VALIDATION_ERROR"
|
||||
assert "Request validation failed" in data["message"]
|
||||
|
||||
def test_import_from_marketplace_admin_access(self, client, admin_headers, test_vendor):
|
||||
"""Test that admin can import for any vendor """
|
||||
def test_import_from_marketplace_admin_access(
|
||||
self, client, admin_headers, test_vendor
|
||||
):
|
||||
"""Test that admin can import for any vendor"""
|
||||
import_data = {
|
||||
"url": "https://example.com/products.csv",
|
||||
"marketplace": "AdminMarket",
|
||||
@@ -94,7 +100,9 @@ class TestMarketplaceImportJobAPI:
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/import-product", headers=admin_headers, json=import_data
|
||||
"/api/v1/marketplace/import-product",
|
||||
headers=admin_headers,
|
||||
json=import_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -102,11 +110,13 @@ class TestMarketplaceImportJobAPI:
|
||||
assert data["marketplace"] == "AdminMarket"
|
||||
assert data["vendor_code"] == test_vendor.vendor_code
|
||||
|
||||
def test_get_marketplace_import_status(self, client, auth_headers, test_marketplace_import_job):
|
||||
def test_get_marketplace_import_status(
|
||||
self, client, auth_headers, test_marketplace_import_job
|
||||
):
|
||||
"""Test getting marketplace import status"""
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/import-status/{test_marketplace_import_job.id}",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -118,8 +128,7 @@ class TestMarketplaceImportJobAPI:
|
||||
def test_get_marketplace_import_status_not_found(self, client, auth_headers):
|
||||
"""Test getting status of non-existent import job"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/import-status/99999",
|
||||
headers=auth_headers
|
||||
"/api/v1/marketplace/import-status/99999", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -127,21 +136,25 @@ class TestMarketplaceImportJobAPI:
|
||||
assert data["error_code"] == "IMPORT_JOB_NOT_FOUND"
|
||||
assert "99999" in data["message"]
|
||||
|
||||
def test_get_marketplace_import_status_unauthorized(self, client, auth_headers, test_marketplace_import_job, other_user):
|
||||
def test_get_marketplace_import_status_unauthorized(
|
||||
self, client, auth_headers, test_marketplace_import_job, other_user
|
||||
):
|
||||
"""Test getting status of unauthorized import job"""
|
||||
# Change job owner to other user
|
||||
test_marketplace_import_job.user_id = other_user.id
|
||||
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/import-status/{test_marketplace_import_job.id}",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
assert data["error_code"] == "IMPORT_JOB_NOT_OWNED"
|
||||
|
||||
def test_get_marketplace_import_jobs(self, client, auth_headers, test_marketplace_import_job):
|
||||
def test_get_marketplace_import_jobs(
|
||||
self, client, auth_headers, test_marketplace_import_job
|
||||
):
|
||||
"""Test getting marketplace import jobs"""
|
||||
response = client.get("/api/v1/marketplace/import-jobs", headers=auth_headers)
|
||||
|
||||
@@ -154,11 +167,13 @@ class TestMarketplaceImportJobAPI:
|
||||
job_ids = [job["job_id"] for job in data]
|
||||
assert test_marketplace_import_job.id in job_ids
|
||||
|
||||
def test_get_marketplace_import_jobs_with_filters(self, client, auth_headers, test_marketplace_import_job):
|
||||
def test_get_marketplace_import_jobs_with_filters(
|
||||
self, client, auth_headers, test_marketplace_import_job
|
||||
):
|
||||
"""Test getting import jobs with filters"""
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/import-jobs?marketplace={test_marketplace_import_job.marketplace}",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -167,13 +182,15 @@ class TestMarketplaceImportJobAPI:
|
||||
assert len(data) >= 1
|
||||
|
||||
for job in data:
|
||||
assert test_marketplace_import_job.marketplace.lower() in job["marketplace"].lower()
|
||||
assert (
|
||||
test_marketplace_import_job.marketplace.lower()
|
||||
in job["marketplace"].lower()
|
||||
)
|
||||
|
||||
def test_get_marketplace_import_jobs_pagination(self, client, auth_headers):
|
||||
"""Test import jobs pagination"""
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/import-jobs?skip=0&limit=5",
|
||||
headers=auth_headers
|
||||
"/api/v1/marketplace/import-jobs?skip=0&limit=5", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -181,9 +198,13 @@ class TestMarketplaceImportJobAPI:
|
||||
assert isinstance(data, list)
|
||||
assert len(data) <= 5
|
||||
|
||||
def test_get_marketplace_import_stats(self, client, auth_headers, test_marketplace_import_job):
|
||||
def test_get_marketplace_import_stats(
|
||||
self, client, auth_headers, test_marketplace_import_job
|
||||
):
|
||||
"""Test getting marketplace import statistics"""
|
||||
response = client.get("/api/v1/marketplace/marketplace-import-stats", headers=auth_headers)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/marketplace-import-stats", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -195,12 +216,15 @@ class TestMarketplaceImportJobAPI:
|
||||
assert isinstance(data["total_jobs"], int)
|
||||
assert data["total_jobs"] >= 1
|
||||
|
||||
def test_cancel_marketplace_import_job(self, client, auth_headers, test_user, test_vendor, db):
|
||||
def test_cancel_marketplace_import_job(
|
||||
self, client, auth_headers, test_user, test_vendor, db
|
||||
):
|
||||
"""Test cancelling a marketplace import job"""
|
||||
# Create a pending job that can be cancelled
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
import uuid
|
||||
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
job = MarketplaceImportJob(
|
||||
status="pending",
|
||||
@@ -219,8 +243,7 @@ class TestMarketplaceImportJobAPI:
|
||||
db.refresh(job)
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/marketplace/import-jobs/{job.id}/cancel",
|
||||
headers=auth_headers
|
||||
f"/api/v1/marketplace/import-jobs/{job.id}/cancel", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -232,15 +255,16 @@ class TestMarketplaceImportJobAPI:
|
||||
def test_cancel_marketplace_import_job_not_found(self, client, auth_headers):
|
||||
"""Test cancelling non-existent import job"""
|
||||
response = client.put(
|
||||
"/api/v1/marketplace/import-jobs/99999/cancel",
|
||||
headers=auth_headers
|
||||
"/api/v1/marketplace/import-jobs/99999/cancel", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "IMPORT_JOB_NOT_FOUND"
|
||||
|
||||
def test_cancel_marketplace_import_job_cannot_cancel(self, client, auth_headers, test_marketplace_import_job, db):
|
||||
def test_cancel_marketplace_import_job_cannot_cancel(
|
||||
self, client, auth_headers, test_marketplace_import_job, db
|
||||
):
|
||||
"""Test cancelling a job that cannot be cancelled"""
|
||||
# Set job to completed status
|
||||
test_marketplace_import_job.status = "completed"
|
||||
@@ -248,7 +272,7 @@ class TestMarketplaceImportJobAPI:
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/marketplace/import-jobs/{test_marketplace_import_job.id}/cancel",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
@@ -256,12 +280,15 @@ class TestMarketplaceImportJobAPI:
|
||||
assert data["error_code"] == "IMPORT_JOB_CANNOT_BE_CANCELLED"
|
||||
assert "completed" in data["message"]
|
||||
|
||||
def test_delete_marketplace_import_job(self, client, auth_headers, test_user, test_vendor, db):
|
||||
def test_delete_marketplace_import_job(
|
||||
self, client, auth_headers, test_user, test_vendor, db
|
||||
):
|
||||
"""Test deleting a marketplace import job"""
|
||||
# Create a completed job that can be deleted
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
import uuid
|
||||
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
job = MarketplaceImportJob(
|
||||
status="completed",
|
||||
@@ -280,8 +307,7 @@ class TestMarketplaceImportJobAPI:
|
||||
db.refresh(job)
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1/marketplace/import-jobs/{job.id}",
|
||||
headers=auth_headers
|
||||
f"/api/v1/marketplace/import-jobs/{job.id}", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -291,20 +317,22 @@ class TestMarketplaceImportJobAPI:
|
||||
def test_delete_marketplace_import_job_not_found(self, client, auth_headers):
|
||||
"""Test deleting non-existent import job"""
|
||||
response = client.delete(
|
||||
"/api/v1/marketplace/import-jobs/99999",
|
||||
headers=auth_headers
|
||||
"/api/v1/marketplace/import-jobs/99999", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert data["error_code"] == "IMPORT_JOB_NOT_FOUND"
|
||||
|
||||
def test_delete_marketplace_import_job_cannot_delete(self, client, auth_headers, test_user, test_vendor, db):
|
||||
def test_delete_marketplace_import_job_cannot_delete(
|
||||
self, client, auth_headers, test_user, test_vendor, db
|
||||
):
|
||||
"""Test deleting a job that cannot be deleted"""
|
||||
# Create a pending job that cannot be deleted
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
import uuid
|
||||
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
job = MarketplaceImportJob(
|
||||
status="pending",
|
||||
@@ -323,8 +351,7 @@ class TestMarketplaceImportJobAPI:
|
||||
db.refresh(job)
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1/marketplace/import-jobs/{job.id}",
|
||||
headers=auth_headers
|
||||
f"/api/v1/marketplace/import-jobs/{job.id}", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
@@ -352,7 +379,9 @@ class TestMarketplaceImportJobAPI:
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_TOKEN"
|
||||
|
||||
def test_admin_can_access_all_jobs(self, client, admin_headers, test_marketplace_import_job):
|
||||
def test_admin_can_access_all_jobs(
|
||||
self, client, admin_headers, test_marketplace_import_job
|
||||
):
|
||||
"""Test that admin can access all import jobs"""
|
||||
response = client.get("/api/v1/marketplace/import-jobs", headers=admin_headers)
|
||||
|
||||
@@ -363,23 +392,28 @@ class TestMarketplaceImportJobAPI:
|
||||
job_ids = [job["job_id"] for job in data]
|
||||
assert test_marketplace_import_job.id in job_ids
|
||||
|
||||
def test_admin_can_view_any_job_status(self, client, admin_headers, test_marketplace_import_job):
|
||||
def test_admin_can_view_any_job_status(
|
||||
self, client, admin_headers, test_marketplace_import_job
|
||||
):
|
||||
"""Test that admin can view any job status"""
|
||||
response = client.get(
|
||||
f"/api/v1/marketplace/import-status/{test_marketplace_import_job.id}",
|
||||
headers=admin_headers
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["job_id"] == test_marketplace_import_job.id
|
||||
|
||||
def test_admin_can_cancel_any_job(self, client, admin_headers, test_user, test_vendor, db):
|
||||
def test_admin_can_cancel_any_job(
|
||||
self, client, admin_headers, test_user, test_vendor, db
|
||||
):
|
||||
"""Test that admin can cancel any job"""
|
||||
# Create a pending job owned by different user
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
import uuid
|
||||
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
job = MarketplaceImportJob(
|
||||
status="pending",
|
||||
@@ -398,8 +432,7 @@ class TestMarketplaceImportJobAPI:
|
||||
db.refresh(job)
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/marketplace/import-jobs/{job.id}/cancel",
|
||||
headers=admin_headers
|
||||
f"/api/v1/marketplace/import-jobs/{job.id}/cancel", headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# tests/integration/api/v1/test_export.py
|
||||
import csv
|
||||
from io import StringIO
|
||||
import uuid
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -13,9 +13,13 @@ from models.database.marketplace_product import MarketplaceProduct
|
||||
@pytest.mark.performance # for the performance test
|
||||
class TestExportFunctionality:
|
||||
|
||||
def test_csv_export_basic_success(self, client, auth_headers, test_marketplace_product):
|
||||
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)
|
||||
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"
|
||||
@@ -26,13 +30,22 @@ class TestExportFunctionality:
|
||||
|
||||
# Check header row
|
||||
header = next(csv_reader)
|
||||
expected_fields = ["marketplace_product_id", "title", "description", "price", "marketplace"]
|
||||
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)
|
||||
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):
|
||||
@@ -43,12 +56,12 @@ class TestExportFunctionality:
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"EXP1_{unique_suffix}",
|
||||
title=f"Amazon MarketplaceProduct {unique_suffix}",
|
||||
marketplace="Amazon"
|
||||
marketplace="Amazon",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"EXP2_{unique_suffix}",
|
||||
title=f"eBay MarketplaceProduct {unique_suffix}",
|
||||
marketplace="eBay"
|
||||
marketplace="eBay",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -56,7 +69,8 @@ class TestExportFunctionality:
|
||||
db.commit()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv?marketplace=Amazon", headers=auth_headers
|
||||
"/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"
|
||||
@@ -72,12 +86,12 @@ class TestExportFunctionality:
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"VENDOR1_{unique_suffix}",
|
||||
title=f"Vendor1 MarketplaceProduct {unique_suffix}",
|
||||
vendor_name="TestVendor1"
|
||||
vendor_name="TestVendor1",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"VENDOR2_{unique_suffix}",
|
||||
title=f"Vendor2 MarketplaceProduct {unique_suffix}",
|
||||
vendor_name="TestVendor2"
|
||||
vendor_name="TestVendor2",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -101,19 +115,19 @@ class TestExportFunctionality:
|
||||
marketplace_product_id=f"COMBO1_{unique_suffix}",
|
||||
title=f"Combo MarketplaceProduct 1 {unique_suffix}",
|
||||
marketplace="Amazon",
|
||||
vendor_name="TestVendor"
|
||||
vendor_name="TestVendor",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"COMBO2_{unique_suffix}",
|
||||
title=f"Combo MarketplaceProduct 2 {unique_suffix}",
|
||||
marketplace="eBay",
|
||||
vendor_name="TestVendor"
|
||||
vendor_name="TestVendor",
|
||||
),
|
||||
MarketplaceProduct(
|
||||
marketplace_product_id=f"COMBO3_{unique_suffix}",
|
||||
title=f"Combo MarketplaceProduct 3 {unique_suffix}",
|
||||
marketplace="Amazon",
|
||||
vendor_name="OtherVendor"
|
||||
vendor_name="OtherVendor",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -122,27 +136,27 @@ class TestExportFunctionality:
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?marketplace=Amazon&name=TestVendor",
|
||||
headers=auth_headers
|
||||
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
|
||||
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
|
||||
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')
|
||||
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
|
||||
@@ -161,27 +175,32 @@ class TestExportFunctionality:
|
||||
title=f"Performance MarketplaceProduct {i}",
|
||||
marketplace="Performance",
|
||||
description=f"Performance test product {i}",
|
||||
price="10.99"
|
||||
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]
|
||||
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)
|
||||
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"
|
||||
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")
|
||||
@@ -197,9 +216,13 @@ class TestExportFunctionality:
|
||||
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):
|
||||
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)
|
||||
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"
|
||||
@@ -217,23 +240,27 @@ class TestExportFunctionality:
|
||||
# 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
|
||||
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"
|
||||
price="19.99",
|
||||
)
|
||||
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
response = client.get("/api/v1/marketplace/product/export-csv", headers=auth_headers)
|
||||
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 (
|
||||
f"MarketplaceProduct with quotes and commas {unique_suffix}" in csv_content
|
||||
)
|
||||
assert "Test Market" in csv_content
|
||||
assert "19.99" in csv_content
|
||||
|
||||
@@ -243,7 +270,12 @@ class TestExportFunctionality:
|
||||
header = next(csv_reader)
|
||||
|
||||
# Verify header contains expected fields
|
||||
expected_fields = ["marketplace_product_id", "title", "marketplace", "price"]
|
||||
expected_fields = [
|
||||
"marketplace_product_id",
|
||||
"title",
|
||||
"marketplace",
|
||||
"price",
|
||||
]
|
||||
for field in expected_fields:
|
||||
assert field in header
|
||||
|
||||
@@ -264,12 +296,15 @@ class TestExportFunctionality:
|
||||
|
||||
assert parsed_successfully, "CSV should be parseable despite special characters"
|
||||
|
||||
def test_csv_export_error_handling_service_failure(self, client, auth_headers, monkeypatch):
|
||||
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
|
||||
@@ -293,7 +328,9 @@ class TestExportFunctionality:
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
content_disposition = response.headers.get("content-disposition", "")
|
||||
@@ -302,7 +339,7 @@ class TestExportFunctionality:
|
||||
# Test with marketplace filter
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/export-csv?marketplace=Amazon",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ class TestMarketplaceProductsAPI:
|
||||
assert data["products"] == []
|
||||
assert data["total"] == 0
|
||||
|
||||
def test_get_products_with_data(self, client, auth_headers, test_marketplace_product):
|
||||
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)
|
||||
|
||||
@@ -25,25 +27,39 @@ class TestMarketplaceProductsAPI:
|
||||
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"])
|
||||
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):
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?search=Test", headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] >= 1
|
||||
@@ -71,7 +87,9 @@ class TestMarketplaceProductsAPI:
|
||||
assert data["title"] == "New MarketplaceProduct"
|
||||
assert data["marketplace"] == "Amazon"
|
||||
|
||||
def test_create_product_duplicate_id_returns_conflict(self, client, auth_headers, test_marketplace_product):
|
||||
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,
|
||||
@@ -93,7 +111,10 @@ class TestMarketplaceProductsAPI:
|
||||
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
|
||||
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"""
|
||||
@@ -114,7 +135,9 @@ class TestMarketplaceProductsAPI:
|
||||
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):
|
||||
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
|
||||
@@ -191,20 +214,28 @@ class TestMarketplaceProductsAPI:
|
||||
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):
|
||||
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
|
||||
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"]["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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/NONEXISTENT", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
@@ -214,7 +245,9 @@ class TestMarketplaceProductsAPI:
|
||||
assert data["details"]["resource_type"] == "MarketplaceProduct"
|
||||
assert data["details"]["identifier"] == "NONEXISTENT"
|
||||
|
||||
def test_update_product_success(self, client, auth_headers, test_marketplace_product):
|
||||
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"}
|
||||
|
||||
@@ -247,7 +280,9 @@ class TestMarketplaceProductsAPI:
|
||||
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):
|
||||
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": ""}
|
||||
|
||||
@@ -264,7 +299,9 @@ class TestMarketplaceProductsAPI:
|
||||
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):
|
||||
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"}
|
||||
|
||||
@@ -281,7 +318,9 @@ class TestMarketplaceProductsAPI:
|
||||
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):
|
||||
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"}
|
||||
|
||||
@@ -298,10 +337,13 @@ class TestMarketplaceProductsAPI:
|
||||
assert "Invalid price format" in data["message"]
|
||||
assert data["details"]["field"] == "price"
|
||||
|
||||
def test_delete_product_success(self, client, auth_headers, test_marketplace_product):
|
||||
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
|
||||
f"/api/v1/marketplace/product/{test_marketplace_product.marketplace_product_id}",
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -309,7 +351,9 @@ class TestMarketplaceProductsAPI:
|
||||
|
||||
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)
|
||||
response = client.delete(
|
||||
"/api/v1/marketplace/product/NONEXISTENT", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
@@ -331,7 +375,9 @@ class TestMarketplaceProductsAPI:
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product/NONEXISTENT", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
|
||||
@@ -4,6 +4,7 @@ import pytest
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.database
|
||||
@@ -13,6 +14,7 @@ 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
|
||||
@@ -29,7 +31,9 @@ class TestPagination:
|
||||
db.commit()
|
||||
|
||||
# Test first page
|
||||
response = client.get("/api/v1/marketplace/product?limit=10&skip=0", headers=auth_headers)
|
||||
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
|
||||
@@ -38,21 +42,29 @@ class TestPagination:
|
||||
assert data["limit"] == 10
|
||||
|
||||
# Test second page
|
||||
response = client.get("/api/v1/marketplace/product?limit=10&skip=10", headers=auth_headers)
|
||||
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)
|
||||
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):
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=-1", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
@@ -61,9 +73,13 @@ class TestPagination:
|
||||
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):
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?limit=0", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
@@ -71,9 +87,13 @@ class TestPagination:
|
||||
assert data["status_code"] == 422
|
||||
assert "Request validation failed" in data["message"]
|
||||
|
||||
def test_pagination_boundary_excessive_limit_validation_error(self, client, auth_headers):
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?limit=10000", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
@@ -84,7 +104,9 @@ class TestPagination:
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=10000&limit=10", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -96,6 +118,7 @@ class TestPagination:
|
||||
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
|
||||
@@ -115,7 +138,7 @@ class TestPagination:
|
||||
# Test first page with filter
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=FilterBrand&limit=5&skip=0",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -124,14 +147,18 @@ class TestPagination:
|
||||
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)]
|
||||
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
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -152,6 +179,7 @@ class TestPagination:
|
||||
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
|
||||
@@ -168,14 +196,22 @@ class TestPagination:
|
||||
db.commit()
|
||||
|
||||
# Get first page
|
||||
response1 = client.get("/api/v1/marketplace/product?limit=5&skip=0", headers=auth_headers)
|
||||
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"]]
|
||||
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)
|
||||
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"]]
|
||||
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)
|
||||
@@ -184,11 +220,13 @@ class TestPagination:
|
||||
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
|
||||
from models.database.vendor import Vendor
|
||||
vendors =[]
|
||||
|
||||
vendors = []
|
||||
for i in range(15):
|
||||
vendor = Vendor(
|
||||
vendor_code=f"PAGEVENDOR{i:03d}_{unique_suffix}",
|
||||
@@ -202,9 +240,7 @@ class TestPagination:
|
||||
db.commit()
|
||||
|
||||
# Test first page (assuming admin endpoint exists)
|
||||
response = client.get(
|
||||
"/api/v1/vendor?limit=5&skip=0", headers=admin_headers
|
||||
)
|
||||
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
|
||||
@@ -215,10 +251,12 @@ class TestPagination:
|
||||
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(
|
||||
@@ -249,7 +287,9 @@ class TestPagination:
|
||||
import time
|
||||
|
||||
start_time = time.time()
|
||||
response = client.get("/api/v1/marketplace/product?skip=1000&limit=10", headers=auth_headers)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=1000&limit=10", headers=auth_headers
|
||||
)
|
||||
end_time = time.time()
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -262,19 +302,25 @@ class TestPagination:
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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):
|
||||
@@ -282,7 +328,7 @@ class TestPagination:
|
||||
# Use a filter that should return no results
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?brand=NonexistentBrand999&limit=10&skip=0",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -294,7 +340,9 @@ class TestPagination:
|
||||
|
||||
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)
|
||||
response = client.get(
|
||||
"/api/v1/marketplace/product?skip=-1", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# tests/integration/api/v1/test_stats_endpoints.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.stats
|
||||
@@ -18,7 +19,9 @@ class TestStatsAPI:
|
||||
assert "unique_vendors" in data
|
||||
assert data["total_products"] >= 1
|
||||
|
||||
def test_get_marketplace_stats(self, client, auth_headers, test_marketplace_product):
|
||||
def test_get_marketplace_stats(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test getting marketplace statistics"""
|
||||
response = client.get("/api/v1/stats/marketplace", headers=auth_headers)
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ class TestVendorsAPI:
|
||||
assert data["name"] == "New Vendor"
|
||||
assert data["is_active"] is True
|
||||
|
||||
def test_create_vendor_duplicate_code_returns_conflict(self, client, auth_headers, test_vendor):
|
||||
def test_create_vendor_duplicate_code_returns_conflict(
|
||||
self, client, auth_headers, test_vendor
|
||||
):
|
||||
"""Test creating vendor with duplicate code returns VendorAlreadyExistsException"""
|
||||
vendor_data = {
|
||||
"vendor_code": test_vendor.vendor_code,
|
||||
@@ -40,7 +42,9 @@ class TestVendorsAPI:
|
||||
assert test_vendor.vendor_code in data["message"]
|
||||
assert data["details"]["vendor_code"] == test_vendor.vendor_code
|
||||
|
||||
def test_create_vendor_missing_vendor_code_validation_error(self, client, auth_headers):
|
||||
def test_create_vendor_missing_vendor_code_validation_error(
|
||||
self, client, auth_headers
|
||||
):
|
||||
"""Test creating vendor without vendor_code returns ValidationException"""
|
||||
vendor_data = {
|
||||
"name": "Vendor without Code",
|
||||
@@ -56,7 +60,9 @@ class TestVendorsAPI:
|
||||
assert "Request validation failed" in data["message"]
|
||||
assert "validation_errors" in data["details"]
|
||||
|
||||
def test_create_vendor_empty_vendor_name_validation_error(self, client, auth_headers):
|
||||
def test_create_vendor_empty_vendor_name_validation_error(
|
||||
self, client, auth_headers
|
||||
):
|
||||
"""Test creating vendor with empty name returns VendorValidationException"""
|
||||
vendor_data = {
|
||||
"vendor_code": "EMPTYNAME",
|
||||
@@ -73,7 +79,9 @@ class TestVendorsAPI:
|
||||
assert "Vendor name is required" in data["message"]
|
||||
assert data["details"]["field"] == "name"
|
||||
|
||||
def test_create_vendor_max_vendors_reached_business_logic_error(self, client, auth_headers, db, test_user):
|
||||
def test_create_vendor_max_vendors_reached_business_logic_error(
|
||||
self, client, auth_headers, db, test_user
|
||||
):
|
||||
"""Test creating vendor when max vendors reached returns MaxVendorsReachedException"""
|
||||
# This test would require creating the maximum allowed vendors first
|
||||
# The exact implementation depends on your business rules
|
||||
@@ -94,8 +102,10 @@ class TestVendorsAPI:
|
||||
assert data["total"] >= 1
|
||||
assert len(data["vendors"]) >= 1
|
||||
|
||||
# Find our test vendor
|
||||
test_vendor_found = any(s["vendor_code"] == test_vendor.vendor_code for s in data["vendors"])
|
||||
# Find our test vendor
|
||||
test_vendor_found = any(
|
||||
s["vendor_code"] == test_vendor.vendor_code for s in data["vendors"]
|
||||
)
|
||||
assert test_vendor_found
|
||||
|
||||
def test_get_vendors_with_filters(self, client, auth_headers, test_vendor):
|
||||
@@ -105,7 +115,7 @@ class TestVendorsAPI:
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
for vendor in data["vendors"]:
|
||||
assert vendor ["is_active"] is True
|
||||
assert vendor["is_active"] is True
|
||||
|
||||
# Test verified_only filter
|
||||
response = client.get("/api/v1/vendor?verified_only=true", headers=auth_headers)
|
||||
@@ -135,7 +145,9 @@ class TestVendorsAPI:
|
||||
assert data["details"]["resource_type"] == "Vendor"
|
||||
assert data["details"]["identifier"] == "NONEXISTENT"
|
||||
|
||||
def test_get_vendor_unauthorized_access(self, client, auth_headers, test_vendor, other_user, db):
|
||||
def test_get_vendor_unauthorized_access(
|
||||
self, client, auth_headers, test_vendor, other_user, db
|
||||
):
|
||||
"""Test accessing vendor owned by another user returns UnauthorizedVendorAccessException"""
|
||||
# Change vendor owner to other user AND make it unverified/inactive
|
||||
# so that non-owner users cannot access it
|
||||
@@ -154,7 +166,9 @@ class TestVendorsAPI:
|
||||
assert test_vendor.vendor_code in data["message"]
|
||||
assert data["details"]["vendor_code"] == test_vendor.vendor_code
|
||||
|
||||
def test_get_vendor_unauthorized_access_with_inactive_vendor(self, client, auth_headers, inactive_vendor):
|
||||
def test_get_vendor_unauthorized_access_with_inactive_vendor(
|
||||
self, client, auth_headers, inactive_vendor
|
||||
):
|
||||
"""Test accessing inactive vendor owned by another user returns UnauthorizedVendorAccessException"""
|
||||
# inactive_vendor fixture already creates an unverified, inactive vendor owned by other_user
|
||||
response = client.get(
|
||||
@@ -168,7 +182,9 @@ class TestVendorsAPI:
|
||||
assert inactive_vendor.vendor_code in data["message"]
|
||||
assert data["details"]["vendor_code"] == inactive_vendor.vendor_code
|
||||
|
||||
def test_get_vendor_public_access_allowed(self, client, auth_headers, verified_vendor):
|
||||
def test_get_vendor_public_access_allowed(
|
||||
self, client, auth_headers, verified_vendor
|
||||
):
|
||||
"""Test accessing verified vendor owned by another user is allowed (public access)"""
|
||||
# verified_vendor fixture creates a verified, active vendor owned by other_user
|
||||
# This should allow public access per your business logic
|
||||
@@ -181,7 +197,9 @@ class TestVendorsAPI:
|
||||
assert data["vendor_code"] == verified_vendor.vendor_code
|
||||
assert data["name"] == verified_vendor.name
|
||||
|
||||
def test_add_product_to_vendor_success(self, client, auth_headers, test_vendor, unique_product):
|
||||
def test_add_product_to_vendor_success(
|
||||
self, client, auth_headers, test_vendor, unique_product
|
||||
):
|
||||
"""Test adding product to vendor successfully"""
|
||||
product_data = {
|
||||
"marketplace_product_id": unique_product.marketplace_product_id, # Use string marketplace_product_id, not database id
|
||||
@@ -193,7 +211,7 @@ class TestVendorsAPI:
|
||||
response = client.post(
|
||||
f"/api/v1/vendor/{test_vendor.vendor_code}/products",
|
||||
headers=auth_headers,
|
||||
json=product_data
|
||||
json=product_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -207,10 +225,15 @@ class TestVendorsAPI:
|
||||
|
||||
# MarketplaceProduct details are nested in the 'marketplace_product' field
|
||||
assert "marketplace_product" in data
|
||||
assert data["marketplace_product"]["marketplace_product_id"] == unique_product.marketplace_product_id
|
||||
assert (
|
||||
data["marketplace_product"]["marketplace_product_id"]
|
||||
== unique_product.marketplace_product_id
|
||||
)
|
||||
assert data["marketplace_product"]["id"] == unique_product.id
|
||||
|
||||
def test_add_product_to_vendor_already_exists_conflict(self, client, auth_headers, test_vendor, test_product):
|
||||
def test_add_product_to_vendor_already_exists_conflict(
|
||||
self, client, auth_headers, test_vendor, test_product
|
||||
):
|
||||
"""Test adding product that already exists in vendor returns ProductAlreadyExistsException"""
|
||||
# test_product fixture already creates a relationship, get the marketplace_product_id string
|
||||
existing_product = test_product.marketplace_product
|
||||
@@ -223,7 +246,7 @@ class TestVendorsAPI:
|
||||
response = client.post(
|
||||
f"/api/v1/vendor/{test_vendor.vendor_code}/products",
|
||||
headers=auth_headers,
|
||||
json=product_data
|
||||
json=product_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
@@ -233,7 +256,9 @@ class TestVendorsAPI:
|
||||
assert test_vendor.vendor_code in data["message"]
|
||||
assert existing_product.marketplace_product_id in data["message"]
|
||||
|
||||
def test_add_nonexistent_product_to_vendor_not_found(self, client, auth_headers, test_vendor):
|
||||
def test_add_nonexistent_product_to_vendor_not_found(
|
||||
self, client, auth_headers, test_vendor
|
||||
):
|
||||
"""Test adding nonexistent product to vendor returns MarketplaceProductNotFoundException"""
|
||||
product_data = {
|
||||
"marketplace_product_id": "NONEXISTENT_PRODUCT", # Use string marketplace_product_id that doesn't exist
|
||||
@@ -243,7 +268,7 @@ class TestVendorsAPI:
|
||||
response = client.post(
|
||||
f"/api/v1/vendor/{test_vendor.vendor_code}/products",
|
||||
headers=auth_headers,
|
||||
json=product_data
|
||||
json=product_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -252,11 +277,12 @@ class TestVendorsAPI:
|
||||
assert data["status_code"] == 404
|
||||
assert "NONEXISTENT_PRODUCT" in data["message"]
|
||||
|
||||
def test_get_products_success(self, client, auth_headers, test_vendor, test_product):
|
||||
def test_get_products_success(
|
||||
self, client, auth_headers, test_vendor, test_product
|
||||
):
|
||||
"""Test getting vendor products successfully"""
|
||||
response = client.get(
|
||||
f"/api/v1/vendor/{test_vendor.vendor_code}/products",
|
||||
headers=auth_headers
|
||||
f"/api/v1/vendor/{test_vendor.vendor_code}/products", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -271,22 +297,21 @@ class TestVendorsAPI:
|
||||
# Test active_only filter
|
||||
response = client.get(
|
||||
f"/api/v1/vendor/{test_vendor.vendor_code}/products?active_only=true",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Test featured_only filter
|
||||
response = client.get(
|
||||
f"/api/v1/vendor/{test_vendor.vendor_code}/products?featured_only=true",
|
||||
headers=auth_headers
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_get_products_from_nonexistent_vendor_not_found(self, client, auth_headers):
|
||||
"""Test getting products from nonexistent vendor returns VendorNotFoundException"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/NONEXISTENT/products",
|
||||
headers=auth_headers
|
||||
"/api/v1/vendor/NONEXISTENT/products", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -295,7 +320,9 @@ class TestVendorsAPI:
|
||||
assert data["status_code"] == 404
|
||||
assert "NONEXISTENT" in data["message"]
|
||||
|
||||
def test_vendor_not_active_business_logic_error(self, client, auth_headers, test_vendor, db):
|
||||
def test_vendor_not_active_business_logic_error(
|
||||
self, client, auth_headers, test_vendor, db
|
||||
):
|
||||
"""Test accessing inactive vendor returns VendorNotActiveException (if enforced)"""
|
||||
# Set vendor to inactive
|
||||
test_vendor.is_active = False
|
||||
@@ -313,7 +340,9 @@ class TestVendorsAPI:
|
||||
assert data["status_code"] == 400
|
||||
assert test_vendor.vendor_code in data["message"]
|
||||
|
||||
def test_vendor_not_verified_business_logic_error(self, client, auth_headers, test_vendor, db):
|
||||
def test_vendor_not_verified_business_logic_error(
|
||||
self, client, auth_headers, test_vendor, db
|
||||
):
|
||||
"""Test operations requiring verification returns VendorNotVerifiedException (if enforced)"""
|
||||
# Set vendor to unverified
|
||||
test_vendor.is_verified = False
|
||||
@@ -328,7 +357,7 @@ class TestVendorsAPI:
|
||||
response = client.post(
|
||||
f"/api/v1/vendor/{test_vendor.vendor_code}/products",
|
||||
headers=auth_headers,
|
||||
json=product_data
|
||||
json=product_data,
|
||||
)
|
||||
|
||||
# If your service requires verification for adding products
|
||||
|
||||
@@ -10,8 +10,9 @@ These tests verify that:
|
||||
5. Vendor context middleware works correctly with API authentication
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from jose import jwt
|
||||
|
||||
|
||||
@@ -26,7 +27,9 @@ class TestVendorAPIAuthentication:
|
||||
# Authentication Tests - /api/v1/vendor/auth/me
|
||||
# ========================================================================
|
||||
|
||||
def test_vendor_auth_me_success(self, client, vendor_user_headers, test_vendor_user):
|
||||
def test_vendor_auth_me_success(
|
||||
self, client, vendor_user_headers, test_vendor_user
|
||||
):
|
||||
"""Test /auth/me endpoint with valid vendor user token"""
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=vendor_user_headers)
|
||||
|
||||
@@ -50,7 +53,7 @@ class TestVendorAPIAuthentication:
|
||||
"""Test /auth/me endpoint with invalid token format"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/auth/me",
|
||||
headers={"Authorization": "Bearer invalid_token_here"}
|
||||
headers={"Authorization": "Bearer invalid_token_here"},
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
@@ -66,7 +69,9 @@ class TestVendorAPIAuthentication:
|
||||
assert data["error_code"] == "FORBIDDEN"
|
||||
assert "Admin users cannot access vendor API" in data["message"]
|
||||
|
||||
def test_vendor_auth_me_with_regular_user_token(self, client, auth_headers, test_user):
|
||||
def test_vendor_auth_me_with_regular_user_token(
|
||||
self, client, auth_headers, test_user
|
||||
):
|
||||
"""Test /auth/me endpoint rejects regular users"""
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=auth_headers)
|
||||
|
||||
@@ -88,14 +93,12 @@ class TestVendorAPIAuthentication:
|
||||
}
|
||||
|
||||
expired_token = jwt.encode(
|
||||
expired_payload,
|
||||
auth_manager.secret_key,
|
||||
algorithm=auth_manager.algorithm
|
||||
expired_payload, auth_manager.secret_key, algorithm=auth_manager.algorithm
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/vendor/auth/me",
|
||||
headers={"Authorization": f"Bearer {expired_token}"}
|
||||
headers={"Authorization": f"Bearer {expired_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
@@ -111,8 +114,7 @@ class TestVendorAPIAuthentication:
|
||||
):
|
||||
"""Test dashboard stats with valid vendor authentication"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
"/api/v1/vendor/dashboard/stats", headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -131,10 +133,7 @@ class TestVendorAPIAuthentication:
|
||||
|
||||
def test_vendor_dashboard_stats_with_admin(self, client, admin_headers):
|
||||
"""Test dashboard stats rejects admin users"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=admin_headers
|
||||
)
|
||||
response = client.get("/api/v1/vendor/dashboard/stats", headers=admin_headers)
|
||||
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
@@ -145,10 +144,7 @@ class TestVendorAPIAuthentication:
|
||||
# Login to get session cookie
|
||||
login_response = client.post(
|
||||
"/api/v1/vendor/auth/login",
|
||||
json={
|
||||
"username": test_vendor_user.username,
|
||||
"password": "vendorpass123"
|
||||
}
|
||||
json={"username": test_vendor_user.username, "password": "vendorpass123"},
|
||||
)
|
||||
assert login_response.status_code == 200
|
||||
|
||||
@@ -169,10 +165,7 @@ class TestVendorAPIAuthentication:
|
||||
# Get a valid session by logging in
|
||||
login_response = client.post(
|
||||
"/api/v1/vendor/auth/login",
|
||||
json={
|
||||
"username": test_vendor_user.username,
|
||||
"password": "vendorpass123"
|
||||
}
|
||||
json={"username": test_vendor_user.username, "password": "vendorpass123"},
|
||||
)
|
||||
assert login_response.status_code == 200
|
||||
|
||||
@@ -191,8 +184,9 @@ class TestVendorAPIAuthentication:
|
||||
response = client.get(endpoint)
|
||||
|
||||
# All should fail with 401 (header required)
|
||||
assert response.status_code == 401, \
|
||||
f"Endpoint {endpoint} should reject cookie-only auth"
|
||||
assert (
|
||||
response.status_code == 401
|
||||
), f"Endpoint {endpoint} should reject cookie-only auth"
|
||||
|
||||
# ========================================================================
|
||||
# Role-Based Access Control Tests
|
||||
@@ -211,13 +205,15 @@ class TestVendorAPIAuthentication:
|
||||
for endpoint in endpoints:
|
||||
# Test with regular user token
|
||||
response = client.get(endpoint, headers=auth_headers)
|
||||
assert response.status_code == 403, \
|
||||
f"Endpoint {endpoint} should reject regular users"
|
||||
assert (
|
||||
response.status_code == 403
|
||||
), f"Endpoint {endpoint} should reject regular users"
|
||||
|
||||
# Test with admin token
|
||||
response = client.get(endpoint, headers=admin_headers)
|
||||
assert response.status_code == 403, \
|
||||
f"Endpoint {endpoint} should reject admin users"
|
||||
assert (
|
||||
response.status_code == 403
|
||||
), f"Endpoint {endpoint} should reject admin users"
|
||||
|
||||
def test_vendor_api_accepts_only_vendor_role(
|
||||
self, client, vendor_user_headers, test_vendor_user
|
||||
@@ -229,8 +225,10 @@ class TestVendorAPIAuthentication:
|
||||
|
||||
for endpoint in endpoints:
|
||||
response = client.get(endpoint, headers=vendor_user_headers)
|
||||
assert response.status_code in [200, 404], \
|
||||
f"Endpoint {endpoint} should accept vendor users (got {response.status_code})"
|
||||
assert response.status_code in [
|
||||
200,
|
||||
404,
|
||||
], f"Endpoint {endpoint} should accept vendor users (got {response.status_code})"
|
||||
|
||||
# ========================================================================
|
||||
# Token Validation Tests
|
||||
@@ -248,8 +246,9 @@ class TestVendorAPIAuthentication:
|
||||
|
||||
for headers in malformed_headers:
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=headers)
|
||||
assert response.status_code == 401, \
|
||||
f"Should reject malformed header: {headers}"
|
||||
assert (
|
||||
response.status_code == 401
|
||||
), f"Should reject malformed header: {headers}"
|
||||
|
||||
def test_token_with_missing_claims(self, client, auth_manager):
|
||||
"""Test token missing required claims"""
|
||||
@@ -261,14 +260,12 @@ class TestVendorAPIAuthentication:
|
||||
}
|
||||
|
||||
invalid_token = jwt.encode(
|
||||
invalid_payload,
|
||||
auth_manager.secret_key,
|
||||
algorithm=auth_manager.algorithm
|
||||
invalid_payload, auth_manager.secret_key, algorithm=auth_manager.algorithm
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/vendor/auth/me",
|
||||
headers={"Authorization": f"Bearer {invalid_token}"}
|
||||
headers={"Authorization": f"Bearer {invalid_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
@@ -298,9 +295,7 @@ class TestVendorAPIAuthentication:
|
||||
db.add(test_vendor_user)
|
||||
db.commit()
|
||||
|
||||
def test_concurrent_requests_with_same_token(
|
||||
self, client, vendor_user_headers
|
||||
):
|
||||
def test_concurrent_requests_with_same_token(self, client, vendor_user_headers):
|
||||
"""Test that the same token can be used for multiple concurrent requests"""
|
||||
# Make multiple requests with the same token
|
||||
responses = []
|
||||
@@ -314,10 +309,7 @@ class TestVendorAPIAuthentication:
|
||||
|
||||
def test_vendor_api_with_empty_authorization_header(self, client):
|
||||
"""Test vendor API with empty Authorization header value"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/auth/me",
|
||||
headers={"Authorization": ""}
|
||||
)
|
||||
response = client.get("/api/v1/vendor/auth/me", headers={"Authorization": ""})
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
@@ -328,17 +320,12 @@ class TestVendorAPIAuthentication:
|
||||
class TestVendorAPIConsistency:
|
||||
"""Test that all vendor API endpoints use consistent authentication"""
|
||||
|
||||
def test_all_vendor_endpoints_require_header_auth(
|
||||
self, client, test_vendor_user
|
||||
):
|
||||
def test_all_vendor_endpoints_require_header_auth(self, client, test_vendor_user):
|
||||
"""Verify all vendor API endpoints require Authorization header"""
|
||||
# Login to establish session
|
||||
client.post(
|
||||
"/api/v1/vendor/auth/login",
|
||||
json={
|
||||
"username": test_vendor_user.username,
|
||||
"password": "vendorpass123"
|
||||
}
|
||||
json={"username": test_vendor_user.username, "password": "vendorpass123"},
|
||||
)
|
||||
|
||||
# All vendor API endpoints (excluding public endpoints like /info)
|
||||
@@ -361,8 +348,9 @@ class TestVendorAPIConsistency:
|
||||
response = client.post(endpoint, json={})
|
||||
|
||||
# All should reject cookie-only auth with 401
|
||||
assert response.status_code == 401, \
|
||||
f"Endpoint {endpoint} should require Authorization header (got {response.status_code})"
|
||||
assert (
|
||||
response.status_code == 401
|
||||
), f"Endpoint {endpoint} should require Authorization header (got {response.status_code})"
|
||||
|
||||
def test_vendor_endpoints_accept_vendor_token(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user
|
||||
@@ -380,5 +368,7 @@ class TestVendorAPIConsistency:
|
||||
response = client.get(endpoint, headers=vendor_user_headers)
|
||||
|
||||
# Should not be authentication/authorization errors
|
||||
assert response.status_code not in [401, 403], \
|
||||
f"Endpoint {endpoint} should accept vendor token (got {response.status_code}: {response.text})"
|
||||
assert response.status_code not in [
|
||||
401,
|
||||
403,
|
||||
], f"Endpoint {endpoint} should accept vendor token (got {response.status_code}: {response.text})"
|
||||
|
||||
@@ -23,8 +23,7 @@ class TestVendorDashboardAPI:
|
||||
):
|
||||
"""Test dashboard stats returns correct data structure"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
"/api/v1/vendor/dashboard/stats", headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -66,9 +65,9 @@ class TestVendorDashboardAPI:
|
||||
self, client, db, test_vendor_user, auth_manager
|
||||
):
|
||||
"""Test that dashboard stats only show data for the authenticated vendor"""
|
||||
from models.database.vendor import Vendor, VendorUser
|
||||
from models.database.product import Product
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.product import Product
|
||||
from models.database.vendor import Vendor, VendorUser
|
||||
|
||||
# Create two separate vendors with different data
|
||||
vendor1 = Vendor(
|
||||
@@ -118,10 +117,7 @@ class TestVendorDashboardAPI:
|
||||
vendor1_headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
||||
|
||||
# Get stats for vendor1
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor1_headers
|
||||
)
|
||||
response = client.get("/api/v1/vendor/dashboard/stats", headers=vendor1_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
@@ -130,9 +126,7 @@ class TestVendorDashboardAPI:
|
||||
assert data["vendor"]["id"] == vendor1.id
|
||||
assert data["products"]["total"] == 3
|
||||
|
||||
def test_dashboard_stats_without_vendor_association(
|
||||
self, client, db, auth_manager
|
||||
):
|
||||
def test_dashboard_stats_without_vendor_association(self, client, db, auth_manager):
|
||||
"""Test dashboard stats for user not associated with any vendor"""
|
||||
from models.database.user import User
|
||||
|
||||
@@ -206,8 +200,7 @@ class TestVendorDashboardAPI:
|
||||
):
|
||||
"""Test dashboard stats for vendor with no data"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
"/api/v1/vendor/dashboard/stats", headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -224,8 +217,8 @@ class TestVendorDashboardAPI:
|
||||
self, client, db, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Test dashboard stats accuracy with actual products"""
|
||||
from models.database.product import Product
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.product import Product
|
||||
|
||||
# Create marketplace products
|
||||
mp = MarketplaceProduct(
|
||||
@@ -249,8 +242,7 @@ class TestVendorDashboardAPI:
|
||||
|
||||
# Get stats
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
"/api/v1/vendor/dashboard/stats", headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -267,8 +259,7 @@ class TestVendorDashboardAPI:
|
||||
|
||||
start_time = time.time()
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
"/api/v1/vendor/dashboard/stats", headers=vendor_user_headers
|
||||
)
|
||||
end_time = time.time()
|
||||
|
||||
@@ -284,8 +275,7 @@ class TestVendorDashboardAPI:
|
||||
responses = []
|
||||
for _ in range(3):
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
"/api/v1/vendor/dashboard/stats", headers=vendor_user_headers
|
||||
)
|
||||
responses.append(response)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user