From ae767e723e924ae9ef106c06e95b5e51f451d4ed Mon Sep 17 00:00:00 2001 From: BHOGALA-SRIKA Date: Tue, 2 Jun 2026 17:31:19 +0530 Subject: [PATCH 1/2] feat: add robust validation and 100% branch coverage for _derive_grade --- backend/main.py | 8 +++ backend/tests/test_ci.py | 133 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/backend/main.py b/backend/main.py index d6a64c9..f61ee07 100644 --- a/backend/main.py +++ b/backend/main.py @@ -154,6 +154,14 @@ def _body_detail(s: int) -> str: def _derive_grade(score: float) -> str: + # 1. Type validation checking: Reject anything that isn't a direct 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 integer or float.") + + # 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).") + if score >= 92: return "A+" if score >= 80: diff --git a/backend/tests/test_ci.py b/backend/tests/test_ci.py index 7cd902d..9f946bb 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,121 @@ 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 +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) \ No newline at end of file From 51788abc90f4e8150d53ef78b58fa8ffd5f3420b Mon Sep 17 00:00:00 2001 From: BHOGALA-SRIKA Date: Tue, 2 Jun 2026 21:55:41 +0530 Subject: [PATCH 2/2] style: fix linter line length, whitespace, and duplicates --- backend/main.py | 18 +++++++++++------- backend/tests/test_ci.py | 41 ---------------------------------------- 2 files changed, 11 insertions(+), 48 deletions(-) diff --git a/backend/main.py b/backend/main.py index f61ee07..c9e2ce0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -154,23 +154,27 @@ def _body_detail(s: int) -> str: def _derive_grade(score: float) -> str: - # 1. Type validation checking: Reject anything that isn't a direct integer or float + # 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 integer or float.") - + 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 9f946bb..f8b05ac 100644 --- a/backend/tests/test_ci.py +++ b/backend/tests/test_ci.py @@ -191,45 +191,4 @@ def test_derive_grade_invalid_types(bad_type): # Now we can safely import from main in total isolation! 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) \ No newline at end of file