FrameBridge 0.3.1 community source release
This commit is contained in:
commit
b23819fff1
1354 files changed
+343716
No files matched your search
@@ -0,0 +1,144 @@
|
||||
"""Real FBPAIR/1 identities and sockets in temporary, isolated profiles."""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from framebridge_expanded import broker, node
|
||||
|
||||
|
||||
class BrokerNodeTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory(prefix="framebridge-expanded-broker-test-")
|
||||
self.root = Path(self.temp.name)
|
||||
self.central = self.root / "central"
|
||||
self.central.mkdir(mode=0o700)
|
||||
with socket.socket() as sock:
|
||||
sock.bind(("127.0.0.1", 0))
|
||||
self.port = sock.getsockname()[1]
|
||||
self.url = "http://127.0.0.1:" + str(self.port)
|
||||
(self.central / "config.json").write_text(json.dumps({
|
||||
"schema": 1, "role": "central", "name": "central-test", "node_id": str(uuid.uuid4()),
|
||||
"ports": {"broker": self.port}, "broker_url": self.url}))
|
||||
self.server = broker.make_server(port=self.port, state_dir=self.central)
|
||||
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
|
||||
self.thread.start()
|
||||
broker.configure(state_dir=self.central)
|
||||
|
||||
def tearDown(self):
|
||||
self.server.shutdown()
|
||||
self.server.server_close()
|
||||
self.thread.join(2)
|
||||
self.temp.cleanup()
|
||||
|
||||
def make_node(self, label):
|
||||
state = self.root / label
|
||||
state.mkdir(mode=0o700)
|
||||
(state / "config.json").write_text(json.dumps({
|
||||
"schema": 1, "role": "node", "name": label, "node_id": str(uuid.uuid4()),
|
||||
"broker_url": self.url}))
|
||||
return state
|
||||
|
||||
def pair(self, label):
|
||||
state = self.make_node(label)
|
||||
public = node.enroll(self.url, label, state)
|
||||
pending = next(p for p in broker.list_pending() if p["node_id"] == public["node_id"])
|
||||
response = broker.approve(pending["request_id"], public["node_id"])
|
||||
self.assertTrue(response["ok"])
|
||||
paired = node.poll_enrollment(state)
|
||||
self.assertEqual(paired["state"], "paired")
|
||||
self.assertFalse(node.status(state)["broker_confirm_required"])
|
||||
return state, public["node_id"]
|
||||
|
||||
def test_http_remote_and_unauthenticated_control_are_rejected(self):
|
||||
for value in ("http://10.0.0.53:1234", "http://example.com", "https://u:p@example.com", "https://example.com/#x"):
|
||||
with self.assertRaises(broker.BrokerError):
|
||||
broker.validate_endpoint(value)
|
||||
with self.assertRaises(broker.BrokerError):
|
||||
broker.post_json(self.url + "/fbpair/v1/control/nodes", {}, state_dir=self.central)
|
||||
|
||||
def test_approval_requires_exact_uuid_and_operator_auth(self):
|
||||
state = self.make_node("node-a")
|
||||
pending = node.enroll(self.url, "node-a", state)
|
||||
item = broker.list_pending()[0]
|
||||
self.assertEqual(item["node_id"], pending["node_id"])
|
||||
self.assertTrue(item["fingerprint"])
|
||||
self.assertEqual(item["fingerprint"], pending["fingerprint"])
|
||||
with self.assertRaises(broker.BrokerError):
|
||||
broker.approve(item["request_id"], str(uuid.uuid4()))
|
||||
self.assertEqual(broker.list_confirmed_nodes(), [])
|
||||
|
||||
def test_two_nodes_identity_confirmation_heartbeat_and_real_read(self):
|
||||
one, first = self.pair("node-a")
|
||||
two, second = self.pair("node-b")
|
||||
self.assertNotEqual(first, second)
|
||||
before = broker.list_confirmed_nodes()
|
||||
self.assertEqual(len(before), 2)
|
||||
self.assertTrue(all(n["ready"] is False for n in before))
|
||||
for state in (one, two):
|
||||
self.assertTrue(node.run(state, once=True)["idle"])
|
||||
self.assertTrue(all(n["ready"] is True for n in broker.list_confirmed_nodes()))
|
||||
self.assertEqual(node.status(one)["node_id"], first)
|
||||
stopped = threading.Event()
|
||||
errors = []
|
||||
|
||||
def pump():
|
||||
while not stopped.is_set():
|
||||
try:
|
||||
node.run(one, once=True)
|
||||
except Exception as exc:
|
||||
errors.append(type(exc).__name__)
|
||||
return
|
||||
stopped.wait(0.05)
|
||||
|
||||
worker = threading.Thread(target=pump, daemon=True)
|
||||
worker.start()
|
||||
try:
|
||||
result = broker.request(first, "system.status", {}, timeout_s=5)
|
||||
self.assertTrue(result["ok"], result)
|
||||
self.assertIsInstance(result["result"], dict)
|
||||
self.assertEqual(result["node_id"], first)
|
||||
finally:
|
||||
stopped.set()
|
||||
worker.join(2)
|
||||
self.assertFalse(errors)
|
||||
|
||||
def test_dispatch_rejects_wrong_node_and_bad_digest_before_adapter(self):
|
||||
state, ident = self.pair("node-a")
|
||||
called = []
|
||||
script = "printf harmless"
|
||||
request = {"request_id": "request-test", "node_id": str(uuid.uuid4()),
|
||||
"expires_at": time.time() + 30, "operation": "admin.exec", "payload": {
|
||||
"authorized": True, "job_id": "test-job", "timeout_s": 10,
|
||||
"script": script, "sha256": hashlib.sha256(script.encode()).hexdigest()}}
|
||||
self.assertEqual(node.dispatch_request(request, called.append, state)["error"], "REQUEST_NODE_MISMATCH")
|
||||
request["node_id"] = ident
|
||||
request["payload"]["sha256"] = "0" * 64
|
||||
self.assertEqual(node.dispatch_request(request, called.append, state)["error"], "SCRIPT_SHA256_MISMATCH")
|
||||
self.assertEqual(called, [])
|
||||
|
||||
def test_confirmed_identity_survives_broker_restart_but_readiness_does_not(self):
|
||||
state, ident = self.pair("node-a")
|
||||
node.run(state, once=True)
|
||||
self.assertTrue(broker.list_confirmed_nodes()[0]["ready"])
|
||||
self.server.shutdown()
|
||||
self.server.server_close()
|
||||
self.thread.join(2)
|
||||
self.server = broker.make_server(port=self.port, state_dir=self.central)
|
||||
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
|
||||
self.thread.start()
|
||||
nodes = broker.list_confirmed_nodes()
|
||||
self.assertEqual(nodes[0]["node_id"], ident)
|
||||
self.assertFalse(nodes[0]["ready"])
|
||||
node.run(state, once=True)
|
||||
self.assertTrue(broker.list_confirmed_nodes()[0]["ready"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user