diff --git a/backend/main.py b/backend/main.py index d6a64c9..c9e2ce0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -154,15 +154,27 @@ def _body_detail(s: int) -> str: def _derive_grade(score: float) -> str: + # 1. Type validation checking: Reject anything that isn't an integer or float + if not isinstance(score, (int, float)) or isinstance(score, bool): + raise ValueError( + f"Invalid input type: {type(score)}. Score must be a numeric float/int." + ) + + # 2. Scale boundaries validation: Must reside strictly between 0.0 and 100.0 inclusive + if score < 0 or score > 100: + raise ValueError(f"Score {score} is out of valid scale range (0.0 - 100.0).") + + # 3. Proceed safely with the evaluation hierarchy if score >= 92: return "A+" - if score >= 80: + elif score >= 80: return "A" - if score >= 65: + elif score >= 65: return "B" - if score >= 50: + elif score >= 50: return "C" - return "D" + else: + return "D" def _to_db_grade(grade: str) -> str: diff --git a/backend/tests/test_ci.py b/backend/tests/test_ci.py index 7cd902d..f8b05ac 100644 --- a/backend/tests/test_ci.py +++ b/backend/tests/test_ci.py @@ -11,6 +11,21 @@ # Ensure the backend directory is on the path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +# --------------------------------------------------------------------------- +# INTERCEPT MOCKS (Must run BEFORE importing any app modules) +# --------------------------------------------------------------------------- +from unittest.mock import MagicMock + +# Inject dummy modules into sys.modules to stop Python from loading heavy/broken packages +mock_supabase = MagicMock() +sys.modules['supabase'] = mock_supabase + +mock_inference = MagicMock() +mock_inference.load_models = MagicMock() +mock_inference.predict_stream_a = MagicMock() +mock_inference.predict_stream_b = MagicMock() +sys.modules['inference'] = mock_inference + import numpy as np @@ -100,3 +115,80 @@ def test_dev_bypass_constants_are_readable(): assert hasattr(auth, "DEV_BYPASS_TOKEN") assert isinstance(auth.DEV_BYPASS_AUTH, bool) assert isinstance(auth.DEV_BYPASS_TOKEN, str) + + +# --------------------------------------------------------------------------- +# main.py — Grade Derivation Logic & Validation Checks +# --------------------------------------------------------------------------- + + + + +# Now we can safely import from main without needing the actual package installed! +import pytest +from main import _derive_grade + +# 1. Test Boundary Values and Valid Grade Ranges +@pytest.mark.parametrize("score, expected_grade", [ + (100, "A+"), + (92, "A+"), # Exact boundary for A+ + (91.9, "A"), # Just below A+ boundary + (80, "A"), # Exact boundary for A + (79.9, "B"), # Just below A boundary + (65, "B"), # Exact boundary for B + (64.9, "C"), # Just below B boundary + (50, "C"), # Exact boundary for C + (49.9, "D"), # Just below C boundary + (0, "D"), # Minimum standard boundary +]) +def test_derive_grade_boundaries(score, expected_grade): + assert _derive_grade(score) == expected_grade + + +# 2. Test Out-of-Scale Inputs (Expected to raise ValueError) +@pytest.mark.parametrize("invalid_score", [ + (-1), # Negative scale boundary + (-50.5), # Extreme negative + (101), # Just over maximum scale + (200.0), # Extreme over-scale +]) +def test_derive_grade_out_of_scale(invalid_score): + with pytest.raises(ValueError): + _derive_grade(invalid_score) + + +# 3. Test Incorrect Data Types (Expected to raise ValueError) +@pytest.mark.parametrize("bad_type", [ + ("95"), # String containing numbers + ("fresh_fish"), # Regular text string + (None), # None Type + ([],), # List/Iterable object +]) +def test_derive_grade_invalid_types(bad_type): + with pytest.raises(ValueError): + _derive_grade(bad_type) + + + +# --------------------------------------------------------------------------- +# main.py — Grade Derivation Logic & Validation Checks +# --------------------------------------------------------------------------- + + +from unittest.mock import MagicMock + +# 1. Trick Python into thinking 'supabase' is already loaded +mock_supabase = MagicMock() +sys.modules['supabase'] = mock_supabase + +# 2. Trick Python into bypassing 'inference' entirely to avoid the broken PyTorch DLLs +mock_inference = MagicMock() +# Provide dummy mock functions so main.py can unpack them during import +mock_inference.load_models = MagicMock() +mock_inference.predict_stream_a = MagicMock() +mock_inference.predict_stream_b = MagicMock() +sys.modules['inference'] = mock_inference + +# Now we can safely import from main in total isolation! +import pytest +