feat: add servicelink RPC mesh endpoint
Build and Push Docker Container / build-and-push (push) Successful in 3m21s

- Add the servicelink submodule and register POST /rpc for node-to-node file operations.
- Require bearer tokens with the mesh scope and apply rate/body-size limits to RPC calls.
- Map database connectivity failures to the existing 504 database error flow, with JSON responses for API routes.
- Cover the new RPC handlers and database error handling with focused pytest tests.
- Bump the NanoShare package version to 1.21.0.
This commit is contained in:
2026-06-13 23:59:35 +02:00
parent cb6422aacb
commit 2982d44e55
11 changed files with 386 additions and 4 deletions
+119
View File
@@ -0,0 +1,119 @@
import asyncio
import importlib.util
import sys
import types
from pathlib import Path
import httpx
from quart import Blueprint, Quart, request
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
class FakeLimiter:
def exempt(self, func):
return func
def limit(self, *_args, **_kwargs):
def decorator(func):
return func
return decorator
class FakeLogger:
def __init__(self):
self.errors = []
def error(self, message):
self.errors.append(message)
def warning(self, message):
pass
class FailingConvex:
async def get_current_favicon(self):
raise httpx.ConnectError("[Errno -2] Name or service not known")
def load_module(module_name, module_path):
spec = importlib.util.spec_from_file_location(module_name, module_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
def install_route_test_modules(monkeypatch, app, logger):
fake_setup = types.ModuleType("my_modules.app.setup")
fake_setup.app = app
fake_setup.LIMITER = FakeLimiter()
monkeypatch.setitem(sys.modules, "my_modules.app.setup", fake_setup)
fake_constens = types.ModuleType("my_modules.app.constens")
fake_constens.BLOCKED_IPS_ACCESSING_TIMES = 3
fake_constens.BLOCKED_IPS_STORED_TIMEFRAME = 60
monkeypatch.setitem(sys.modules, "my_modules.app.constens", fake_constens)
fake_logger_module = types.ModuleType("my_modules.app.logger")
fake_logger_module.logger = logger
monkeypatch.setitem(sys.modules, "my_modules.app.logger", fake_logger_module)
fake_functions = types.ModuleType("my_modules.functions")
fake_functions.get_ip = lambda: "203.0.113.10"
fake_functions.enforce_custom_limit = lambda *_args, **_kwargs: None
fake_functions.get_request_context = lambda: types.SimpleNamespace(path=request.path)
fake_functions.is_valid_uuid = lambda value: True
monkeypatch.setitem(sys.modules, "my_modules.functions", fake_functions)
def register_template_routes(app):
side_main = Blueprint("side_main", __name__)
async def index():
return "ok"
side_main.add_url_rule("/", "index", index)
side_main.add_url_rule("/files", "files_list", index)
side_main.add_url_rule("/access", "access_list", index)
app.register_blueprint(side_main)
auth_login = Blueprint("auth_login", __name__)
auth_login.add_url_rule("/login", "login", index)
auth_login.add_url_rule("/logout", "logout", index)
app.register_blueprint(auth_login)
def load_errors_and_basics(monkeypatch, app):
logger = FakeLogger()
install_route_test_modules(monkeypatch, app, logger)
root = Path(__file__).resolve().parents[1]
errors = load_module("test_routes_handeling_errorsAndBots", root / "routes" / "handeling" / "errorsAndBots.py")
basics = load_module("test_routes_handeling_basics", root / "routes" / "handeling" / "basics.py")
return errors, basics, logger
def test_convex_connect_error_is_returned_as_global_database_error(monkeypatch):
async def run_test():
app = Quart(__name__, template_folder=str(Path(__file__).resolve().parents[1] / "templates" / "side"))
register_template_routes(app)
app.convex = FailingConvex()
_errors, basics, logger = load_errors_and_basics(monkeypatch, app)
app.register_blueprint(basics.basic_bp)
response = await app.test_client().get("/favicon.ico")
assert response.status_code == 504
assert any("Name or service not known" in str(error) for error in logger.errors)
asyncio.run(run_test())
def test_api_convex_connect_error_returns_json_database_error(monkeypatch):
async def run_test():
app = Quart(__name__, template_folder=str(Path(__file__).resolve().parents[1] / "templates" / "side"))
load_errors_and_basics(monkeypatch, app)
@app.get("/api/failing")
async def failing_api():
raise httpx.ConnectError("[Errno -2] Name or service not known")
response = await app.test_client().get("/api/failing")
payload = await response.get_json()
assert response.status_code == 504
assert payload["error"] == "Database Error"
asyncio.run(run_test())