fix(api): accept RRD query parameters during HTTP dispatch

This commit is contained in:
yellowcooln authored and Rightup committed 2026-10-29 14:04:46 +00:00
1 parent 71495b6c3d
commit 72266e24f5
2 files changed
+115 -1

No files matched your search

+2 -1
View File
@@ -4277,7 +4277,8 @@ class APIEndpoints:
@cherrypy.expose
@cherrypy.tools.json_out()
def rrd_data(self):
def rrd_data(self, **kwargs):
# CherryPy dispatches query parameters as keywords before _get_params reads them.
try:
params = self._get_params(
{"start_time": None, "end_time": None, "resolution": "average"}
+113
View File
@@ -0,0 +1,113 @@
"""Exercise RRD query parameters through authenticated CherryPy dispatch."""
import json
import threading
from http.client import HTTPConnection
from types import SimpleNamespace
from unittest.mock import Mock
from wsgiref.simple_server import WSGIRequestHandler, make_server
import cherrypy
import pytest
from repeater.web.api_endpoints import APIEndpoints
from repeater.web.auth.cherrypy_tool import register_require_auth_tool
class QuietHandler(WSGIRequestHandler):
def log_message(self, format, *args):
pass
@pytest.fixture
def rrd_http(monkeypatch):
# Direct-handler tests may leave synthetic request/response objects behind.
monkeypatch.setattr(cherrypy, "request", cherrypy._ThreadLocalProxy("request"))
monkeypatch.setattr(cherrypy, "response", cherrypy._ThreadLocalProxy("response"))
register_require_auth_tool()
monkeypatch.setitem(cherrypy.config, "jwt_handler", SimpleNamespace(verify_jwt=lambda _: None))
monkeypatch.setitem(
cherrypy.config,
"token_manager",
SimpleNamespace(
verify_token=lambda token: (
{"id": "test", "name": "test"} if token == "test-key" else None
)
),
)
storage = SimpleNamespace(
get_metrics_data=Mock(return_value={"timestamps": [1, 2], "metrics": {}})
)
api = APIEndpoints.__new__(APIEndpoints)
api.daemon_instance = SimpleNamespace(repeater_handler=SimpleNamespace(storage=storage))
app = cherrypy.Application(
SimpleNamespace(api=api),
config={"/api": {"tools.require_auth.on": True}},
)
with make_server("127.0.0.1", 0, app, handler_class=QuietHandler) as server:
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
def get(query="", *, authenticated=True):
connection = HTTPConnection("127.0.0.1", server.server_port, timeout=5)
try:
headers = {"X-API-Key": "test-key"} if authenticated else {}
connection.request("GET", "/api/rrd_data" + query, headers=headers)
response = connection.getresponse()
return response.status, response.read()
finally:
connection.close()
try:
yield get, storage.get_metrics_data
finally:
server.shutdown()
thread.join(timeout=5)
def test_rrd_query_parameters_reach_storage_over_http(rrd_http):
get, read_metrics = rrd_http
status, body = get("?start_time=1&end_time=2&resolution=average")
assert status == 200, body
assert json.loads(body) == {
"success": True,
"data": {"timestamps": [1, 2], "metrics": {}},
}
read_metrics.assert_called_once_with(start_time="1", end_time="2", resolution="average")
@pytest.mark.parametrize(
("query", "expected"),
[
("", {"start_time": None, "end_time": None, "resolution": "average"}),
(
"?start_time=10&resolution=max",
{"start_time": "10", "end_time": None, "resolution": "max"},
),
],
)
def test_rrd_http_preserves_defaults_and_partial_queries(rrd_http, query, expected):
get, read_metrics = rrd_http
status, body = get(query)
assert status == 200, body
assert json.loads(body)["success"] is True
read_metrics.assert_called_once_with(**expected)
def test_rrd_http_query_requires_authentication(rrd_http):
get, read_metrics = rrd_http
status, _ = get("?start_time=1&end_time=2&resolution=average", authenticated=False)
assert status == 401
read_metrics.assert_not_called()
def test_rrd_http_preserves_storage_parameter_errors(rrd_http):
get, read_metrics = rrd_http
read_metrics.side_effect = ValueError("invalid time range")
status, body = get("?start_time=invalid&end_time=2&resolution=average")
assert status == 200, body
assert json.loads(body) == {
"success": False,
"error": "Invalid parameter format: invalid time range",
}
read_metrics.assert_called_once_with(start_time="invalid", end_time="2", resolution="average")