mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-18 04:49:13 +00:00
"Pixlet install failed: Resource not found" is Flask's 404 handler. The
install button posts to /api/v3/starlark/install-pixlet, that route does
not exist, and app.py's generic 404 answers with
{"status": "error", "message": "Resource not found"}
which the button prints verbatim -- naming neither the resource nor the
cause.
The routes were added by #253 and removed by #330, which rewrote
api_v3.py (3272 lines changed) and dropped all thirteen Starlark routes
with it. Nothing else moved: the frontend still calls them and
plugin-repos/starlark-apps still implements the work behind them, so
every Starlark call in the UI has been landing on the 404 handler since.
Restores the two the Pixlet flow needs -- install-pixlet, and the status
call the button reloads afterwards -- verbatim from 1c4d5c52^, plus the
three helpers they use (_get_starlark_plugin, _find_pixlet_binary,
_read_starlark_manifest) and _STARLARK_APPS_DIR.
The installer itself is fine; I checked rather than assumed. Against the
live release, the naming the script builds matches what tronbyt/pixlet
publishes, and the URL resolves:
latest tag v0.53.1
asset published pixlet_v0.53.1_linux-arm64.tar.gz
script would request pixlet_v0.53.1_linux-arm64.tar.gz
HEAD 200
So the missing route was the whole fault.
Deliberately scoped to the reported bug. The other eleven routes are
still missing -- apps CRUD, config, toggle, render, repository browse
and install, repository categories -- and the rest of the Starlark page
is still dead. #463 covers those alongside behavioural changes; this
stands alone so the install can be fixed without them.
test_starlark_pixlet_routes.py asserts both routes are registered and
answer in the shape the JS reads, including the not-yet-loaded-plugin
case a first-time user is in when they press Install. 8 of its 9 tests
fail against main.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9
110 lines
4.7 KiB
Python
110 lines
4.7 KiB
Python
"""The Starlark routes the frontend calls must exist.
|
|
|
|
`plugins_manager.js` posts to /api/v3/starlark/install-pixlet and then reloads
|
|
/api/v3/starlark/status. Neither route existed: #330 rewrote api_v3.py and
|
|
dropped all thirteen Starlark routes that #253 had added, so both calls fell
|
|
through to Flask's 404 handler, which answers
|
|
|
|
{"status": "error", "message": "Resource not found"}
|
|
|
|
and the button reported "Pixlet install failed: Resource not found" -- a
|
|
message that names neither the resource nor the cause.
|
|
|
|
These assert the routes are registered and answer in the shape the frontend
|
|
reads, so a future rewrite of this file cannot silently drop them again.
|
|
"""
|
|
|
|
import json
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
from web_interface.app import app
|
|
app.config['TESTING'] = True
|
|
with app.test_client() as c:
|
|
yield c
|
|
|
|
|
|
class TestRoutesAreRegistered:
|
|
"""The failure was a missing route, so check the URL map directly."""
|
|
|
|
@pytest.mark.parametrize("rule,method", [
|
|
("/api/v3/starlark/install-pixlet", "POST"),
|
|
("/api/v3/starlark/status", "GET"),
|
|
])
|
|
def test_route_exists(self, rule, method):
|
|
from web_interface.app import app
|
|
matches = [r for r in app.url_map.iter_rules()
|
|
if r.rule == rule and method in r.methods]
|
|
assert matches, (
|
|
f"{method} {rule} is not registered; the frontend calls it and "
|
|
"would get Flask's generic 'Resource not found'")
|
|
|
|
|
|
class TestInstallPixlet:
|
|
def test_it_does_not_404(self, client):
|
|
with patch('web_interface.blueprints.api_v3.subprocess.run') as run:
|
|
run.return_value = MagicMock(returncode=0, stdout="ok", stderr="")
|
|
resp = client.post('/api/v3/starlark/install-pixlet')
|
|
assert resp.status_code != 404, "the route is still missing"
|
|
assert resp.get_json().get('message') != 'Resource not found'
|
|
|
|
def test_success_is_reported_in_the_shape_the_button_reads(self, client):
|
|
with patch('web_interface.blueprints.api_v3.subprocess.run') as run:
|
|
run.return_value = MagicMock(returncode=0, stdout="done", stderr="")
|
|
resp = client.post('/api/v3/starlark/install-pixlet')
|
|
body = resp.get_json()
|
|
assert body['status'] == 'success', body
|
|
assert 'message' in body, "the JS shows data.message on success"
|
|
|
|
def test_a_failed_download_says_why(self, client):
|
|
with patch('web_interface.blueprints.api_v3.subprocess.run') as run:
|
|
run.return_value = MagicMock(returncode=1, stdout="", stderr="no such release")
|
|
resp = client.post('/api/v3/starlark/install-pixlet')
|
|
body = resp.get_json()
|
|
assert body['status'] == 'error'
|
|
assert 'no such release' in body['message'], \
|
|
"the installer's own stderr is what tells the user what went wrong"
|
|
|
|
def test_a_timeout_is_reported_rather_than_hanging(self, client):
|
|
import subprocess as sp
|
|
with patch('web_interface.blueprints.api_v3.subprocess.run',
|
|
side_effect=sp.TimeoutExpired(cmd='x', timeout=300)):
|
|
resp = client.post('/api/v3/starlark/install-pixlet')
|
|
assert resp.get_json()['status'] == 'error'
|
|
assert 'timed out' in resp.get_json()['message'].lower()
|
|
|
|
|
|
class TestStarlarkStatus:
|
|
def test_it_does_not_404(self, client):
|
|
resp = client.get('/api/v3/starlark/status')
|
|
assert resp.status_code != 404, "the route is still missing"
|
|
assert resp.get_json().get('message') != 'Resource not found'
|
|
|
|
def test_it_reports_pixlet_availability_without_the_plugin_loaded(self, client):
|
|
# The status call runs before install too -- it must answer even when
|
|
# starlark-apps is not loaded, which is the state a user is in when
|
|
# they press the install button for the first time.
|
|
with patch('web_interface.blueprints.api_v3._get_starlark_plugin', return_value=None):
|
|
resp = client.get('/api/v3/starlark/status')
|
|
body = resp.get_json()
|
|
assert body['status'] == 'success', body
|
|
assert 'pixlet_available' in body
|
|
assert body['plugin_loaded'] is False
|
|
|
|
|
|
class TestTheInstallerScriptIsActuallyThere:
|
|
def test_download_pixlet_script_exists_and_is_executable(self):
|
|
# install_pixlet chmods and runs this; a missing file is the one error
|
|
# it reports as a 404 of its own, which would look identical to the
|
|
# bug being fixed here.
|
|
import os
|
|
from pathlib import Path
|
|
from web_interface.blueprints.api_v3 import PROJECT_ROOT
|
|
script = Path(PROJECT_ROOT) / 'scripts' / 'download_pixlet.sh'
|
|
assert script.is_file(), f"{script} is missing; install_pixlet would 404"
|
|
assert os.access(script, os.R_OK)
|