mirror of
https://github.com/jazzband/django-eav2.git
synced 2026-03-16 22:40:26 +00:00
319 lines
10 KiB
Python
319 lines
10 KiB
Python
import pytest
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.exceptions import ValidationError
|
|
from django.db import IntegrityError
|
|
|
|
from eav.models import Attribute, Value
|
|
from test_project.models import Doctor, Patient
|
|
|
|
|
|
@pytest.fixture
|
|
def patient_ct() -> ContentType:
|
|
"""Return the content type for the Patient model."""
|
|
return ContentType.objects.get_for_model(Patient)
|
|
|
|
|
|
@pytest.fixture
|
|
def doctor_ct() -> ContentType:
|
|
"""Return the content type for the Doctor model."""
|
|
# We use Doctor model for UUID tests since it already uses UUID as primary key
|
|
return ContentType.objects.get_for_model(Doctor)
|
|
|
|
|
|
@pytest.fixture
|
|
def attribute() -> Attribute:
|
|
"""Create and return a test attribute."""
|
|
return Attribute.objects.create(
|
|
name="test_attribute",
|
|
datatype="text",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def patient() -> Patient:
|
|
"""Create and return a patient with integer PK."""
|
|
# Patient model uses auto-incrementing integer primary keys
|
|
return Patient.objects.create(name="Patient with Int PK")
|
|
|
|
|
|
@pytest.fixture
|
|
def doctor() -> Doctor:
|
|
"""Create and return a doctor with UUID PK."""
|
|
# Doctor model uses UUID primary keys, ideal for testing entity_uuid constraints
|
|
return Doctor.objects.create(name="Doctor with UUID PK")
|
|
|
|
|
|
class TestValueModelValidation:
|
|
"""Test Value model Python-level validation (via full_clean in save)."""
|
|
|
|
@pytest.mark.django_db
|
|
def test_unique_entity_id_validation(
|
|
self,
|
|
patient_ct: ContentType,
|
|
attribute: Attribute,
|
|
patient: Patient,
|
|
) -> None:
|
|
"""
|
|
Test that model validation prevents duplicate entity_id values.
|
|
|
|
The model's save() method calls full_clean() which should detect the
|
|
duplicate before it hits the database constraint.
|
|
"""
|
|
# Create first value - this should succeed
|
|
Value.objects.create(
|
|
entity_ct=patient_ct,
|
|
entity_id=patient.id,
|
|
attribute=attribute,
|
|
value_text="First value",
|
|
)
|
|
|
|
# Try to create a second value with the same entity_ct, attribute, and entity_id
|
|
# This should fail with ValidationError from full_clean()
|
|
with pytest.raises(ValidationError) as excinfo:
|
|
Value.objects.create(
|
|
entity_ct=patient_ct,
|
|
entity_id=patient.id,
|
|
attribute=attribute,
|
|
value_text="Second value",
|
|
)
|
|
|
|
# Verify the error message indicates uniqueness violation
|
|
assert "already exists" in str(excinfo.value)
|
|
|
|
@pytest.mark.django_db
|
|
def test_unique_entity_uuid_validation(
|
|
self,
|
|
doctor_ct: ContentType,
|
|
attribute: Attribute,
|
|
doctor: Doctor,
|
|
) -> None:
|
|
"""
|
|
Test that model validation prevents duplicate entity_uuid values.
|
|
|
|
The model's full_clean() should detect the duplicate before it hits
|
|
the database constraint.
|
|
"""
|
|
# Create first value with UUID - this should succeed
|
|
Value.objects.create(
|
|
entity_ct=doctor_ct,
|
|
entity_uuid=doctor.id,
|
|
attribute=attribute,
|
|
value_text="First UUID value",
|
|
)
|
|
|
|
# Try to create a second value with the same entity_ct,
|
|
# attribute, and entity_uuid
|
|
with pytest.raises(ValidationError) as excinfo:
|
|
Value.objects.create(
|
|
entity_ct=doctor_ct,
|
|
entity_uuid=doctor.id,
|
|
attribute=attribute,
|
|
value_text="Second UUID value",
|
|
)
|
|
|
|
# Verify the error message indicates uniqueness violation
|
|
assert "already exists" in str(excinfo.value)
|
|
|
|
@pytest.mark.django_db
|
|
def test_entity_id_xor_entity_uuid_validation(
|
|
self,
|
|
patient_ct: ContentType,
|
|
attribute: Attribute,
|
|
patient: Patient,
|
|
doctor: Doctor,
|
|
) -> None:
|
|
"""
|
|
Test that model validation enforces XOR between entity_id and entity_uuid.
|
|
|
|
The model's full_clean() should detect if both or neither field is provided.
|
|
"""
|
|
# Try to create with both ID types
|
|
with pytest.raises(ValidationError):
|
|
Value.objects.create(
|
|
entity_ct=patient_ct,
|
|
entity_id=patient.id,
|
|
entity_uuid=doctor.id,
|
|
attribute=attribute,
|
|
value_text="Both IDs provided",
|
|
)
|
|
|
|
# Try to create with neither ID type
|
|
with pytest.raises(ValidationError):
|
|
Value.objects.create(
|
|
entity_ct=patient_ct,
|
|
entity_id=None,
|
|
entity_uuid=None,
|
|
attribute=attribute,
|
|
value_text="No IDs provided",
|
|
)
|
|
|
|
|
|
class TestValueDatabaseConstraints:
|
|
"""
|
|
Test Value model database constraints when bypassing model validation.
|
|
|
|
These tests use bulk_create() which bypasses the save() method and its
|
|
full_clean() validation, allowing us to test the database constraints directly.
|
|
"""
|
|
|
|
@pytest.mark.django_db
|
|
def test_unique_entity_id_constraint(
|
|
self,
|
|
patient_ct: ContentType,
|
|
attribute: Attribute,
|
|
patient: Patient,
|
|
) -> None:
|
|
"""
|
|
Test that database constraints prevent duplicate entity_id values.
|
|
|
|
Even when bypassing model validation with bulk_create, the database
|
|
constraint should still prevent duplicates.
|
|
"""
|
|
# Create first value - this should succeed
|
|
Value.objects.create(
|
|
entity_ct=patient_ct,
|
|
entity_id=patient.id,
|
|
attribute=attribute,
|
|
value_text="First value",
|
|
)
|
|
|
|
# Try to bulk create a duplicate value, bypassing model validation
|
|
with pytest.raises(IntegrityError):
|
|
Value.objects.bulk_create(
|
|
[
|
|
Value(
|
|
entity_ct=patient_ct,
|
|
entity_id=patient.id,
|
|
attribute=attribute,
|
|
value_text="Second value",
|
|
),
|
|
],
|
|
)
|
|
|
|
@pytest.mark.django_db
|
|
def test_unique_entity_uuid_constraint(
|
|
self,
|
|
doctor_ct: ContentType,
|
|
attribute: Attribute,
|
|
doctor: Doctor,
|
|
) -> None:
|
|
"""
|
|
Test that database constraints prevent duplicate entity_uuid values.
|
|
|
|
Even when bypassing model validation, the database constraint should
|
|
still prevent duplicates.
|
|
"""
|
|
# Create first value with UUID - this should succeed
|
|
Value.objects.create(
|
|
entity_ct=doctor_ct,
|
|
entity_uuid=doctor.id,
|
|
attribute=attribute,
|
|
value_text="First UUID value",
|
|
)
|
|
|
|
# Try to bulk create a duplicate value, bypassing model validation
|
|
with pytest.raises(IntegrityError):
|
|
Value.objects.bulk_create(
|
|
[
|
|
Value(
|
|
entity_ct=doctor_ct,
|
|
entity_uuid=doctor.id,
|
|
attribute=attribute,
|
|
value_text="Second UUID value",
|
|
),
|
|
],
|
|
)
|
|
|
|
@pytest.mark.django_db
|
|
def test_entity_id_and_entity_uuid_constraint(
|
|
self,
|
|
patient_ct: ContentType,
|
|
attribute: Attribute,
|
|
patient: Patient,
|
|
doctor: Doctor,
|
|
) -> None:
|
|
"""
|
|
Test that database constraints prevent having both entity_id and entity_uuid.
|
|
|
|
Even when bypassing model validation, the database constraint should
|
|
prevent having both fields set.
|
|
"""
|
|
# Try to bulk create with both ID types
|
|
with pytest.raises(IntegrityError):
|
|
Value.objects.bulk_create(
|
|
[
|
|
Value(
|
|
entity_ct=patient_ct,
|
|
entity_id=patient.id,
|
|
entity_uuid=doctor.id,
|
|
attribute=attribute,
|
|
value_text="Both IDs provided",
|
|
),
|
|
],
|
|
)
|
|
|
|
@pytest.mark.django_db
|
|
def test_neither_entity_id_nor_entity_uuid_constraint(
|
|
self,
|
|
patient_ct: ContentType,
|
|
attribute: Attribute,
|
|
) -> None:
|
|
"""
|
|
Test that database constraints prevent having neither entity_id nor entity_uuid.
|
|
|
|
Even when bypassing model validation, the database constraint should
|
|
prevent having neither field set.
|
|
"""
|
|
# Try to bulk create with neither ID type
|
|
with pytest.raises(IntegrityError):
|
|
Value.objects.bulk_create(
|
|
[
|
|
Value(
|
|
entity_ct=patient_ct,
|
|
entity_id=None,
|
|
entity_uuid=None,
|
|
attribute=attribute,
|
|
value_text="No IDs provided",
|
|
),
|
|
],
|
|
)
|
|
|
|
@pytest.mark.django_db
|
|
def test_happy_path_constraints(
|
|
self,
|
|
patient_ct: ContentType,
|
|
doctor_ct: ContentType,
|
|
attribute: Attribute,
|
|
patient: Patient,
|
|
doctor: Doctor,
|
|
) -> None:
|
|
"""
|
|
Test that valid values pass both database constraints.
|
|
|
|
Values with either entity_id or entity_uuid (but not both) should be accepted.
|
|
"""
|
|
# Test with entity_id using bulk_create
|
|
values = Value.objects.bulk_create(
|
|
[
|
|
Value(
|
|
entity_ct=patient_ct,
|
|
entity_id=patient.id,
|
|
attribute=attribute,
|
|
value_text="Integer ID bulk created",
|
|
),
|
|
],
|
|
)
|
|
assert len(values) == 1
|
|
|
|
# Test with entity_uuid using bulk_create
|
|
values = Value.objects.bulk_create(
|
|
[
|
|
Value(
|
|
entity_ct=doctor_ct,
|
|
entity_uuid=doctor.id,
|
|
attribute=attribute,
|
|
value_text="UUID bulk created",
|
|
),
|
|
],
|
|
)
|
|
assert len(values) == 1
|