spr migrated books, and tester

This commit is contained in:
buenosairesam
2025-12-31 09:07:27 -03:00
parent 21b8eab3cb
commit cccc6b5a93
136 changed files with 15763 additions and 472 deletions

View File

@@ -0,0 +1 @@
# Example tests - used when no room-specific tests are configured

View File

@@ -0,0 +1,36 @@
"""
Example health check test.
This is a fallback test that works without room-specific configuration.
Replace with room tests via cfg/<room>/tester/tests/
"""
import httpx
import pytest
class TestHealth:
"""Basic health check tests."""
@pytest.fixture
def base_url(self):
"""Base URL for the API under test."""
import os
return os.getenv("TEST_BASE_URL", "http://localhost:8000")
def test_health_endpoint(self, base_url):
"""Test that /health endpoint responds."""
try:
response = httpx.get(f"{base_url}/health", timeout=5)
assert response.status_code == 200
except httpx.ConnectError:
pytest.skip("API not running - set TEST_BASE_URL or start the service")
def test_root_endpoint(self, base_url):
"""Test that root endpoint responds."""
try:
response = httpx.get(base_url, timeout=5)
assert response.status_code in [200, 301, 302, 307, 308]
except httpx.ConnectError:
pytest.skip("API not running - set TEST_BASE_URL or start the service")

View File

@@ -1 +0,0 @@
# Contract tests for mascotas app endpoints

View File

@@ -1,53 +0,0 @@
"""
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])

View File

@@ -1,171 +0,0 @@
"""
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"])

View File

