| from __future__ import annotations
|
|
|
| import pytest
|
| from httpx import AsyncClient
|
|
|
| from conftest import generate_valid_transaction_data
|
|
|
|
|
| pytestmark = [pytest.mark.asyncio, pytest.mark.validation]
|
|
|
|
|
| async def test_invalid_email_format_returns_422(client: AsyncClient) -> None:
|
| """Invalid emails should be rejected during registration."""
|
|
|
| response = await client.post("/api/auth/register", json={"email": "bad-email", "password": "StrongPass123"})
|
|
|
| assert response.status_code == 422, f"Invalid email should return 422, got {response.text}"
|
|
|
|
|
| @pytest.mark.parametrize(
|
| ("payload", "field"),
|
| [
|
| ({"amount": "10.00", "type": "invalid", "category": "Food", "date": "2024-01-01"}, "type"),
|
| ({"amount": "10.00", "type": "income", "category": "Food", "date": "invalid-date"}, "date"),
|
| ({"amount": "not-a-number", "type": "income", "category": "Food", "date": "2024-01-01"}, "amount"),
|
| ],
|
| )
|
| async def test_invalid_transaction_fields_return_422(
|
| admin_client: AsyncClient,
|
| payload: dict[str, str],
|
| field: str,
|
| ) -> None:
|
| """Malformed transaction input should return the normalized validation payload."""
|
|
|
| response = await admin_client.post("/api/transactions", json=payload)
|
|
|
| assert response.status_code == 422, f"Invalid field {field} should return 422, got {response.text}"
|
| assert any(error["field"] == field for error in response.json()["errors"]), f"422 payload should include the {field} field"
|
|
|
|
|
| async def test_422_response_has_correct_format(client: AsyncClient) -> None:
|
| """Validation errors should follow the app's normalized response format."""
|
|
|
| response = await client.post("/api/auth/register", json={"email": "still-bad"})
|
|
|
| assert response.status_code == 422, f"Invalid registration payload should return 422, got {response.text}"
|
| payload = response.json()
|
| assert payload["detail"] == "Validation error", "Validation response should use the shared detail string"
|
| assert isinstance(payload["errors"], list) and payload["errors"], "Validation response should include a non-empty errors list"
|
|
|
|
|
| async def test_extra_fields_are_ignored_on_registration(client: AsyncClient) -> None:
|
| """Unexpected fields should be ignored under the current Pydantic configuration."""
|
|
|
| response = await client.post(
|
| "/api/auth/register",
|
| json={
|
| "email": "extrafields@example.com",
|
| "password": "StrongPass123",
|
| "full_name": "Extra Fields",
|
| "ignored_field": "value",
|
| },
|
| )
|
|
|
| assert response.status_code == 201, f"Extra fields should be ignored, got {response.text}"
|
| assert "ignored_field" not in response.json(), "Ignored fields should not be echoed back in the response"
|
|
|
|
|
| async def test_amount_precision_validation(admin_client: AsyncClient) -> None:
|
| """Amounts with too many decimal places should fail validation."""
|
|
|
| response = await admin_client.post("/api/transactions", json=generate_valid_transaction_data(amount="10.999"))
|
|
|
| assert response.status_code == 422, f"Invalid decimal precision should return 422, got {response.text}"
|
|
|
|
|
| async def test_end_date_before_start_date_returns_422(analyst_client: AsyncClient) -> None:
|
| """Invalid date ranges should fail query validation."""
|
|
|
| response = await analyst_client.get(
|
| "/api/transactions",
|
| params={"start_date": "2024-01-10", "end_date": "2024-01-01"},
|
| )
|
|
|
| assert response.status_code == 422, f"Invalid date range should return 422, got {response.text}"
|
|
|