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:
@@ -5,9 +5,10 @@ System tests for error handling across the LetzVendor API.
|
||||
Tests the complete error handling flow from FastAPI through custom exception handlers
|
||||
to ensure proper HTTP status codes, error structures, and client-friendly responses.
|
||||
"""
|
||||
import pytest
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.system
|
||||
class TestErrorHandling:
|
||||
@@ -16,9 +17,7 @@ class TestErrorHandling:
|
||||
def test_invalid_json_request(self, client, auth_headers):
|
||||
"""Test handling of malformed JSON requests"""
|
||||
response = client.post(
|
||||
"/api/v1/vendor",
|
||||
headers=auth_headers,
|
||||
content="{ invalid json syntax"
|
||||
"/api/v1/vendor", headers=auth_headers, content="{ invalid json syntax"
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
@@ -31,9 +30,7 @@ class TestErrorHandling:
|
||||
"""Test validation errors for missing required fields"""
|
||||
# Missing name
|
||||
response = client.post(
|
||||
"/api/v1/vendor",
|
||||
headers=auth_headers,
|
||||
json={"vendor_code": "TESTVENDOR"}
|
||||
"/api/v1/vendor", headers=auth_headers, json={"vendor_code": "TESTVENDOR"}
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
@@ -48,10 +45,7 @@ class TestErrorHandling:
|
||||
response = client.post(
|
||||
"/api/v1/vendor",
|
||||
headers=auth_headers,
|
||||
json={
|
||||
"vendor_code": "INVALID@VENDOR!",
|
||||
"name": "Test Vendor"
|
||||
}
|
||||
json={"vendor_code": "INVALID@VENDOR!", "name": "Test Vendor"},
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
@@ -92,7 +86,7 @@ class TestErrorHandling:
|
||||
assert data["status_code"] == 401
|
||||
|
||||
def test_vendor_not_found(self, client, auth_headers):
|
||||
"""Test accessing non-existent vendor """
|
||||
"""Test accessing non-existent vendor"""
|
||||
response = client.get("/api/v1/vendor/NONEXISTENT", headers=auth_headers)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -104,7 +98,9 @@ class TestErrorHandling:
|
||||
|
||||
def test_product_not_found(self, client, auth_headers):
|
||||
"""Test accessing non-existent product"""
|
||||
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()
|
||||
@@ -117,7 +113,7 @@ class TestErrorHandling:
|
||||
"""Test creating vendor with duplicate vendor code"""
|
||||
vendor_data = {
|
||||
"vendor_code": test_vendor.vendor_code,
|
||||
"name": "Duplicate Vendor"
|
||||
"name": "Duplicate Vendor",
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/vendor", headers=auth_headers, json=vendor_data)
|
||||
@@ -128,25 +124,34 @@ class TestErrorHandling:
|
||||
assert data["status_code"] == 409
|
||||
assert data["details"]["vendor_code"] == test_vendor.vendor_code.upper()
|
||||
|
||||
def test_duplicate_product_creation(self, client, auth_headers, test_marketplace_product):
|
||||
def test_duplicate_product_creation(
|
||||
self, client, auth_headers, test_marketplace_product
|
||||
):
|
||||
"""Test creating product with duplicate product ID"""
|
||||
product_data = {
|
||||
"marketplace_product_id": test_marketplace_product.marketplace_product_id,
|
||||
"title": "Duplicate MarketplaceProduct",
|
||||
"gtin": "1234567890123"
|
||||
"gtin": "1234567890123",
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/marketplace/product", headers=auth_headers, json=product_data)
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 409
|
||||
data = response.json()
|
||||
assert data["error_code"] == "PRODUCT_ALREADY_EXISTS"
|
||||
assert data["status_code"] == 409
|
||||
assert 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_unauthorized_vendor_access(self, client, auth_headers, inactive_vendor):
|
||||
"""Test accessing vendor without proper permissions"""
|
||||
response = client.get(f"/api/v1/vendor/{inactive_vendor.vendor_code}", headers=auth_headers)
|
||||
response = client.get(
|
||||
f"/api/v1/vendor/{inactive_vendor.vendor_code}", headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
@@ -154,27 +159,33 @@ class TestErrorHandling:
|
||||
assert data["status_code"] == 403
|
||||
assert data["details"]["vendor_code"] == inactive_vendor.vendor_code
|
||||
|
||||
def test_insufficient_permissions(self, client, auth_headers, admin_only_endpoint="/api/v1/admin/users"):
|
||||
def test_insufficient_permissions(
|
||||
self, client, auth_headers, admin_only_endpoint="/api/v1/admin/users"
|
||||
):
|
||||
"""Test accessing admin endpoints with regular user"""
|
||||
response = client.get(admin_only_endpoint, headers=auth_headers)
|
||||
|
||||
assert response.status_code in [403, 404] # 403 for permission denied, 404 if endpoint doesn't exist
|
||||
assert response.status_code in [
|
||||
403,
|
||||
404,
|
||||
] # 403 for permission denied, 404 if endpoint doesn't exist
|
||||
if response.status_code == 403:
|
||||
data = response.json()
|
||||
assert data["error_code"] in ["ADMIN_REQUIRED", "INSUFFICIENT_PERMISSIONS"]
|
||||
assert data["status_code"] == 403
|
||||
|
||||
def test_business_logic_violation_max_vendors(self, client, auth_headers, monkeypatch):
|
||||
def test_business_logic_violation_max_vendors(
|
||||
self, client, auth_headers, monkeypatch
|
||||
):
|
||||
"""Test business logic violation - creating too many vendors"""
|
||||
# This test would require mocking the vendor limit check
|
||||
# For now, test the error structure when creating multiple vendors
|
||||
vendors_created = []
|
||||
for i in range(6): # Assume limit is 5
|
||||
vendor_data = {
|
||||
"vendor_code": f"VENDOR{i:03d}",
|
||||
"name": f"Test Vendor {i}"
|
||||
}
|
||||
response = client.post("/api/v1/vendor", headers=auth_headers, json=vendor_data)
|
||||
vendor_data = {"vendor_code": f"VENDOR{i:03d}", "name": f"Test Vendor {i}"}
|
||||
response = client.post(
|
||||
"/api/v1/vendor", headers=auth_headers, json=vendor_data
|
||||
)
|
||||
vendors_created.append(response)
|
||||
|
||||
# At least one should succeed, and if limit is enforced, later ones should fail
|
||||
@@ -193,10 +204,12 @@ class TestErrorHandling:
|
||||
product_data = {
|
||||
"marketplace_product_id": "TESTPROD001",
|
||||
"title": "Test MarketplaceProduct",
|
||||
"gtin": "invalid_gtin_format"
|
||||
"gtin": "invalid_gtin_format",
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/marketplace/product", headers=auth_headers, json=product_data)
|
||||
response = client.post(
|
||||
"/api/v1/marketplace/product", headers=auth_headers, json=product_data
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
data = response.json()
|
||||
@@ -204,13 +217,15 @@ class TestErrorHandling:
|
||||
assert data["status_code"] == 422
|
||||
assert data["details"]["field"] == "gtin"
|
||||
|
||||
def test_inventory_insufficient_quantity(self, client, auth_headers, test_vendor, test_marketplace_product):
|
||||
def test_inventory_insufficient_quantity(
|
||||
self, client, auth_headers, test_vendor, test_marketplace_product
|
||||
):
|
||||
"""Test business logic error for insufficient inventory"""
|
||||
# First create some inventory
|
||||
inventory_data = {
|
||||
"gtin": test_marketplace_product.gtin,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 5
|
||||
"quantity": 5,
|
||||
}
|
||||
client.post("/api/v1/inventory", headers=auth_headers, json=inventory_data)
|
||||
|
||||
@@ -218,9 +233,11 @@ class TestErrorHandling:
|
||||
remove_data = {
|
||||
"gtin": test_marketplace_product.gtin,
|
||||
"location": "WAREHOUSE_A",
|
||||
"quantity": 10 # More than the 5 we added
|
||||
"quantity": 10, # More than the 5 we added
|
||||
}
|
||||
response = client.post("/api/v1/inventory/remove", headers=auth_headers, json=remove_data)
|
||||
response = client.post(
|
||||
"/api/v1/inventory/remove", headers=auth_headers, json=remove_data
|
||||
)
|
||||
|
||||
# This should ALWAYS fail with insufficient inventory error
|
||||
assert response.status_code == 400
|
||||
@@ -257,7 +274,7 @@ class TestErrorHandling:
|
||||
response = client.post(
|
||||
"/api/v1/vendor",
|
||||
headers=headers,
|
||||
content="<vendor ><code>TEST</code></vendor >"
|
||||
content="<vendor ><code>TEST</code></vendor >",
|
||||
)
|
||||
|
||||
assert response.status_code in [400, 415, 422]
|
||||
@@ -268,7 +285,7 @@ class TestErrorHandling:
|
||||
vendor_data = {
|
||||
"vendor_code": "LARGEVENDOR",
|
||||
"name": "Large Vendor",
|
||||
"description": large_description
|
||||
"description": large_description,
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/vendor", headers=auth_headers, json=vendor_data)
|
||||
@@ -310,10 +327,12 @@ class TestErrorHandling:
|
||||
# Test invalid marketplace
|
||||
import_data = {
|
||||
"marketplace": "INVALID_MARKETPLACE",
|
||||
"vendor_code": test_vendor.vendor_code
|
||||
"vendor_code": test_vendor.vendor_code,
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/imports", headers=auth_headers, json=import_data)
|
||||
response = client.post(
|
||||
"/api/v1/imports", headers=auth_headers, json=import_data
|
||||
)
|
||||
|
||||
if response.status_code == 422:
|
||||
data = response.json()
|
||||
@@ -329,10 +348,12 @@ class TestErrorHandling:
|
||||
# Test with potentially problematic external data
|
||||
import_data = {
|
||||
"marketplace": "LETZSHOP",
|
||||
"external_url": "https://nonexistent-marketplace.com/api"
|
||||
"external_url": "https://nonexistent-marketplace.com/api",
|
||||
}
|
||||
|
||||
response = client.post("/api/v1/imports", headers=auth_headers, json=import_data)
|
||||
response = client.post(
|
||||
"/api/v1/imports", headers=auth_headers, json=import_data
|
||||
)
|
||||
|
||||
# If it's a real external service error, check structure
|
||||
if response.status_code == 502:
|
||||
@@ -356,7 +377,9 @@ class TestErrorHandling:
|
||||
# All error responses should have these fields
|
||||
required_fields = ["error_code", "message", "status_code"]
|
||||
for field in required_fields:
|
||||
assert field in data, f"Missing {field} in error response for {endpoint}"
|
||||
assert (
|
||||
field in data
|
||||
), f"Missing {field} in error response for {endpoint}"
|
||||
|
||||
# Details field should be present (can be empty dict)
|
||||
assert "details" in data
|
||||
@@ -420,5 +443,7 @@ class TestErrorRecovery:
|
||||
|
||||
# Check that error was logged (if your app logs 404s as errors)
|
||||
# Adjust based on your logging configuration
|
||||
error_logs = [record for record in caplog.records if record.levelno >= logging.ERROR]
|
||||
error_logs = [
|
||||
record for record in caplog.records if record.levelno >= logging.ERROR
|
||||
]
|
||||
# May or may not have logs depending on whether 404s are logged as errors
|
||||
|
||||
Reference in New Issue
Block a user