Compare commits
1
Commits
b472f91f19
...
v1.6.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8180082f90 |
@@ -98,8 +98,10 @@ def _validate_field_value(kind: str, field: SourceConfigField, value: Any) -> No
|
|||||||
_check_type(field, value)
|
_check_type(field, value)
|
||||||
if field.name == "path" and not value.startswith("/dev/"):
|
if field.name == "path" and not value.startswith("/dev/"):
|
||||||
raise SourceProfileError("warmtelink_serial config path must start with '/dev/'.")
|
raise SourceProfileError("warmtelink_serial config path must start with '/dev/'.")
|
||||||
if field.name in {"broker_port", "sample_interval_s", "baudrate"} and value <= 0:
|
if field.name in {"broker_port", "baudrate"} and value <= 0:
|
||||||
raise SourceProfileError(f"Config field {field.name!r} must be greater than zero.")
|
raise SourceProfileError(f"Config field {field.name!r} must be greater than zero.")
|
||||||
|
if field.name == "sample_interval_s" and value < 0:
|
||||||
|
raise SourceProfileError("Config field 'sample_interval_s' must not be negative.")
|
||||||
if field.name == "data_bits" and value != 7:
|
if field.name == "data_bits" and value != 7:
|
||||||
raise SourceProfileError("warmtelink_serial data_bits must be 7.")
|
raise SourceProfileError("warmtelink_serial data_bits must be 7.")
|
||||||
if field.name == "parity" and value != "N":
|
if field.name == "parity" and value != "N":
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ from alembic import command
|
|||||||
from alembic.config import Config
|
from alembic.config import Config
|
||||||
from sqlalchemy import create_engine, event, inspect, text
|
from sqlalchemy import create_engine, event, inspect, text
|
||||||
|
|
||||||
|
from app.integrations.meter_sources import sanitize_source_config, validate_source_config
|
||||||
|
|
||||||
|
|
||||||
def _config(database_url: str) -> Config:
|
def _config(database_url: str) -> Config:
|
||||||
config = Config("alembic_app.ini")
|
config = Config("alembic_app.ini")
|
||||||
@@ -108,7 +110,7 @@ def test_populated_revision_14_adopts_dsmr_history_at_revision_16(tmp_path: Path
|
|||||||
{"key": "DSMR_INGEST_ENABLED", "value": "true", "at": start},
|
{"key": "DSMR_INGEST_ENABLED", "value": "true", "at": start},
|
||||||
{"key": "DSMR_MQTT_TOPIC", "value": "historic/dsmr", "at": start},
|
{"key": "DSMR_MQTT_TOPIC", "value": "historic/dsmr", "at": start},
|
||||||
{"key": "DSMR_TARIFF_TOPIC", "value": "historic/tariff", "at": start},
|
{"key": "DSMR_TARIFF_TOPIC", "value": "historic/tariff", "at": start},
|
||||||
{"key": "DSMR_SAMPLE_INTERVAL_S", "value": "15", "at": start},
|
{"key": "DSMR_SAMPLE_INTERVAL_S", "value": "0", "at": start},
|
||||||
{"key": "MQTT_BROKER_HOST", "value": "mqtt.example.invalid", "at": start},
|
{"key": "MQTT_BROKER_HOST", "value": "mqtt.example.invalid", "at": start},
|
||||||
{"key": "MQTT_BROKER_PORT", "value": "1884", "at": start},
|
{"key": "MQTT_BROKER_PORT", "value": "1884", "at": start},
|
||||||
{"key": "MQTT_USERNAME", "value": "historic-user", "at": start},
|
{"key": "MQTT_USERNAME", "value": "historic-user", "at": start},
|
||||||
@@ -159,11 +161,14 @@ def test_populated_revision_14_adopts_dsmr_history_at_revision_16(tmp_path: Path
|
|||||||
text("SELECT id, enabled, config FROM meter_source WHERE kind = 'dsmr_mqtt'")
|
text("SELECT id, enabled, config FROM meter_source WHERE kind = 'dsmr_mqtt'")
|
||||||
).one()
|
).one()
|
||||||
assert source.enabled == 1
|
assert source.enabled == 1
|
||||||
assert json.loads(source.config) == {
|
source_config = json.loads(source.config)
|
||||||
|
assert source_config == {
|
||||||
"broker_host": "mqtt.example.invalid", "broker_port": 1884,
|
"broker_host": "mqtt.example.invalid", "broker_port": 1884,
|
||||||
"username": "historic-user", "password": "historic-password", "tls_enabled": True,
|
"username": "historic-user", "password": "historic-password", "tls_enabled": True,
|
||||||
"topic": "historic/dsmr", "tariff_topic": "historic/tariff", "sample_interval_s": 15,
|
"topic": "historic/dsmr", "tariff_topic": "historic/tariff", "sample_interval_s": 0,
|
||||||
}
|
}
|
||||||
|
assert validate_source_config("dsmr_mqtt", source_config) == source_config
|
||||||
|
assert sanitize_source_config("dsmr_mqtt", source_config)["sample_interval_s"] == 0
|
||||||
assert connection.execute(text("SELECT value FROM app_config WHERE key = 'DSMR_MQTT_TOPIC'")).scalar_one() == "historic/dsmr"
|
assert connection.execute(text("SELECT value FROM app_config WHERE key = 'DSMR_MQTT_TOPIC'")).scalar_one() == "historic/dsmr"
|
||||||
assert dict(connection.execute(text("SELECT key, value FROM app_config")).all()) == config_before
|
assert dict(connection.execute(text("SELECT key, value FROM app_config")).all()) == config_before
|
||||||
assert connection.execute(text("SELECT group_concat(telegram_id) FROM dsmr_reading")).scalar_one() == "77,78,77"
|
assert connection.execute(text("SELECT group_concat(telegram_id) FROM dsmr_reading")).scalar_one() == "77,78,77"
|
||||||
|
|||||||
@@ -68,6 +68,32 @@ def test_secret_sanitize_and_mask_merge_keep_old_value():
|
|||||||
assert merged["topic"] == "new/topic"
|
assert merged["topic"] == "new/topic"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dsmr_zero_interval_validates_sanitizes_and_merges_unchanged():
|
||||||
|
config = validate_source_config("dsmr_mqtt", {"sample_interval_s": 0})
|
||||||
|
|
||||||
|
assert config["sample_interval_s"] == 0
|
||||||
|
assert sanitize_source_config("dsmr_mqtt", config)["sample_interval_s"] == 0
|
||||||
|
assert merge_source_config("dsmr_mqtt", config, {"topic": "new/topic"})["sample_interval_s"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("kind", "config"),
|
||||||
|
[
|
||||||
|
("dsmr_mqtt", {"sample_interval_s": -1}),
|
||||||
|
("dsmr_mqtt", {"sample_interval_s": False}),
|
||||||
|
("dsmr_mqtt", {"broker_port": 0}),
|
||||||
|
("dsmr_mqtt", {"broker_port": -1}),
|
||||||
|
("dsmr_mqtt", {"broker_port": False}),
|
||||||
|
("warmtelink_serial", {"path": "/dev/warmtelink", "baudrate": 0}),
|
||||||
|
("warmtelink_serial", {"path": "/dev/warmtelink", "baudrate": -1}),
|
||||||
|
("warmtelink_serial", {"path": "/dev/warmtelink", "baudrate": False}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_numeric_source_profile_constraints_still_reject_invalid_values(kind, config):
|
||||||
|
with pytest.raises(SourceProfileError):
|
||||||
|
validate_source_config(kind, config)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def session(tmp_path):
|
def session(tmp_path):
|
||||||
engine = create_engine(f"sqlite:///{tmp_path / 'source_services.db'}")
|
engine = create_engine(f"sqlite:///{tmp_path / 'source_services.db'}")
|
||||||
|
|||||||
Reference in New Issue
Block a user