Files
2026-09-19 18:08:02 +02:00

186 lines
6.1 KiB
Python

"""Config generator unit tests (spec section 64: configuration generation)."""
from __future__ import annotations
import pytest
from app.configgen import generate_config
from app.models import BrokerSettings, ListenerConfig
@pytest.fixture()
def session(db_session):
yield db_session
def test_generate_with_one_listener(session):
session.add(
ListenerConfig(
name="Production MQTT",
port=1883,
protocol="mqtt",
allow_anonymous=False,
max_connections=-1,
tls_enabled=False,
require_certificate=False,
use_identity_as_username=False,
)
)
session.commit()
result = generate_config(session)
assert "listener 1883" 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 == []
def test_generate_includes_global_settings(session):
session.add(ListenerConfig(name="l", port=1883, protocol="mqtt"))
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
def test_generate_with_empty_model_warns_instead_of_raising(session):
"""Fresh-platform state: preview must work, not 500 (config page fix)."""
result = generate_config(session)
assert "global_plugin /usr/lib/mosquitto_dynamic_security.so" in result.config_text
assert "persistence true" in result.config_text
assert any("No enabled listeners" in w for w in result.warnings)
def test_disabled_listeners_are_omitted(session):
session.add(ListenerConfig(name="off", port=1884, protocol="mqtt", enabled=False))
session.add(ListenerConfig(name="on", port=1883, protocol="mqtt"))
session.commit()
result = generate_config(session)
assert "listener 1883" in result.config_text
assert "listener 1884" not in result.config_text
def test_unpublished_port_listener_warns(session):
"""Listeners on ports not published by compose warn: they are only
reachable inside the Docker network (spec section 5)."""
session.add(ListenerConfig(name="internal-only", port=8884, protocol="mqtt"))
session.commit()
result = generate_config(session)
assert any(
"not published to the host" in w and "8884" in w for w in result.warnings
)
def test_published_port_listener_does_not_warn(session):
session.add(ListenerConfig(name="prod", port=8883, protocol="mqtt"))
session.commit()
result = generate_config(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(
name="tls-later",
port=8883,
protocol="mqtt",
tls_enabled=True,
tls_min_version="tlsv1.2",
)
)
session.commit()
result = generate_config(session)
assert any("no certificate is bound" in w for w in result.warnings)
assert "tls_version tlsv1.2" in result.config_text
def test_tls_listener_with_bound_certificate_emits_certfile(session):
"""With PKI material bound, the generator emits real cert paths (spec 30)."""
from datetime import UTC, datetime
from app.crypto import encrypt_secret
from app.models import Certificate
from app.pki import create_ca, issue_server_certificate
ca_issued = create_ca(
common_name="Test CA", validity_days=3650, key_algorithm="ecdsa-p256"
)
ca = Certificate(
common_name="Test CA",
certificate_pem=ca_issued.certificate_pem,
private_key_pem=encrypt_secret(ca_issued.private_key_pem),
expiry_date=ca_issued.not_valid_after,
is_ca=True,
key_algorithm="ecdsa-p256",
serial_number=str(ca_issued.serial_number),
dns_sans=[],
ip_sans=[],
fingerprint="aa:bb",
status="issued",
)
session.add(ca)
session.flush()
leaf = issue_server_certificate(
ca_certificate_pem=ca_issued.certificate_pem,
ca_private_key_pem=ca_issued.private_key_pem,
common_name="broker.local",
validity_days=365,
)
cert = Certificate(
common_name="broker.local",
certificate_pem=leaf.certificate_pem,
private_key_pem=encrypt_secret(leaf.private_key_pem),
expiry_date=leaf.not_valid_after,
is_ca=False,
parent_id=ca.id,
key_algorithm="ecdsa-p256",
serial_number=str(leaf.serial_number),
dns_sans=["broker.local"],
ip_sans=[],
fingerprint="cc:dd",
status="issued",
)
session.add(cert)
session.flush()
session.add(
ListenerConfig(
name="tls-prod",
port=8883,
protocol="mqtt",
tls_enabled=True,
tls_min_version="tlsv1.2",
cert_id=cert.id,
)
)
session.commit()
result = generate_config(session)
text = result.config_text
assert f"certfile /mosquitto/certs/{cert.id}.crt" in text
assert f"keyfile /mosquitto/certs/{cert.id}.key" in text
assert f"cafile /mosquitto/certs/{ca.id}.crt" in text
assert "tls_version tlsv1.2" in text
assert result.warnings == []