import math import pytest from enum import Enum from app.services.answer_utils import ( normalize_question_type, parse_float, parse_nat_range, split_answer_tokens, is_valid_nat_answer, normalize_choice_answer, ) class DummyEnum(Enum): MCQ = "MCQ" NAT = "QuestionType.NAT" def test_normalize_question_type(): assert normalize_question_type(" MCQ ") == "mcq" assert normalize_question_type("NAT") == "nat" assert normalize_question_type("QuestionType.MSQ") == "msq" assert normalize_question_type(DummyEnum.MCQ) == "mcq" assert normalize_question_type(DummyEnum.NAT) == "nat" assert normalize_question_type(None) == "" assert normalize_question_type("") == "" def test_parse_float(): assert parse_float("1.23") == 1.23 assert parse_float("-0.5") == -0.5 assert parse_float("1e-5") == 1e-5 assert parse_float(42) == 42.0 # Invalid strings assert parse_float("abc") is None assert parse_float("1.2.3") is None assert parse_float("") is None assert parse_float(None) is None # Non-finite values assert parse_float(float("inf")) is None assert parse_float(float("-inf")) is None assert parse_float(float("nan")) is None def test_parse_nat_range(): assert parse_nat_range("1-2") == (1.0, 2.0) assert parse_nat_range("1.5 : 2.5") == (1.5, 2.5) assert parse_nat_range(" 10 to 20 ") == (10.0, 20.0) assert parse_nat_range("-2--1") == (-2.0, -1.0) assert parse_nat_range("-5 : -3.5") == (-5.0, -3.5) # Invalid ranges (lo > hi) assert parse_nat_range("2-1") is None assert parse_nat_range("5 to 3") is None # Invalid formats assert parse_nat_range("1-2-3") is None assert parse_nat_range("abc-def") is None assert parse_nat_range("1") is None assert parse_nat_range(None) is None def test_split_answer_tokens(): assert split_answer_tokens("A, B, C") == ["A", "B", "C"] assert split_answer_tokens("A ; B / C") == ["A", "B", "C"] assert split_answer_tokens(" word1 , word2 ") == ["word1", "word2"] assert split_answer_tokens("A") == ["A"] assert split_answer_tokens("") == [] assert split_answer_tokens(" ") == [] assert split_answer_tokens(None) == [] assert split_answer_tokens(",;/") == [] def test_is_valid_nat_answer(): assert is_valid_nat_answer("1.5") is True assert is_valid_nat_answer("1.5:2.5") is True assert is_valid_nat_answer("-10 to -5") is True assert is_valid_nat_answer("1.5, 2.5:3.5, 4") is True assert is_valid_nat_answer("abc") is False assert is_valid_nat_answer("1.5, abc") is False assert is_valid_nat_answer("") is False assert is_valid_nat_answer(None) is False assert is_valid_nat_answer("2-1") is False # Invalid range (lo > hi) assert is_valid_nat_answer("1.5-2.5-3.5") is False def test_normalize_choice_answer(): assert normalize_choice_answer("a, b, c") == "A,B,C" assert normalize_choice_answer(" a ; B / c ") == "A,B,C" assert normalize_choice_answer("option1") == "OPTION1" assert normalize_choice_answer(" ") == "" assert normalize_choice_answer(None) == "" from app.models.models import Question from app.services.scoring import evaluate_answer def test_nat_colon_range_scores_boundary_values(): question = Question( question_type="nat", question_text="Probability", options=[], correct_answer="0.44:0.45", marks=2.0, negative_marks=0.0, ) for selected in ("0.44", "0.445", "0.45"): assert evaluate_answer(question, selected) == (True, 2.0) assert evaluate_answer(question, "0.46") == (False, 0.0) def test_nat_hyphen_negative_range_still_works(): assert parse_nat_range("-2--1") == (-2.0, -1.0) assert is_valid_nat_answer("-2--1") is True