Merge branch 'master' of https://git.d-popov.com/popov/neuron-tai
This commit is contained in:
@@ -295,6 +295,61 @@ def test_relay_circuit_relay_proxies_message():
|
||||
assert received_via_relay, "NAT'd peer did not receive message via circuit relay"
|
||||
|
||||
|
||||
def test_relay_rpc_round_trips_http_request_to_peer():
|
||||
"""Relay /rpc/<peer> sends one HTTP-shaped request to a connected peer."""
|
||||
import websockets.sync.client as wsc # type: ignore[import]
|
||||
from meshnet_relay.server import RelayServer
|
||||
|
||||
relay = RelayServer(host="127.0.0.1", port=0)
|
||||
port = relay.start()
|
||||
ready = threading.Event()
|
||||
|
||||
def run_peer():
|
||||
with wsc.connect(f"ws://127.0.0.1:{port}/ws") as ws:
|
||||
ws.send(json.dumps({
|
||||
"topic": "peer-register",
|
||||
"version": 1,
|
||||
"from_peer": "rpc_peer",
|
||||
"msg_id": "rpc-reg-001",
|
||||
"payload": {"peer_id": "rpc_peer", "addr": ""},
|
||||
}))
|
||||
ws.recv()
|
||||
ready.set()
|
||||
envelope = json.loads(ws.recv(timeout=3))
|
||||
payload = envelope["payload"]
|
||||
ws.send(json.dumps({
|
||||
"topic": "relay-http-response",
|
||||
"version": 1,
|
||||
"from_peer": "rpc_peer",
|
||||
"payload": {
|
||||
"request_id": payload["request_id"],
|
||||
"status": 200,
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
"body": json.dumps({"ok": True, "path": payload["path"]}),
|
||||
},
|
||||
}))
|
||||
|
||||
peer_thread = threading.Thread(target=run_peer, daemon=True)
|
||||
peer_thread.start()
|
||||
assert ready.wait(timeout=5)
|
||||
|
||||
with wsc.connect(f"ws://127.0.0.1:{port}/rpc/rpc_peer") as ws:
|
||||
ws.send(json.dumps({
|
||||
"request_id": "req-1",
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
"body": "{}",
|
||||
}))
|
||||
response = json.loads(ws.recv(timeout=5))
|
||||
|
||||
relay.stop()
|
||||
peer_thread.join(timeout=3)
|
||||
|
||||
assert response["status"] == 200
|
||||
assert json.loads(response["body"]) == {"ok": True, "path": "/v1/chat/completions"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tracker gossip fields tests
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -349,6 +404,41 @@ def test_tracker_accepts_registration_without_gossip_fields():
|
||||
assert "node_id" in resp
|
||||
|
||||
|
||||
def test_tracker_network_map_exposes_relay_and_registered_peer():
|
||||
import json as _json
|
||||
import urllib.request
|
||||
|
||||
from meshnet_tracker.server import TrackerServer
|
||||
|
||||
tracker = TrackerServer(host="127.0.0.1", port=0, relay_url="wss://ai.neuron.d-popov.com/ws")
|
||||
port = tracker.start()
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
f"{url}/v1/nodes/register",
|
||||
data=_json.dumps({
|
||||
"endpoint": "http://192.0.2.10:7000",
|
||||
"model": "stub-model",
|
||||
"shard_start": 0,
|
||||
"shard_end": 31,
|
||||
"relay_addr": "wss://ai.neuron.d-popov.com/rpc/peer123",
|
||||
"peer_id": "peer123",
|
||||
}).encode(),
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=5):
|
||||
pass
|
||||
with urllib.request.urlopen(f"{url}/v1/network/map", timeout=5) as resp:
|
||||
body = _json.loads(resp.read())
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
assert body["relay_url"] == "wss://ai.neuron.d-popov.com/ws"
|
||||
assert body["nodes"][0]["relay_addr"] == "wss://ai.neuron.d-popov.com/rpc/peer123"
|
||||
assert body["nodes"][0]["peer_id"] == "peer123"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mDNS (no-op without zeroconf installed)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path
|
||||
@@ -354,3 +355,113 @@ def test_legacy_start_subcommand_accepted(monkeypatch):
|
||||
|
||||
# Exited (either 0 or via KeyboardInterrupt caught in _cmd_start)
|
||||
# The important thing is no unhandled exception from arg parsing
|
||||
|
||||
|
||||
def test_legacy_start_treats_repo_model_as_model_id(monkeypatch):
|
||||
"""`meshnet-node start --model org/repo` enters the HF model startup path."""
|
||||
from meshnet_node.cli import main
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_run_startup(*args, **kwargs):
|
||||
captured.update(kwargs)
|
||||
class _FakeNode:
|
||||
chat_completion_count = 0
|
||||
def stop(self): pass
|
||||
return _FakeNode()
|
||||
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
"meshnet-node", "start",
|
||||
"--tracker", "http://192.168.0.179:8081",
|
||||
"--model", "Qwen/Qwen2.5-0.5B-Instruct",
|
||||
"--port", "0",
|
||||
])
|
||||
|
||||
with patch("meshnet_node.startup.run_startup", side_effect=fake_run_startup):
|
||||
with patch("time.sleep", side_effect=KeyboardInterrupt):
|
||||
try:
|
||||
main()
|
||||
except SystemExit as exc:
|
||||
assert exc.code == 0
|
||||
|
||||
assert captured["model"] == "Qwen2.5-0.5B-Instruct"
|
||||
assert captured["model_id"] == "Qwen/Qwen2.5-0.5B-Instruct"
|
||||
|
||||
|
||||
def test_legacy_start_without_port_uses_next_available_port(monkeypatch):
|
||||
"""Omitting --port skips an occupied default port before startup loads the model."""
|
||||
from meshnet_node.cli import main
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_run_startup(*args, **kwargs):
|
||||
captured.update(kwargs)
|
||||
class _FakeNode:
|
||||
chat_completion_count = 0
|
||||
def stop(self): pass
|
||||
return _FakeNode()
|
||||
|
||||
occupied = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
occupied.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
occupied.bind(("127.0.0.1", 7000))
|
||||
occupied.listen(1)
|
||||
try:
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
"meshnet-node", "start",
|
||||
"--tracker", "http://192.168.0.179:8081",
|
||||
"--model", "Qwen/Qwen2.5-0.5B-Instruct",
|
||||
"--host", "127.0.0.1",
|
||||
])
|
||||
|
||||
with patch("meshnet_node.startup.run_startup", side_effect=fake_run_startup):
|
||||
with patch("time.sleep", side_effect=KeyboardInterrupt):
|
||||
try:
|
||||
main()
|
||||
except SystemExit as exc:
|
||||
assert exc.code == 0
|
||||
finally:
|
||||
occupied.close()
|
||||
|
||||
assert captured["port"] == 7001
|
||||
|
||||
|
||||
def test_default_cli_passes_advertise_host(monkeypatch):
|
||||
"""The documented no-subcommand LAN flag reaches startup."""
|
||||
from meshnet_node.cli import main
|
||||
|
||||
saved = {
|
||||
"model_hf_repo": "Qwen/Qwen2.5-0.5B-Instruct",
|
||||
"model_name": "Qwen2.5-0.5B-Instruct",
|
||||
"quantization": "nf4",
|
||||
"tracker_url": "http://localhost:8080",
|
||||
"wallet_path": "",
|
||||
"download_dir": "",
|
||||
"port": 7000,
|
||||
"host": "0.0.0.0",
|
||||
}
|
||||
captured = {}
|
||||
|
||||
def fake_run_startup(*args, **kwargs):
|
||||
captured.update(kwargs)
|
||||
class _FakeNode:
|
||||
chat_completion_count = 0
|
||||
def stop(self): pass
|
||||
return _FakeNode()
|
||||
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
"meshnet-node",
|
||||
"--tracker", "http://192.168.0.179:8081",
|
||||
"--advertise-host", "192.168.0.42",
|
||||
"--no-tui",
|
||||
])
|
||||
|
||||
with patch("meshnet_node.config.load_config", return_value=saved):
|
||||
with patch("meshnet_node.startup.run_startup", side_effect=fake_run_startup):
|
||||
with patch("meshnet_node.dashboard.run_dashboard", side_effect=KeyboardInterrupt):
|
||||
try:
|
||||
main()
|
||||
except SystemExit as exc:
|
||||
assert exc.code == 0
|
||||
|
||||
assert captured["tracker_url"] == "http://192.168.0.179:8081"
|
||||
assert captured["advertise_host"] == "192.168.0.42"
|
||||
|
||||
Reference in New Issue
Block a user