@@ -1,171 +0,0 @@
"""
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)

View File

@@ -1 +0,0 @@
# Contract tests for productos app endpoints

View File

@@ -1,149 +0,0 @@
"""
Contract Tests: Cart API
Endpoint: /productos/api/v1/cart/
App: productos
Related Tickets:
- VET-538: Test creación de cart vinculado al petowner
- VET-535: Establecer y definir test para las apis vinculadas al procesos de solicitar turno general
Context: In the turnero general flow (Step 2), a cart is created for the guest petowner.
The cart holds selected services and calculates price summary (subtotals, discounts, total).
TBD: CartViewSet needs pagination/filtering - list endpoint hangs on large dataset.
See productos/api/v1/viewsets.py:93
"""
import pytest
from ..base import ContractTestCase
from ..endpoints import Endpoints
from ..helpers import sample_pet_owner, unique_email
class TestCartCreate(ContractTestCase):
"""POST /productos/api/v1/cart/
VET-538: Tests for cart creation linked to petowner (Step 2 of turnero flow)
"""
def _create_petowner(self):
"""Helper to create a pet owner"""
data = sample_pet_owner(unique_email("cart_owner"))
response = self.post(Endpoints.PET_OWNERS, data)
return response.data["id"]
def test_create_cart_for_petowner(self):
"""
Creating a cart returns 201 and links to petowner.
Request (from production turnero):
POST /productos/api/v1/cart/
{
"petowner": 12345,
"services": []
}
Response (201):
{
"id": 789,
"petowner": 12345,
"veterinarian": null,
"items": [],
"resume": [
{"concept": "SUBTOTAL", "amount": "0.00", "order": 1},
{"concept": "COSTO_SERVICIO", "amount": "0.00", "order": 2},
{"concept": "DESCUENTO", "amount": "0.00", "order": 3},
{"concept": "TOTAL", "amount": "0.00", "order": 4},
{"concept": "ADELANTO", "amount": "0.00", "order": 5}
],
"extra_details": "",
"pets": [],
"pet_reasons": []
}
"""
owner_id = self._create_petowner()
data = {
"petowner": owner_id,
"services": []
}
response = self.post(Endpoints.CART, data)
self.assert_status(response, 201)
self.assert_has_fields(response.data, "id", "petowner", "items")
self.assertEqual(response.data["petowner"], owner_id)
def test_cart_has_price_summary_fields(self):
"""
Cart response includes price summary fields.
These fields are critical for turnero flow - user needs to see:
- resume: array with price breakdown (SUBTOTAL, DESCUENTO, TOTAL, etc)
- items: cart items with individual pricing
"""
owner_id = self._create_petowner()
data = {"petowner": owner_id, "services": []}
response = self.post(Endpoints.CART, data)
self.assert_status(response, 201)
# Price fields should exist (may be 0 for empty cart)
self.assert_has_fields(response.data, "resume", "items")
def test_empty_cart_has_zero_totals(self):
"""
Empty cart (no services) has zero price totals.
Validates initial state before services are added.
"""
owner_id = self._create_petowner()
data = {"petowner": owner_id, "services": []}
response = self.post(Endpoints.CART, data)
self.assert_status(response, 201)
# Empty cart should have resume with zero amounts
self.assertIn("resume", response.data)
# Find TOTAL concept in resume
total_item = next((item for item in response.data["resume"] if item["concept"] == "TOTAL"), None)
self.assertIsNotNone(total_item)
self.assertEqual(total_item["amount"], "0.00")
class TestCartRetrieve(ContractTestCase):
"""GET /productos/api/v1/cart/{id}/"""
def _create_petowner_with_cart(self):
"""Helper to create petowner and cart"""
owner_data = sample_pet_owner(unique_email("cart_owner"))
owner_response = self.post(Endpoints.PET_OWNERS, owner_data)
owner_id = owner_response.data["id"]
cart_data = {"petowner": owner_id, "services": []}
cart_response = self.post(Endpoints.CART, cart_data)
return cart_response.data["id"]
def test_get_cart_by_id_returns_200(self):
"""GET cart by ID returns cart details"""
cart_id = self._create_petowner_with_cart()
response = self.get(Endpoints.CART_DETAIL.format(id=cart_id))
self.assert_status(response, 200)
self.assertEqual(response.data["id"], cart_id)
def test_detail_returns_404_for_nonexistent(self):
"""GET /cart/{id}/ returns 404 for non-existent cart"""
response = self.get(Endpoints.CART_DETAIL.format(id=999999))
self.assert_status(response, 404)
class TestCartList(ContractTestCase):
"""GET /productos/api/v1/cart/"""
@pytest.mark.skip(reason="TBD: Cart list hangs - needs pagination/filtering. Checking if dead code.")
def test_list_returns_200(self):
"""GET /cart/ returns 200"""
response = self.get(Endpoints.CART)
self.assert_status(response, 200)

View File

@@ -1,112 +0,0 @@
"""
Contract Tests: Categories API
Endpoint: /productos/api/v1/categories/
App: productos
Returns service categories filtered by location availability.
Categories without available services in location should be hidden.
"""
from ..base import ContractTestCase
from ..endpoints import Endpoints
class TestCategoriesList(ContractTestCase):
"""GET /productos/api/v1/categories/"""
def test_list_returns_200(self):
"""GET categories returns 200"""
response = self.get(Endpoints.CATEGORIES, params={"page_size": 10})
self.assert_status(response, 200)
def test_returns_list(self):
"""GET categories returns a list"""
response = self.get(Endpoints.CATEGORIES, params={"page_size": 10})
self.assert_status(response, 200)
data = response.data
# Handle paginated or non-paginated response
categories = data["results"] if isinstance(data, dict) and "results" in data else data
self.assertIsInstance(categories, list)
def test_categories_have_required_fields(self):
"""
Each category should have id, name, and description.
Request (from production turnero):
GET /productos/api/v1/categories/
Response (200):
[
{
"id": 1,
"name": "Consulta General",
"description": "Consultas veterinarias generales"
},
{
"id": 2,
"name": "Vacunación",
"description": "Servicios de vacunación"
}
]
"""
response = self.get(Endpoints.CATEGORIES, params={"page_size": 10})
data = response.data
categories = data["results"] if isinstance(data, dict) and "results" in data else data
if len(categories) > 0:
category = categories[0]
self.assert_has_fields(category, "id", "name", "description")
def test_only_active_categories_returned(self):
"""
Only active categories are returned in the list.
Business rule: Inactive categories should not be visible to users.
"""
response = self.get(Endpoints.CATEGORIES, params={"page_size": 50})
data = response.data
categories = data["results"] if isinstance(data, dict) and "results" in data else data
# All categories should be active (no 'active': False in response)
# This is enforced at queryset level in CategoryViewSet
self.assertIsInstance(categories, list)
class TestCategoryRetrieve(ContractTestCase):
"""GET /productos/api/v1/categories/{id}/"""
def test_get_category_by_id_returns_200(self):
"""
GET category by ID returns category details.
First fetch list to get a valid ID, then retrieve that category.
"""
# Get first category
list_response = self.get(Endpoints.CATEGORIES, params={"page_size": 1})
if list_response.status_code != 200:
self.skipTest("No categories available for testing")
data = list_response.data
categories = data["results"] if isinstance(data, dict) and "results" in data else data
if len(categories) == 0:
self.skipTest("No categories available for testing")
category_id = categories[0]["id"]
# Test detail endpoint
response = self.get(f"{Endpoints.CATEGORIES}{category_id}/")
self.assert_status(response, 200)
self.assertEqual(response.data["id"], category_id)
def test_nonexistent_category_returns_404(self):
"""GET non-existent category returns 404"""
response = self.get(f"{Endpoints.CATEGORIES}999999/")
self.assert_status(response, 404)

View File

@@ -1,122 +0,0 @@
"""
Contract Tests: Services API
Endpoint: /productos/api/v1/services/
App: productos
Returns available veterinary services filtered by pet type and location.
Critical for vet assignment automation.
"""
from ..base import ContractTestCase
from ..endpoints import Endpoints
from ..helpers import sample_pet_owner, unique_email, SAMPLE_CAT, SAMPLE_DOG
class TestServicesList(ContractTestCase):
"""GET /productos/api/v1/services/"""
def test_list_returns_200(self):
"""GET services returns 200"""
response = self.get(Endpoints.SERVICES, params={"page_size": 10})
self.assert_status(response, 200)
def test_returns_list(self):
"""GET services returns a list"""
response = self.get(Endpoints.SERVICES, params={"page_size": 10})
self.assert_status(response, 200)
data = response.data
# Handle paginated or non-paginated response
services = data["results"] if isinstance(data, dict) and "results" in data else data
self.assertIsInstance(services, list)
def test_services_have_required_fields(self):
"""Each service should have id and name"""
response = self.get(Endpoints.SERVICES, params={"page_size": 10})
data = response.data
services = data["results"] if isinstance(data, dict) and "results" in data else data
if len(services) > 0:
service = services[0]
self.assert_has_fields(service, "id", "name")
def test_accepts_pet_id_filter(self):
"""Services endpoint accepts pet_id parameter"""
response = self.get(Endpoints.SERVICES, params={"pet_id": 1})
# Should not error (even if pet doesn't exist, endpoint should handle gracefully)
self.assertIn(response.status_code, [200, 404])
class TestServicesFiltering(ContractTestCase):
"""GET /productos/api/v1/services/ with filters"""
def _create_owner_with_cat(self):
"""Helper to create owner and cat"""
owner_data = sample_pet_owner(unique_email("service_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 _create_owner_with_dog(self):
"""Helper to create owner and dog"""
owner_data = sample_pet_owner(unique_email("service_owner"))
owner_response = self.post(Endpoints.PET_OWNERS, owner_data)
owner_id = owner_response.data["id"]
pet_data = {**SAMPLE_DOG, "owner": owner_id}
pet_response = self.post(Endpoints.PETS, pet_data)
return pet_response.data["id"]
def test_filter_services_by_cat(self):
"""
Services filtered by cat pet_id returns appropriate services.
Request (from production turnero):
GET /productos/api/v1/services/?pet_id=123
Response structure validates services available for CAT type.
"""
cat_id = self._create_owner_with_cat()
response = self.get(Endpoints.SERVICES, params={"pet_id": cat_id, "page_size": 10})
# Should return services or handle gracefully
self.assertIn(response.status_code, [200, 404])
if response.status_code == 200:
data = response.data
services = data["results"] if isinstance(data, dict) and "results" in data else data
self.assertIsInstance(services, list)
def test_filter_services_by_dog(self):
"""
Services filtered by dog pet_id returns appropriate services.
Different pet types may have different service availability.
"""
dog_id = self._create_owner_with_dog()
response = self.get(Endpoints.SERVICES, params={"pet_id": dog_id, "page_size": 10})
self.assertIn(response.status_code, [200, 404])
if response.status_code == 200:
data = response.data
services = data["results"] if isinstance(data, dict) and "results" in data else data
self.assertIsInstance(services, list)
def test_services_without_pet_returns_all(self):
"""
Services without pet filter returns all available services.
Used for initial service browsing before pet selection.
"""
response = self.get(Endpoints.SERVICES, params={"page_size": 10})
self.assert_status(response, 200)
data = response.data
services = data["results"] if isinstance(data, dict) and "results" in data else data
self.assertIsInstance(services, list)

View File

@@ -1 +0,0 @@
# Contract tests for solicitudes app endpoints

View File

@@ -1,56 +0,0 @@
"""
Contract Tests: Service Requests API
Endpoint: /solicitudes/service-requests/
App: solicitudes
Creates and manages service requests (appointment bookings).
"""
from ..base import ContractTestCase
from ..endpoints import Endpoints
class TestServiceRequestList(ContractTestCase):
"""GET /solicitudes/service-requests/"""
def test_list_returns_200(self):
"""GET should return list of service requests (with pagination)"""
response = self.get(Endpoints.SERVICE_REQUESTS, params={"page_size": 1})
self.assert_status(response, 200)
def test_returns_list(self):
"""GET should return a list (possibly paginated)"""
response = self.get(Endpoints.SERVICE_REQUESTS, params={"page_size": 10})
data = response.data
requests_list = data["results"] if isinstance(data, dict) and "results" in data else data
self.assertIsInstance(requests_list, list)
class TestServiceRequestFields(ContractTestCase):
"""Field validation for service requests"""
def test_has_state_field(self):
"""Service requests should have a state/status field"""
response = self.get(Endpoints.SERVICE_REQUESTS, params={"page_size": 1})
data = response.data
requests_list = data["results"] if isinstance(data, dict) and "results" in data else data
if len(requests_list) > 0:
req = requests_list[0]
has_state = "state" in req or "status" in req
self.assertTrue(has_state, "Service request should have state/status field")
class TestServiceRequestCreate(ContractTestCase):
"""POST /solicitudes/service-requests/"""
def test_create_requires_fields(self):
"""Creating service request with empty data should fail"""
response = self.post(Endpoints.SERVICE_REQUESTS, {})
# Should return 400 with validation errors
self.assert_status(response, 400)

View File

@@ -1 +0,0 @@
# Contract tests for frontend workflows (compositions of endpoint tests)

View File

@@ -1,65 +0,0 @@
"""
Workflow Test: General Turnero Flow
This is a COMPOSITION test that validates the full turnero flow
by calling endpoints in sequence. Use this to ensure the flow works
end-to-end, but individual endpoint behavior is tested in app folders.
Flow:
1. Check coverage at address
2. Create pet owner (guest with mock email)
3. Create pet for owner
4. Get available services for pet
5. Create service request
Frontend route: /turnos/
User type: Guest (invitado)
"""
from ..base import ContractTestCase
from ..endpoints import Endpoints
from ..helpers import sample_pet_owner, unique_email, SAMPLE_CAT
class TestTurneroGeneralFlow(ContractTestCase):
"""
End-to-end flow test for general turnero.
Note: This tests the SEQUENCE of calls, not individual endpoint behavior.
Individual endpoint tests are in mascotas/, productos/, solicitudes/.
"""
def test_full_flow_sequence(self):
"""
Complete turnero flow should work end-to-end.
This test validates that a guest user can complete the full
appointment booking flow.
"""
# Step 0: Check coverage at address
coverage_response = self.get(Endpoints.COVERAGE_CHECK, params={
"lat": -34.6037,
"lng": -58.3816,
})
self.assert_status(coverage_response, 200)
# Step 1: Create pet owner (frontend creates mock email for guest)
mock_email = unique_email("invitado")
owner_data = sample_pet_owner(mock_email)
owner_response = self.post(Endpoints.PET_OWNERS, owner_data)
self.assert_status(owner_response, 201)
owner_id = owner_response.data["id"]
# Step 2: Create pet for owner
pet_data = {**SAMPLE_CAT, "owner": owner_id}
pet_response = self.post(Endpoints.PETS, pet_data)
self.assert_status(pet_response, 201)
pet_id = pet_response.data["id"]
# Step 3: Get services (optionally filtered by pet)
services_response = self.get(Endpoints.SERVICES, params={"pet_id": pet_id})
# Services endpoint may return 200 even without pet filter
self.assertIn(services_response.status_code, [200, 404])
# Note: Steps 4-5 (select date/time, create service request) require
# more setup (available times, cart, etc.) and are tested separately.