@@ -125,8 +125,9 @@ async def mock_aiter_bytes() -> AsyncIterable[bytes]:
125125 r for r in results if r .metadata .get ("fetch_status" ) == "success"
126126 ]
127127
128- assert len (status_parts ) == 1
129- assert "Fetched successfully" in status_parts [0 ].text
128+ assert len (status_parts ) == 2 # Processing + success status
129+ assert any ("Processing 1 URL(s)" in part .text for part in status_parts )
130+ assert any ("Fetched successfully" in part .text for part in status_parts )
130131 assert len (content_parts ) == 1
131132 assert content_parts [0 ].text == "Test Content"
132133 assert content_parts [0 ].metadata ["source_url" ] == "https://example.com"
@@ -159,23 +160,26 @@ async def test_failed_fetch_with_mocking(self) -> None:
159160 part = processor .ProcessorPart ("Visit https://notfound.com" )
160161 results = [r async for r in p .call (part )]
161162
162- # Should have status and failure parts
163+ # Should have status parts and exception part
163164 status_parts = [
164165 r for r in results if r .substream_name == processor .STATUS_STREAM
165166 ]
166- failure_parts = [
167- r for r in results if r .metadata .get ("fetch_status " ) == "failure "
167+ exception_parts = [
168+ r for r in results if r .metadata .get ("exception_type " ) == "RuntimeError "
168169 ]
169170
170- assert len (status_parts ) == 1
171- assert "Fetch failed" in status_parts [0 ].text
172- assert len (failure_parts ) == 1
173- assert failure_parts [0 ].text == "" # Empty content for failures
174- assert "404" in failure_parts [0 ].metadata ["fetch_error" ]
171+ assert (
172+ len (status_parts ) == 3
173+ ) # Processing + failure status + exception part
174+ assert any ("Processing 1 URL(s)" in part .text for part in status_parts )
175+ assert any ("Fetch failed" in part .text for part in status_parts )
176+ assert len (exception_parts ) == 1
177+ assert "An unexpected error occurred" in exception_parts [0 ].text
178+ assert "404" in exception_parts [0 ].metadata ["original_exception" ]
175179
176180 @pytest .mark .anyio
177181 async def test_fail_on_error_config (self ) -> None :
178- """Test that fail_on_error configuration raises exceptions ."""
182+ """Test that errors are converted to exception parts by the decorator ."""
179183 config = FetchConfig (fail_on_error = True )
180184 p = UrlFetchProcessor (config )
181185
@@ -194,10 +198,14 @@ async def test_fail_on_error_config(self) -> None:
194198 mock_client .get .side_effect = error
195199
196200 part = processor .ProcessorPart ("Visit https://error.com" )
201+ results = [r async for r in p .call (part )]
197202
198- with pytest .raises (RuntimeError ):
199- async for _ in p .call (part ):
200- pass
203+ # With decorator, exceptions are converted to exception parts
204+ exception_parts = [
205+ r for r in results if r .metadata .get ("exception_type" ) == "RuntimeError"
206+ ]
207+ assert len (exception_parts ) == 1
208+ assert "Request Error" in exception_parts [0 ].metadata ["original_exception" ]
201209
202210 @pytest .mark .anyio
203211 async def test_content_processor_raw_config (self ) -> None :
@@ -470,19 +478,22 @@ async def test_url_validation_integration(self) -> None:
470478 part = processor .ProcessorPart ("Visit https://malicious.com" )
471479 results = [r async for r in p .call (part )]
472480
473- # Should have status and failure parts
481+ # Should have status parts and exception part
474482 status_parts = [
475483 r for r in results if r .substream_name == processor .STATUS_STREAM
476484 ]
477- failure_parts = [
478- r for r in results if r .metadata .get ("fetch_status " ) == "failure "
485+ exception_parts = [
486+ r for r in results if r .metadata .get ("exception_type " ) == "RuntimeError "
479487 ]
480488
481- assert len (status_parts ) == 1
482- assert "Fetch failed" in status_parts [0 ].text
483- assert len (failure_parts ) == 1
484- error_msg = failure_parts [0 ].metadata ["fetch_error" ]
485- assert "Security validation failed" in error_msg
489+ assert len (status_parts ) == 3 # Processing + failure status + exception part
490+ assert any ("Processing 1 URL(s)" in part .text for part in status_parts )
491+ assert any ("Fetch failed" in part .text for part in status_parts )
492+ assert len (exception_parts ) == 1
493+ assert (
494+ "Domain 'malicious.com' not in allowed list"
495+ in exception_parts [0 ].metadata ["original_exception" ]
496+ )
486497
487498 @pytest .mark .anyio
488499 async def test_create_success_part_validation_errors (self ) -> None :
@@ -558,13 +569,34 @@ async def test_response_size_limits(self) -> None:
558569 part = processor .ProcessorPart ("Visit https://example.com" )
559570 results = [r async for r in p .call (part )]
560571
561- # Should have failure parts due to size limit
562- failure_parts = [
563- r for r in results if r .metadata .get ("fetch_status" ) == "failure"
572+ # Should have status parts and exception part
573+ status_parts = [
574+ r
575+ for r in results
576+ if r .substream_name == processor .STATUS_STREAM # type: ignore
577+ ]
578+ exception_parts = [
579+ r
580+ for r in results
581+ # type: ignore
582+ if r .metadata .get ("exception_type" ) == "RuntimeError"
564583 ]
565- assert len (failure_parts ) == 1
566- error_msg = failure_parts [0 ].metadata ["fetch_error" ]
567- assert "Response too large" in error_msg
584+
585+ assert (
586+ len (status_parts ) == 3
587+ ) # Processing + failure status + exception part
588+ assert any (
589+ "Processing 1 URL(s)" in part .text # type: ignore
590+ for part in status_parts
591+ )
592+ assert any (
593+ "Fetch failed" in part .text for part in status_parts # type: ignore
594+ )
595+ assert len (exception_parts ) == 1
596+ original_exception = exception_parts [0 ].metadata [ # type: ignore
597+ "original_exception"
598+ ]
599+ assert "Response too large" in original_exception
568600
569601 @pytest .mark .anyio
570602 async def test_streaming_response_size_exceeded (self ) -> None :
@@ -595,10 +627,31 @@ async def mock_aiter_bytes() -> AsyncIterable[bytes]:
595627 part = processor .ProcessorPart ("Visit https://example.com" )
596628 results = [r async for r in p .call (part )]
597629
598- # Should have failure parts due to size limit during streaming
599- failure_parts = [
600- r for r in results if r .metadata .get ("fetch_status" ) == "failure"
630+ # Should have status parts and exception part
631+ status_parts = [
632+ r
633+ for r in results
634+ if r .substream_name == processor .STATUS_STREAM # type: ignore
635+ ]
636+ exception_parts = [
637+ r
638+ for r in results
639+ # type: ignore
640+ if r .metadata .get ("exception_type" ) == "RuntimeError"
641+ ]
642+
643+ assert (
644+ len (status_parts ) == 3
645+ ) # Processing + failure status + exception part
646+ assert any (
647+ "Processing 1 URL(s)" in part .text # type: ignore
648+ for part in status_parts
649+ )
650+ assert any (
651+ "Fetch failed" in part .text for part in status_parts # type: ignore
652+ )
653+ assert len (exception_parts ) == 1
654+ original_exception = exception_parts [0 ].metadata [ # type: ignore
655+ "original_exception"
601656 ]
602- assert len (failure_parts ) == 1
603- error_msg = failure_parts [0 ].metadata ["fetch_error" ]
604- assert "Response exceeded" in error_msg
657+ assert "Response exceeded" in original_exception
0 commit comments