# ABOUTME: Tests for basic selection resolver. # ABOUTME: Tests animal ID validation for product collection. import time import pytest from animaltrack.events.payloads import AnimalCohortCreatedPayload from animaltrack.events.store import EventStore from animaltrack.projections import ProjectionRegistry from animaltrack.projections.animal_registry import AnimalRegistryProjection from animaltrack.projections.event_animals import EventAnimalsProjection from animaltrack.projections.intervals import IntervalProjection from animaltrack.selection import SelectionResolverError, resolve_selection from animaltrack.services.animal import AnimalService @pytest.fixture def event_store(seeded_db): """Create an EventStore for testing.""" return EventStore(seeded_db) @pytest.fixture def projection_registry(seeded_db): """Create a ProjectionRegistry with animal projections registered.""" registry = ProjectionRegistry() registry.register(AnimalRegistryProjection(seeded_db)) registry.register(EventAnimalsProjection(seeded_db)) registry.register(IntervalProjection(seeded_db)) return registry @pytest.fixture def animal_service(seeded_db, event_store, projection_registry): """Create an AnimalService for testing.""" return AnimalService(seeded_db, event_store, projection_registry) @pytest.fixture def valid_location_id(seeded_db): """Get a valid location ID from seeds.""" row = seeded_db.execute("SELECT id FROM locations WHERE name = 'Strip 1'").fetchone() return row[0] def make_cohort_payload( location_id: str, count: int = 3, species: str = "duck", ) -> AnimalCohortCreatedPayload: """Create a cohort payload for testing.""" return AnimalCohortCreatedPayload( species=species, count=count, life_stage="adult", sex="unknown", location_id=location_id, origin="purchased", ) @pytest.fixture def animal_ids(seeded_db, animal_service, valid_location_id): """Create a cohort and return the animal IDs.""" payload = make_cohort_payload(valid_location_id, count=5) ts_utc = int(time.time() * 1000) event = animal_service.create_cohort(payload, ts_utc, "test_user") return event.entity_refs["animal_ids"] class TestResolveSelectionValid: """Tests for resolve_selection with valid inputs.""" def test_returns_validated_ids_when_all_exist(self, seeded_db, animal_ids): """resolve_selection returns the IDs when all are valid and alive.""" result = resolve_selection(seeded_db, animal_ids) assert result == animal_ids def test_handles_single_animal(self, seeded_db, animal_ids): """resolve_selection works with a single animal.""" single_id = [animal_ids[0]] result = resolve_selection(seeded_db, single_id) assert result == single_id def test_handles_subset_of_animals(self, seeded_db, animal_ids): """resolve_selection works with a subset of animals.""" subset = animal_ids[:2] result = resolve_selection(seeded_db, subset) assert result == subset class TestResolveSelectionErrors: """Tests for resolve_selection error cases.""" def test_raises_for_nonexistent_animal(self, seeded_db, animal_ids): """Raises SelectionResolverError for animal not found.""" fake_id = "01ARZ3NDEKTSV4RRFFQ69G5FAV" ids_with_fake = animal_ids[:1] + [fake_id] with pytest.raises(SelectionResolverError, match="not found"): resolve_selection(seeded_db, ids_with_fake) def test_raises_for_dead_animal(self, seeded_db, animal_ids): """Raises SelectionResolverError for animal with status != 'alive'.""" # Mark the first animal as dead dead_id = animal_ids[0] seeded_db.execute( "UPDATE animal_registry SET status = 'dead' WHERE animal_id = ?", (dead_id,), ) with pytest.raises(SelectionResolverError, match="not alive"): resolve_selection(seeded_db, [dead_id]) def test_raises_for_mixed_valid_invalid(self, seeded_db, animal_ids): """Raises SelectionResolverError when mix of valid and invalid animals.""" # Mark one as dead dead_id = animal_ids[0] seeded_db.execute( "UPDATE animal_registry SET status = 'dead' WHERE animal_id = ?", (dead_id,), ) # Mix: one dead, one alive mixed_ids = [dead_id, animal_ids[1]] with pytest.raises(SelectionResolverError): resolve_selection(seeded_db, mixed_ids) def test_raises_for_empty_list(self, seeded_db): """Raises SelectionResolverError for empty resolved_ids list.""" with pytest.raises(SelectionResolverError, match="empty"): resolve_selection(seeded_db, [])