Skip to content

Commit 17ea790

Browse files
Harden session recording object parsing
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 294412f commit 17ea790

2 files changed

Lines changed: 71 additions & 1 deletion

File tree

‎hyperbrowser/client/managers/session_utils.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,23 @@ def parse_session_recordings_response_data(
4545
f"{index} but got {type(recording).__name__}"
4646
)
4747
try:
48-
parsed_recordings.append(SessionRecording(**dict(recording)))
48+
recording_payload = dict(recording)
49+
except HyperbrowserError:
50+
raise
51+
except Exception as exc:
52+
raise HyperbrowserError(
53+
f"Failed to read session recording object at index {index}",
54+
original_error=exc,
55+
) from exc
56+
for key in recording_payload.keys():
57+
if isinstance(key, str):
58+
continue
59+
raise HyperbrowserError(
60+
"Expected session recording object keys to be strings at index "
61+
f"{index}"
62+
)
63+
try:
64+
parsed_recordings.append(SessionRecording(**recording_payload))
4965
except HyperbrowserError:
5066
raise
5167
except Exception as exc:

‎tests/test_session_recording_utils.py‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
from collections.abc import Iterator, Mapping
23
from types import MappingProxyType
34

45
import pytest
@@ -183,6 +184,59 @@ def test_parse_session_recordings_response_data_wraps_invalid_items():
183184
assert exc_info.value.original_error is not None
184185

185186

187+
def test_parse_session_recordings_response_data_wraps_unreadable_recording_items():
188+
class _BrokenRecordingMapping(Mapping[str, object]):
189+
def __iter__(self) -> Iterator[str]:
190+
raise RuntimeError("cannot iterate recording object")
191+
192+
def __len__(self) -> int:
193+
return 1
194+
195+
def __getitem__(self, key: str) -> object:
196+
_ = key
197+
return "ignored"
198+
199+
with pytest.raises(
200+
HyperbrowserError,
201+
match="Failed to read session recording object at index 0",
202+
) as exc_info:
203+
parse_session_recordings_response_data([_BrokenRecordingMapping()])
204+
205+
assert exc_info.value.original_error is not None
206+
207+
208+
def test_parse_session_recordings_response_data_preserves_hyperbrowser_recording_read_errors():
209+
class _BrokenRecordingMapping(Mapping[str, object]):
210+
def __iter__(self) -> Iterator[str]:
211+
raise HyperbrowserError("custom recording read failure")
212+
213+
def __len__(self) -> int:
214+
return 1
215+
216+
def __getitem__(self, key: str) -> object:
217+
_ = key
218+
return "ignored"
219+
220+
with pytest.raises(
221+
HyperbrowserError, match="custom recording read failure"
222+
) as exc_info:
223+
parse_session_recordings_response_data([_BrokenRecordingMapping()])
224+
225+
assert exc_info.value.original_error is None
226+
227+
228+
def test_parse_session_recordings_response_data_rejects_non_string_recording_keys():
229+
with pytest.raises(
230+
HyperbrowserError,
231+
match="Expected session recording object keys to be strings at index 0",
232+
):
233+
parse_session_recordings_response_data(
234+
[
235+
{1: "bad-key"}, # type: ignore[dict-item]
236+
]
237+
)
238+
239+
186240
def test_parse_session_recordings_response_data_wraps_unreadable_list_iteration():
187241
class _BrokenRecordingList(list):
188242
def __iter__(self):

0 commit comments

Comments
 (0)