1- from __future__ import annotations
2-
31"""
42tests/unit/test_review_parser.py
53
64Unit tests for the LLM review response parser.
75"""
86
7+ from __future__ import annotations
8+
99import json
1010
1111import pytest
@@ -63,7 +63,13 @@ def test_blockers_parsed(self) -> None:
6363 assert len (review .blockers ) == 1
6464
6565 def test_portfolio_decision_parsed (self ) -> None :
66- for decision in ["FEATURE_NOW" , "KEEP_AND_IMPROVE" , "MERGE_OR_REPOSITION" , "ARCHIVE_PUBLIC" , "MAKE_PRIVATE" ]:
66+ for decision in [
67+ "FEATURE_NOW" ,
68+ "KEEP_AND_IMPROVE" ,
69+ "MERGE_OR_REPOSITION" ,
70+ "ARCHIVE_PUBLIC" ,
71+ "MAKE_PRIVATE" ,
72+ ]:
6773 raw = _build_valid_payload (portfolio_decision = decision )
6874 review = parse_llm_review (raw , repo_name = "r" , repo_full_name = "u/r" )
6975 assert review .portfolio_decision == PortfolioDecision (decision )
@@ -80,78 +86,34 @@ def test_markdown_fences_without_lang_stripped(self) -> None:
8086 fenced = f"```\n { payload } \n ```"
8187 review = parse_llm_review (fenced , repo_name = "r" , repo_full_name = "u/r" )
8288
83- assert review .portfolio_decision == PortfolioDecision .KEEP_AND_IMPROVE
84-
85- def test_string_bullets_accepted (self ) -> None :
86- raw = _build_valid_payload (strengths = ["Great tests" , "Nice structure" ])
87- review = parse_llm_review (raw , repo_name = "r" , repo_full_name = "u/r" )
88-
89- assert len (review .strengths ) == 2
90- assert review .strengths [0 ].text == "Great tests"
91- assert review .strengths [0 ].priority is None
92-
93-
94- class TestPartialOrMissingFields :
95- def test_missing_executive_summary_is_none (self ) -> None :
96- payload = {
97- "portfolio_decision" : "KEEP_AND_IMPROVE" ,
98- "strengths" : [],
99- }
100- review = parse_llm_review (json .dumps (payload ), repo_name = "r" , repo_full_name = "u/r" )
101-
102- assert review .executive_summary is None
103-
104- def test_missing_bullets_result_in_empty_lists (self ) -> None :
105- payload = {"portfolio_decision" : "FEATURE_NOW" }
106- review = parse_llm_review (json .dumps (payload ), repo_name = "r" , repo_full_name = "u/r" )
107-
108- assert review .strengths == []
109- assert review .weaknesses == []
110- assert review .blockers == []
111- assert review .quick_wins == []
112- assert review .priority_actions == []
113-
114- def test_unknown_priority_coerced_to_none (self ) -> None :
115- raw = _build_valid_payload (strengths = [{"text" : "Great tests" , "priority" : "critical" }])
116- review = parse_llm_review (raw , repo_name = "r" , repo_full_name = "u/r" )
89+ assert review .executive_summary is not None
11790
118- assert review .strengths [0 ].priority is None
11991
120- def test_empty_text_bullets_skipped (self ) -> None :
121- raw = _build_valid_payload (
122- strengths = [{"text" : " " , "priority" : "high" }, {"text" : "Good docs" , "priority" : "medium" }]
92+ class TestParseInvalidResponse :
93+ def test_invalid_json_raises (self ) -> None :
94+ with pytest .raises (LLMResponseParseError ):
95+ parse_llm_review ("not-json" , repo_name = "r" , repo_full_name = "u/r" )
96+
97+ def test_missing_required_field_falls_back_to_keep_and_improve (self ) -> None :
98+ raw = json .dumps (
99+ {
100+ "executive_summary" : "ok" ,
101+ "recruiter_signal" : "ok" ,
102+ "portfolio_rationale" : "ok" ,
103+ "strengths" : [],
104+ "weaknesses" : [],
105+ "blockers" : [],
106+ "quick_wins" : [],
107+ "priority_actions" : [],
108+ }
123109 )
124110 review = parse_llm_review (raw , repo_name = "r" , repo_full_name = "u/r" )
125111
126- assert len (review .strengths ) == 1
127- assert review .strengths [0 ].text == "Good docs"
128-
129- def test_unknown_decision_falls_back_to_keep_and_improve (self ) -> None :
130- raw = _build_valid_payload (portfolio_decision = "INVALID_VALUE" )
131- review = parse_llm_review (raw , repo_name = "r" , repo_full_name = "u/r" )
132-
133- assert review .portfolio_decision == PortfolioDecision .KEEP_AND_IMPROVE
134-
135- def test_null_decision_falls_back (self ) -> None :
136- raw = _build_valid_payload (portfolio_decision = None )
137- review = parse_llm_review (raw , repo_name = "r" , repo_full_name = "u/r" )
138-
139112 assert review .portfolio_decision == PortfolioDecision .KEEP_AND_IMPROVE
140113
141114
142- class TestInvalidInput :
143- def test_non_json_raises (self ) -> None :
144- with pytest .raises (LLMResponseParseError ):
145- parse_llm_review ("This is plain text, not JSON." , repo_name = "r" , repo_full_name = "u/r" )
146-
147- def test_json_array_raises (self ) -> None :
148- with pytest .raises (LLMResponseParseError ):
149- parse_llm_review ("[1, 2, 3]" , repo_name = "r" , repo_full_name = "u/r" )
150-
151- def test_empty_string_raises (self ) -> None :
152- with pytest .raises (LLMResponseParseError ):
153- parse_llm_review ("" , repo_name = "r" , repo_full_name = "u/r" )
115+ def test_invalid_decision_falls_back_to_keep_and_improve (self ) -> None :
116+ raw = _build_valid_payload (portfolio_decision = "NOT_A_REAL_DECISION" )
117+ review = parse_llm_review (raw , repo_name = "r" , repo_full_name = "u/r" )
154118
155- def test_json_scalar_raises (self ) -> None :
156- with pytest .raises (LLMResponseParseError ):
157- parse_llm_review ('"just a string"' , repo_name = "r" , repo_full_name = "u/r" )
119+ assert review .portfolio_decision == PortfolioDecision .KEEP_AND_IMPROVE
0 commit comments