🧪 Tester view
Untested behaviours, edge cases, test gap inventory · QA Engineer, SDET, Test Lead
⚡ Agent trace · 13 steps · $0.0497 · click to expand
search_claims(query='error exception handle', category='any') →
search_claims(query='test spec', category='any') →
search_claims(query='behavioral', category='behavioral') →
search_claims(query='edge case boundary limit', category='any') →
search_claims(query='behavioral', category='any') →
search_claims(query='behavioural', category='any') →
search_claims(query='a', category='any') →
get_file_claims(file_path='src/requests/adapters.py') →
get_file_claims(file_path='src/requests/sessions.py') →
get_file_claims(file_path='src/requests/models.py') →
read_source_file(file_path='src/requests/adapters.py', start_line=634) →
read_source_file(file_path='src/requests/sessions.py', start_line=186) →
read_source_file(file_path='src/requests/sessions.py', start_line=752) →
read_source_file(file_path='src/requests/sessions.py', start_line=812) →
read_source_file(file_path='src/requests/adapters.py', start_line=694) →
read_source_file(file_path='src/requests/models.py', start_line=422) →
read_source_file(file_path='src/requests/models.py', start_line=912) →
read_source_file(file_path='src/requests/models.py', start_line=482) →
read_source_file(file_path='src/requests/models.py', start_line=1031) →
read_source_file(file_path='src/requests/models.py', start_line=1091) →
read_source_file(file_path='src/requests/models.py', start_line=184) →
get_file_claims(file_path='tests/test_requests.py') →
get_file_claims(file_path='tests/test_lowlevel.py') →
get_file_claims(file_path='tests/test_utils.py')
Test Intelligence Report
High-Risk Untested Behaviours
1. send() method error re-raise fallthrough (CRITICAL)
File: src/requests/adapters.py:737-746
The send() method catches (_SSLError, _HTTPError) in a combined handler. Inside, it checks isinstance(e, _SSLError) and isinstance(e, ReadTimeoutError) and isinstance(e, _InvalidHeader). If none of those match, the exception is re-raised as-is (raise on line 746). This means any _HTTPError subclass that is NOT ReadTimeoutError or _InvalidHeader will propagate as a raw urllib3 exception, bypassing the Requests exception hierarchy entirely. No test covers this fallthrough path.
Risk: A new urllib3 version could introduce a new _HTTPError subclass that leaks through as an un-wrapped exception, breaking user code that catches requests.exceptions.RequestException.
2. resolve_redirects consumes body with bare except (HIGH)
File: src/requests/sessions.py:211-214
try:
resp.content # Consume socket so it can be released
except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
resp.raw.read(decode_content=False)
When resp.content raises one of these three exceptions, the fallback reads the raw socket. If resp.raw.read() itself raises an exception, it propagates unhandled. There is no test that simulates a failure in both resp.content AND resp.raw.read(). This would cause the redirect loop to abort with an unexpected exception.
3. _encode_files — fp.read() on non-seekable stream (HIGH)
File: src/requests/models.py:239-240
elif isinstance(fp, _SupportsRead):
fdata = fp.read()
If fp is a readable stream (e.g., a socket or pipe), fp.read() will block indefinitely or raise. There is no timeout or size limit. No test verifies behavior with a non-seekable, non-rewindable stream that has no known size.
4. prepare_body — json.dumps failure only catches ValueError (MEDIUM)
File: src/requests/models.py (around line 442)
The claim states: "raises InvalidJSONError if json.dumps fails with ValueError". But json.dumps can also raise TypeError (e.g., for non-serializable objects like a complex number). A TypeError would propagate as an unhandled exception, not as InvalidJSONError. No test covers non-serializable JSON types beyond NaN.
5. cert_verify — path existence check order (MEDIUM)
File: src/requests/adapters.py:307-363
The method raises OSError if the CA bundle path or cert/key file paths do not exist. However, the check for CA bundle path happens before the check for cert/key files. If both are invalid, only the CA bundle error is raised. No test verifies the behavior when both verify and cert point to non-existent files simultaneously.
Edge Case Inventory
URL Preparation Edge Cases
| Edge Case | File:Line | Description |
|---|---|---|
| Wildcard hostname | src/requests/models.py:531 |
Host starting with * or . raises InvalidURL("URL has an invalid label.") |
| Non-HTTP schemes | src/requests/models.py:503-505 |
URLs with non-http schemes (mailto, data, magnet) bypass all URL processing |
| Fragment-only redirect | src/requests/sessions.py:231-234 |
If redirect Location has no fragment but original URL had one, the original fragment is preserved |
| Protocol-relative redirect | src/requests/sessions.py:225-227 |
Redirect to //host/path prepends the scheme from the original response URL |
| Bytes URL | src/requests/models.py:492-493 |
Bytes URLs are decoded as UTF-8; no fallback if decoding fails |
| Empty path | src/requests/models.py:113-131 |
path_url defaults to '/' if parsed path is empty |
Timeout Edge Cases
| Edge Case | File:Line | Description |
|---|---|---|
| Tuple with 3+ elements | src/requests/adapters.py:681-689 |
Raises ValueError with specific message |
| Non-numeric timeout | src/requests/adapters.py:692-693 |
Passed directly to TimeoutSauce — urllib3 may raise TypeError |
None timeout |
src/requests/adapters.py:692-693 |
TimeoutSauce(connect=None, read=None) — urllib3 treats as no timeout |
File Upload Edge Cases
| Edge Case | File:Line | Description |
|---|---|---|
None file value |
src/requests/models.py:241-242 |
Skipped with continue — silently excluded from multipart body |
| File tuple with 4 elements | src/requests/models.py:231-232 |
4th element is custom headers for the part |
data is a string with files |
src/requests/models.py:197-198 |
Raises ValueError("Data must not be a string.") |
fp is str/bytes/bytearray |
src/requests/models.py:237-238 |
Used directly as file data (not read from a file object) |
Response Content Edge Cases
| Edge Case | File:Line | Description |
|---|---|---|
status_code == 0 or raw is None |
src/requests/models.py:1039-1040 |
content property returns None |
| Already consumed content | src/requests/models.py:1036-1037 |
Raises RuntimeError("The content for this response was already consumed") |
chunk_size not int |
src/requests/models.py:958-961 |
Raises TypeError |
| Stream consumed twice | src/requests/models.py:956-957 |
Raises StreamConsumedError() |
| Encoding lookup failure | src/requests/models.py:1076-1083 |
Falls back to str(content, errors="replace") with no encoding |
Cookie Edge Cases
| Edge Case | File:Line | Description |
|---|---|---|
| Duplicate names, different domains | tests/test_requests.py:1377-1396 |
get() raises CookieConflictError without domain |
| Duplicate names, different paths | tests/test_requests.py:1398-1407 |
get() raises CookieConflictError |
Error Handling Gaps
1. Silent failure in resolve_redirects body consumption (HIGH)
File: src/requests/sessions.py:211-214
The except clause catches ChunkedEncodingError, ContentDecodingError, and RuntimeError but does not handle the case where resp.raw.read(decode_content=False) itself fails. If the raw socket read fails (e.g., connection reset), the exception propagates unhandled and the redirect loop terminates abruptly.
2. _encode_files — no error for unreadable file object (MEDIUM)
File: src/requests/models.py:239-240
If fp is a _SupportsRead object whose .read() method raises (e.g., a closed file, a socket with timeout), the exception propagates unhandled. There is no try/except or validation.
3. prepare_url — bytes decode has no fallback (MEDIUM)
File: src/requests/models.py:492-493
if isinstance(url, bytes):
url = url.decode("utf8")
If the bytes URL is not valid UTF-8, this raises UnicodeDecodeError which propagates as an unhandled exception. No test covers non-UTF-8 bytes URLs.
4. _get_idna_encoded_host — imports inside method (LOW)
File: src/requests/models.py:473
import idna
The idna module is imported inside the method. If idna is not installed, this raises ImportError which propagates as an unhandled exception. No graceful fallback or informative error message.
5. send() — LocationValueError catch (LOW)
File: src/requests/adapters.py:665-666
except LocationValueError as e:
raise InvalidURL(e, request=request)
The InvalidURL is raised with e as the first argument, but InvalidURL expects (message, request). The e from LocationValueError is a string, so this works, but the request parameter is passed as a keyword argument. If LocationValueError ever changes its args, this could break.
6. json() method — UnicodeDecodeError silently swallowed (MEDIUM)
File: src/requests/models.py:1106-1111
except UnicodeDecodeError:
# Wrong UTF codec detected...
pass
When the guessed UTF encoding fails, the error is silently swallowed and the method falls through to self.text (line 1115). This means a malformed UTF-16/32 response will silently fall back to charset detection, potentially returning incorrect data. No warning is issued.
Suggested Test Scenarios
Scenario 1: send() fallthrough with unknown _HTTPError subclass
Given a HTTPAdapter instance
When conn.urlopen raises a custom _HTTPError subclass that is not ReadTimeoutError or _InvalidHeader
Then the exception should be wrapped in a requests.exceptions.RequestException
But currently it is re-raised as the raw urllib3 exception
File: src/requests/adapters.py:737-746
Scenario 2: Double failure in redirect body consumption
Given a Session following a redirect
When resp.content raises RuntimeError AND resp.raw.read() also raises an exception (e.g., ConnectionError)
Then the redirect loop should raise a meaningful requests.exceptions.RequestException
But currently the raw exception from resp.raw.read() propagates unhandled
File: src/requests/sessions.py:211-214
Scenario 3: Non-UTF-8 bytes URL
Given a PreparedRequest
When prepare_url is called with url=b'\xff\xfe' (invalid UTF-8 bytes)
Then it should raise InvalidURL with a descriptive message
But currently it raises UnicodeDecodeError
File: src/requests/models.py:492-493
Scenario 4: json.dumps raises TypeError
Given a PreparedRequest
When prepare_body is called with json=complex(1,2) (or any non-serializable type that raises TypeError instead of ValueError)
Then it should raise InvalidJSONError
But currently it raises TypeError
File: src/requests/models.py (around line 442)
Scenario 5: Both verify and cert paths invalid
Given an HTTPAdapter
When cert_verify is called with verify="/nonexistent/ca.pem" and cert="/nonexistent/cert.pem"
Then both errors should be reported (or at least the cert error after the CA error is fixed)
But currently only the CA bundle error is raised
File: src/requests/adapters.py:307-363
Scenario 6: Non-seekable readable stream as file upload
Given a PreparedRequest
When _encode_files is called with a files dict containing a readable stream (e.g., io.BytesIO that has been closed)
Then it should raise a meaningful error
But currently fp.read() may raise an unhandled exception
File: src/requests/models.py:239-240
Scenario 7: Redirect with fragment and no Location header
Given a SessionRedirectMixin
When resolve_redirects receives a response with status 302 but no Location header
Then get_redirect_target returns None and the loop terminates
But the fragment from the original URL is silently dropped
File: src/requests/sessions.py:202-203
Scenario 8: iter_content with chunk_size=None and stream=True
Given a Response with stream=True
When iter_content(chunk_size=None, decode_unicode=False) is called
Then self.raw.stream(None, decode_content=True) is called — urllib3's behavior with None chunk_size is undocumented
File: src/requests/models.py:937
Complex Code Paths
1. HTTPAdapter.send() — Exception dispatch (Cyclomatic complexity: ~15)
File: src/requests/adapters.py:634-748
This method has:
- 3 branches for timeout resolution (tuple, TimeoutSauce, else)
- 7 exception handlers in the urlopen try block
- 5 nested isinstance checks inside the MaxRetryError handler
- 4 branches inside the (_SSLError, _HTTPError) handler
Risk: The exception dispatch logic is fragile. The MaxRetryError handler checks e.reason against 5 different types in a specific order. If urllib3 adds a new reason type, it falls through to raise ConnectionError(e, request=request) which may mask the actual error.
2. resolve_redirects() — Redirect loop (Cyclomatic complexity: ~12)
File: src/requests/sessions.py:186-307
This generator method handles:
- Body consumption with error recovery (lines 211-214)
- Protocol-relative URLs (lines 225-227)
- Fragment preservation/update (lines 231-234)
- Relative URL resolution (lines 240-243)
- Method rewriting for 301/302/303 (via rebuild_method)
- Auth stripping logic (via rebuild_auth)
- Proxy rebuilding (via rebuild_proxies)
- Cookie handling
- Redirect limit enforcement
Risk: The interaction between fragment preservation, URL resolution, and method rewriting is complex. A redirect chain that combines protocol-relative URLs, fragment changes, and method changes (303 -> GET) could produce unexpected results.
3. PreparedRequest.prepare() — Multi-stage preparation
File: src/requests/models.py:422-449
The prepare method calls 7 sub-methods in a specific order:
1. prepare_method — uppercases method
2. prepare_url — URL validation, IDNA encoding, scheme checking
3. prepare_headers — case-insensitive dict, header validation
4. prepare_cookies — cookie jar conversion
5. prepare_body — JSON/data/files handling
6. prepare_auth — URL auth extraction, tuple/callable auth
7. prepare_hooks — hook registration
Risk: The ordering is critical. prepare_auth must run after prepare_url (to extract auth from URL) and after prepare_body (to allow auth to modify body). prepare_hooks must run last. Any change to this order could break authentication schemes like OAuth.
4. Response.json() — Encoding detection and fallback
File: src/requests/models.py:1087-1120
This method has:
- UTF encoding detection via guess_json_utf (line 1102)
- Try/except for UnicodeDecodeError (silently swallowed, line 1106)
- Try/except for JSONDecodeError (re-raised as RequestsJSONDecodeError, line 1112)
- Fallback to self.text (line 1115)
- Final JSONDecodeError catch (line 1117)
Risk: The silent swallowing of UnicodeDecodeError means a response with a wrong UTF encoding marker will silently fall back to charset detection, potentially returning incorrect data without any warning to the caller.
5. merge_environment_settings() — Environment variable precedence
File: src/requests/sessions.py:831-868
This method merges settings from:
- Environment variables (REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE)
- Environment proxies (get_environ_proxies)
- Session defaults (self.proxies, self.stream, self.verify, self.cert)
- Method arguments
Risk: The precedence rules are subtle. verify from environment only applies if verify is True or verify is None (line 855). If a caller passes verify=False, environment CA bundles are ignored. But proxies from environment are always merged via setdefault. This asymmetry could confuse users.