From 3f719cc32a5a5f2e29c56c21eedb28a0aa8f865f Mon Sep 17 00:00:00 2001 From: Matysh Date: Tue, 28 Jul 2026 16:09:00 +0300 Subject: [PATCH] test: match the new upload url shape; keep the config cap under the WS frame limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_upload_ok still asserted the old '?v=' url — uploads take a free name now, so the name itself is the cache key and the query is gone. MAX_CONFIG_BYTES was 12 MB, above the WebSocket frame limit: a payload that big never reaches the handler, the socket just closes with 1009 and the user sees a dropped connection instead of an actionable error. 4 MB is far above any real configuration and comfortably inside the frame. test_upload_never_overwrites listed the whole shared test config folder. --- custom_components/houseplan/validation.py | 5 ++++- tests_backend/test_ha_upload.py | 5 ++++- tests_backend/test_ha_websocket.py | 7 +++++-- 3 files changed, 13 insertions(+), 4 deletions(-) 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))