# tests/integration/api/v1/test_vendor_endpoints.py import pytest @pytest.mark.integration @pytest.mark.api @pytest.mark.vendors class TestVendorsAPI: def test_create_vendor_success(self, client, auth_headers): """Test creating a new vendor successfully""" vendor_data = { "vendor_code": "NEWVENDOR001", "name": "New Vendor", "description": "A new test vendor ", } response = client.post("/api/v1/vendor", headers=auth_headers, json=vendor_data) assert response.status_code == 200 data = response.json() assert data["vendor_code"] == "NEWVENDOR001" 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 ): """Test creating vendor with duplicate code returns VendorAlreadyExistsException""" vendor_data = { "vendor_code": test_vendor.vendor_code, "name": "Different Name", "description": "Different description", } response = client.post("/api/v1/vendor", headers=auth_headers, json=vendor_data) assert response.status_code == 409 data = response.json() assert data["error_code"] == "VENDOR_ALREADY_EXISTS" assert data["status_code"] == 409 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 ): """Test creating vendor without vendor_code returns ValidationException""" vendor_data = { "name": "Vendor without Code", "description": "Missing vendor code", } response = client.post("/api/v1/vendor", headers=auth_headers, json=vendor_data) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" assert data["status_code"] == 422 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 ): """Test creating vendor with empty name returns VendorValidationException""" vendor_data = { "vendor_code": "EMPTYNAME", "name": "", # Empty vendor name "description": "Vendor with empty name", } response = client.post("/api/v1/vendor", headers=auth_headers, json=vendor_data) assert response.status_code == 422 data = response.json() assert data["error_code"] == "INVALID_VENDOR_DATA" assert data["status_code"] == 422 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 ): """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 # For now, we'll test the structure of what the error should look like # In a real scenario, you'd create max_vendors number of vendors first # Assuming max vendors is enforced at service level # This test validates the expected response structure # Implementation depends on your max_vendors business logic def test_get_vendors_success(self, client, auth_headers, test_vendor): """Test getting vendors list successfully""" response = client.get("/api/v1/vendor", headers=auth_headers) assert response.status_code == 200 data = response.json() 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"] ) assert test_vendor_found def test_get_vendors_with_filters(self, client, auth_headers, test_vendor): """Test getting vendors with filtering options""" # Test active_only filter response = client.get("/api/v1/vendor?active_only=true", headers=auth_headers) assert response.status_code == 200 data = response.json() for vendor in data["vendors"]: assert vendor["is_active"] is True # Test verified_only filter response = client.get("/api/v1/vendor?verified_only=true", headers=auth_headers) assert response.status_code == 200 # Response should only contain verified vendors def test_get_vendor_by_code_success(self, client, auth_headers, test_vendor): """Test getting specific vendor successfully""" response = client.get( f"/api/v1/vendor/{test_vendor.vendor_code}", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["vendor_code"] == test_vendor.vendor_code assert data["name"] == test_vendor.name def test_get_vendor_by_code_not_found(self, client, auth_headers): """Test getting nonexistent vendor returns VendorNotFoundException""" response = client.get("/api/v1/vendor/NONEXISTENT", headers=auth_headers) assert response.status_code == 404 data = response.json() assert data["error_code"] == "VENDOR_NOT_FOUND" assert data["status_code"] == 404 assert "NONEXISTENT" in data["message"] 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 ): """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 test_vendor.owner_user_id = other_user.id test_vendor.is_verified = False # Make it not publicly accessible db.commit() response = client.get( f"/api/v1/vendor/{test_vendor.vendor_code}", headers=auth_headers ) assert response.status_code == 403 data = response.json() assert data["error_code"] == "UNAUTHORIZED_VENDOR_ACCESS" assert data["status_code"] == 403 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 ): """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( f"/api/v1/vendor/{inactive_vendor.vendor_code}", headers=auth_headers ) assert response.status_code == 403 data = response.json() assert data["error_code"] == "UNAUTHORIZED_VENDOR_ACCESS" assert data["status_code"] == 403 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 ): """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 response = client.get( f"/api/v1/vendor/{verified_vendor.vendor_code}", headers=auth_headers ) assert response.status_code == 200 data = response.json() 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 ): """Test adding product to vendor successfully""" product_data = { "marketplace_product_id": unique_product.marketplace_product_id, # Use string marketplace_product_id, not database id "price": 29.99, "is_active": True, "is_featured": False, } response = client.post( f"/api/v1/vendor/{test_vendor.vendor_code}/products", headers=auth_headers, json=product_data, ) assert response.status_code == 200 data = response.json() # The response structure contains nested product data assert data["vendor_id"] == test_vendor.id assert data["price"] == 29.99 assert data["is_active"] is True assert data["is_featured"] is False # 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"]["id"] == unique_product.id 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 product_data = { "marketplace_product_id": existing_product.marketplace_product_id, # Use string marketplace_product_id "price": 29.99, } response = client.post( f"/api/v1/vendor/{test_vendor.vendor_code}/products", 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 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 ): """Test adding nonexistent product to vendor returns MarketplaceProductNotFoundException""" product_data = { "marketplace_product_id": "NONEXISTENT_PRODUCT", # Use string marketplace_product_id that doesn't exist "price": 29.99, } response = client.post( f"/api/v1/vendor/{test_vendor.vendor_code}/products", headers=auth_headers, json=product_data, ) assert response.status_code == 404 data = response.json() assert data["error_code"] == "PRODUCT_NOT_FOUND" assert data["status_code"] == 404 assert "NONEXISTENT_PRODUCT" in data["message"] 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 ) assert response.status_code == 200 data = response.json() assert data["total"] >= 1 assert len(data["products"]) >= 1 assert "vendor" in data assert data["vendor"]["vendor_code"] == test_vendor.vendor_code def test_get_products_with_filters(self, client, auth_headers, test_vendor): """Test getting vendor products with filtering""" # Test active_only filter response = client.get( f"/api/v1/vendor/{test_vendor.vendor_code}/products?active_only=true", 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, ) 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 ) assert response.status_code == 404 data = response.json() assert data["error_code"] == "VENDOR_NOT_FOUND" 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 ): """Test accessing inactive vendor returns VendorNotActiveException (if enforced)""" # Set vendor to inactive test_vendor.is_active = False db.commit() # Depending on your business logic, this might return an error response = client.get( f"/api/v1/vendor/{test_vendor.vendor_code}", headers=auth_headers ) # If your service enforces active vendor requirement if response.status_code == 400: data = response.json() assert data["error_code"] == "VENDOR_NOT_ACTIVE" 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 ): """Test operations requiring verification returns VendorNotVerifiedException (if enforced)""" # Set vendor to unverified test_vendor.is_verified = False db.commit() # Test adding products (might require verification) product_data = { "marketplace_product_id": 1, "price": 29.99, } response = client.post( f"/api/v1/vendor/{test_vendor.vendor_code}/products", headers=auth_headers, json=product_data, ) # If your service requires verification for adding products if response.status_code == 400: data = response.json() assert data["error_code"] == "VENDOR_NOT_VERIFIED" assert data["status_code"] == 400 assert test_vendor.vendor_code in data["message"] def test_get_vendor_without_auth_returns_invalid_token(self, client): """Test that vendor endpoints require authentication returns InvalidTokenException""" response = client.get("/api/v1/vendor") assert response.status_code == 401 data = response.json() assert data["error_code"] == "INVALID_TOKEN" assert data["status_code"] == 401 def test_pagination_validation_errors(self, client, auth_headers): """Test pagination parameter validation""" # Test negative skip response = client.get("/api/v1/vendor?skip=-1", headers=auth_headers) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" # Test zero limit response = client.get("/api/v1/vendor?limit=0", headers=auth_headers) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" # Test excessive limit response = client.get("/api/v1/vendor?limit=10000", headers=auth_headers) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" def test_exception_structure_consistency(self, client, auth_headers): """Test that all vendor exceptions follow the consistent WizamartException structure""" # Test with a known error case response = client.get("/api/v1/vendor/NONEXISTENT", headers=auth_headers) assert response.status_code == 404 data = response.json() # Verify exception structure matches WizamartException.to_dict() required_fields = ["error_code", "message", "status_code"] for field in required_fields: assert field in data, f"Missing required field: {field}" assert isinstance(data["error_code"], str) assert isinstance(data["message"], str) assert isinstance(data["status_code"], int) # Details field should be present for domain-specific exceptions if "details" in data: assert isinstance(data["details"], dict)