Skip to content

Commit 99f6f70

Browse files
Test metadata prefix control-char sanitization paths
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 4a0f92d commit 99f6f70

3 files changed

Lines changed: 160 additions & 0 deletions

File tree

tests/test_extension_create_utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,29 @@ def test_normalize_extension_create_input_uses_default_not_file_prefix_when_meta
209209
assert exc_info.value.original_error is None
210210

211211

212+
def test_normalize_extension_create_input_sanitizes_control_chars_in_metadata_missing_prefix(
213+
tmp_path, monkeypatch: pytest.MonkeyPatch
214+
):
215+
missing_path = tmp_path / "missing-extension.zip"
216+
params = CreateExtensionParams(name="missing-extension", file_path=missing_path)
217+
monkeypatch.setattr(
218+
extension_create_utils,
219+
"EXTENSION_OPERATION_METADATA",
220+
SimpleNamespace(
221+
missing_file_message_prefix="Custom\tmissing prefix",
222+
not_file_message_prefix="Custom extension not-file prefix",
223+
),
224+
)
225+
226+
with pytest.raises(
227+
HyperbrowserError,
228+
match="Custom\\?missing prefix:",
229+
) as exc_info:
230+
normalize_extension_create_input(params)
231+
232+
assert exc_info.value.original_error is None
233+
234+
212235
def test_normalize_extension_create_input_rejects_control_character_path():
213236
params = CreateExtensionParams(
214237
name="bad-extension",

tests/test_extension_manager.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,50 @@ def _open_binary_file_stub(file_path, *, open_error_message): # type: ignore[no
254254
)
255255

256256

257+
def test_sync_extension_create_sanitizes_control_chars_in_metadata_open_prefix(
258+
monkeypatch: pytest.MonkeyPatch,
259+
):
260+
manager = SyncExtensionManager(_FakeClient(_SyncTransport()))
261+
manager._OPERATION_METADATA = type(
262+
"_Metadata",
263+
(),
264+
{
265+
"create_operation_name": "create extension",
266+
"open_file_error_prefix": "Custom\textension open",
267+
},
268+
)()
269+
params = CreateExtensionParams(name="my-extension", file_path="/tmp/ignored.zip")
270+
captured: dict[str, str] = {}
271+
272+
@contextmanager
273+
def _open_binary_file_stub(file_path, *, open_error_message): # type: ignore[no-untyped-def]
274+
captured["file_path"] = file_path
275+
captured["open_error_message"] = open_error_message
276+
yield io.BytesIO(b"content")
277+
278+
monkeypatch.setattr(
279+
sync_extension_module,
280+
"normalize_extension_create_input",
281+
lambda _: ("bad\tpath.zip", {"name": "my-extension"}),
282+
)
283+
monkeypatch.setattr(
284+
sync_extension_module,
285+
"open_binary_file",
286+
_open_binary_file_stub,
287+
)
288+
monkeypatch.setattr(
289+
sync_extension_module,
290+
"create_extension_resource",
291+
lambda **kwargs: SimpleNamespace(id="ext_sync_mock"),
292+
)
293+
294+
response = manager.create(params)
295+
296+
assert response.id == "ext_sync_mock"
297+
assert captured["file_path"] == "bad\tpath.zip"
298+
assert captured["open_error_message"] == "Custom?extension open: bad?path.zip"
299+
300+
257301
def test_async_extension_create_does_not_mutate_params_and_closes_file(tmp_path):
258302
transport = _AsyncTransport()
259303
manager = AsyncExtensionManager(_FakeClient(transport))
@@ -424,6 +468,57 @@ async def run():
424468
)
425469

426470

