from __future__ import annotations
import json
import pytest
from libbeachcomber.exceptions import ProtocolError, ServerError
from libbeachcomber.protocol import (
build_context_request,
build_get_request,
build_list_request,
build_poke_request,
build_status_request,
decode_response,
encode_request,
)
class TestEncodeRequest:
def test_includes_op(self) -> None:
raw = encode_request("get", key="git.branch")
obj = json.loads(raw)
assert obj["op"] == "get"
def test_includes_extra_fields(self) -> None:
raw = encode_request("get", key="git.branch", path="/repo")
obj = json.loads(raw)
assert obj["key"] == "git.branch"
assert obj["path"] == "/repo"
def test_ends_with_newline(self) -> None:
raw = encode_request("list")
assert raw.endswith(b"\n")
def test_is_bytes(self) -> None:
assert isinstance(encode_request("status"), bytes)
def test_compact_json_no_extra_spaces(self) -> None:
raw = encode_request("get", key="k")
assert b": " not in raw
assert b", " not in raw
class TestDecodeResponse:
def test_ok_true_returns_dict(self) -> None:
resp = decode_response('{"ok":true,"data":"main","age_ms":10,"stale":false}')
assert resp["data"] == "main"
assert resp["age_ms"] == 10
def test_ok_false_raises_server_error(self) -> None:
with pytest.raises(ServerError) as exc_info:
decode_response('{"ok":false,"error":"unknown provider"}')
assert "unknown provider" in str(exc_info.value)
def test_ok_false_no_error_field(self) -> None:
with pytest.raises(ServerError) as exc_info:
decode_response('{"ok":false}')
assert "unknown error" in str(exc_info.value)
def test_invalid_json_raises_protocol_error(self) -> None:
with pytest.raises(ProtocolError):
decode_response("not json at all")
def test_empty_line_raises_protocol_error(self) -> None:
with pytest.raises(ProtocolError):
decode_response("")
def test_whitespace_only_raises_protocol_error(self) -> None:
with pytest.raises(ProtocolError):
decode_response(" \n")
def test_json_array_raises_protocol_error(self) -> None:
with pytest.raises(ProtocolError):
decode_response("[1,2,3]")
def test_missing_ok_field_raises_protocol_error(self) -> None:
with pytest.raises(ProtocolError, match="missing 'ok'"):
decode_response('{"data":"foo"}')
def test_strips_trailing_newline(self) -> None:
resp = decode_response('{"ok":true}\n')
assert resp["ok"] is True
def test_strips_whitespace(self) -> None:
resp = decode_response(' {"ok":true} ')
assert resp["ok"] is True
class TestBuildRequests:
def test_get_request_key_only(self) -> None:
obj = json.loads(build_get_request("git.branch"))
assert obj == {"op": "get", "key": "git.branch"}
def test_get_request_with_path(self) -> None:
obj = json.loads(build_get_request("git.branch", "/repo"))
assert obj == {"op": "get", "key": "git.branch", "path": "/repo"}
def test_get_request_path_none_omitted(self) -> None:
obj = json.loads(build_get_request("hostname", None))
assert "path" not in obj
def test_poke_request_key_only(self) -> None:
obj = json.loads(build_poke_request("git"))
assert obj == {"op": "poke", "key": "git"}
def test_poke_request_with_path(self) -> None:
obj = json.loads(build_poke_request("git", "/repo"))
assert obj["path"] == "/repo"
def test_context_request(self) -> None:
obj = json.loads(build_context_request("/my/dir"))
assert obj == {"op": "context", "path": "/my/dir"}
def test_list_request(self) -> None:
obj = json.loads(build_list_request())
assert obj == {"op": "list"}
def test_status_request(self) -> None:
obj = json.loads(build_status_request())
assert obj == {"op": "status"}