@@ -1572,6 +1572,37 @@ def test_pro_snapshot_matches_pro(self, review_mod):
15721572 assert snapshot == base
15731573
15741574
1575+ class TestExtractResponseText :
1576+ def test_prefers_output_text_field (self , review_mod ):
1577+ result = {"output_text" : "Direct text." , "output" : []}
1578+ assert review_mod ._extract_response_text (result ) == "Direct text."
1579+
1580+ def test_walks_output_items_when_output_text_null (self , review_mod ):
1581+ result = {
1582+ "output_text" : None ,
1583+ "output" : [{"type" : "message" , "content" : [
1584+ {"type" : "output_text" , "text" : "Walked text." },
1585+ ]}],
1586+ }
1587+ assert review_mod ._extract_response_text (result ) == "Walked text."
1588+
1589+ def test_concatenates_multiple_blocks (self , review_mod ):
1590+ result = {
1591+ "output_text" : None ,
1592+ "output" : [{"type" : "message" , "content" : [
1593+ {"type" : "output_text" , "text" : "A" },
1594+ {"type" : "output_text" , "text" : "B" },
1595+ ]}],
1596+ }
1597+ assert review_mod ._extract_response_text (result ) == "AB"
1598+
1599+ def test_empty_when_no_output (self , review_mod ):
1600+ assert review_mod ._extract_response_text ({"output_text" : None , "output" : []}) == ""
1601+
1602+ def test_empty_when_missing_keys (self , review_mod ):
1603+ assert review_mod ._extract_response_text ({}) == ""
1604+
1605+
15751606class TestResponsesAPIConstants :
15761607 def test_endpoint_is_responses (self , review_mod ):
15771608 assert "responses" in review_mod .ENDPOINT
@@ -1652,8 +1683,63 @@ def test_timeout_passed_through(self, review_mod, mock_urlopen):
16521683 review_mod .call_openai ("test" , "gpt-5.4" , "fake-key" , timeout = 900 )
16531684 assert mock_urlopen ["timeout" ] == 900
16541685
1655- def test_incomplete_status_exits (self , review_mod , mock_urlopen ):
1656- """Non-completed status should cause sys.exit."""
1686+ def test_missing_status_with_valid_output_succeeds (self , review_mod , mock_urlopen ):
1687+ """Valid content should be accepted even when status field is absent."""
1688+ mock_urlopen ["response_data" ] = {
1689+ "output_text" : None ,
1690+ "output" : [{
1691+ "type" : "message" ,
1692+ "content" : [{"type" : "output_text" , "text" : "Good review." }],
1693+ }],
1694+ "usage" : {"input_tokens" : 10 , "output_tokens" : 5 },
1695+ }
1696+ content , _ = review_mod .call_openai ("test" , "gpt-5.4" , "fake-key" )
1697+ assert content == "Good review."
1698+
1699+ def test_status_none_with_valid_output_succeeds (self , review_mod , mock_urlopen ):
1700+ """status=None should not prevent content extraction."""
1701+ mock_urlopen ["response_data" ] = {
1702+ "status" : None ,
1703+ "output_text" : None ,
1704+ "output" : [{
1705+ "type" : "message" ,
1706+ "content" : [{"type" : "output_text" , "text" : "Good review." }],
1707+ }],
1708+ "usage" : {"input_tokens" : 10 , "output_tokens" : 5 },
1709+ }
1710+ content , _ = review_mod .call_openai ("test" , "gpt-5.4" , "fake-key" )
1711+ assert content == "Good review."
1712+
1713+ def test_output_text_convenience_field_used (self , review_mod , mock_urlopen ):
1714+ """When output_text is populated (SDK-style), use it directly."""
1715+ mock_urlopen ["response_data" ] = {
1716+ "status" : "completed" ,
1717+ "output_text" : "SDK-provided text." ,
1718+ "output" : [],
1719+ "usage" : {"input_tokens" : 10 , "output_tokens" : 5 },
1720+ }
1721+ content , _ = review_mod .call_openai ("test" , "gpt-5.4" , "fake-key" )
1722+ assert content == "SDK-provided text."
1723+
1724+ def test_multiple_output_text_blocks_concatenated (self , review_mod , mock_urlopen ):
1725+ """Multiple output_text blocks should be concatenated in order."""
1726+ mock_urlopen ["response_data" ] = {
1727+ "status" : "completed" ,
1728+ "output_text" : None ,
1729+ "output" : [{
1730+ "type" : "message" ,
1731+ "content" : [
1732+ {"type" : "output_text" , "text" : "Part 1. " },
1733+ {"type" : "output_text" , "text" : "Part 2." },
1734+ ],
1735+ }],
1736+ "usage" : {"input_tokens" : 10 , "output_tokens" : 5 },
1737+ }
1738+ content , _ = review_mod .call_openai ("test" , "gpt-5.4" , "fake-key" )
1739+ assert content == "Part 1. Part 2."
1740+
1741+ def test_failed_status_no_content_exits (self , review_mod , mock_urlopen ):
1742+ """Failed status with no usable content should exit."""
16571743 mock_urlopen ["response_data" ] = {
16581744 "status" : "failed" ,
16591745 "output_text" : None ,
@@ -1664,7 +1750,7 @@ def test_incomplete_status_exits(self, review_mod, mock_urlopen):
16641750 review_mod .call_openai ("test" , "gpt-5.4" , "fake-key" )
16651751
16661752 def test_empty_output_exits (self , review_mod , mock_urlopen ):
1667- """Empty output items should cause sys. exit."""
1753+ """Empty output items with completed status should exit."""
16681754 mock_urlopen ["response_data" ] = {
16691755 "status" : "completed" ,
16701756 "output_text" : None ,
0 commit comments