Files
browser-cli/tests/test_transport.py
T
daniel156161 fd5447cbb9
Testing / remote-protocol-compat (0.9.3) (push) Successful in 42s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 44s
Package Extension / package-extension (push) Successful in 43s
Build & Publish Package / publish (push) Successful in 43s
Testing / test (push) Successful in 45s
refactor(api): namespaced SDK + dedicated transport layer
Restructure the Python API and internals around composable namespaces and
a standalone transport/endpoint layer. Bump to 0.12.0.

Python API:
- Replace flat methods (b.tabs_list(), b.group_list()) with namespaces:
  b.nav, b.tabs, b.groups, b.windows, b.dom, b.extract, b.page, b.storage,
  b.cookies, b.session, b.perf, b.extension.
- Shrink browser_cli/__init__.py to a thin composition root; move all
  behaviour into browser_cli/sdk/ (one module per namespace + factories,
  base, routing).

Internals:
- Add browser_cli/transport.py and remote_transport.py to isolate IPC from
  command logic; client.py now delegates instead of owning transport.
- Add browser_cli/endpoints.py for endpoint resolution and
  browser_cli/errors.py for shared error types.
- Extract markdown rendering into browser_cli/markdown.py (out of extract).
- Add USER_AGENT to version_manager.

Tooling & tests:
- Add justfile with common dev tasks.
- Update CLI commands and demo to the namespaced API.
- Rework tests for the new layout; add test_transport.py and
  test_refactor_boundaries.py to lock in module boundaries.

BREAKING CHANGE: flat API methods are removed in favour of namespaces
(e.g. b.tabs_list() -> b.tabs.list(), b.group_list() -> b.groups.list()).
2026-06-11 13:58:41 +02:00

118 lines
5.3 KiB
Python

"""Unit tests for the response transport codec (compression + msgpack)."""
import base64
import json
import pytest
from browser_cli import transport
def _sample(n=200):
return {
"id": "abc",
"success": True,
"data": {"items": [{"url": f"https://example.com/{i}", "title": f"Tab {i}"} for i in range(n)]},
}
# ── backward compatibility ──────────────────────────────────────────────────────
def test_no_accept_encoding_returns_bare_json():
obj = _sample()
enc = transport.encode_response(obj, accept=None)
assert enc == json.dumps(obj).encode("utf-8") # byte-identical to old wire format
assert enc[:1] in (b"{", b"[")
def test_empty_accept_returns_bare_json():
obj = _sample()
assert transport.encode_response(obj, accept={}) == json.dumps(obj).encode("utf-8")
def test_decode_plain_json_without_tag():
obj = _sample()
assert transport.decode_response(json.dumps(obj).encode("utf-8")) == obj
def test_decode_none_and_empty():
assert transport.decode_response(None) is None
with pytest.raises(ValueError):
transport.decode_response(b"")
# ── compression roundtrips ───────────────────────────────────────────────────────
@pytest.mark.parametrize("comp", ["zlib", "gzip", "zstd"])
def test_compression_roundtrip(comp):
if comp == "zstd" and not transport.zstd_available():
pytest.skip("zstandard not installed")
obj = _sample()
enc = transport.encode_response(obj, accept={"ser": ["json"], "comp": [comp]})
assert enc[:1] not in (b"{", b"[") # tagged, not plain
assert len(enc) < len(json.dumps(obj)) # actually smaller
assert transport.decode_response(enc) == obj
def test_small_payload_not_compressed():
obj = {"id": "x", "success": True, "data": "ok"}
enc = transport.encode_response(obj, accept={"ser": ["json"], "comp": ["zlib"]}, threshold=512)
assert enc == json.dumps(obj).encode("utf-8") # below threshold → bare json
def test_unknown_compression_in_accept_falls_back_to_plain():
obj = _sample()
enc = transport.encode_response(obj, accept={"ser": ["json"], "comp": ["brotli"]})
assert enc == json.dumps(obj).encode("utf-8")
# ── msgpack ──────────────────────────────────────────────────────────────────────
def test_msgpack_roundtrip():
if not transport.msgpack_available():
pytest.skip("msgpack not installed")
obj = _sample()
enc = transport.encode_response(obj, accept={"ser": ["msgpack"], "comp": []})
assert enc[0] >> 4 == transport.SER_MSGPACK
assert transport.decode_response(enc) == obj
def test_msgpack_with_compression_roundtrip():
if not (transport.msgpack_available() and transport.zstd_available()):
pytest.skip("msgpack/zstd not installed")
obj = _sample()
enc = transport.encode_response(obj, accept={"ser": ["msgpack"], "comp": ["zstd"]})
tag = enc[0]
assert tag >> 4 == transport.SER_MSGPACK
assert tag & 0x0F == transport.COMP_ZSTD
assert transport.decode_response(enc) == obj
# ── raw screenshot hoisting ───────────────────────────────────────────────────────
def _screenshot_obj():
png = bytes(range(256)) * 40
url = "data:image/png;base64," + base64.b64encode(png).decode("ascii")
return {"id": "x", "success": True, "data": {"dataUrl": url}}, url
def test_screenshot_roundtrip_restores_data_url_exactly():
if not transport.msgpack_available():
pytest.skip("msgpack not installed")
obj, url = _screenshot_obj()
enc = transport.encode_response(obj, accept={"ser": ["msgpack"], "comp": []}, command="tabs.screenshot")
dec = transport.decode_response(enc)
assert dec == obj
assert dec["data"]["dataUrl"] == url
def test_screenshot_not_hoisted_for_other_commands():
if not transport.msgpack_available():
pytest.skip("msgpack not installed")
import msgpack
obj, _ = _screenshot_obj()
enc = transport.encode_response(obj, accept={"ser": ["msgpack"], "comp": []}, command="extract.text")
body = msgpack.unpackb(enc[1:], raw=False)
assert isinstance(body["data"]["dataUrl"], str) # left as base64 string
def test_screenshot_json_path_leaves_base64():
obj, url = _screenshot_obj()
enc = transport.encode_response(obj, accept={"ser": ["json"], "comp": ["gzip"]}, command="tabs.screenshot")
assert transport.decode_response(enc)["data"]["dataUrl"] == url
# ── negotiation preference ────────────────────────────────────────────────────────
def test_server_prefers_strongest_mutual_codec():
if not transport.zstd_available():
pytest.skip("zstandard not installed")
obj = _sample()
# client accepts gzip and zstd; server should pick zstd (its top preference)
enc = transport.encode_response(obj, accept={"ser": ["json"], "comp": ["gzip", "zstd"]})
assert enc[0] & 0x0F == transport.COMP_ZSTD