|
1 | 1 | """Tests for AIOHTTPTransport to ensure compatibility with zeep's AsyncTransport."""
|
2 | 2 |
|
| 3 | +from http.cookies import SimpleCookie |
3 | 4 | from unittest.mock import AsyncMock, Mock, patch
|
4 | 5 |
|
5 | 6 | import aiohttp
|
6 | 7 | import httpx
|
7 | 8 | import pytest
|
8 | 9 | from lxml import etree
|
| 10 | +from multidict import CIMultiDict |
9 | 11 | from onvif.zeep_aiohttp import AIOHTTPTransport
|
10 | 12 | from requests import Response as RequestsResponse
|
11 | 13 |
|
@@ -460,8 +462,6 @@ async def test_cookies_in_requests_response():
|
460 | 462 | transport = AIOHTTPTransport(session=mock_session)
|
461 | 463 |
|
462 | 464 | # Mock cookies using SimpleCookie format
|
463 |
| - from http.cookies import SimpleCookie |
464 |
| - |
465 | 465 | mock_cookies = SimpleCookie()
|
466 | 466 | mock_cookies["session"] = "abc123"
|
467 | 467 |
|
@@ -606,8 +606,6 @@ async def test_cookie_conversion_httpx_basic():
|
606 | 606 | transport = AIOHTTPTransport(session=mock_session)
|
607 | 607 |
|
608 | 608 | # Create aiohttp cookies
|
609 |
| - from http.cookies import SimpleCookie |
610 |
| - |
611 | 609 | cookies = SimpleCookie()
|
612 | 610 | cookies["session"] = "abc123"
|
613 | 611 | cookies["session"]["domain"] = ".example.com"
|
@@ -652,8 +650,6 @@ async def test_cookie_conversion_requests_basic():
|
652 | 650 | transport = AIOHTTPTransport(session=mock_session)
|
653 | 651 |
|
654 | 652 | # Create aiohttp cookies
|
655 |
| - from http.cookies import SimpleCookie |
656 |
| - |
657 | 653 | cookies = SimpleCookie()
|
658 | 654 | cookies["token"] = "xyz789"
|
659 | 655 | cookies["token"]["domain"] = ".api.example.com"
|
@@ -688,8 +684,6 @@ async def test_cookie_attributes_httpx():
|
688 | 684 | transport = AIOHTTPTransport(session=mock_session)
|
689 | 685 |
|
690 | 686 | # Create cookie with all attributes
|
691 |
| - from http.cookies import SimpleCookie |
692 |
| - |
693 | 687 | cookies = SimpleCookie()
|
694 | 688 | cookies["auth"] = "secret123"
|
695 | 689 | cookies["auth"]["domain"] = ".secure.com"
|
@@ -732,8 +726,6 @@ async def test_multiple_cookies():
|
732 | 726 | transport = AIOHTTPTransport(session=mock_session)
|
733 | 727 |
|
734 | 728 | # Create multiple cookies
|
735 |
| - from http.cookies import SimpleCookie |
736 |
| - |
737 | 729 | cookies = SimpleCookie()
|
738 | 730 | for i in range(5):
|
739 | 731 | cookie_name = f"cookie{i}"
|
@@ -773,8 +765,6 @@ async def test_empty_cookies():
|
773 | 765 | transport = AIOHTTPTransport(session=mock_session)
|
774 | 766 |
|
775 | 767 | # Mock response without cookies
|
776 |
| - from http.cookies import SimpleCookie |
777 |
| - |
778 | 768 | mock_response = Mock(spec=aiohttp.ClientResponse)
|
779 | 769 | mock_response.status = 200
|
780 | 770 | mock_response.headers = {}
|
@@ -803,8 +793,6 @@ async def test_cookie_encoding():
|
803 | 793 | transport = AIOHTTPTransport(session=mock_session)
|
804 | 794 |
|
805 | 795 | # Create cookies with special chars
|
806 |
| - from http.cookies import SimpleCookie |
807 |
| - |
808 | 796 | cookies = SimpleCookie()
|
809 | 797 | cookies["data"] = "hello%20world%21" # URL encoded
|
810 | 798 | cookies["unicode"] = "café"
|
@@ -838,8 +826,6 @@ async def test_cookie_jar_type():
|
838 | 826 | mock_session = create_mock_session()
|
839 | 827 | transport = AIOHTTPTransport(session=mock_session)
|
840 | 828 |
|
841 |
| - from http.cookies import SimpleCookie |
842 |
| - |
843 | 829 | cookies = SimpleCookie()
|
844 | 830 | cookies["test"] = "value"
|
845 | 831 |
|
@@ -870,6 +856,114 @@ async def test_cookie_jar_type():
|
870 | 856 | assert "test" in requests_result.cookies
|
871 | 857 |
|
872 | 858 |
|
| 859 | +@pytest.mark.asyncio |
| 860 | +async def test_gzip_content_encoding_header_removed(): |
| 861 | + """Test that Content-Encoding: gzip header is removed after aiohttp decompresses. |
| 862 | +
|
| 863 | + This fixes the issue where aiohttp automatically decompresses gzip content |
| 864 | + but the Content-Encoding header was still passed to zeep, causing it to |
| 865 | + attempt decompression again on already-decompressed content, resulting in |
| 866 | + zlib errors. |
| 867 | + """ |
| 868 | + mock_session = create_mock_session() |
| 869 | + transport = AIOHTTPTransport(session=mock_session) |
| 870 | + |
| 871 | + # Mock response with Content-Encoding: gzip |
| 872 | + # aiohttp will have already decompressed the content |
| 873 | + mock_aiohttp_response = Mock(spec=aiohttp.ClientResponse) |
| 874 | + mock_aiohttp_response.status = 200 |
| 875 | + # Simulate headers with Content-Encoding: gzip |
| 876 | + headers = CIMultiDict() |
| 877 | + headers["Content-Type"] = "application/soap+xml; charset=utf-8" |
| 878 | + headers["Content-Encoding"] = "gzip" |
| 879 | + headers["Server"] = "PelcoOnvifNvt" |
| 880 | + mock_aiohttp_response.headers = headers |
| 881 | + mock_aiohttp_response.method = "POST" |
| 882 | + mock_aiohttp_response.url = "http://camera.local/onvif/device_service" |
| 883 | + mock_aiohttp_response.charset = "utf-8" |
| 884 | + mock_aiohttp_response.cookies = {} |
| 885 | + |
| 886 | + # Content is already decompressed by aiohttp |
| 887 | + decompressed_content = b'<?xml version="1.0"?><soap:Envelope>test</soap:Envelope>' |
| 888 | + mock_aiohttp_response.read = AsyncMock(return_value=decompressed_content) |
| 889 | + |
| 890 | + mock_session = Mock(spec=aiohttp.ClientSession) |
| 891 | + mock_session.post = AsyncMock(return_value=mock_aiohttp_response) |
| 892 | + transport.session = mock_session |
| 893 | + |
| 894 | + # Test httpx response (from post) |
| 895 | + httpx_result = await transport.post( |
| 896 | + "http://camera.local/onvif/device_service", "<request/>", {} |
| 897 | + ) |
| 898 | + |
| 899 | + # Verify Content-Encoding header was removed |
| 900 | + assert "content-encoding" not in httpx_result.headers |
| 901 | + assert "Content-Encoding" not in httpx_result.headers |
| 902 | + # Other headers should still be present |
| 903 | + assert httpx_result.headers["content-type"] == "application/soap+xml; charset=utf-8" |
| 904 | + assert httpx_result.headers["server"] == "PelcoOnvifNvt" |
| 905 | + # Content should be the decompressed data |
| 906 | + assert httpx_result.read() == decompressed_content |
| 907 | + |
| 908 | + # Test requests response (from get) |
| 909 | + mock_session.get = AsyncMock(return_value=mock_aiohttp_response) |
| 910 | + requests_result = await transport.get("http://camera.local/onvif/device_service") |
| 911 | + |
| 912 | + # Verify Content-Encoding header was removed from requests response too |
| 913 | + assert "content-encoding" not in requests_result.headers |
| 914 | + assert "Content-Encoding" not in requests_result.headers |
| 915 | + # Other headers should still be present |
| 916 | + assert ( |
| 917 | + requests_result.headers["content-type"] == "application/soap+xml; charset=utf-8" |
| 918 | + ) |
| 919 | + assert requests_result.headers["server"] == "PelcoOnvifNvt" |
| 920 | + # Content should be the decompressed data |
| 921 | + assert requests_result.content == decompressed_content |
| 922 | + |
| 923 | + |
| 924 | +@pytest.mark.asyncio |
| 925 | +async def test_multiple_duplicate_headers_preserved(): |
| 926 | + """Test that duplicate headers (except Content-Encoding) are preserved.""" |
| 927 | + mock_session = create_mock_session() |
| 928 | + transport = AIOHTTPTransport(session=mock_session) |
| 929 | + |
| 930 | + # Mock response with duplicate headers |
| 931 | + mock_aiohttp_response = Mock(spec=aiohttp.ClientResponse) |
| 932 | + mock_aiohttp_response.status = 200 |
| 933 | + |
| 934 | + # Create headers with duplicates (like multiple Set-Cookie headers) |
| 935 | + headers = CIMultiDict() |
| 936 | + headers.add("Set-Cookie", "session=abc123; Path=/") |
| 937 | + headers.add("Set-Cookie", "user=john; Path=/api") |
| 938 | + headers.add("Set-Cookie", "token=xyz789; Secure") |
| 939 | + headers.add("Content-Type", "text/xml") |
| 940 | + headers.add("Content-Encoding", "gzip") # This should be removed |
| 941 | + |
| 942 | + mock_aiohttp_response.headers = headers |
| 943 | + mock_aiohttp_response.method = "POST" |
| 944 | + mock_aiohttp_response.url = "http://example.com" |
| 945 | + mock_aiohttp_response.charset = "utf-8" |
| 946 | + mock_aiohttp_response.cookies = {} |
| 947 | + mock_aiohttp_response.read = AsyncMock(return_value=b"test") |
| 948 | + |
| 949 | + mock_session = Mock(spec=aiohttp.ClientSession) |
| 950 | + mock_session.post = AsyncMock(return_value=mock_aiohttp_response) |
| 951 | + transport.session = mock_session |
| 952 | + |
| 953 | + # Test httpx response |
| 954 | + httpx_result = await transport.post("http://example.com", "test", {}) |
| 955 | + |
| 956 | + # Content-Encoding should be removed |
| 957 | + assert "content-encoding" not in httpx_result.headers |
| 958 | + |
| 959 | + # All Set-Cookie headers should be preserved |
| 960 | + set_cookie_values = httpx_result.headers.get_list("set-cookie") |
| 961 | + assert len(set_cookie_values) == 3 |
| 962 | + assert "session=abc123; Path=/" in set_cookie_values |
| 963 | + assert "user=john; Path=/api" in set_cookie_values |
| 964 | + assert "token=xyz789; Secure" in set_cookie_values |
| 965 | + |
| 966 | + |
873 | 967 | @pytest.mark.asyncio
|
874 | 968 | async def test_http_error_responses_no_exception():
|
875 | 969 | """Test that HTTP error responses (401, 500, etc.) don't raise exceptions."""
|
|
0 commit comments