shop product refactoring

This commit is contained in:
2025-10-04 23:38:53 +02:00
parent 4d2866af5e
commit 0114b6c46e
68 changed files with 2234 additions and 2236 deletions

View File

@@ -1,6 +1,6 @@
# tests/system/test_error_handling.py
"""
System tests for error handling across the LetzShop API.
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.
@@ -16,7 +16,7 @@ class TestErrorHandling:
def test_invalid_json_request(self, client, auth_headers):
"""Test handling of malformed JSON requests"""
response = client.post(
"/api/v1/shop",
"/api/v1/vendor ",
headers=auth_headers,
content="{ invalid json syntax"
)
@@ -27,13 +27,13 @@ class TestErrorHandling:
assert data["message"] == "Request validation failed"
assert "validation_errors" in data["details"]
def test_missing_required_fields_shop_creation(self, client, auth_headers):
def test_missing_required_fields_vendor_creation(self, client, auth_headers):
"""Test validation errors for missing required fields"""
# Missing shop_name
# Missing vendor_name
response = client.post(
"/api/v1/shop",
"/api/v1/vendor ",
headers=auth_headers,
json={"shop_code": "TESTSHOP"}
json={"vendor_code": "TESTSHOP"}
)
assert response.status_code == 422
@@ -42,28 +42,28 @@ class TestErrorHandling:
assert data["status_code"] == 422
assert "validation_errors" in data["details"]
def test_invalid_field_format_shop_creation(self, client, auth_headers):
def test_invalid_field_format_vendor_creation(self, client, auth_headers):
"""Test validation errors for invalid field formats"""
# Invalid shop_code format (contains special characters)
# Invalid vendor_code format (contains special characters)
response = client.post(
"/api/v1/shop",
"/api/v1/vendor ",
headers=auth_headers,
json={
"shop_code": "INVALID@SHOP!",
"shop_name": "Test Shop"
"vendor_code": "INVALID@SHOP!",
"vendor_name": "Test Shop"
}
)
assert response.status_code == 422
data = response.json()
assert data["error_code"] == "INVALID_SHOP_DATA"
assert data["error_code"] == "INVALID_VENDOR_DATA"
assert data["status_code"] == 422
assert data["details"]["field"] == "shop_code"
assert data["details"]["field"] == "vendor_code"
assert "letters, numbers, underscores, and hyphens" in data["message"]
def test_missing_authentication_token(self, client):
"""Test authentication required endpoints without token"""
response = client.get("/api/v1/shop")
response = client.get("/api/v1/vendor ")
assert response.status_code == 401
data = response.json()
@@ -73,7 +73,7 @@ class TestErrorHandling:
def test_invalid_authentication_token(self, client):
"""Test endpoints with invalid JWT token"""
headers = {"Authorization": "Bearer invalid_token_here"}
response = client.get("/api/v1/shop", headers=headers)
response = client.get("/api/v1/vendor ", headers=headers)
assert response.status_code == 401
data = response.json()
@@ -85,19 +85,19 @@ class TestErrorHandling:
# This would require creating an expired token for testing
expired_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.expired.token"
headers = {"Authorization": f"Bearer {expired_token}"}
response = client.get("/api/v1/shop", headers=headers)
response = client.get("/api/v1/vendor ", headers=headers)
assert response.status_code == 401
data = response.json()
assert data["status_code"] == 401
def test_shop_not_found(self, client, auth_headers):
"""Test accessing non-existent shop"""
response = client.get("/api/v1/shop/NONEXISTENT", headers=auth_headers)
def test_vendor_not_found(self, client, auth_headers):
"""Test accessing non-existent vendor """
response = client.get("/api/v1/vendor /NONEXISTENT", headers=auth_headers)
assert response.status_code == 404
data = response.json()
assert data["error_code"] == "SHOP_NOT_FOUND"
assert data["error_code"] == "VENDOR_NOT_FOUND"
assert data["status_code"] == 404
assert data["details"]["resource_type"] == "Shop"
assert data["details"]["identifier"] == "NONEXISTENT"
@@ -113,20 +113,20 @@ class TestErrorHandling:
assert data["details"]["resource_type"] == "MarketplaceProduct"
assert data["details"]["identifier"] == "NONEXISTENT"
def test_duplicate_shop_creation(self, client, auth_headers, test_shop):
"""Test creating shop with duplicate shop code"""
shop_data = {
"shop_code": test_shop.shop_code,
"shop_name": "Duplicate Shop"
def test_duplicate_vendor_creation(self, client, auth_headers, test_vendor):
"""Test creating vendor with duplicate vendor code"""
vendor_data = {
"vendor_code": test_vendor.vendor_code,
"vendor_name": "Duplicate Shop"
}
response = client.post("/api/v1/shop", headers=auth_headers, json=shop_data)
response = client.post("/api/v1/vendor ", headers=auth_headers, json=vendor_data)
assert response.status_code == 409
data = response.json()
assert data["error_code"] == "SHOP_ALREADY_EXISTS"
assert data["error_code"] == "VENDOR_ALREADY_EXISTS"
assert data["status_code"] == 409
assert data["details"]["shop_code"] == test_shop.shop_code.upper()
assert data["details"]["vendor_code"] == test_vendor.vendor_code.upper()
def test_duplicate_product_creation(self, client, auth_headers, test_marketplace_product):
"""Test creating product with duplicate product ID"""
@@ -144,15 +144,15 @@ class TestErrorHandling:
assert data["status_code"] == 409
assert data["details"]["marketplace_product_id"] == test_marketplace_product.marketplace_product_id
def test_unauthorized_shop_access(self, client, auth_headers, inactive_shop):
"""Test accessing shop without proper permissions"""
response = client.get(f"/api/v1/shop/{inactive_shop.shop_code}", headers=auth_headers)
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)
assert response.status_code == 403
data = response.json()
assert data["error_code"] == "UNAUTHORIZED_SHOP_ACCESS"
assert data["error_code"] == "UNAUTHORIZED_VENDOR_ACCESS"
assert data["status_code"] == 403
assert data["details"]["shop_code"] == inactive_shop.shop_code
assert data["details"]["vendor_code"] == inactive_vendor.vendor_code
def test_insufficient_permissions(self, client, auth_headers, admin_only_endpoint="/api/v1/admin/users"):
"""Test accessing admin endpoints with regular user"""
@@ -164,29 +164,29 @@ class TestErrorHandling:
assert data["error_code"] in ["ADMIN_REQUIRED", "INSUFFICIENT_PERMISSIONS"]
assert data["status_code"] == 403
def test_business_logic_violation_max_shops(self, client, auth_headers, monkeypatch):
"""Test business logic violation - creating too many shops"""
# This test would require mocking the shop limit check
# For now, test the error structure when creating multiple shops
shops_created = []
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
shop_data = {
"shop_code": f"SHOP{i:03d}",
"shop_name": f"Test Shop {i}"
vendor_data = {
"vendor_code": f"SHOP{i:03d}",
"vendor_name": f"Test Vendor {i}"
}
response = client.post("/api/v1/shop", headers=auth_headers, json=shop_data)
shops_created.append(response)
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
success_count = sum(1 for r in shops_created if r.status_code in [200, 201])
success_count = sum(1 for r in vendors_created if r.status_code in [200, 201])
assert success_count >= 1
# If any failed due to limit, check error structure
failed_responses = [r for r in shops_created if r.status_code == 400]
failed_responses = [r for r in vendors_created if r.status_code == 400]
if failed_responses:
data = failed_responses[0].json()
assert data["error_code"] == "MAX_SHOPS_REACHED"
assert "max_shops" in data["details"]
assert data["error_code"] == "MAX_VENDORS_REACHED"
assert "max_vendors" in data["details"]
def test_validation_error_invalid_gtin(self, client, auth_headers):
"""Test validation error for invalid GTIN format"""
@@ -204,7 +204,7 @@ class TestErrorHandling:
assert data["status_code"] == 422
assert data["details"]["field"] == "gtin"
def test_stock_insufficient_quantity(self, client, auth_headers, test_shop, test_marketplace_product):
def test_stock_insufficient_quantity(self, client, auth_headers, test_vendor, test_marketplace_product):
"""Test business logic error for insufficient stock"""
# First create some stock
stock_data = {
@@ -246,7 +246,7 @@ class TestErrorHandling:
def test_method_not_allowed(self, client, auth_headers):
"""Test 405 for wrong HTTP method on existing endpoints"""
# Try DELETE on an endpoint that only supports GET
response = client.delete("/api/v1/shop", headers=auth_headers)
response = client.delete("/api/v1/vendor ", headers=auth_headers)
assert response.status_code == 405
# FastAPI automatically handles 405 errors
@@ -255,9 +255,9 @@ class TestErrorHandling:
"""Test handling of unsupported content types"""
headers = {**auth_headers, "Content-Type": "application/xml"}
response = client.post(
"/api/v1/shop",
"/api/v1/vendor ",
headers=headers,
content="<shop><code>TEST</code></shop>"
content="<vendor ><code>TEST</code></vendor >"
)
assert response.status_code in [400, 415, 422]
@@ -265,13 +265,13 @@ class TestErrorHandling:
def test_large_payload_handling(self, client, auth_headers):
"""Test handling of unusually large payloads"""
large_description = "x" * 100000 # Very long description
shop_data = {
"shop_code": "LARGESHOP",
"shop_name": "Large Shop",
vendor_data = {
"vendor_code": "LARGESHOP",
"vendor_name": "Large Shop",
"description": large_description
}
response = client.post("/api/v1/shop", headers=auth_headers, json=shop_data)
response = client.post("/api/v1/vendor ", headers=auth_headers, json=vendor_data)
# Should either accept it or reject with appropriate error
assert response.status_code in [200, 201, 413, 422]
@@ -285,7 +285,7 @@ class TestErrorHandling:
# Make rapid requests to potentially trigger rate limiting
responses = []
for _ in range(50): # Aggressive request count
response = client.get("/api/v1/shop", headers=auth_headers)
response = client.get("/api/v1/vendor ", headers=auth_headers)
responses.append(response)
# Check if any rate limiting occurred and verify error structure
@@ -305,12 +305,12 @@ class TestErrorHandling:
response = client.get("/health")
assert response.status_code == 200
def test_marketplace_import_errors(self, client, auth_headers, test_shop):
def test_marketplace_import_errors(self, client, auth_headers, test_vendor):
"""Test marketplace import specific errors"""
# Test invalid marketplace
import_data = {
"marketplace": "INVALID_MARKETPLACE",
"shop_code": test_shop.shop_code
"vendor_code": test_vendor.vendor_code
}
response = client.post("/api/v1/imports", headers=auth_headers, json=import_data)
@@ -344,7 +344,7 @@ class TestErrorHandling:
def test_error_response_consistency(self, client, auth_headers):
"""Test that all error responses follow consistent structure"""
test_cases = [
("/api/v1/shop/NONEXISTENT", 404),
("/api/v1/vendor /NONEXISTENT", 404),
("/api/v1/marketplace/product/NONEXISTENT", 404),
]
@@ -365,7 +365,7 @@ class TestErrorHandling:
def test_cors_error_handling(self, client):
"""Test CORS errors are handled properly"""
# Test preflight request
response = client.options("/api/v1/shop")
response = client.options("/api/v1/vendor ")
# Should either succeed or be handled gracefully
assert response.status_code in [200, 204, 405]
@@ -373,7 +373,7 @@ class TestErrorHandling:
def test_authentication_error_details(self, client):
"""Test authentication error provides helpful details"""
# Test missing Authorization header
response = client.get("/api/v1/shop")
response = client.get("/api/v1/vendor ")
assert response.status_code == 401
data = response.json()
@@ -406,7 +406,7 @@ class TestErrorRecovery:
assert health_response.status_code == 200
# API endpoints may or may not work depending on system state
api_response = client.get("/api/v1/shop", headers=auth_headers)
api_response = client.get("/api/v1/vendor ", headers=auth_headers)
# Should get either data or a proper error, not a crash
assert api_response.status_code in [200, 401, 403, 500, 503]
@@ -416,7 +416,7 @@ class TestErrorRecovery:
with caplog.at_level(logging.ERROR):
# Trigger an error
client.get("/api/v1/shop/NONEXISTENT", headers=auth_headers)
client.get("/api/v1/vendor /NONEXISTENT", headers=auth_headers)
# Check that error was logged (if your app logs 404s as errors)
# Adjust based on your logging configuration