@@ -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\t extension 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\t path.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\t path.zip"
298+ assert captured ["open_error_message" ] == "Custom?extension open: bad?path.zip"
299+
300+
257301def 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\t extension 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\t path.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\t path.zip"
519+ assert captured ["open_error_message" ] == "Custom?extension open: bad?path.zip"
520+
521+
427522def test_sync_extension_create_raises_hyperbrowser_error_when_file_missing (tmp_path ):
428523 transport = _SyncTransport ()
429524 manager = SyncExtensionManager (_FakeClient (transport ))
0 commit comments