# ABOUTME: Tests for event-related Pydantic models. # ABOUTME: Validates Event, EventRevision, EventTombstone, IdempotencyNonce, EventAnimal. import pytest from pydantic import ValidationError from animaltrack.models.events import ( Event, EventAnimal, EventRevision, EventTombstone, IdempotencyNonce, ) class TestEvent: """Tests for the Event model.""" def test_valid_event(self): """Event with all required fields validates successfully.""" event = Event( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", type="ProductCollected", ts_utc=1704067200000, actor="ppetru", entity_refs={"location_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW"}, payload={"product_code": "egg.duck", "quantity": 10}, ) assert event.id == "01ARZ3NDEKTSV4RRFFQ69G5FAV" assert event.type == "ProductCollected" assert event.version == 1 def test_event_version_defaults_to_1(self): """Event version defaults to 1.""" event = Event( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", type="FeedGiven", ts_utc=1704067200000, actor="ines", entity_refs={}, payload={}, ) assert event.version == 1 def test_event_id_must_be_26_chars(self): """Event with ID not exactly 26 chars raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: Event( id="short", type="FeedGiven", ts_utc=1704067200000, actor="ppetru", entity_refs={}, payload={}, ) assert "26 characters" in str(exc_info.value) def test_event_id_too_long(self): """Event with ID longer than 26 chars raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: Event( id="01ARZ3NDEKTSV4RRFFQ69G5FAVX", # 27 chars type="FeedGiven", ts_utc=1704067200000, actor="ppetru", entity_refs={}, payload={}, ) assert "26 characters" in str(exc_info.value) def test_event_requires_type(self): """Event without type raises ValidationError.""" with pytest.raises(ValidationError): Event( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", ts_utc=1704067200000, actor="ppetru", entity_refs={}, payload={}, ) def test_event_entity_refs_must_be_dict(self): """Event entity_refs must be a dict.""" with pytest.raises(ValidationError): Event( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", type="FeedGiven", ts_utc=1704067200000, actor="ppetru", entity_refs="not a dict", payload={}, ) def test_event_payload_must_be_dict(self): """Event payload must be a dict.""" with pytest.raises(ValidationError): Event( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", type="FeedGiven", ts_utc=1704067200000, actor="ppetru", entity_refs={}, payload="not a dict", ) def test_event_with_custom_version(self): """Event can be created with custom version.""" event = Event( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", type="FeedGiven", ts_utc=1704067200000, actor="ppetru", entity_refs={}, payload={}, version=3, ) assert event.version == 3 class TestEventRevision: """Tests for the EventRevision model.""" def test_valid_event_revision(self): """EventRevision with all required fields validates successfully.""" revision = EventRevision( event_id="01ARZ3NDEKTSV4RRFFQ69G5FAV", version=1, ts_utc=1704067200000, actor="ppetru", entity_refs={"location_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW"}, payload={"quantity": 10}, edited_at_utc=1704153600000, edited_by="ines", ) assert revision.event_id == "01ARZ3NDEKTSV4RRFFQ69G5FAV" assert revision.version == 1 assert revision.edited_by == "ines" def test_event_revision_event_id_must_be_26_chars(self): """EventRevision event_id must be exactly 26 chars.""" with pytest.raises(ValidationError) as exc_info: EventRevision( event_id="short", version=1, ts_utc=1704067200000, actor="ppetru", entity_refs={}, payload={}, edited_at_utc=1704153600000, edited_by="ines", ) assert "26 characters" in str(exc_info.value) def test_event_revision_requires_edited_by(self): """EventRevision requires edited_by field.""" with pytest.raises(ValidationError): EventRevision( event_id="01ARZ3NDEKTSV4RRFFQ69G5FAV", version=1, ts_utc=1704067200000, actor="ppetru", entity_refs={}, payload={}, edited_at_utc=1704153600000, ) class TestEventTombstone: """Tests for the EventTombstone model.""" def test_valid_event_tombstone(self): """EventTombstone with all required fields validates successfully.""" tombstone = EventTombstone( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", ts_utc=1704067200000, actor="ppetru", target_event_id="01ARZ3NDEKTSV4RRFFQ69G5FAW", reason="Duplicate entry", ) assert tombstone.id == "01ARZ3NDEKTSV4RRFFQ69G5FAV" assert tombstone.target_event_id == "01ARZ3NDEKTSV4RRFFQ69G5FAW" assert tombstone.reason == "Duplicate entry" def test_event_tombstone_reason_optional(self): """EventTombstone reason is optional.""" tombstone = EventTombstone( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", ts_utc=1704067200000, actor="ppetru", target_event_id="01ARZ3NDEKTSV4RRFFQ69G5FAW", ) assert tombstone.reason is None def test_event_tombstone_id_must_be_26_chars(self): """EventTombstone id must be exactly 26 chars.""" with pytest.raises(ValidationError) as exc_info: EventTombstone( id="short", ts_utc=1704067200000, actor="ppetru", target_event_id="01ARZ3NDEKTSV4RRFFQ69G5FAW", ) assert "26 characters" in str(exc_info.value) def test_event_tombstone_target_event_id_must_be_26_chars(self): """EventTombstone target_event_id must be exactly 26 chars.""" with pytest.raises(ValidationError) as exc_info: EventTombstone( id="01ARZ3NDEKTSV4RRFFQ69G5FAV", ts_utc=1704067200000, actor="ppetru", target_event_id="short", ) assert "26 characters" in str(exc_info.value) class TestIdempotencyNonce: """Tests for the IdempotencyNonce model.""" def test_valid_idempotency_nonce(self): """IdempotencyNonce with all required fields validates successfully.""" nonce = IdempotencyNonce( nonce="01ARZ3NDEKTSV4RRFFQ69G5FAV", actor="ppetru", route="/actions/product-collected", created_at_utc=1704067200000, ) assert nonce.nonce == "01ARZ3NDEKTSV4RRFFQ69G5FAV" assert nonce.route == "/actions/product-collected" def test_idempotency_nonce_requires_all_fields(self): """IdempotencyNonce requires all fields.""" with pytest.raises(ValidationError): IdempotencyNonce( nonce="01ARZ3NDEKTSV4RRFFQ69G5FAV", actor="ppetru", ) class TestEventAnimal: """Tests for the EventAnimal model.""" def test_valid_event_animal(self): """EventAnimal with all required fields validates successfully.""" event_animal = EventAnimal( event_id="01ARZ3NDEKTSV4RRFFQ69G5FAV", animal_id="01ARZ3NDEKTSV4RRFFQ69G5FAW", ts_utc=1704067200000, ) assert event_animal.event_id == "01ARZ3NDEKTSV4RRFFQ69G5FAV" assert event_animal.animal_id == "01ARZ3NDEKTSV4RRFFQ69G5FAW" def test_event_animal_event_id_must_be_26_chars(self): """EventAnimal event_id must be exactly 26 chars.""" with pytest.raises(ValidationError) as exc_info: EventAnimal( event_id="short", animal_id="01ARZ3NDEKTSV4RRFFQ69G5FAW", ts_utc=1704067200000, ) assert "26 characters" in str(exc_info.value) def test_event_animal_animal_id_must_be_26_chars(self): """EventAnimal animal_id must be exactly 26 chars.""" with pytest.raises(ValidationError) as exc_info: EventAnimal( event_id="01ARZ3NDEKTSV4RRFFQ69G5FAV", animal_id="short", ts_utc=1704067200000, ) assert "26 characters" in str(exc_info.value)