@@ -24,8 +24,9 @@ def _load_module():
2424
2525
2626class _FakeResponse :
27- def __init__ (self , content : str = "LGTM" ) -> None :
27+ def __init__ (self , content : str = "LGTM" , finish_reason : str = "stop" ) -> None :
2828 self ._content = content
29+ self ._finish_reason = finish_reason
2930
3031 def __enter__ (self ):
3132 return self
@@ -35,7 +36,14 @@ def __exit__(self, *_args):
3536
3637 def read (self ) -> bytes :
3738 return json .dumps (
38- {"choices" : [{"message" : {"content" : self ._content }}]}
39+ {
40+ "choices" : [
41+ {
42+ "finish_reason" : self ._finish_reason ,
43+ "message" : {"content" : self ._content },
44+ }
45+ ]
46+ }
3947 ).encode ()
4048
4149
@@ -74,6 +82,25 @@ def fake_urlopen(request, *, timeout):
7482 self .assertIn ("## 项目规则(CLAUDE.md)\n project rules" , payload ["messages" ][1 ]["content" ])
7583 self .assertEqual (captured ["timeout" ], module .TIMEOUT_S )
7684
85+ def test_truncated_content_retries_instead_of_publishing_partial_review (self ) -> None :
86+ module = _load_module ()
87+ responses = [
88+ _FakeResponse ("未完成的 finding" , finish_reason = "length" ),
89+ _FakeResponse ("完整 review" ),
90+ ]
91+
92+ with patch .object (
93+ module .urllib .request ,
94+ "urlopen" ,
95+ side_effect = responses ,
96+ ) as urlopen :
97+ result = module ._call_deepseek (
98+ "secret-value" , "PR title" , "diff body" , "project rules"
99+ )
100+
101+ self .assertEqual (result , "完整 review" )
102+ self .assertEqual (urlopen .call_count , 2 )
103+
77104 def test_empty_content_retries_then_returns_review (self ) -> None :
78105 module = _load_module ()
79106 responses = [_FakeResponse ("" ), _FakeResponse ("最终 review" )]
0 commit comments