Files

176 lines
6.6 KiB
Python
Raw Permalink Normal View History

"""Tests for rental request router (T05)."""
import pytest
from unittest.mock import AsyncMock, patch
from sqlalchemy import select
from app.models.rental_request import RentalRequest, RentalRequestItem
VALID_RENTAL_PAYLOAD = {
"event_name": "Sommerfestival 2026",
"date_start": "2026-08-15",
"date_end": "2026-08-17",
"location": "Berlin",
"person_count": 500,
"contact_name": "Max Veranstalter",
"contact_company": "Event GmbH",
"contact_email": "max@event.de",
"contact_phone": "+49 30 1234567",
"contact_street": "Hauptstr. 42",
"contact_postalcode": "10115",
"contact_city": "Berlin",
"message": "Bitte um Angebot",
"items": [{"equipment_id": 1, "quantity": 2}],
}
@pytest.mark.asyncio
async def test_rental_valid_returns_201(client, seeded_equipment):
"""POST /api/rental-requests with valid data returns 201 with reference number."""
with patch(
"app.services.rentman_service.RentmanService.create_project_request",
new_callable=AsyncMock,
return_value={"id": "rentman-123"},
), patch(
"app.services.rentman_service.RentmanService.add_equipment_to_request",
new_callable=AsyncMock,
return_value={"id": "eq-1"},
), patch(
"app.services.email_service.EmailService.send_rental_confirmation",
new_callable=AsyncMock,
return_value=True,
):
resp = await client.post("/api/rental-requests", json=VALID_RENTAL_PAYLOAD)
assert resp.status_code == 201
data = resp.json()
assert "reference_number" in data
ref = data["reference_number"]
assert ref.startswith("HMS-")
parts = ref.split("-")
assert len(parts) == 3
assert parts[1].isdigit()
assert len(parts[2]) == 5
assert parts[2].isdigit()
assert data["status"] == "pending"
@pytest.mark.asyncio
async def test_rental_date_end_before_start_returns_422(client, seeded_equipment):
"""POST /api/rental-requests with date_end < date_start returns 422."""
payload = {**VALID_RENTAL_PAYLOAD, "date_start": "2026-08-20", "date_end": "2026-08-15"}
resp = await client.post("/api/rental-requests", json=payload)
assert resp.status_code == 422
@pytest.mark.asyncio
async def test_rental_empty_items_returns_422(client, seeded_equipment):
"""POST /api/rental-requests with empty items array returns 422."""
payload = {**VALID_RENTAL_PAYLOAD, "items": []}
resp = await client.post("/api/rental-requests", json=payload)
assert resp.status_code == 422
@pytest.mark.asyncio
async def test_rental_saved_to_db(client, seeded_equipment, test_db):
"""POST /api/rental-requests saves to rental_requests and rental_request_items tables."""
with patch(
"app.services.rentman_service.RentmanService.create_project_request",
new_callable=AsyncMock,
return_value={"id": "rentman-456"},
), patch(
"app.services.rentman_service.RentmanService.add_equipment_to_request",
new_callable=AsyncMock,
return_value={"id": "eq-2"},
), patch(
"app.services.email_service.EmailService.send_rental_confirmation",
new_callable=AsyncMock,
return_value=True,
):
resp = await client.post("/api/rental-requests", json=VALID_RENTAL_PAYLOAD)
assert resp.status_code == 201
ref_number = resp.json()["reference_number"]
result = await test_db.execute(
select(RentalRequest).where(RentalRequest.reference_number == ref_number)
)
rental = result.scalar_one_or_none()
assert rental is not None
assert rental.event_name == "Sommerfestival 2026"
assert rental.contact_name == "Max Veranstalter"
assert rental.status == "pending"
assert rental.rentman_request_id == "rentman-456"
assert rental.rentman_sync_status == "success"
items_result = await test_db.execute(
select(RentalRequestItem).where(RentalRequestItem.rental_request_id == rental.id)
)
items = items_result.scalars().all()
assert len(items) == 1
assert items[0].equipment_id == 1
assert items[0].quantity == 2
assert items[0].equipment_name == "L-Acoustics K2"
@pytest.mark.asyncio
async def test_rental_triggers_rentman(client, seeded_equipment):
"""POST /api/rental-requests triggers Rentman create_project_request."""
with patch(
"app.services.rentman_service.RentmanService.create_project_request",
new_callable=AsyncMock,
return_value={"id": "rentman-789"},
) as mock_create, patch(
"app.services.rentman_service.RentmanService.add_equipment_to_request",
new_callable=AsyncMock,
return_value={"id": "eq-3"},
) as mock_add, patch(
"app.services.email_service.EmailService.send_rental_confirmation",
new_callable=AsyncMock,
return_value=True,
):
resp = await client.post("/api/rental-requests", json=VALID_RENTAL_PAYLOAD)
assert resp.status_code == 201
mock_create.assert_awaited_once()
mock_add.assert_awaited_once()
@pytest.mark.asyncio
async def test_rental_triggers_email(client, seeded_equipment):
"""POST /api/rental-requests triggers confirmation email."""
with patch(
"app.services.rentman_service.RentmanService.create_project_request",
new_callable=AsyncMock,
return_value={"id": "rentman-email-test"},
), patch(
"app.services.rentman_service.RentmanService.add_equipment_to_request",
new_callable=AsyncMock,
return_value={"id": "eq-4"},
), patch(
"app.services.email_service.EmailService.send_rental_confirmation",
new_callable=AsyncMock,
return_value=True,
) as mock_email:
resp = await client.post("/api/rental-requests", json=VALID_RENTAL_PAYLOAD)
assert resp.status_code == 201
mock_email.assert_awaited_once()
call_kwargs = mock_email.call_args.kwargs
assert call_kwargs["to_addr"] == "max@event.de"
assert call_kwargs["event_name"] == "Sommerfestival 2026"
assert call_kwargs["reference_number"].startswith("HMS-")
assert len(call_kwargs["items"]) == 1
@pytest.mark.asyncio
async def test_rental_rentman_failure_still_returns_201(client, seeded_equipment):
"""POST /api/rental-requests still returns 201 when Rentman API fails."""
with patch(
"app.services.rentman_service.RentmanService.create_project_request",
new_callable=AsyncMock,
side_effect=Exception("Rentman API error"),
), patch(
"app.services.email_service.EmailService.send_rental_confirmation",
new_callable=AsyncMock,
return_value=True,
):
resp = await client.post("/api/rental-requests", json=VALID_RENTAL_PAYLOAD)
assert resp.status_code == 201