mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-14 23:28:05 +00:00
fix: bound the logo download, and stop malformed input reading as a fault
Review findings on the coverage branch.
The download size cap I added checked len(response.content), which has
already buffered the whole body -- it stopped the bytes reaching disk but
not memory, which was the point. A server that omits Content-Length and
never stops sending would still exhaust the process. Stream it instead,
counting as it arrives, into a sibling .part file that is replaced over
the target only once it decodes. A transfer that dies midway now leaves
nothing behind rather than a truncated logo for load_logo() to cache.
The follower's control-message handler caught three exception types, but
two reachable UDP payloads raise others: a bare JSON scalar makes
msg.get() raise AttributeError, and an "sx" carrying a non-numeric x
raises ValueError or TypeError from float(). Those escaped to the outer
handler, skipping the legacy-PNG fallback and -- since this branch added
a backoff there -- charging one malformed packet a 0.1s stall on the
receive path. The legacy-PNG path also decoded without the dimension cap
its TCP counterpart applies, so a crafted 65KB frame could force a large
allocation on the render thread; both paths now share one constant.
Three repo_url handlers called .strip() on client input without checking
it was a string, so {"repo_url": 12345} answered 500. The credentials
upload parsed the same file twice, the second time inside a bare except
that a preceding parse had already made unreachable. And both raw-config
handlers kept a json.JSONDecodeError arm that get_json(silent=True) had
turned into dead code, collapsing "sent something unparseable" into "sent
nothing" -- they now say which.
Two of the new tests were not testing what they claimed. The pruning
round-trip wrote ten backups inside one second, so all ten landed on the
same int(time.time()) filename and overwrote each other; it never reached
the limit it asserted. And the sync clock helper patched attributes on the
stdlib time module, freezing time process-wide for every daemon thread
earlier tests had left running.
Full suite: 3352 passed, coverage 54%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NohXi78cwsAKtN1sCfxjUh
This commit is contained in:
@@ -22,6 +22,7 @@ import stat
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -192,14 +193,25 @@ class TestBackupPruning:
|
||||
upload(api_v3_client, VALID_CREDENTIALS)
|
||||
assert len(backups(plugin_dir)) == 3 # 2 seeded + 1 new
|
||||
|
||||
def test_repeated_uploads_stay_bounded(self, api_v3_client, plugin_dir):
|
||||
def test_repeated_uploads_stay_bounded(
|
||||
self, api_v3_client, plugin_dir, api_v3_module, monkeypatch):
|
||||
# The backup filename carries int(time.time()), so uploads inside
|
||||
# the same second all write the same name and overwrite each other.
|
||||
# Advance a fake clock a second per round — otherwise this never
|
||||
# reaches six backups and the bound holds for the wrong reason.
|
||||
clock = {"now": int(time.time())}
|
||||
monkeypatch.setattr(
|
||||
api_v3_module, "time", SimpleNamespace(time=lambda: clock["now"]))
|
||||
for i in range(10):
|
||||
clock["now"] += 1
|
||||
upload(api_v3_client, {"installed": {"round": i}})
|
||||
# Distinct mtimes so ordering is well-defined between rounds.
|
||||
for path in backups(plugin_dir):
|
||||
os.utime(path, (path.stat().st_mtime, path.stat().st_mtime))
|
||||
time.sleep(0.01)
|
||||
assert len(backups(plugin_dir)) <= 5
|
||||
os.utime(plugin_dir / "credentials.json",
|
||||
(clock["now"], clock["now"]))
|
||||
remaining = backups(plugin_dir)
|
||||
assert len(remaining) == 5
|
||||
# And they are the five most recent rounds, not an arbitrary five.
|
||||
kept = sorted(int(p.name.rsplit(".", 1)[1]) for p in remaining)
|
||||
assert kept == [clock["now"] - 4 + i for i in range(5)]
|
||||
|
||||
def test_unremovable_backup_does_not_fail_the_upload(
|
||||
self, api_v3_client, plugin_dir, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user