Files
tina.schellander df7653dcba Fix HTTP compatibility with Elgato Key Light firmware
The light's TCP stack resets HTTP/1.1 GET connections but requires
HTTP/1.1 for PUT requests. Switch from requests to raw http.client
with HTTP/1.0 for GETs and HTTP/1.1 + Connection: close for PUTs.
Add retry logic (5 attempts, 1s delay) for intermittent resets.
2026-02-07 11:33:11 +01:00

120 lines
4.1 KiB
Python

"""Tests for elgato_cli.api."""
import json
from unittest.mock import patch, MagicMock
import requests
from elgato_cli.api import ElgatoLight, LightState, mired_to_kelvin, kelvin_to_mired
LIGHT_RESPONSE = {
"numberOfLights": 1,
"lights": [{"on": 1, "brightness": 50, "temperature": 200}],
}
def _make_mock_conn(response_data, status=200):
"""Create a mock HTTPConnection that returns the given response."""
mock_conn = MagicMock()
mock_resp = MagicMock()
mock_resp.status = status
mock_resp.reason = "OK" if status == 200 else "Error"
mock_resp.read.return_value = json.dumps(response_data).encode()
mock_conn.getresponse.return_value = mock_resp
return mock_conn
class TestMiredKelvin:
def test_mired_to_kelvin(self):
assert mired_to_kelvin(143) == 6993
assert mired_to_kelvin(344) == 2907
assert mired_to_kelvin(200) == 5000
def test_kelvin_to_mired(self):
assert kelvin_to_mired(5000) == 200
assert kelvin_to_mired(7000) == 143
assert kelvin_to_mired(2900) == 345
def test_roundtrip(self):
for mired in [143, 200, 250, 300, 344]:
assert abs(kelvin_to_mired(mired_to_kelvin(mired)) - mired) <= 1
class TestElgatoLight:
@patch("elgato_cli.api.http.client.HTTPConnection")
def test_get_state(self, mock_http_cls):
mock_http_cls.return_value = _make_mock_conn(LIGHT_RESPONSE)
light = ElgatoLight("192.168.1.100")
state = light.get_state()
assert state == LightState(on=True, brightness=50, temperature=200)
mock_http_cls.assert_called_with("192.168.1.100", 9123, timeout=5)
@patch("elgato_cli.api.http.client.HTTPConnection")
def test_set_state_partial(self, mock_http_cls):
updated = {
"numberOfLights": 1,
"lights": [{"on": 1, "brightness": 80, "temperature": 200}],
}
# First call: get_state (GET), second call: put (PUT)
mock_http_cls.side_effect = [
_make_mock_conn(LIGHT_RESPONSE),
_make_mock_conn(updated),
]
light = ElgatoLight("192.168.1.100")
state = light.set_state(brightness=80)
assert state.brightness == 80
assert mock_http_cls.call_count == 2
@patch("elgato_cli.api.http.client.HTTPConnection")
def test_toggle(self, mock_http_cls):
toggled = {
"numberOfLights": 1,
"lights": [{"on": 0, "brightness": 50, "temperature": 200}],
}
# toggle calls: get_state, then set_state(get_state + put)
mock_http_cls.side_effect = [
_make_mock_conn(LIGHT_RESPONSE), # toggle -> get_state
_make_mock_conn(LIGHT_RESPONSE), # set_state -> get_state
_make_mock_conn(toggled), # set_state -> put
]
light = ElgatoLight("192.168.1.100")
state = light.toggle()
assert state.on is False
@patch("elgato_cli.api.http.client.HTTPConnection")
def test_get_info(self, mock_http_cls):
info_resp = {
"productName": "Elgato Key Light",
"serialNumber": "AB12CD34",
"firmwareVersion": "1.0.3",
}
mock_http_cls.return_value = _make_mock_conn(info_resp)
light = ElgatoLight("192.168.1.100")
info = light.get_info()
assert info["productName"] == "Elgato Key Light"
@patch("elgato_cli.api.time.sleep")
@patch("elgato_cli.api.http.client.HTTPConnection")
def test_retry_on_connection_error(self, mock_http_cls, mock_sleep):
fail_conn = MagicMock()
fail_conn.request.side_effect = ConnectionError("reset")
ok_conn = _make_mock_conn(LIGHT_RESPONSE)
mock_http_cls.side_effect = [fail_conn, ok_conn]
light = ElgatoLight("192.168.1.100")
state = light.get_state()
assert state == LightState(on=True, brightness=50, temperature=200)
assert mock_http_cls.call_count == 2
mock_sleep.assert_called_once_with(1.0)
def test_custom_port(self):
light = ElgatoLight("10.0.0.5", port=8080)
assert light.base_url == "http://10.0.0.5:8080/elgato"