@@ -202,3 +202,85 @@ def test_exact_tag_length_boundary():
202202 assert events [0 ] == snapshot (
203203 PartStartEvent (index = 0 , part = ThinkingPart (content = '' , part_kind = 'thinking' ), event_kind = 'part_start' )
204204 )
205+
206+
207+ def test_buffered_content_flushed_on_finalize ():
208+ """Test that buffered content is flushed when finalize is called."""
209+ manager = ModelResponsePartsManager ()
210+ thinking_tags = ('<think>' , '</think>' )
211+
212+ # Buffer partial tag
213+ events = list (manager .handle_text_delta (vendor_part_id = 'content' , content = '<thi' , thinking_tags = thinking_tags ))
214+ assert len (events ) == 0 # Buffered
215+
216+ # Finalize should flush buffer
217+ final_events = list (manager .finalize ())
218+ assert len (final_events ) == 1
219+ assert final_events [0 ] == snapshot (
220+ PartStartEvent (index = 0 , part = TextPart (content = '<thi' , part_kind = 'text' ), event_kind = 'part_start' )
221+ )
222+
223+
224+ def test_finalize_flushes_all_buffers ():
225+ """Test that finalize flushes all vendor_part_id buffers."""
226+ manager = ModelResponsePartsManager ()
227+ thinking_tags = ('<think>' , '</think>' )
228+
229+ # Buffer for vendor_id_1
230+ list (manager .handle_text_delta (vendor_part_id = 'id1' , content = '<th' , thinking_tags = thinking_tags ))
231+
232+ # Buffer for vendor_id_2
233+ list (manager .handle_text_delta (vendor_part_id = 'id2' , content = '<thi' , thinking_tags = thinking_tags ))
234+
235+ # Finalize should flush both
236+ final_events = list (manager .finalize ())
237+ assert len (final_events ) == 2
238+
239+ # Both should become TextParts
240+ parts = manager .get_parts ()
241+ assert len (parts ) == 2
242+ assert all (isinstance (p , TextPart ) for p in parts )
243+ # Note: order may vary, so check content exists
244+ text_parts = [p for p in parts if isinstance (p , TextPart )]
245+ contents = {p .content for p in text_parts }
246+ assert contents == {'<th' , '<thi' }
247+
248+
249+ def test_finalize_with_no_buffer ():
250+ """Test that finalize is safe when buffer is empty."""
251+ manager = ModelResponsePartsManager ()
252+ events = list (manager .finalize ())
253+ assert len (events ) == 0 # No events, no errors
254+
255+
256+ def test_finalize_with_empty_buffered_content ():
257+ """Test that finalize handles empty string in buffer (covers 83->82 branch)."""
258+ manager = ModelResponsePartsManager ()
259+ # Add both empty and non-empty content to test the branch where buffered_content is falsy
260+ # This ensures the loop continues after skipping the empty content
261+ manager ._thinking_tag_buffer ['id1' ] = '' # Will be skipped # pyright: ignore[reportPrivateUsage]
262+ manager ._thinking_tag_buffer ['id2' ] = 'content' # Will be flushed # pyright: ignore[reportPrivateUsage]
263+ events = list (manager .finalize ())
264+ assert len (events ) == 1 # Only non-empty content produces events
265+ assert isinstance (events [0 ], PartStartEvent )
266+ assert events [0 ].part == TextPart (content = 'content' )
267+ assert manager ._thinking_tag_buffer == {} # Buffer should be cleared # pyright: ignore[reportPrivateUsage]
268+
269+
270+ def test_get_parts_after_finalize ():
271+ """Test that get_parts returns flushed content after finalize (unit test)."""
272+ # NOTE: This is a unit test of the manager. Real integration testing with
273+ # StreamedResponse is done in test_finalize_integration().
274+ manager = ModelResponsePartsManager ()
275+ thinking_tags = ('<think>' , '</think>' )
276+
277+ list (manager .handle_text_delta (vendor_part_id = 'content' , content = '<thi' , thinking_tags = thinking_tags ))
278+
279+ # Before finalize
280+ assert manager .get_parts () == [] # Buffer not included
281+
282+ # Finalize
283+ list (manager .finalize ())
284+
285+ # After finalize
286+ assert manager .get_parts () == snapshot ([TextPart (content = '<thi' , part_kind = 'text' )])
0 commit comments