""" Contract Tests: Pet Owners API Endpoint: /mascotas/api/v1/pet-owners/ App: mascotas Related Tickets: - VET-536: Paso 0 - Test creación del petowner invitado - VET-535: Establecer y definir test para las apis vinculadas al procesos de solicitar turno general Context: In the turnero general flow (guest booking), a "guest" pet owner is created with a mock email (e.g., invitado-1759415377297@example.com). This user is fundamental for subsequent steps as it provides the address used to filter available services. TBD: PetOwnerViewSet needs pagination - currently loads all records on list(). See mascotas/views/api/v1/views/petowner_views.py:72 Using email filter in tests to avoid loading 14k+ records. """ import time from ..base import ContractTestCase from ..endpoints import Endpoints from ..helpers import sample_pet_owner class TestPetOwnerCreate(ContractTestCase): """POST /mascotas/api/v1/pet-owners/ VET-536: Tests for guest petowner creation (Step 0 of turnero flow) """ def test_create_returns_201(self): """ Creating a pet owner returns 201 with the created resource. Request (from production turnero): POST /mascotas/api/v1/pet-owners/ { "first_name": "Juan", "last_name": "Pérez", "email": "invitado-1733929847293@example.com", "phone": "1155667788", "address": "Av. Santa Fe 1234, Buenos Aires", "geo_latitude": -34.5955, "geo_longitude": -58.4166 } Response (201): { "id": 12345, "first_name": "Juan", "last_name": "Pérez", "email": "invitado-1733929847293@example.com", "phone": "1155667788", "address": "Av. Santa Fe 1234, Buenos Aires", "geo_latitude": -34.5955, "geo_longitude": -58.4166, "pets": [], "created_at": "2024-12-11T15:30:47.293Z" } """ data = sample_pet_owner() response = self.post(Endpoints.PET_OWNERS, data) self.assert_status(response, 201) self.assert_has_fields(response.data, "id", "email", "first_name", "last_name") self.assertEqual(response.data["email"], data["email"]) def test_requires_email(self): """ Pet owner creation requires email (current behavior). Note: The turnero guest flow uses a mock email created by frontend (e.g., invitado-1759415377297@example.com). The API always requires email. This test ensures the contract enforcement - no petowner without email. """ data = { "address": "Av. Corrientes 1234", "first_name": "Invitado", "last_name": str(int(time.time())), } response = self.post(Endpoints.PET_OWNERS, data) self.assert_status(response, 400) def test_duplicate_email_returns_existing(self): """ Creating pet owner with existing email returns the existing record. Note: API has upsert behavior - returns 200 with existing record, not 400 error. This allows frontend to "create or get" in one call. Important for guest flow - if user refreshes/retries, we don't create duplicates. """ data = sample_pet_owner() first_response = self.post(Endpoints.PET_OWNERS, data) first_id = first_response.data["id"] response = self.post(Endpoints.PET_OWNERS, data) # Same email # Returns 200 with existing record (upsert behavior) self.assert_status(response, 200) self.assertEqual(response.data["id"], first_id) def test_address_and_geolocation_persisted(self): """ Pet owner address and geolocation coordinates are persisted correctly. The address is critical for the turnero flow - it's used to filter available services by location. Geolocation (lat/lng) may be obtained from Google Maps API. """ data = sample_pet_owner() response = self.post(Endpoints.PET_OWNERS, data) self.assert_status(response, 201) self.assert_has_fields(response.data, "address", "geo_latitude", "geo_longitude") self.assertEqual(response.data["address"], data["address"]) # Verify geolocation fields are numeric (not null/empty) self.assertIsNotNone(response.data.get("geo_latitude")) self.assertIsNotNone(response.data.get("geo_longitude")) class TestPetOwnerRetrieve(ContractTestCase): """GET /mascotas/api/v1/pet-owners/{id}/""" def test_get_by_id_returns_200(self): """GET pet owner by ID returns owner details""" # Create owner first data = sample_pet_owner() create_response = self.post(Endpoints.PET_OWNERS, data) owner_id = create_response.data["id"] response = self.get(Endpoints.PET_OWNER_DETAIL.format(id=owner_id)) self.assert_status(response, 200) self.assertEqual(response.data["id"], owner_id) self.assert_has_fields(response.data, "id", "first_name", "last_name", "address", "pets") def test_nonexistent_returns_404(self): """GET non-existent owner returns 404""" response = self.get(Endpoints.PET_OWNER_DETAIL.format(id=999999)) self.assert_status(response, 404) class TestPetOwnerList(ContractTestCase): """GET /mascotas/api/v1/pet-owners/""" def test_list_with_email_filter_returns_200(self): """GET pet owners filtered by email returns 200""" # Filter by email to avoid loading 14k+ records (no pagination on this endpoint) response = self.get(Endpoints.PET_OWNERS, params={"email": "nonexistent@test.com"}) self.assert_status(response, 200) def test_list_filter_by_email_works(self): """Can filter pet owners by email""" # Create a pet owner first data = sample_pet_owner() self.post(Endpoints.PET_OWNERS, data) # Filter by that email response = self.get(Endpoints.PET_OWNERS, params={"email": data["email"]}) self.assert_status(response, 200) # Should find exactly one results = response.data if isinstance(response.data, list) else response.data.get("results", []) self.assertEqual(len(results), 1) self.assertEqual(results[0]["email"], data["email"])