Files
devplacepy/tests/unit/services/nodes/service.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

149 lines
4.5 KiB
Python

# retoor <retoor@molodetz.nl>
import time
import pytest
from devplacepy.database import get_table, set_consent, set_setting
from devplacepy.database.nodes import (
create_device,
revoke_device,
save_node_settings,
suspend_device,
)
from devplacepy.services.nodes.service import NodesService
from devplacepy.utils import register_account
from tests.conftest import run_async
from tests.unit.services.nodes.session import FakeSocket
from devplacepy.services.nodes.session import NodeSession
_counter = [0]
def member():
_counter[0] += 1
name = f"svc{int(time.time() * 1000)}{_counter[0]}"
uid, _, _ = register_account(
name, f"{name}@t.dev", "secret123", age_band="adult", accepted_terms=True
)
set_consent("user", uid, "bandwidth_sharing", True)
save_node_settings(uid, {"enabled": True})
return uid
def connected(service, uid):
device, _ = create_device(uid, "x", "native")
socket = FakeSocket()
session = NodeSession(socket, device)
service.registry.admit(session)
return device, socket
@pytest.fixture
def service(local_db):
set_setting("nodes_enabled", "1")
yield NodesService()
set_setting("nodes_enabled", "0")
def reconcile(service):
return run_async(service.reconcile())
def test_healthy_nodes_stay_connected(service):
_, socket = connected(service, member())
assert reconcile(service) == 0
assert socket.closed is None
def test_the_kill_switch_disconnects_everyone_in_one_tick(service):
sockets = [connected(service, member())[1] for _ in range(4)]
set_setting("nodes_enabled", "0")
assert reconcile(service) == 4
assert service.registry.count() == 0
assert all(socket.sent[-1] == {"type": "kick", "reason": "kill_switch"} for socket in sockets)
def test_withdrawn_consent_disconnects_that_owner_only(service):
leaving, staying = member(), member()
_, gone = connected(service, leaving)
_, kept = connected(service, staying)
set_consent("user", leaving, "bandwidth_sharing", False)
assert reconcile(service) == 1
assert gone.sent[-1]["reason"] == "no_consent"
assert kept.closed is None and service.registry.count() == 1
def test_a_revoked_device_is_kicked(service):
uid = member()
device, socket = connected(service, uid)
revoke_device(device["uid"], uid)
assert reconcile(service) == 1
assert socket.sent[-1]["reason"] == "revoked"
def test_a_suspended_device_is_kicked(service):
uid = member()
device, socket = connected(service, uid)
suspend_device(device["uid"], "test", 30)
assert reconcile(service) == 1
assert socket.sent[-1]["reason"] == "suspended"
def test_donation_turned_off_by_the_member_disconnects(service):
uid = member()
_, socket = connected(service, uid)
save_node_settings(uid, {"enabled": False})
assert reconcile(service) == 1
assert socket.sent[-1]["reason"] == "not_enabled"
def test_a_deleted_account_is_disconnected(service):
uid = member()
_, socket = connected(service, uid)
get_table("users").update(
{"uid": uid, "deletion_requested_at": "2026-01-01T00:00:00+00:00"}, ["uid"]
)
assert reconcile(service) == 1
assert socket.sent[-1]["reason"] == "deletion_pending"
def test_the_gate_is_asked_once_for_all_owners_per_tick(service, monkeypatch):
uid, other = member(), member()
for owner in (uid, uid, uid, other):
connected(service, owner)
calls = []
original = service.gate.allows_many
def spy(uids):
uids = list(uids)
calls.append(sorted(set(uids)))
return original(uids)
monkeypatch.setattr(service.gate, "allows_many", spy)
reconcile(service)
assert calls == [sorted({uid, other})]
def test_an_empty_registry_costs_nothing(service, monkeypatch):
monkeypatch.setattr(service.gate, "allows_many", lambda uids: 1 / 0)
assert reconcile(service) == 0
def test_stopping_the_service_kicks_all(service):
_, socket = connected(service, member())
run_async(service.on_disable())
assert socket.sent[-1]["reason"] == "service_stopped"
assert service.registry.count() == 0
def test_metrics_report_connected_and_capacity(service):
connected(service, member())
labels = {item["label"]: item["value"] for item in service.collect_metrics()["stats"]}
assert labels["connected"] == 1 and labels["capacity"] > 0
def test_the_service_has_the_kill_switch_as_a_config_field(service):
keys = [field.key for field in service.all_fields()]
assert "nodes_enabled" in keys and "nodes_max_total" in keys