mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-13 14:48:06 +00:00
The /wifi/* routes drive the host's real networking and the registry
routes reach GitHub, and neither had endpoint-level tests. Covering them
surfaced a bug affecting six endpoints.
Six handlers read their body as `request.get_json() or {}`. The `or {}`
says every field is optional and a missing body should fall back to
defaults — but get_json() without silent=True raises UnsupportedMediaType
when there is no JSON Content-Type, and it raises before `or {}` is ever
evaluated. Each handler's catch-all then reported that as a 500. So
POSTing with no body — what curl sends by default, and what a fetch()
without options sends — failed on /plugins/store/refresh,
/display/on-demand/start, /plugins/config/reset,
/plugins/of-the-day/json/delete, /plugins/{id}/limits and
/plugins/authenticate/spotify. The shipped UI always sends a JSON object,
which is why this stayed hidden.
All six now use silent=True. test_api_v3_optional_body.py covers the
affected endpoints and adds a source check, since the combination of
`or <default>` with a non-silent read is self-contradictory wherever it
appears and is easier to catch by inspection than by exercising each
endpoint by hand.
Also adds test/_api_v3_test_helpers.py: the blueprint holds its managers
on a module-level singleton rather than in Flask app state, so a test
that mocks them leaks into every later test unless the originals are
restored. The existing _make_client() does this for unittest classes;
this is the pytest-fixture equivalent, for the five suites still to come.
69 endpoint tests: connect/disconnect/AP/radio including the string-aware
boolean coercion these endpoints deliberately use, the radio's
lockout-refusal path, registry refresh and fetch-from-URL, and a guard
that WiFiManager is never constructed for real.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NohXi78cwsAKtN1sCfxjUh
76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
"""
|
|
Shared scaffolding for api_v3 blueprint tests.
|
|
|
|
Not a test module (the leading underscore keeps pytest from collecting
|
|
it). It is the pytest-fixture equivalent of ``_make_client()`` in
|
|
test_uninstall_and_reconcile_endpoint.py, which is unittest-style and
|
|
requires ``self.addCleanup``.
|
|
|
|
The api_v3 blueprint keeps its managers as attributes on a module-level
|
|
singleton, not in Flask app state, so replacing them with mocks leaks
|
|
into every later test that imports api_v3 unless the originals are put
|
|
back. ``api_v3_client`` snapshots and restores them around each test.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from flask import Flask
|
|
|
|
|
|
# Every manager attribute the blueprint reads. Anything missing here keeps
|
|
# whatever a previously-run test left on the singleton.
|
|
API_V3_MANAGER_ATTRS = (
|
|
'config_manager', 'plugin_manager', 'plugin_store_manager',
|
|
'plugin_state_manager', 'saved_repositories_manager', 'schema_manager',
|
|
'operation_queue', 'operation_history', 'cache_manager',
|
|
)
|
|
|
|
_SENTINEL = object()
|
|
|
|
|
|
def build_app(blueprint):
|
|
app = Flask(__name__)
|
|
app.config['TESTING'] = True
|
|
app.config['SECRET_KEY'] = 'test'
|
|
app.register_blueprint(blueprint, url_prefix='/api/v3')
|
|
return app
|
|
|
|
|
|
@pytest.fixture
|
|
def api_v3_module():
|
|
"""The api_v3 module with every manager replaced by a MagicMock.
|
|
|
|
Restores the original attributes afterwards. Tests point individual
|
|
managers at real objects (a ConfigManager over tmp_path, say) or set
|
|
them to None to exercise the not-initialized branches.
|
|
"""
|
|
from web_interface.blueprints import api_v3 as module
|
|
|
|
originals = {
|
|
name: getattr(module.api_v3, name, _SENTINEL)
|
|
for name in API_V3_MANAGER_ATTRS
|
|
}
|
|
for name in API_V3_MANAGER_ATTRS:
|
|
setattr(module.api_v3, name, MagicMock())
|
|
# Default to the direct path; queue tests opt in explicitly.
|
|
module.api_v3.operation_queue = None
|
|
|
|
yield module
|
|
|
|
for name, original in originals.items():
|
|
if original is _SENTINEL:
|
|
if hasattr(module.api_v3, name):
|
|
try:
|
|
delattr(module.api_v3, name)
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
setattr(module.api_v3, name, original)
|
|
|
|
|
|
@pytest.fixture
|
|
def api_v3_client(api_v3_module):
|
|
"""Flask test client wired to the mocked blueprint."""
|
|
return build_app(api_v3_module.api_v3).test_client()
|