mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-17 20:39:13 +00:00
Fixes to the api_v3 package split (PR #553), one per finding verified against the actual code: - __init__.py: _redact_credentials only blanked scalar values under a credential-named key; a bare list of secrets under such a key (e.g. tokens: ["a", "b"]) passed through untouched, since the list branch recursed with no memory that its key looked like a credential. Nested dicts still walk normally (a documented, tested behaviour -- a container like secrets: {api_key: ..., note: ...} is a section name, not a value to blank outright), but any value reached under a credential-shaped key is now actually blanked. - __init__.py: the OAuth helper script's raw stderr/stdout went to logger.error unredacted (CWE-532) right next to a comment claiming this was deliberate; the HTTP response already used the existing redact_text helper. Routed the log line through the same helper. - __init__.py / starlark.py: the standalone Starlark manifest fallback (used when the plugin instance isn't loaded) read-modified-wrote manifest.json with no lock, unlike StarlarkAppsPlugin._update_manifest_safe (plugin-repos/starlark-apps/manager.py), which already holds an flock for the same file when the plugin is loaded. Added _starlark_manifest_lock, mirroring that pattern, and wrapped every standalone read-modify-write call site in it. The app-config update route also wrote config.json and the manifest as two separate, non-transactional writes (a second, distinct finding at the same call site); config.json is now rolled back if the manifest write that follows it fails. - backup.py: restore options used bare bool() on values from the request, so {"restore_secrets": "false"} restored secrets anyway (bool("false") is True). Switched to the existing _coerce_to_bool helper already used for this exact purpose elsewhere in the package. - config.py: an automated import-rewrite mangled four user-facing validation strings and their neighbouring comments -- "Invalid start time" had become "Invalid start _pkg.time" (and likewise for "end time") in both the schedule and dim-schedule per-day validation paths. - display.py: `import _pkg.time as time_module` -- _pkg is a local alias for the package, not a real importable module, so this raised ModuleNotFoundError whenever a caller restarted an already-running display service via /display/on-demand/start, after the on-demand request was already written to cache. Fixed to `import time`. Audited the rest of the package for the same `_pkg.<module>` import mistake; every other `_pkg.` reference is a legitimate attribute read-through (`_pkg.time.time()`, `_pkg._get_starlark_plugin()`, ...), not a broken import statement. - fonts.py: validate_file_upload's max_size_mb parameter is silently unused by that helper (it only checks filename/extension) -- the font upload route saved arbitrarily large files as a result. Added the same seek-and-check pattern already used for the sibling .star upload. - wifi.py: two ad hoc, inconsistent bool coercions. POST /wifi/ap/auto-enable used bare bool(), so a JSON string "false" enabled it. POST /wifi/radio's enabled/force parsing recognized real bool and some strings but not int 1/0 (1 is True is False in Python). Factored one small _parse_bool_ish helper local to this file and used it at all three sites. Not changed: the "unknown/misspelled restore option keys default to True" half of the backup.py finding -- the file's own comment documents that a missing key deliberately means "restore everything," matching the already-existing JSON-parse-failure guard a few lines above it; only the bool-coercion defect was a real bug. Added or extended regression tests for every fix, following each area's existing test conventions. Full suite: 4328 passed, 62 skipped, 2 failed on both this branch and origin/main (missing tzdata package breaks two timezone-alias tests in test_onboarding_checklist.py, unrelated to this change) -- no new failures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3bPMESe2TfrGvbs1ef9c5
55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
"""Regression test: per-day schedule validation errors must say "time", not
|
|
"_pkg.time".
|
|
|
|
The split into a package rewrote every bare `time` reference that needed to
|
|
read through the shared module as `_pkg.time` (see the package's own
|
|
docstring on why -- tests patch it, so it has to be read back live rather
|
|
than bound by value). That rewrite was mechanical and matched the substring
|
|
"time" inside string literals and comments too, so the user-facing message
|
|
|
|
"Invalid start time for {day}: ..."
|
|
|
|
came out as
|
|
|
|
"Invalid start _pkg.time for {day}: ..."
|
|
|
|
in both POST /config/schedule and POST /config/dim-schedule, for both the
|
|
start and end time of a per-day entry.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from test._api_v3_test_helpers import api_v3_client, api_v3_module # noqa: F401,E402
|
|
|
|
|
|
@pytest.mark.parametrize("url", [
|
|
"/api/v3/config/schedule",
|
|
"/api/v3/config/dim-schedule",
|
|
])
|
|
class TestPerDayTimeErrorsAreNotCorrupted:
|
|
def test_invalid_start_time_message(self, api_v3_client, api_v3_module, url):
|
|
response = api_v3_client.post(url, json={
|
|
"mode": "per_day",
|
|
"monday_start": "not-a-time",
|
|
})
|
|
assert response.status_code == 400
|
|
message = response.get_json()["message"]
|
|
assert "_pkg" not in message, message
|
|
assert message.startswith("Invalid start time for monday:"), message
|
|
|
|
def test_invalid_end_time_message(self, api_v3_client, api_v3_module, url):
|
|
response = api_v3_client.post(url, json={
|
|
"mode": "per_day",
|
|
"monday_start": "07:00",
|
|
"monday_end": "not-a-time",
|
|
})
|
|
assert response.status_code == 400
|
|
message = response.get_json()["message"]
|
|
assert "_pkg" not in message, message
|
|
assert message.startswith("Invalid end time for monday:"), message
|