471+
def test_async_extension_create_sanitizes_control_chars_in_metadata_open_prefix(
472+
monkeypatch: pytest.MonkeyPatch,
473+
):
474+
manager = AsyncExtensionManager(_FakeClient(_AsyncTransport()))
475+
manager._OPERATION_METADATA = type(
476+
"_Metadata",
477+
(),
478+
{
479+
"create_operation_name": "create extension",
480+
"open_file_error_prefix": "Custom\textension open",
481+
},
482+
)()
483+
params = CreateExtensionParams(name="my-extension", file_path="/tmp/ignored.zip")
484+
captured: dict[str, str] = {}
485+
486+
@contextmanager
487+
def _open_binary_file_stub(file_path, *, open_error_message): # type: ignore[no-untyped-def]
488+
captured["file_path"] = file_path
489+
captured["open_error_message"] = open_error_message
490+
yield io.BytesIO(b"content")
491+
492+
async def _create_extension_resource_async_stub(**kwargs):
493+
_ = kwargs
494+
return SimpleNamespace(id="ext_async_mock")
495+
496+
monkeypatch.setattr(
497+
async_extension_module,
498+
"normalize_extension_create_input",
499+
lambda _: ("bad\tpath.zip", {"name": "my-extension"}),
500+
)
501+
monkeypatch.setattr(
502+
async_extension_module,
503+
"open_binary_file",
504+
_open_binary_file_stub,
505+
)
506+
monkeypatch.setattr(
507+
async_extension_module,
508+
"create_extension_resource_async",
509+
_create_extension_resource_async_stub,
510+
)
511+
512+
async def run():
513+
return await manager.create(params)
514+
515+
response = asyncio.run(run())
516+
517+
assert response.id == "ext_async_mock"
518+
assert captured["file_path"] == "bad\tpath.zip"
519+
assert captured["open_error_message"] == "Custom?extension open: bad?path.zip"
520+
521+
427522
def test_sync_extension_create_raises_hyperbrowser_error_when_file_missing(tmp_path):
428523
transport = _SyncTransport()
429524
manager = SyncExtensionManager(_FakeClient(transport))

tests/test_session_upload_utils.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,48 @@ def _open_binary_file_stub(file_path, *, open_error_message): # type: ignore[no
346346
assert captured["open_error_message"] == "Failed to open upload file at path: bad?path.txt"
347347

348348

349+
def test_open_upload_files_from_input_sanitizes_control_chars_in_metadata_open_prefix(
350+
monkeypatch: pytest.MonkeyPatch,
351+
):
352+
captured: dict[str, str] = {}
353+
354+
@contextmanager
355+
def _open_binary_file_stub(file_path, *, open_error_message): # type: ignore[no-untyped-def]
356+
captured["file_path"] = file_path
357+
captured["open_error_message"] = open_error_message
358+
yield io.BytesIO(b"content")
359+
360+
monkeypatch.setattr(
361+
session_upload_utils,
362+
"normalize_upload_file_input",
363+
lambda file_input: ("bad\tpath.txt", None),
364+
)
365+
monkeypatch.setattr(
366+
session_upload_utils,
367+
"SESSION_OPERATION_METADATA",
368+
type(
369+
"_Metadata",
370+
(),
371+
{
372+
"upload_missing_file_message_prefix": "Custom missing prefix",
373+
"upload_not_file_message_prefix": "Custom not-file prefix",
374+
"upload_open_file_error_prefix": "Custom\topen",
375+
},
376+
)(),
377+
)
378+
monkeypatch.setattr(
379+
session_upload_utils,
380+
"open_binary_file",
381+
_open_binary_file_stub,
382+
)
383+
384+
with open_upload_files_from_input("ignored-input") as files:
385+
assert files["file"].read() == b"content"
386+
387+
assert captured["file_path"] == "bad\tpath.txt"
388+
assert captured["open_error_message"] == "Custom?open: bad?path.txt"
389+
390+
349391
def test_open_upload_files_from_input_rejects_missing_normalized_file_object(
350392
monkeypatch: pytest.MonkeyPatch,
351393
):

0 commit comments

Comments
 (0)