Files
devplacepy/tests/unit/services/nodes/frames.py
T
retoorandClaude Sonnet 5.5 42b81271b8 Add community node socket: devices, tickets, consent gate, kill switch
Phase 3 of plan.md. Device registration with one-time secrets, scoped single-use
socket tickets, the lock-owner WS /nodes/ws with handshake and heartbeat eviction,
a single batch ConsentGate, and NodesService which disconnects nodes within one tick
when consent, device or the nodes_enabled kill switch changes. A reconcile of 2000
sessions measured about 110 ms after replacing per-session queries with batches.
Devii may withdraw but never grant bandwidth_sharing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 09:14:53 +02:00

110 lines
4.0 KiB
Python

# retoor <retoor@molodetz.nl>
import json
import pytest
from devplacepy.services.nodes import frames
from devplacepy.services.nodes.frames import FrameError, decode, encode
from devplacepy.services.nodes.spec import (
CLIENT_TO_SERVER,
CLOSE_FRAME_TOO_LARGE,
CLOSE_PROTOCOL,
SERVER_TO_CLIENT,
SPEC,
)
HELLO = {
"type": "hello",
"protocol": 1,
"client_version": "0.1.0",
"kind": "native",
"platform": "linux/amd64",
"modes": ["tunnel"],
}
def reason(raw, direction=CLIENT_TO_SERVER):
with pytest.raises(FrameError) as info:
decode(raw, direction)
return info.value
def test_a_valid_frame_round_trips():
assert decode(json.dumps(HELLO)) == HELLO
assert decode(json.dumps({"type": "pong"})) == {"type": "pong"}
@pytest.mark.parametrize("raw", ["", "nope", "[]", "1", "null", '"x"'])
def test_non_object_json_is_rejected(raw):
assert reason(raw).code == "bad_json"
def test_unknown_and_wrong_direction_types_are_rejected():
assert reason(json.dumps({"type": "teleport"})).code == "unknown_frame"
assert reason(json.dumps({"type": "welcome"})).code == "unknown_frame"
assert reason(json.dumps({"type": 5})).code == "unknown_frame"
assert reason(json.dumps({})).code == "unknown_frame"
def test_missing_unexpected_and_mistyped_fields_are_rejected():
assert reason(json.dumps({**HELLO, "protocol": None})).code == "bad_field_type"
missing = {key: value for key, value in HELLO.items() if key != "platform"}
assert reason(json.dumps(missing)).code == "missing_field"
assert reason(json.dumps({**HELLO, "extra": 1})).code == "unexpected_field"
assert reason(json.dumps({**HELLO, "protocol": "1"})).code == "bad_field_type"
assert reason(json.dumps({**HELLO, "modes": "tunnel"})).code == "bad_field_type"
def test_a_bool_is_not_an_int():
assert reason(json.dumps({**HELLO, "protocol": True})).code == "bad_field_type"
usage = {"type": "usage", "bytes_up": False, "bytes_down": 1}
assert reason(json.dumps(usage)).code == "bad_field_type"
def test_optional_fields_may_be_absent_but_not_wrong():
with_limits = {**HELLO, "limits": {"a": 1}}
assert decode(json.dumps(with_limits))["limits"] == {"a": 1}
assert reason(json.dumps({**HELLO, "limits": []})).code == "bad_field_type"
def test_the_size_cap_is_enforced_before_and_after_parsing():
huge = "x" * (frames.ceiling(CLIENT_TO_SERVER) + 1)
error = reason(huge)
assert error.code == "frame_too_large"
assert error.close_code == CLOSE_FRAME_TOO_LARGE
padded = {**HELLO, "platform": "x" * SPEC.frame("hello", CLIENT_TO_SERVER).max_bytes}
assert reason(json.dumps(padded)).code == "frame_too_large"
assert reason(b"{" + b" " * frames.ceiling(CLIENT_TO_SERVER)).close_code == (
CLOSE_FRAME_TOO_LARGE
)
def test_the_big_frame_may_exceed_the_small_cap():
body = "A" * (SPEC.frame("hello", CLIENT_TO_SERVER).max_bytes * 2)
result = {"type": "job_result", "job_id": "j", "status": 200, "body_b64": body}
assert decode(json.dumps(result))["body_b64"] == body
def test_protocol_errors_close_with_the_protocol_code():
assert reason("nope").close_code == CLOSE_PROTOCOL
def test_encode_validates_what_the_server_sends():
text = encode("kick", SERVER_TO_CLIENT, reason="bye")
assert json.loads(text) == {"type": "kick", "reason": "bye"}
with pytest.raises(FrameError):
encode("kick", SERVER_TO_CLIENT)
with pytest.raises(FrameError):
encode("kick", CLIENT_TO_SERVER, reason="x")
with pytest.raises(FrameError):
encode("kick", SERVER_TO_CLIENT, reason="x", extra=1)
def test_every_frame_in_the_spec_can_be_built_and_decoded():
samples = {"str": "x", "int": 1, "bool": True, "object": {}, "list": []}
for spec_frame in SPEC.frames:
fields = {item.name: samples[item.type] for item in spec_frame.fields}
text = encode(spec_frame.type, spec_frame.direction, **fields)
assert decode(text, spec_frame.direction)["type"] == spec_frame.type