- Created pyproject.toml for project metadata and dependencies. - Added requirements.txt for development and production dependencies. - Implemented basic test structure in the tests module. - Developed unit tests for ElytraClient, including Pydantic validation and error handling.
178 lines
5.6 KiB
Python
178 lines
5.6 KiB
Python
"""Tests for the Elytra PIM Client with Pydantic validation"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
from pydantic import ValidationError
|
|
|
|
from elytra_client import (
|
|
ElytraClient,
|
|
SingleProductResponse,
|
|
SingleNewProductRequestBody,
|
|
)
|
|
from elytra_client.exceptions import ElytraAuthenticationError, ElytraNotFoundError
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
"""Create a test client"""
|
|
return ElytraClient(
|
|
base_url="https://test.example.com/api/v1",
|
|
api_key="test-api-key",
|
|
)
|
|
|
|
|
|
def test_client_initialization(client):
|
|
"""Test client initialization"""
|
|
assert client.base_url == "https://test.example.com/api/v1"
|
|
assert client.api_key == "test-api-key"
|
|
assert client.timeout == 30
|
|
|
|
|
|
def test_client_context_manager():
|
|
"""Test client context manager"""
|
|
with ElytraClient(
|
|
base_url="https://test.example.com/api/v1",
|
|
api_key="test-api-key",
|
|
) as client:
|
|
assert client is not None
|
|
|
|
|
|
@patch("elytra_client.client.requests.Session.request")
|
|
def test_get_products_with_pydantic_validation(mock_request, client):
|
|
"""Test get_products with Pydantic validation"""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {
|
|
"items": [
|
|
{
|
|
"id": 1,
|
|
"clientId": 231,
|
|
"productName": "Product 1",
|
|
"treeId": 0,
|
|
"created": "2025-04-08T12:00:00Z",
|
|
"modified": "2025-04-08T12:00:00Z",
|
|
"creatorUserId": 235,
|
|
"modifierUserId": 235,
|
|
"objectStatus": "original",
|
|
"originalId": 0,
|
|
"attributes": [],
|
|
}
|
|
],
|
|
"total": 1,
|
|
"page": 1,
|
|
}
|
|
mock_request.return_value = mock_response
|
|
|
|
result = client.get_products(lang="en", page=1, limit=10)
|
|
|
|
assert result["total"] == 1
|
|
assert len(result["items"]) == 1
|
|
# Items should be Pydantic models
|
|
assert isinstance(result["items"][0], SingleProductResponse)
|
|
assert result["items"][0].productName == "Product 1"
|
|
mock_request.assert_called_once()
|
|
|
|
|
|
@patch("elytra_client.client.requests.Session.request")
|
|
def test_get_product_returns_pydantic_model(mock_request, client):
|
|
"""Test get_product returns Pydantic model"""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {
|
|
"id": 123,
|
|
"clientId": 231,
|
|
"productName": "Test Product",
|
|
"treeId": 0,
|
|
"created": "2025-04-08T12:00:00Z",
|
|
"modified": "2025-04-08T12:00:00Z",
|
|
"creatorUserId": 235,
|
|
"modifierUserId": 235,
|
|
"objectStatus": "original",
|
|
"originalId": 0,
|
|
"attributes": [],
|
|
}
|
|
mock_request.return_value = mock_response
|
|
|
|
result = client.get_product(product_id=123, lang="en")
|
|
|
|
assert isinstance(result, SingleProductResponse)
|
|
assert result.id == 123
|
|
assert result.productName == "Test Product"
|
|
|
|
|
|
@patch("elytra_client.client.requests.Session.request")
|
|
def test_create_product_with_pydantic(mock_request, client):
|
|
"""Test product creation with Pydantic validation"""
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {
|
|
"id": 999,
|
|
"clientId": 231,
|
|
"productName": "NEW-PRODUCT-001",
|
|
"treeId": 0,
|
|
"created": "2025-04-08T12:00:00Z",
|
|
"modified": "2025-04-08T12:00:00Z",
|
|
"creatorUserId": 235,
|
|
"modifierUserId": 235,
|
|
"objectStatus": "original",
|
|
"originalId": 0,
|
|
"attributes": [],
|
|
}
|
|
mock_request.return_value = mock_response
|
|
|
|
# Create with Pydantic model
|
|
new_product = SingleNewProductRequestBody(
|
|
productName="NEW-PRODUCT-001",
|
|
parentId=1,
|
|
attributeGroupId=10,
|
|
) # type: ignore - validation happens automatically, so type checker should recognize this as valid
|
|
|
|
result = client.create_product(new_product)
|
|
|
|
assert isinstance(result, SingleProductResponse)
|
|
assert result.id == 999
|
|
assert result.productName == "NEW-PRODUCT-001"
|
|
|
|
|
|
def test_pydantic_validation_on_creation():
|
|
"""Test Pydantic validation on model creation"""
|
|
# Valid model
|
|
valid_product = SingleNewProductRequestBody(
|
|
productName="VALID-PRODUCT",
|
|
parentId=1,
|
|
attributeGroupId=10,
|
|
) # type: ignore - validation happens automatically, so type checker should recognize this as valid
|
|
assert valid_product.productName == "VALID-PRODUCT"
|
|
|
|
# Invalid model - missing required field
|
|
with pytest.raises(ValidationError):
|
|
SingleNewProductRequestBody(
|
|
productName="INVALID-PRODUCT",
|
|
# Missing parentId - required
|
|
attributeGroupId=10,
|
|
) # type: ignore - this will raise a ValidationError, so type checker should recognize this as invalid
|
|
|
|
|
|
@patch("elytra_client.client.requests.Session.request")
|
|
def test_authentication_error(mock_request, client):
|
|
"""Test authentication error handling"""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 401
|
|
mock_response.text = "Unauthorized"
|
|
mock_request.return_value.raise_for_status.side_effect = (
|
|
__import__("requests").exceptions.HTTPError(response=mock_response)
|
|
)
|
|
|
|
with pytest.raises(ElytraAuthenticationError):
|
|
client.get_products()
|
|
|
|
|
|
@patch("elytra_client.client.requests.Session.request")
|
|
def test_not_found_error(mock_request, client):
|
|
"""Test not found error handling"""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 404
|
|
mock_response.text = "Not Found"
|
|
mock_request.return_value.raise_for_status.side_effect = (
|
|
__import__("requests").exceptions.HTTPError(response=mock_response)
|
|
)
|
|
|
|
with pytest.raises(ElytraNotFoundError):
|
|
client.get_product(product_id=999)
|