spr migrated books, and tester
This commit is contained in:
1
cfg/amar/tester/tests/mascotas/__init__.py
Normal file
1
cfg/amar/tester/tests/mascotas/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Contract tests for mascotas app endpoints
|
||||
53
cfg/amar/tester/tests/mascotas/test_coverage.py
Normal file
53
cfg/amar/tester/tests/mascotas/test_coverage.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Contract Tests: Coverage Check API
|
||||
|
||||
Endpoint: /mascotas/api/v1/coverage/check/
|
||||
App: mascotas
|
||||
|
||||
Used to check if a location has veterinary coverage before proceeding with turnero.
|
||||
"""
|
||||
|
||||
from ..base import ContractTestCase
|
||||
from ..endpoints import Endpoints
|
||||
|
||||
|
||||
class TestCoverageCheck(ContractTestCase):
|
||||
"""GET /mascotas/api/v1/coverage/check/"""
|
||||
|
||||
def test_with_coordinates_returns_200(self):
|
||||
"""Coverage check should accept lat/lng parameters"""
|
||||
response = self.get(Endpoints.COVERAGE_CHECK, params={
|
||||
"lat": -34.6037,
|
||||
"lng": -58.3816,
|
||||
})
|
||||
|
||||
self.assert_status(response, 200)
|
||||
|
||||
def test_returns_coverage_boolean(self):
|
||||
"""Coverage check should return coverage boolean"""
|
||||
response = self.get(Endpoints.COVERAGE_CHECK, params={
|
||||
"lat": -34.6037,
|
||||
"lng": -58.3816,
|
||||
})
|
||||
|
||||
self.assert_status(response, 200)
|
||||
self.assert_has_fields(response.data, "coverage")
|
||||
self.assertIsInstance(response.data["coverage"], bool)
|
||||
|
||||
def test_returns_vet_count(self):
|
||||
"""Coverage check should return number of available vets"""
|
||||
response = self.get(Endpoints.COVERAGE_CHECK, params={
|
||||
"lat": -34.6037,
|
||||
"lng": -58.3816,
|
||||
})
|
||||
|
||||
self.assert_status(response, 200)
|
||||
self.assert_has_fields(response.data, "vet_count")
|
||||
self.assertIsInstance(response.data["vet_count"], int)
|
||||
|
||||
def test_without_coordinates_fails(self):
|
||||
"""Coverage check without coordinates should fail"""
|
||||
response = self.get(Endpoints.COVERAGE_CHECK)
|
||||
|
||||
# Should return 400 or similar error
|
||||
self.assertIn(response.status_code, [400, 422])
|
||||
171
cfg/amar/tester/tests/mascotas/test_pet_owners.py
Normal file
171
cfg/amar/tester/tests/mascotas/test_pet_owners.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
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"])
|
||||
171
cfg/amar/tester/tests/mascotas/test_pets.py
Normal file
171
cfg/amar/tester/tests/mascotas/test_pets.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
Contract Tests: Pets API
|
||||
|
||||
Endpoint: /mascotas/api/v1/pets/
|
||||
App: mascotas
|
||||
|
||||
Related Tickets:
|
||||
- VET-537: Paso 1 - Test creación de la mascota vinculada al petowner invitado
|
||||
- VET-535: Establecer y definir test para las apis vinculadas al procesos de solicitar turno general
|
||||
|
||||
Context: In the turnero general flow (Step 1), a pet is created and linked to the guest
|
||||
pet owner. The pet data (type, name, neutered status) combined with the owner's address
|
||||
is used to filter available services and veterinarians.
|
||||
"""
|
||||
|
||||
from ..base import ContractTestCase
|
||||
from ..endpoints import Endpoints
|
||||
from ..helpers import (
|
||||
sample_pet_owner,
|
||||
unique_email,
|
||||
SAMPLE_CAT,
|
||||
SAMPLE_DOG,
|
||||
SAMPLE_NEUTERED_CAT,
|
||||
)
|
||||
|
||||
|
||||
class TestPetCreate(ContractTestCase):
|
||||
"""POST /mascotas/api/v1/pets/
|
||||
|
||||
VET-537: Tests for pet creation linked to guest petowner (Step 1 of turnero flow)
|
||||
"""
|
||||
|
||||
def _create_owner(self):
|
||||
"""Helper to create a pet owner"""
|
||||
data = sample_pet_owner(unique_email("pet_owner"))
|
||||
response = self.post(Endpoints.PET_OWNERS, data)
|
||||
return response.data["id"]
|
||||
|
||||
def test_create_cat_returns_201(self):
|
||||
"""
|
||||
Creating a cat returns 201 with pet_type CAT.
|
||||
|
||||
Request (from production turnero):
|
||||
POST /mascotas/api/v1/pets/
|
||||
{
|
||||
"name": "Luna",
|
||||
"pet_type": "CAT",
|
||||
"is_neutered": false,
|
||||
"owner": 12345
|
||||
}
|
||||
|
||||
Response (201):
|
||||
{
|
||||
"id": 67890,
|
||||
"name": "Luna",
|
||||
"pet_type": "CAT",
|
||||
"is_neutered": false,
|
||||
"owner": 12345,
|
||||
"breed": null,
|
||||
"birth_date": null,
|
||||
"created_at": "2024-12-11T15:31:15.123Z"
|
||||
}
|
||||
"""
|
||||
owner_id = self._create_owner()
|
||||
data = {**SAMPLE_CAT, "owner": owner_id}
|
||||
|
||||
response = self.post(Endpoints.PETS, data)
|
||||
|
||||
self.assert_status(response, 201)
|
||||
self.assert_has_fields(response.data, "id", "name", "pet_type", "owner")
|
||||
self.assertEqual(response.data["pet_type"], "CAT")
|
||||
self.assertEqual(response.data["name"], "TestCat")
|
||||
|
||||
def test_create_dog_returns_201(self):
|
||||
"""
|
||||
Creating a dog returns 201 with pet_type DOG.
|
||||
|
||||
Validates that both major pet types (CAT/DOG) are supported in the contract.
|
||||
"""
|
||||
owner_id = self._create_owner()
|
||||
data = {**SAMPLE_DOG, "owner": owner_id}
|
||||
|
||||
response = self.post(Endpoints.PETS, data)
|
||||
|
||||
self.assert_status(response, 201)
|
||||
self.assertEqual(response.data["pet_type"], "DOG")
|
||||
|
||||
def test_neutered_status_persisted(self):
|
||||
"""
|
||||
Neutered status is persisted correctly.
|
||||
|
||||
This is important business data that may affect service recommendations
|
||||
or veterinarian assignments.
|
||||
"""
|
||||
owner_id = self._create_owner()
|
||||
data = {**SAMPLE_NEUTERED_CAT, "owner": owner_id}
|
||||
|
||||
response = self.post(Endpoints.PETS, data)
|
||||
|
||||
self.assert_status(response, 201)
|
||||
self.assertTrue(response.data["is_neutered"])
|
||||
|
||||
def test_requires_owner(self):
|
||||
"""
|
||||
Pet creation without owner should fail.
|
||||
|
||||
Enforces the required link between pet and petowner - critical for the
|
||||
turnero flow where pets must be associated with the guest user.
|
||||
"""
|
||||
data = SAMPLE_CAT.copy()
|
||||
|
||||
response = self.post(Endpoints.PETS, data)
|
||||
|
||||
self.assert_status(response, 400)
|
||||
|
||||
def test_invalid_pet_type_rejected(self):
|
||||
"""
|
||||
Invalid pet_type should be rejected.
|
||||
|
||||
Currently only CAT and DOG are supported. This test ensures the contract
|
||||
validates pet types correctly.
|
||||
"""
|
||||
owner_id = self._create_owner()
|
||||
data = {
|
||||
"name": "InvalidPet",
|
||||
"pet_type": "HAMSTER",
|
||||
"owner": owner_id,
|
||||
}
|
||||
|
||||
response = self.post(Endpoints.PETS, data)
|
||||
|
||||
self.assert_status(response, 400)
|
||||
|
||||
|
||||
class TestPetRetrieve(ContractTestCase):
|
||||
"""GET /mascotas/api/v1/pets/{id}/"""
|
||||
|
||||
def _create_owner_with_pet(self):
|
||||
"""Helper to create owner and pet"""
|
||||
owner_data = sample_pet_owner(unique_email("pet_owner"))
|
||||
owner_response = self.post(Endpoints.PET_OWNERS, owner_data)
|
||||
owner_id = owner_response.data["id"]
|
||||
|
||||
pet_data = {**SAMPLE_CAT, "owner": owner_id}
|
||||
pet_response = self.post(Endpoints.PETS, pet_data)
|
||||
return pet_response.data["id"]
|
||||
|
||||
def test_get_by_id_returns_200(self):
|
||||
"""GET pet by ID returns pet details"""
|
||||
pet_id = self._create_owner_with_pet()
|
||||
|
||||
response = self.get(Endpoints.PET_DETAIL.format(id=pet_id))
|
||||
|
||||
self.assert_status(response, 200)
|
||||
self.assertEqual(response.data["id"], pet_id)
|
||||
|
||||
def test_nonexistent_returns_404(self):
|
||||
"""GET non-existent pet returns 404"""
|
||||
response = self.get(Endpoints.PET_DETAIL.format(id=999999))
|
||||
|
||||
self.assert_status(response, 404)
|
||||
|
||||
|
||||
class TestPetList(ContractTestCase):
|
||||
"""GET /mascotas/api/v1/pets/"""
|
||||
|
||||
def test_list_returns_200(self):
|
||||
"""GET pets list returns 200 (with pagination)"""
|
||||
response = self.get(Endpoints.PETS, params={"page_size": 1})
|
||||
|
||||
self.assert_status(response, 200)
|
||||
Reference in New Issue
Block a user