shop product refactoring
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user