Skip to content

Commit dbe471e

Browse files
Improve extension parser key and value diagnostics
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent e5ad2c5 commit dbe471e

2 files changed

Lines changed: 96 additions & 1 deletion

File tree

hyperbrowser/client/managers/extension_utils.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,33 @@ def parse_extension_list_response_data(response_data: Any) -> List[ExtensionResp
106106
f"{index} but got {_get_type_name(extension)}"
107107
)
108108
try:
109-
extension_payload = dict(extension)
109+
extension_keys = list(extension.keys())
110110
except HyperbrowserError:
111111
raise
112112
except Exception as exc:
113113
raise HyperbrowserError(
114114
f"Failed to read extension object at index {index}",
115115
original_error=exc,
116116
) from exc
117+
for key in extension_keys:
118+
if isinstance(key, str):
119+
continue
120+
raise HyperbrowserError(
121+
f"Expected extension object keys to be strings at index {index}"
122+
)
123+
extension_payload: dict[str, object] = {}
124+
for key in extension_keys:
125+
try:
126+
extension_payload[key] = extension[key]
127+
except HyperbrowserError:
128+
raise
129+
except Exception as exc:
130+
key_display = _format_key_display(key)
131+
raise HyperbrowserError(
132+
"Failed to read extension object value for key "
133+
f"'{key_display}' at index {index}",
134+
original_error=exc,
135+
) from exc
117136
try:
118137
parsed_extensions.append(ExtensionResponse(**extension_payload))
119138
except HyperbrowserError:

tests/test_extension_utils.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,79 @@ def __getitem__(self, key: str) -> object:
287287
parse_extension_list_response_data({"extensions": [_BrokenExtensionMapping()]})
288288

289289
assert exc_info.value.original_error is None
290+
291+
292+
def test_parse_extension_list_response_data_rejects_non_string_extension_keys():
293+
with pytest.raises(
294+
HyperbrowserError,
295+
match="Expected extension object keys to be strings at index 0",
296+
):
297+
parse_extension_list_response_data(
298+
{
299+
"extensions": [
300+
{1: "invalid-key"}, # type: ignore[dict-item]
301+
]
302+
}
303+
)
304+
305+
306+
def test_parse_extension_list_response_data_wraps_extension_value_read_failures():
307+
class _BrokenValueLookupMapping(Mapping[str, object]):
308+
def __iter__(self) -> Iterator[str]:
309+
yield "name"
310+
311+
def __len__(self) -> int:
312+
return 1
313+
314+
def __getitem__(self, key: str) -> object:
315+
_ = key
316+
raise RuntimeError("cannot read extension value")
317+
318+
with pytest.raises(
319+
HyperbrowserError,
320+
match="Failed to read extension object value for key 'name' at index 0",
321+
) as exc_info:
322+
parse_extension_list_response_data({"extensions": [_BrokenValueLookupMapping()]})
323+
324+
assert exc_info.value.original_error is not None
325+
326+
327+
def test_parse_extension_list_response_data_sanitizes_extension_value_read_keys():
328+
class _BrokenValueLookupMapping(Mapping[str, object]):
329+
def __iter__(self) -> Iterator[str]:
330+
yield "bad\tkey"
331+
332+
def __len__(self) -> int:
333+
return 1
334+
335+
def __getitem__(self, key: str) -> object:
336+
_ = key
337+
raise RuntimeError("cannot read extension value")
338+
339+
with pytest.raises(
340+
HyperbrowserError,
341+
match="Failed to read extension object value for key 'bad\\?key' at index 0",
342+
) as exc_info:
343+
parse_extension_list_response_data({"extensions": [_BrokenValueLookupMapping()]})
344+
345+
assert exc_info.value.original_error is not None
346+
347+
348+
def test_parse_extension_list_response_data_preserves_hyperbrowser_value_read_errors():
349+
class _BrokenValueLookupMapping(Mapping[str, object]):
350+
def __iter__(self) -> Iterator[str]:
351+
yield "name"
352+
353+
def __len__(self) -> int:
354+
return 1
355+
356+
def __getitem__(self, key: str) -> object:
357+
_ = key
358+
raise HyperbrowserError("custom extension value read failure")
359+
360+
with pytest.raises(
361+
HyperbrowserError, match="custom extension value read failure"
362+
) as exc_info:
363+
parse_extension_list_response_data({"extensions": [_BrokenValueLookupMapping()]})
364+
365+
assert exc_info.value.original_error is None

0 commit comments

Comments
 (0)