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:
2025-11-28 19:30:17 +01:00
parent 13f0094743
commit 21c13ca39b
236 changed files with 8450 additions and 6545 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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})"

View File

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