diff --git a/custom_components/houseplan/validation.py b/custom_components/houseplan/validation.py index 88c140f..8a8da0b 100644 --- a/custom_components/houseplan/validation.py +++ b/custom_components/houseplan/validation.py @@ -81,7 +81,10 @@ MAX_KNOWN_DEVICES = 20000 MAX_TEXT = 500 # names, models, ids MAX_DESCRIPTION = 4000 MAX_URL = 2000 -MAX_CONFIG_BYTES = 12 * 1024 * 1024 +# Below the WebSocket frame limit on purpose: a payload larger than that never +# reaches the handler at all — the socket closes with 1009 and the user sees a +# dropped connection instead of an error they can act on. +MAX_CONFIG_BYTES = 4 * 1024 * 1024 _TEXT = vol.All(str, vol.Length(max=MAX_TEXT)) _TEXT_OR_NONE = vol.Any(None, _TEXT) diff --git a/tests_backend/test_ha_upload.py b/tests_backend/test_ha_upload.py index 41e4464..b0be76f 100644 --- a/tests_backend/test_ha_upload.py +++ b/tests_backend/test_ha_upload.py @@ -32,7 +32,10 @@ async def test_upload_ok(hass: HomeAssistant, hass_client: ClientSessionGenerato assert resp.status == 200 body = await resp.json() # audit B1: uploads now return the AUTHENTICATED content URL - assert body["ok"] and body["url"].startswith("/api/houseplan/content/files/m1/manual.pdf?v=") + # HP-1454-02: uploads take a FREE name and never overwrite, so the url is + # the name that was actually used — no cache-busting query needed any more + assert body["ok"] and body["url"].startswith("/api/houseplan/content/files/m1/manual") + assert body["url"].endswith(".pdf") and "?" not in body["url"] async def test_upload_bad_ext(hass: HomeAssistant, hass_client: ClientSessionGenerator) -> None: diff --git a/tests_backend/test_ha_websocket.py b/tests_backend/test_ha_websocket.py index 9b9d029..afa43ef 100644 --- a/tests_backend/test_ha_websocket.py +++ b/tests_backend/test_ha_websocket.py @@ -558,7 +558,7 @@ async def test_config_write_is_capped_by_total_size( cfg = await _cfg([{"id": "f1", "plan_url": None}]) # every field inside the caps, the whole thing far past them blob = "d" * MAX_TEXT - cfg["settings"] = {"known_devices": [blob] * (MAX_CONFIG_BYTES // MAX_TEXT + 10)} + cfg["settings"] = {"known_devices": [blob] * (MAX_CONFIG_BYTES // MAX_TEXT + 100)} resp = await _save(client, cfg, 0) assert not resp["success"] and resp["error"]["code"] == "too_large" @@ -686,7 +686,10 @@ async def test_upload_never_overwrites_an_existing_attachment( assert first != second, "the second upload must not take the first name" folder = os.path.join(hass.config.path(FILES_DIR), "m1") - names = sorted(await hass.async_add_executor_job(os.listdir, folder)) + # the HA test config dir is shared across the module — look only at ours + names = sorted( + n for n in await hass.async_add_executor_job(os.listdir, folder) if n.startswith("manual") + ) assert names == ["manual (2).pdf", "manual.pdf"] got = await http.get(first.replace(CONTENT_URL, CONTENT_URL))