111 lines
4.2 KiB
Python
111 lines
4.2 KiB
Python
import pytest
|
|
|
|
from lingkong_motor_service.config import ConfigError, Settings
|
|
|
|
|
|
def test_defaults_match_connected_ms3008_installation() -> None:
|
|
settings = Settings()
|
|
|
|
assert settings.can_channel == 0
|
|
assert settings.can_device == 0
|
|
assert settings.can_bitrate == 1_000_000
|
|
assert settings.motor_ids == (1, 2)
|
|
assert settings.open_control_raw == 400
|
|
assert settings.close_control_raw == 400
|
|
assert settings.broadcast_frequency_hz == 10
|
|
assert settings.derate_after_s == 5
|
|
assert settings.derated_control_raw == 100
|
|
assert settings.temperature_limit_c == 80
|
|
assert settings.temperature_poll_interval_s == 0.5
|
|
assert settings.http_host == "0.0.0.0"
|
|
assert settings.api_token is None
|
|
|
|
|
|
@pytest.mark.parametrize("field", ["open_control_raw", "close_control_raw"])
|
|
@pytest.mark.parametrize("value", [851, 0, True, 1.5])
|
|
def test_rejects_invalid_open_loop_magnitude(field: str, value: object) -> None:
|
|
with pytest.raises(ConfigError, match="1..850"):
|
|
Settings(**{field: value}).validate()
|
|
|
|
|
|
@pytest.mark.parametrize("motor_ids", [(), (1, 1), (0,), (3,), (5,), (True,)])
|
|
def test_motor_ids_are_nonempty_unique_and_in_broadcast_range(
|
|
motor_ids: tuple[int, ...],
|
|
) -> None:
|
|
with pytest.raises(ConfigError, match="MOTOR_IDS"):
|
|
Settings(motor_ids=motor_ids).validate()
|
|
|
|
|
|
def test_environment_values_are_parsed(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("MOTOR_CAN_CHANNEL", "1")
|
|
monkeypatch.setenv("MOTOR_CAN_DEVICE", "2")
|
|
monkeypatch.setenv("MOTOR_IDS", "2, 1")
|
|
monkeypatch.setenv("MOTOR_OPEN_CONTROL_RAW", "123")
|
|
monkeypatch.setenv("MOTOR_CLOSE_CONTROL_RAW", "234")
|
|
monkeypatch.setenv("MOTOR_DERATE_AFTER_S", "6")
|
|
monkeypatch.setenv("MOTOR_DERATED_CONTROL_RAW", "90")
|
|
monkeypatch.setenv("MOTOR_TEMPERATURE_LIMIT_C", "75")
|
|
monkeypatch.setenv("MOTOR_TEMPERATURE_POLL_INTERVAL_S", "0.25")
|
|
|
|
settings = Settings.from_env()
|
|
|
|
assert settings.can_channel == 1
|
|
assert settings.can_device == 2
|
|
assert settings.motor_ids == (2, 1)
|
|
assert settings.open_control_raw == 123
|
|
assert settings.close_control_raw == 234
|
|
assert settings.derate_after_s == 6
|
|
assert settings.derated_control_raw == 90
|
|
assert settings.temperature_limit_c == 75
|
|
assert settings.temperature_poll_interval_s == 0.25
|
|
|
|
|
|
@pytest.mark.parametrize("channel", [-1, 2, "CAN1", True])
|
|
def test_canalystii_channel_is_physical_zero_or_one(channel: object) -> None:
|
|
with pytest.raises(ConfigError, match="panel CAN1"):
|
|
Settings(can_channel=channel).validate()
|
|
|
|
|
|
@pytest.mark.parametrize("device", [-1, True, "0"])
|
|
def test_canalystii_device_index_is_non_negative_integer(device: object) -> None:
|
|
with pytest.raises(ConfigError, match="MOTOR_CAN_DEVICE"):
|
|
Settings(can_device=device).validate()
|
|
|
|
|
|
def test_broadcast_frequency_is_loaded_from_environment(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setenv("MOTOR_BROADCAST_FREQUENCY_HZ", "250")
|
|
assert Settings.from_env().broadcast_frequency_hz == 250
|
|
|
|
|
|
@pytest.mark.parametrize("frequency", [False, float("nan"), 0.0, 1001.0])
|
|
def test_broadcast_frequency_must_be_finite_and_bounded(frequency: float) -> None:
|
|
with pytest.raises(ConfigError, match="MOTOR_BROADCAST_FREQUENCY_HZ"):
|
|
Settings(broadcast_frequency_hz=frequency).validate()
|
|
|
|
|
|
def test_external_http_binding_does_not_require_api_token() -> None:
|
|
Settings(http_host="0.0.0.0", api_token=None).validate()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("field", "value", "message"),
|
|
[
|
|
("derate_after_s", 0, "MOTOR_DERATE_AFTER_S"),
|
|
("derated_control_raw", 851, "MOTOR_DERATED_CONTROL_RAW"),
|
|
("temperature_limit_c", 128, "MOTOR_TEMPERATURE_LIMIT_C"),
|
|
("temperature_poll_interval_s", 0, "MOTOR_TEMPERATURE_POLL_INTERVAL_S"),
|
|
],
|
|
)
|
|
def test_power_and_temperature_settings_are_bounded(
|
|
field: str, value: object, message: str
|
|
) -> None:
|
|
with pytest.raises(ConfigError, match=message):
|
|
Settings(**{field: value}).validate()
|
|
|
|
|
|
def test_derated_value_cannot_increase_the_configured_output() -> None:
|
|
with pytest.raises(ConfigError, match="must not exceed"):
|
|
Settings(open_control_raw=50, derated_control_raw=100).validate()
|