django-eav2/tests/test_value.py

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