@@ -205,7 +205,7 @@ def test_host_port_nondefault_wss(make_request) -> None:
205
205
206
206
def test_host_port_none_port (make_request ) -> None :
207
207
req = make_request ("get" , "unix://localhost/path" )
208
- assert req .headers ["Host" ] == "localhost"
208
+ assert req .headers [hdrs . HOST ] == "localhost"
209
209
210
210
211
211
def test_host_port_err (make_request ) -> None :
@@ -220,17 +220,17 @@ def test_hostname_err(make_request) -> None:
220
220
221
221
def test_host_header_host_first (make_request ) -> None :
222
222
req = make_request ("get" , "http://python.org/" )
223
- assert list (req .headers )[0 ] == "Host"
223
+ assert list (req .headers )[0 ] == hdrs . HOST
224
224
225
225
226
226
def test_host_header_host_without_port (make_request ) -> None :
227
227
req = make_request ("get" , "http://python.org/" )
228
- assert req .headers [" HOST" ] == "python.org"
228
+ assert req .headers [hdrs . HOST ] == "python.org"
229
229
230
230
231
231
def test_host_header_host_with_default_port (make_request ) -> None :
232
232
req = make_request ("get" , "http://python.org:80/" )
233
- assert req .headers [" HOST" ] == "python.org"
233
+ assert req .headers [hdrs . HOST ] == "python.org"
234
234
235
235
236
236
def test_host_header_host_with_nondefault_port (make_request ) -> None :
@@ -348,12 +348,12 @@ def test_skip_default_useragent_header(make_request) -> None:
348
348
349
349
def test_headers (make_request ) -> None :
350
350
req = make_request (
351
- "post" , "http://python.org/" , headers = {"Content-Type" : "text/plain" }
351
+ "post" , "http://python.org/" , headers = {hdrs . CONTENT_TYPE : "text/plain" }
352
352
)
353
353
354
- assert "CONTENT-TYPE" in req .headers
355
- assert req .headers ["CONTENT-TYPE" ] == "text/plain"
356
- assert req .headers ["ACCEPT-ENCODING" ] == "gzip, deflate, br"
354
+ assert hdrs . CONTENT_TYPE in req .headers
355
+ assert req .headers [hdrs . CONTENT_TYPE ] == "text/plain"
356
+ assert req .headers [hdrs . ACCEPT_ENCODING ] == "gzip, deflate, br"
357
357
358
358
359
359
def test_headers_list (make_request ) -> None :
@@ -979,7 +979,7 @@ async def test_body_with_size_sets_content_length(
979
979
async def test_body_payload_with_size_no_content_length (
980
980
loop : asyncio .AbstractEventLoop ,
981
981
) -> None :
982
- """Test that when a body payload with size is set directly , Content-Length is added."""
982
+ """Test that when a body payload is set via update_body , Content-Length is added."""
983
983
# Create a payload with a known size
984
984
data = b"payload data"
985
985
bytes_payload = payload .BytesPayload (data )
@@ -991,23 +991,28 @@ async def test_body_payload_with_size_no_content_length(
991
991
loop = loop ,
992
992
)
993
993
994
- # Set body directly (bypassing update_body_from_data to avoid it setting Content-Length)
995
- req ._body = bytes_payload
996
-
997
- # Ensure conditions for the code path we want to test
998
- assert req ._body is not None
999
- assert hdrs .CONTENT_LENGTH not in req .headers
1000
- assert req ._body .size is not None
1001
- assert not req .chunked
994
+ # Initially no body should be set
995
+ assert req ._body is None
996
+ # POST method with None body should have Content-Length: 0
997
+ assert req .headers [hdrs .CONTENT_LENGTH ] == "0"
1002
998
1003
- # Now trigger update_transfer_encoding which should set Content-Length
1004
- req .update_transfer_encoding ( )
999
+ # Update body using the public method
1000
+ await req .update_body ( bytes_payload )
1005
1001
1006
1002
# Verify Content-Length was set from body.size
1007
- assert req .headers ["CONTENT-LENGTH" ] == str (len (data ))
1003
+ assert req .headers [hdrs . CONTENT_LENGTH ] == str (len (data ))
1008
1004
assert req .body is bytes_payload
1009
1005
assert req ._body is bytes_payload # Access _body which is the Payload
1006
+ assert req ._body is not None # type: ignore[unreachable]
1010
1007
assert req ._body .size == len (data )
1008
+
1009
+ # Set body back to None
1010
+ await req .update_body (None )
1011
+
1012
+ # Verify Content-Length is back to 0 for POST with None body
1013
+ assert req .headers [hdrs .CONTENT_LENGTH ] == "0"
1014
+ assert req ._body is None
1015
+
1011
1016
await req .close ()
1012
1017
1013
1018
@@ -1980,8 +1985,8 @@ async def test_update_body_updates_content_length(
1980
1985
1981
1986
# Clear body
1982
1987
await req .update_body (None )
1983
- # For None body, Content-Length should not be set
1984
- assert "Content-Length" not in req .headers
1988
+ # For None body with POST method , Content-Length should be set to 0
1989
+ assert req .headers [ hdrs . CONTENT_LENGTH ] == "0"
1985
1990
1986
1991
await req .close ()
1987
1992
@@ -2075,4 +2080,149 @@ async def test_expect100_with_body_becomes_none() -> None:
2075
2080
req ._body = None
2076
2081
2077
2082
await req .write_bytes (mock_writer , mock_conn , None )
2083
+
2084
+
2085
+ @pytest .mark .parametrize (
2086
+ ("method" , "data" , "expected_content_length" ),
2087
+ [
2088
+ # GET methods should not have Content-Length with None body
2089
+ ("GET" , None , None ),
2090
+ ("HEAD" , None , None ),
2091
+ ("OPTIONS" , None , None ),
2092
+ ("TRACE" , None , None ),
2093
+ # POST methods should have Content-Length: 0 with None body
2094
+ ("POST" , None , "0" ),
2095
+ ("PUT" , None , "0" ),
2096
+ ("PATCH" , None , "0" ),
2097
+ ("DELETE" , None , "0" ),
2098
+ # Empty bytes should always set Content-Length: 0
2099
+ ("GET" , b"" , "0" ),
2100
+ ("HEAD" , b"" , "0" ),
2101
+ ("POST" , b"" , "0" ),
2102
+ ("PUT" , b"" , "0" ),
2103
+ # Non-empty bytes should set appropriate Content-Length
2104
+ ("GET" , b"test" , "4" ),
2105
+ ("POST" , b"test" , "4" ),
2106
+ ("PUT" , b"hello world" , "11" ),
2107
+ ("PATCH" , b"data" , "4" ),
2108
+ ("DELETE" , b"x" , "1" ),
2109
+ ],
2110
+ )
2111
+ def test_content_length_for_methods (
2112
+ method : str ,
2113
+ data : Optional [bytes ],
2114
+ expected_content_length : Optional [str ],
2115
+ loop : asyncio .AbstractEventLoop ,
2116
+ ) -> None :
2117
+ """Test that Content-Length header is set correctly for all HTTP methods."""
2118
+ req = ClientRequest (method , URL ("http://python.org/" ), data = data , loop = loop )
2119
+
2120
+ actual_content_length = req .headers .get (hdrs .CONTENT_LENGTH )
2121
+ assert actual_content_length == expected_content_length
2122
+
2123
+
2124
+ @pytest .mark .parametrize ("method" , ["GET" , "HEAD" , "OPTIONS" , "TRACE" ])
2125
+ def test_get_methods_classification (method : str ) -> None :
2126
+ """Test that GET-like methods are correctly classified."""
2127
+ assert method in ClientRequest .GET_METHODS
2128
+
2129
+
2130
+ @pytest .mark .parametrize ("method" , ["POST" , "PUT" , "PATCH" , "DELETE" ])
2131
+ def test_non_get_methods_classification (method : str ) -> None :
2132
+ """Test that POST-like methods are not in GET_METHODS."""
2133
+ assert method not in ClientRequest .GET_METHODS
2134
+
2135
+
2136
+ async def test_content_length_with_string_data (loop : asyncio .AbstractEventLoop ) -> None :
2137
+ """Test Content-Length when data is a string."""
2138
+ data = "Hello, World!"
2139
+ req = ClientRequest ("POST" , URL ("http://python.org/" ), data = data , loop = loop )
2140
+ # String should be encoded to bytes, default encoding is utf-8
2141
+ assert req .headers [hdrs .CONTENT_LENGTH ] == str (len (data .encode ("utf-8" )))
2142
+ await req .close ()
2143
+
2144
+
2145
+ async def test_content_length_with_async_iterable (
2146
+ loop : asyncio .AbstractEventLoop ,
2147
+ ) -> None :
2148
+ """Test that async iterables use chunked encoding, not Content-Length."""
2149
+
2150
+ async def data_gen () -> AsyncIterator [bytes ]:
2151
+ yield b"chunk1" # pragma: no cover
2152
+
2153
+ req = ClientRequest ("POST" , URL ("http://python.org/" ), data = data_gen (), loop = loop )
2154
+ assert hdrs .CONTENT_LENGTH not in req .headers
2155
+ assert req .chunked
2156
+ assert req .headers [hdrs .TRANSFER_ENCODING ] == "chunked"
2157
+ await req .close ()
2158
+
2159
+
2160
+ async def test_content_length_not_overridden (loop : asyncio .AbstractEventLoop ) -> None :
2161
+ """Test that explicitly set Content-Length is not overridden."""
2162
+ req = ClientRequest (
2163
+ "POST" ,
2164
+ URL ("http://python.org/" ),
2165
+ data = b"test" ,
2166
+ headers = {hdrs .CONTENT_LENGTH : "100" },
2167
+ loop = loop ,
2168
+ )
2169
+ # Should keep the explicitly set value
2170
+ assert req .headers [hdrs .CONTENT_LENGTH ] == "100"
2171
+ await req .close ()
2172
+
2173
+
2174
+ async def test_content_length_with_formdata (loop : asyncio .AbstractEventLoop ) -> None :
2175
+ """Test Content-Length with FormData."""
2176
+ form = aiohttp .FormData ()
2177
+ form .add_field ("field" , "value" )
2178
+
2179
+ req = ClientRequest ("POST" , URL ("http://python.org/" ), data = form , loop = loop )
2180
+ # FormData with known size should set Content-Length
2181
+ assert hdrs .CONTENT_LENGTH in req .headers
2182
+ await req .close ()
2183
+
2184
+
2185
+ async def test_no_content_length_with_chunked (loop : asyncio .AbstractEventLoop ) -> None :
2186
+ """Test that chunked encoding prevents Content-Length header."""
2187
+ req = ClientRequest (
2188
+ "POST" ,
2189
+ URL ("http://python.org/" ),
2190
+ data = b"test" ,
2191
+ chunked = True ,
2192
+ loop = loop ,
2193
+ )
2194
+ assert hdrs .CONTENT_LENGTH not in req .headers
2195
+ assert req .headers [hdrs .TRANSFER_ENCODING ] == "chunked"
2196
+ await req .close ()
2197
+
2198
+
2199
+ @pytest .mark .parametrize ("method" , ["POST" , "PUT" , "PATCH" , "DELETE" ])
2200
+ async def test_update_body_none_sets_content_length_zero (
2201
+ method : str , loop : asyncio .AbstractEventLoop
2202
+ ) -> None :
2203
+ """Test that updating body to None sets Content-Length: 0 for POST-like methods."""
2204
+ # Create request with initial body
2205
+ req = ClientRequest (method , URL ("http://python.org/" ), data = b"initial" , loop = loop )
2206
+ assert req .headers [hdrs .CONTENT_LENGTH ] == "7"
2207
+
2208
+ # Update body to None
2209
+ await req .update_body (None )
2210
+ assert req .headers [hdrs .CONTENT_LENGTH ] == "0"
2211
+ assert req ._body is None
2212
+ await req .close ()
2213
+
2214
+
2215
+ @pytest .mark .parametrize ("method" , ["GET" , "HEAD" , "OPTIONS" , "TRACE" ])
2216
+ async def test_update_body_none_no_content_length_for_get_methods (
2217
+ method : str , loop : asyncio .AbstractEventLoop
2218
+ ) -> None :
2219
+ """Test that updating body to None doesn't set Content-Length for GET-like methods."""
2220
+ # Create request with initial body
2221
+ req = ClientRequest (method , URL ("http://python.org/" ), data = b"initial" , loop = loop )
2222
+ assert req .headers [hdrs .CONTENT_LENGTH ] == "7"
2223
+
2224
+ # Update body to None
2225
+ await req .update_body (None )
2226
+ assert hdrs .CONTENT_LENGTH not in req .headers
2227
+ assert req ._body is None
2078
2228
await req .close ()
0 commit comments