176 lines
6.6 KiB
Python
176 lines
6.6 KiB
Python
"""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
|