@@ -867,3 +867,151 @@ async def test_batch_fetch_headers_preserves_uid_mapping(self, email_client):
867867 assert len (result ) == 2
868868 assert result ["100" ]["subject" ] == "First"
869869 assert result ["200" ]["subject" ] == "Second"
870+
871+ @pytest .mark .asyncio
872+ async def test_batch_fetch_headers_skips_non_bytes_items (self , email_client ):
873+ """Test that _batch_fetch_headers skips non-bytes items in response."""
874+ mock_imap = AsyncMock ()
875+ mock_imap .uid = AsyncMock (
876+ return_value = (
877+ None ,
878+ [
879+ "not bytes" , # Should be skipped
880+ b"1 FETCH (BODY[HEADER] {50}" ,
881+ bytearray (b"From: a@test.com\r \n Subject: Test\r \n \r \n " ),
882+ b" UID 100)" ,
883+ ],
884+ )
885+ )
886+
887+ result = await email_client ._batch_fetch_headers (mock_imap , ["100" ])
888+
889+ assert "100" in result
890+ assert result ["100" ]["subject" ] == "Test"
891+
892+ @pytest .mark .asyncio
893+ async def test_batch_fetch_headers_skips_items_without_body_header (self , email_client ):
894+ """Test that _batch_fetch_headers skips bytes without BODY[HEADER]."""
895+ mock_imap = AsyncMock ()
896+ mock_imap .uid = AsyncMock (
897+ return_value = (
898+ None ,
899+ [
900+ b"some other data" , # No BODY[HEADER], should be skipped
901+ b"1 FETCH (BODY[HEADER] {50}" ,
902+ bytearray (b"From: a@test.com\r \n Subject: Test\r \n \r \n " ),
903+ b" UID 100)" ,
904+ ],
905+ )
906+ )
907+
908+ result = await email_client ._batch_fetch_headers (mock_imap , ["100" ])
909+
910+ assert "100" in result
911+ assert result ["100" ]["subject" ] == "Test"
912+
913+ @pytest .mark .asyncio
914+ async def test_batch_fetch_headers_skips_truncated_response (self , email_client ):
915+ """Test that _batch_fetch_headers skips when i+2 >= len(data)."""
916+ mock_imap = AsyncMock ()
917+ mock_imap .uid = AsyncMock (
918+ return_value = (
919+ None ,
920+ [
921+ b"1 FETCH (BODY[HEADER] {50}" ,
922+ bytearray (b"From: a@test.com\r \n Subject: Test\r \n \r \n " ),
923+ # Missing UID line (i+2 doesn't exist)
924+ ],
925+ )
926+ )
927+
928+ result = await email_client ._batch_fetch_headers (mock_imap , ["100" ])
929+
930+ # Should return empty dict since response is truncated
931+ assert result == {}
932+
933+ @pytest .mark .asyncio
934+ async def test_batch_fetch_headers_skips_non_bytearray_content (self , email_client ):
935+ """Test that _batch_fetch_headers skips when data[i+1] is not bytearray."""
936+ mock_imap = AsyncMock ()
937+ mock_imap .uid = AsyncMock (
938+ return_value = (
939+ None ,
940+ [
941+ b"1 FETCH (BODY[HEADER] {50}" ,
942+ b"not a bytearray" , # Should be bytearray, not bytes
943+ b" UID 100)" ,
944+ ],
945+ )
946+ )
947+
948+ result = await email_client ._batch_fetch_headers (mock_imap , ["100" ])
949+
950+ # Should return empty dict since content is not bytearray
951+ assert result == {}
952+
953+ @pytest .mark .asyncio
954+ async def test_batch_fetch_headers_skips_non_bytes_uid_item (self , email_client ):
955+ """Test that _batch_fetch_headers skips when data[i+2] is not bytes."""
956+ mock_imap = AsyncMock ()
957+ mock_imap .uid = AsyncMock (
958+ return_value = (
959+ None ,
960+ [
961+ b"1 FETCH (BODY[HEADER] {50}" ,
962+ bytearray (b"From: a@test.com\r \n Subject: Test\r \n \r \n " ),
963+ 12345 , # Not bytes, should result in uid_item = None
964+ ],
965+ )
966+ )
967+
968+ result = await email_client ._batch_fetch_headers (mock_imap , ["100" ])
969+
970+ # Should return empty dict since UID item is not bytes
971+ assert result == {}
972+
973+ @pytest .mark .asyncio
974+ async def test_batch_fetch_headers_skips_missing_uid_in_response (self , email_client ):
975+ """Test that _batch_fetch_headers skips when UID regex doesn't match."""
976+ mock_imap = AsyncMock ()
977+ mock_imap .uid = AsyncMock (
978+ return_value = (
979+ None ,
980+ [
981+ b"1 FETCH (BODY[HEADER] {50}" ,
982+ bytearray (b"From: a@test.com\r \n Subject: Test\r \n \r \n " ),
983+ b" NO_UID_HERE)" , # No UID in this line
984+ ],
985+ )
986+ )
987+
988+ result = await email_client ._batch_fetch_headers (mock_imap , ["100" ])
989+
990+ # Should return empty dict since UID regex doesn't match
991+ assert result == {}
992+
993+ @pytest .mark .asyncio
994+ async def test_batch_fetch_headers_handles_mixed_valid_invalid (self , email_client ):
995+ """Test that _batch_fetch_headers processes valid items and skips invalid ones."""
996+ mock_imap = AsyncMock ()
997+ mock_imap .uid = AsyncMock (
998+ return_value = (
999+ None ,
1000+ [
1001+ # Invalid: truncated (no UID line)
1002+ b"1 FETCH (BODY[HEADER] {50}" ,
1003+ bytearray (b"From: bad@test.com\r \n Subject: Bad\r \n \r \n " ),
1004+ # Valid email
1005+ b"2 FETCH (BODY[HEADER] {50}" ,
1006+ bytearray (b"From: good@test.com\r \n Subject: Good\r \n \r \n " ),
1007+ b" UID 200)" ,
1008+ ],
1009+ )
1010+ )
1011+
1012+ result = await email_client ._batch_fetch_headers (mock_imap , ["100" , "200" ])
1013+
1014+ # Only the valid email should be in results
1015+ assert len (result ) == 1
1016+ assert "200" in result
1017+ assert result ["200" ]["subject" ] == "Good"
0 commit comments