from datetime import date, datetime import pytest from fastapi import HTTPException from app.api.v1.users import ( _deserialize_answer_value, _normalize_answer_value, _normalize_volunteer_level, _parse_options, _serialize_options, ) from app.models.models import ProfileQuestion from app.schemas import QuestionOption def make_question(input_type: str, options_json: str | None = None) -> ProfileQuestion: return ProfileQuestion( key=f"{input_type}_question", label=f"{input_type.title()} Question", input_type=input_type, options_json=options_json, ) def test_option_parsing_and_serialization_filters_invalid_items() -> None: assert _parse_options('[{"label":" Yes ","value":" yes "}, {"label":"","value":"no"}, "bad"]') == [ {"label": "Yes", "value": "yes"} ] assert _parse_options("not-json") == [] serialized = _serialize_options([QuestionOption(label="Private Pilot", value="ppl")]) assert _parse_options(serialized) == [{"label": "Private Pilot", "value": "ppl"}] @pytest.mark.parametrize( ("value", "expected"), [ (True, "true"), ("yes", "true"), ("0", "false"), (False, "false"), ], ) def test_boolean_answers_are_normalized(value: object, expected: str) -> None: assert _normalize_answer_value(make_question("boolean"), value) == expected def test_invalid_boolean_answer_raises_400() -> None: with pytest.raises(HTTPException) as exc: _normalize_answer_value(make_question("boolean"), "maybe") assert exc.value.status_code == 400 @pytest.mark.parametrize( ("value", "expected"), [ (3, "3"), ("3.50", "3.5"), (date(2026, 5, 4), "2026-05-04"), (datetime(2026, 5, 4, 12, 30), "2026-05-04"), ], ) def test_number_and_date_answers_are_normalized(value: object, expected: str) -> None: input_type = "date" if isinstance(value, (date, datetime)) else "number" assert _normalize_answer_value(make_question(input_type), value) == expected def test_select_answers_must_match_configured_options() -> None: question = make_question("select", '[{"label":"Private Pilot","value":"ppl"}]') assert _normalize_answer_value(question, "ppl") == "ppl" with pytest.raises(HTTPException) as exc: _normalize_answer_value(question, "cpl") assert exc.value.status_code == 400 def test_empty_answers_clear_existing_values() -> None: assert _normalize_answer_value(make_question("text"), "") is None assert _normalize_answer_value(make_question("text"), None) is None def test_answer_deserialization_restores_frontend_types() -> None: assert _deserialize_answer_value(make_question("boolean"), "true") is True assert _deserialize_answer_value(make_question("boolean"), "false") is False assert _deserialize_answer_value(make_question("number"), "10") == 10 assert _deserialize_answer_value(make_question("number"), "10.5") == 10.5 assert _deserialize_answer_value(make_question("text"), "SASA") == "SASA" @pytest.mark.parametrize( ("value", "expected"), [ ("yes", "yes"), ("true", "yes"), ("0", "no"), ("", None), (None, None), ], ) def test_volunteer_level_accepts_boolean_like_values(value: str | None, expected: str | None) -> None: assert _normalize_volunteer_level(value) == expected def test_invalid_volunteer_level_raises_400() -> None: with pytest.raises(HTTPException) as exc: _normalize_volunteer_level("sometimes") assert exc.value.status_code == 400