This commit is contained in:
Jonas Bengtsson
2026-09-19 18:08:02 +02:00
parent a16a01b1e7
commit 680ad7a624
14 changed files with 483 additions and 14 deletions
+21
View File
@@ -224,6 +224,27 @@ def client(db_session, monkeypatch):
# The PKI client-cert endpoint validates the dynsec client exists.
monkeypatch.setattr(pki_router, "dynsec_client", fake)
# The control sidecar is not running in tests: fake its responses so
# deploy/system endpoints can be exercised (broker_alive always true).
from app.routers import broker as broker_router
from app.routers import system as system_router
def _fake_call_control(path, *, method="POST", json_body=None):
if path == "/deploy":
return {
"status": "deployed",
"broker_healthy": True,
"validation": {"valid": True},
}
if path == "/restart":
return {"status": "restarting"}
if path == "/status":
return {"broker_pid_alive": True, "config_file": "/mosquitto/config/mosquitto.conf"}
raise AssertionError(f"unexpected control path in tests: {path}")
monkeypatch.setattr(broker_router, "_call_control", _fake_call_control)
monkeypatch.setattr(system_router, "_call_control", _fake_call_control)
with TestClient(main_module.app) as test_client:
yield test_client
+64
View File
@@ -130,6 +130,70 @@ def test_client_id_binding(admin_client):
assert refused.status_code == 400
# --- System: broker restart + restart-required flag (spec 28, 29) -------------
def test_deploy_sets_restart_required_on_listener_change(admin_client):
"""Adding a listener definition vs the bootstrap config must flag a
pending restart (reload cannot add listeners, spec section 28)."""
# No deployed revision yet -> compared against the bootstrap config
# (listener 1883 0.0.0.0). A listener on 8883 differs from that.
admin_client.post(
"/api/v1/broker/listeners",
json={"name": "New TLS", "port": 8883, "protocol": "mqtt"},
)
deploy = admin_client.post(
"/api/v1/broker/config/deploy", json={"comment": "listener added"}
)
assert deploy.status_code == 200
body = deploy.json()
assert body["restart_required"] is True
# The status endpoint reflects the same flag (drives the glow).
status = admin_client.get("/api/v1/system/status").json()
assert status["restart_required"] is True
assert status["broker_alive"] is True
def test_deploy_without_listener_change_needs_no_restart(admin_client):
"""Deploying a config whose listener lines match the previous deployed
revision must NOT set the flag (everything else is reload-safe)."""
admin_client.post(
"/api/v1/broker/listeners",
json={"name": "Baseline", "port": 1883, "protocol": "mqtt"},
)
first = admin_client.post(
"/api/v1/broker/config/deploy", json={"comment": "baseline"}
).json()
# 1883 matches the bootstrap signature on the FIRST deploy; the flag
# may or may not be set then. The SECOND deploy (identical listener
# lines) must clear it.
second = admin_client.post(
"/api/v1/broker/config/deploy", json={"comment": "no change"}
).json()
assert second["restart_required"] is False
def test_restart_clears_the_flag(admin_client):
admin_client.post(
"/api/v1/broker/config/deploy", json={"comment": "flag set"}
)
restarted = admin_client.post("/api/v1/system/broker/restart")
assert restarted.status_code == 200
assert restarted.json()["status"] == "restarting"
status = admin_client.get("/api/v1/system/status").json()
assert status["restart_required"] is False
def test_system_endpoints_rbac(auditor_client):
"""Status is read-only (auditor may read); restart is privileged."""
assert auditor_client.get("/api/v1/system/status").status_code == 200
assert (
auditor_client.post("/api/v1/system/broker/restart").status_code == 403
)
def test_role_acl_lifecycle(admin_client):
admin_client.post("/api/v1/mqtt/roles", json={"rolename": "telemetry"})
+14
View File
@@ -84,6 +84,20 @@ def test_published_port_listener_does_not_warn(session):
assert not any("not published to the host" in w for w in result.warnings)
def test_internal_settings_never_leak_into_config(session):
"""Platform-internal BrokerSettings keys (restart_required) are state,
not broker options: emitting them fails `--test-config` with 'Unknown
configuration variable' (found live in the restart E2E)."""
session.add(ListenerConfig(name="l", port=1883, protocol="mqtt"))
session.add(BrokerSettings(key="restart_required", value="true", value_type="bool"))
session.add(BrokerSettings(key="max_packet_size", value="2000000", value_type="int"))
session.commit()
result = generate_config(session)
assert "max_packet_size 2000000" in result.config_text
assert "restart_required" not in result.config_text
def test_tls_listener_without_pki_warns(session):
session.add(
ListenerConfig(