Skip to content

Commit 2962511

Browse files
Improve session recording value-read diagnostics
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent dbe471e commit 2962511

2 files changed

Lines changed: 97 additions & 2 deletions

File tree

hyperbrowser/client/managers/session_utils.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@
66
from .response_utils import parse_response_model
77

88
T = TypeVar("T")
9+
_MAX_KEY_DISPLAY_LENGTH = 120
10+
_TRUNCATED_KEY_DISPLAY_SUFFIX = "... (truncated)"
11+
12+
13+
def _format_recording_key_display(key: str) -> str:
14+
normalized_key = "".join(
15+
"?" if ord(character) < 32 or ord(character) == 127 else character
16+
for character in key
17+
).strip()
18+
if not normalized_key:
19+
return "<blank key>"
20+
if len(normalized_key) <= _MAX_KEY_DISPLAY_LENGTH:
21+
return normalized_key
22+
available_length = _MAX_KEY_DISPLAY_LENGTH - len(_TRUNCATED_KEY_DISPLAY_SUFFIX)
23+
if available_length <= 0:
24+
return _TRUNCATED_KEY_DISPLAY_SUFFIX
25+
return f"{normalized_key[:available_length]}{_TRUNCATED_KEY_DISPLAY_SUFFIX}"
926

1027

1128
def parse_session_response_model(
@@ -45,20 +62,33 @@ def parse_session_recordings_response_data(
4562
f"{index} but got {type(recording).__name__}"
4663
)
4764
try:
48-
recording_payload = dict(recording)
65+
recording_keys = list(recording.keys())
4966
except HyperbrowserError:
5067
raise
5168
except Exception as exc:
5269
raise HyperbrowserError(
5370
f"Failed to read session recording object at index {index}",
5471
original_error=exc,
5572
) from exc
56-
for key in recording_payload.keys():
73+
for key in recording_keys:
5774
if isinstance(key, str):
5875
continue
5976
raise HyperbrowserError(
6077
f"Expected session recording object keys to be strings at index {index}"
6178
)
79+
recording_payload: dict[str, object] = {}
80+
for key in recording_keys:
81+
try:
82+
recording_payload[key] = recording[key]
83+
except HyperbrowserError:
84+
raise
85+
except Exception as exc:
86+
key_display = _format_recording_key_display(key)
87+
raise HyperbrowserError(
88+
"Failed to read session recording object value for key "
89+
f"'{key_display}' at index {index}",
90+
original_error=exc,
91+
) from exc
6292
try:
6393
parsed_recordings.append(SessionRecording(**recording_payload))
6494
except HyperbrowserError:

tests/test_session_recording_utils.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,71 @@ def test_parse_session_recordings_response_data_rejects_non_string_recording_key
237237
)
238238

239239

240+
def test_parse_session_recordings_response_data_wraps_recording_value_read_failures():
241+
class _BrokenValueLookupMapping(Mapping[str, object]):
242+
def __iter__(self) -> Iterator[str]:
243+
yield "type"
244+
245+
def __len__(self) -> int:
246+
return 1
247+
248+
def __getitem__(self, key: str) -> object:
249+
_ = key
250+
raise RuntimeError("cannot read recording value")
251+
252+
with pytest.raises(
253+
HyperbrowserError,
254+
match="Failed to read session recording object value for key 'type' at index 0",
255+
) as exc_info:
256+
parse_session_recordings_response_data([_BrokenValueLookupMapping()])
257+
258+
assert exc_info.value.original_error is not None
259+
260+
261+
def test_parse_session_recordings_response_data_sanitizes_recording_value_keys():
262+
class _BrokenValueLookupMapping(Mapping[str, object]):
263+
def __iter__(self) -> Iterator[str]:
264+
yield "bad\tkey"
265+
266+
def __len__(self) -> int:
267+
return 1
268+
269+
def __getitem__(self, key: str) -> object:
270+
_ = key
271+
raise RuntimeError("cannot read recording value")
272+
273+
with pytest.raises(
274+
HyperbrowserError,
275+
match=(
276+
"Failed to read session recording object value "
277+
"for key 'bad\\?key' at index 0"
278+
),
279+
) as exc_info:
280+
parse_session_recordings_response_data([_BrokenValueLookupMapping()])
281+
282+
assert exc_info.value.original_error is not None
283+
284+
285+
def test_parse_session_recordings_response_data_preserves_hyperbrowser_value_read_errors():
286+
class _BrokenValueLookupMapping(Mapping[str, object]):
287+
def __iter__(self) -> Iterator[str]:
288+
yield "type"
289+
290+
def __len__(self) -> int:
291+
return 1
292+
293+
def __getitem__(self, key: str) -> object:
294+
_ = key
295+
raise HyperbrowserError("custom recording value read failure")
296+
297+
with pytest.raises(
298+
HyperbrowserError, match="custom recording value read failure"
299+
) as exc_info:
300+
parse_session_recordings_response_data([_BrokenValueLookupMapping()])
301+
302+
assert exc_info.value.original_error is None
303+
304+
240305
def test_parse_session_recordings_response_data_wraps_unreadable_list_iteration():
241306
class _BrokenRecordingList(list):
242307
def __iter__(self):

0 commit comments

Comments
 (0)