🚧 WIP
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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"})
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user