This commit is contained in:
Jonas Bengtsson
2026-09-17 06:23:29 +02:00
parent 8d2c54beae
commit ee4800993d
17 changed files with 1043 additions and 189 deletions
+44 -4
View File
@@ -87,16 +87,19 @@ def client(db_session, monkeypatch):
# Fake dynsec so no broker is needed for API tests.
class FakeDynsec:
"""Mirrors the corrected wire contract: _command returns the `data`
object; client objects use `disabled` (absent == enabled)."""
def __init__(self):
self.clients: dict[str, dict] = {}
self.roles: dict[str, dict] = {}
self.groups: dict[str, dict] = {}
def _command(self, command, **kwargs):
return {"success": True}
return {}
def create_client(self, username, password, **kwargs):
self.clients[username] = {"username": username, "enabled": True, **kwargs}
self.clients[username] = {"username": username, "disabled": False, **kwargs}
def list_clients(self):
return list(self.clients.values())
@@ -105,10 +108,10 @@ def client(db_session, monkeypatch):
self.clients.pop(username, None)
def disable_client(self, username):
self.clients[username]["enabled"] = False
self.clients[username]["disabled"] = True
def enable_client(self, username):
self.clients[username]["enabled"] = True
self.clients[username]["disabled"] = False
def set_client_password(self, username, password):
pass
@@ -140,6 +143,43 @@ def client(db_session, monkeypatch):
def get_role(self, rolename):
return self.roles[rolename]
def add_client_role(self, username, rolename, priority=-1):
client = self.clients.setdefault(username, {"username": username, "disabled": False})
client["roles"] = [
*client.get("roles", []),
{"rolename": rolename, "priority": priority},
]
def remove_client_role(self, username, rolename):
client = self.clients.get(username, {})
client["roles"] = [
r for r in client.get("roles", []) if r["rolename"] != rolename
]
def add_group_client(self, groupname, username, priority=1):
self.clients.setdefault(username, {}).setdefault("groups", []).append(
{"groupname": groupname, "priority": priority}
)
def remove_group_client(self, groupname, username):
client = self.clients.get(username, {})
client["groups"] = [
g for g in client.get("groups", []) if g["groupname"] != groupname
]
def add_group_role(self, groupname, rolename, priority=-1):
group = self.groups.setdefault(groupname, {"groupname": groupname, "roles": []})
group["roles"] = [
*group.get("roles", []),
{"rolename": rolename, "priority": priority},
]
def remove_group_role(self, groupname, rolename):
group = self.groups.get(groupname, {})
group["roles"] = [
r for r in group.get("roles", []) if r["rolename"] != rolename
]
def connect(self):
pass
+61 -1
View File
@@ -99,6 +99,66 @@ def test_group_lifecycle(admin_client):
assert admin_client.delete("/api/v1/mqtt/groups/sensors").status_code == 204
def test_client_role_assignment(admin_client):
admin_client.post("/api/v1/mqtt/roles", json={"rolename": "telemetry"})
admin_client.post("/api/v1/mqtt/clients", json={"username": "assign-test"})
response = admin_client.post(
"/api/v1/mqtt/clients/assign-test/roles",
json={"rolename": "telemetry", "priority": 5},
)
assert response.status_code == 204
client = admin_client.get("/api/v1/mqtt/clients/assign-test").json()
roles = {r["rolename"] for r in client["roles"]}
assert "telemetry" in roles
assert (
admin_client.delete("/api/v1/mqtt/clients/assign-test/roles/telemetry").status_code
== 204
)
client = admin_client.get("/api/v1/mqtt/clients/assign-test").json()
assert all(r["rolename"] != "telemetry" for r in client["roles"])
def test_group_member_assignment(admin_client):
admin_client.post("/api/v1/mqtt/groups", json={"groupname": "sensors"})
admin_client.post("/api/v1/mqtt/clients", json={"username": "member-test"})
response = admin_client.post(
"/api/v1/mqtt/groups/sensors/clients",
json={"username": "member-test", "priority": 1},
)
assert response.status_code == 204
client = admin_client.get("/api/v1/mqtt/clients/member-test").json()
groups = {g["groupname"] for g in client["groups"]}
assert "sensors" in groups
assert (
admin_client.delete("/api/v1/mqtt/groups/sensors/clients/member-test").status_code
== 204
)
client = admin_client.get("/api/v1/mqtt/clients/member-test").json()
assert all(g["groupname"] != "sensors" for g in client["groups"])
def test_group_role_assignment(admin_client):
admin_client.post("/api/v1/mqtt/groups", json={"groupname": "sensors"})
admin_client.post("/api/v1/mqtt/roles", json={"rolename": "telemetry"})
response = admin_client.post(
"/api/v1/mqtt/groups/sensors/roles",
json={"rolename": "telemetry", "priority": 2},
)
assert response.status_code == 204
assert (
admin_client.delete("/api/v1/mqtt/groups/sensors/roles/telemetry").status_code
== 204
)
def test_listener_management_and_preview(admin_client):
response = admin_client.post(
"/api/v1/broker/listeners",
@@ -114,7 +174,7 @@ def test_listener_management_and_preview(admin_client):
# Generated config must not use options deprecated in 2.1 (spec 3.1).
assert "password_file" not in config_text
assert "acl_file" not in config_text
assert "per_listener_settings false" in config_text
assert "per_listener_settings" not in config_text
delete = admin_client.delete(f"/api/v1/broker/listeners/{listener_id}")
assert delete.status_code == 204
+2 -1
View File
@@ -30,9 +30,10 @@ def test_generate_with_one_listener(session):
result = generate_config(session)
assert "listener 1883" in result.config_text
assert "per_listener_settings false" in result.config_text
assert "global_plugin /usr/lib/mosquitto_dynamic_security.so" in result.config_text
assert "allow_anonymous false" in result.config_text
# Must NOT use options deprecated in 2.1 (spec 3.1).
assert "per_listener_settings" not in result.config_text
assert result.warnings == []