mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-05 06:45:09 +00:00
test(starlark): keep the route tests off the network, out of the repo, and order-independent (#764)
Three hygiene problems in test_starlark_pixlet_routes.py: - GET /plugins/installed calls plugin_store_manager.get_registry_info() per plugin, which calls fetch_registry(); on a cold cache that downloads plugins.json from raw.githubusercontent.com (10s timeout plus retries). The three listing tests in TestInstalledAppsAppearWithTheOtherPlugins therefore went to the network and stalled without a connection. The client fixture now stubs fetch_registry on the app's store manager. - _STARLARK_APPS_DIR, _STARLARK_MANIFEST_FILE and _STARLARK_MANIFEST_LOCK_FILE are three separate module constants. Four fixtures redirected only the first two, so _starlark_manifest_lock() still took its lock at the repo's starlark-apps/manifest.json.lock (Linux only; Windows has no fcntl). The path is gitignored, so `git status` never showed it. One autouse fixture now redirects all three to tmp_path for every test in the file, and the class fixtures build on it. - The TestTheStoreUsesTheTokenTheUserConfigured unit tests patch plugin_store_manager / config_manager on the api_v3 blueprint. Those attributes only exist once web_interface/app.py has been imported, which happens as a side effect of the first test that uses the client fixture. Run before any such test, the four non-client tests raised AttributeError. They now patch with create=True. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,11 +35,35 @@ def client(monkeypatch):
|
||||
# and several tests below stub subprocess. Pin it: no test in this file
|
||||
# is about AP mode.
|
||||
monkeypatch.setattr(web_app, 'is_ap_mode_active', lambda: False)
|
||||
# GET /plugins/installed looks up each plugin's registry entry, and on a
|
||||
# cold cache that fetches plugins.json from GitHub -- so without a
|
||||
# connection those tests sat in the HTTP retry loop. No test in this
|
||||
# file is about the registry.
|
||||
monkeypatch.setattr(web_app.plugin_store_manager, 'fetch_registry',
|
||||
lambda *args, **kwargs: {'plugins': []})
|
||||
app.config['TESTING'] = True
|
||||
with app.test_client() as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def starlark_apps_dir(tmp_path, monkeypatch):
|
||||
"""Point every Starlark storage path at tmp_path for every test.
|
||||
|
||||
The manifest, its directory and the lock file are three separate module
|
||||
constants. Fixtures that redirected the first two but not the lock left
|
||||
the lock pointing at the repo, and on Linux (where the lock is taken)
|
||||
each test created starlark-apps/manifest.json.lock in the checkout. The
|
||||
directory is not created here; tests that need it make it.
|
||||
"""
|
||||
from web_interface.blueprints import api_v3 as module
|
||||
apps_dir = tmp_path / "starlark-apps"
|
||||
monkeypatch.setattr(module, '_STARLARK_APPS_DIR', apps_dir)
|
||||
monkeypatch.setattr(module, '_STARLARK_MANIFEST_FILE', apps_dir / 'manifest.json')
|
||||
monkeypatch.setattr(module, '_STARLARK_MANIFEST_LOCK_FILE', apps_dir / 'manifest.json.lock')
|
||||
return apps_dir
|
||||
|
||||
|
||||
class TestRoutesAreRegistered:
|
||||
"""The failure was a missing route, so check the URL map directly.
|
||||
|
||||
@@ -398,13 +422,9 @@ class TestTheManifestSurvivesConcurrentWriters:
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def starlark_dir(self, tmp_path, monkeypatch):
|
||||
from web_interface.blueprints import api_v3 as module
|
||||
apps_dir = tmp_path / "starlark-apps"
|
||||
apps_dir.mkdir()
|
||||
monkeypatch.setattr(module, '_STARLARK_APPS_DIR', apps_dir)
|
||||
monkeypatch.setattr(module, '_STARLARK_MANIFEST_FILE', apps_dir / 'manifest.json')
|
||||
return apps_dir
|
||||
def starlark_dir(self, starlark_apps_dir):
|
||||
starlark_apps_dir.mkdir()
|
||||
return starlark_apps_dir
|
||||
|
||||
def test_each_writer_gets_its_own_temp_file(self, starlark_dir):
|
||||
from web_interface.blueprints import api_v3 as module
|
||||
@@ -560,13 +580,9 @@ class TestTheManifestStaysRelocatable:
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def starlark_dir(self, tmp_path, monkeypatch):
|
||||
from web_interface.blueprints import api_v3 as module
|
||||
apps_dir = tmp_path / "starlark-apps"
|
||||
apps_dir.mkdir()
|
||||
monkeypatch.setattr(module, '_STARLARK_APPS_DIR', apps_dir)
|
||||
monkeypatch.setattr(module, '_STARLARK_MANIFEST_FILE', apps_dir / 'manifest.json')
|
||||
return apps_dir
|
||||
def starlark_dir(self, starlark_apps_dir):
|
||||
starlark_apps_dir.mkdir()
|
||||
return starlark_apps_dir
|
||||
|
||||
def _install(self, tmp_path):
|
||||
from web_interface.blueprints import api_v3 as module
|
||||
@@ -606,13 +622,10 @@ class TestManifestLockPreventsLostUpdates:
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def starlark_dir(self, tmp_path, monkeypatch):
|
||||
def starlark_dir(self, starlark_apps_dir):
|
||||
from web_interface.blueprints import api_v3 as module
|
||||
apps_dir = tmp_path / "starlark-apps"
|
||||
apps_dir = starlark_apps_dir
|
||||
apps_dir.mkdir()
|
||||
monkeypatch.setattr(module, '_STARLARK_APPS_DIR', apps_dir)
|
||||
monkeypatch.setattr(module, '_STARLARK_MANIFEST_FILE', apps_dir / 'manifest.json')
|
||||
monkeypatch.setattr(module, '_STARLARK_MANIFEST_LOCK_FILE', apps_dir / 'manifest.json.lock')
|
||||
module._write_starlark_manifest({'apps': {}})
|
||||
return apps_dir
|
||||
|
||||
@@ -700,12 +713,10 @@ class TestConfigAndManifestStayInSync:
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def app_dir(self, tmp_path, monkeypatch):
|
||||
def app_dir(self, starlark_apps_dir):
|
||||
from web_interface.blueprints import api_v3 as module
|
||||
apps_dir = tmp_path / "starlark-apps"
|
||||
apps_dir = starlark_apps_dir
|
||||
apps_dir.mkdir()
|
||||
monkeypatch.setattr(module, '_STARLARK_APPS_DIR', apps_dir)
|
||||
monkeypatch.setattr(module, '_STARLARK_MANIFEST_FILE', apps_dir / 'manifest.json')
|
||||
one_app_dir = apps_dir / 'demo'
|
||||
one_app_dir.mkdir()
|
||||
module._write_starlark_manifest({'apps': {'demo': {'name': 'Demo', 'enabled': True}}})
|
||||
@@ -1049,13 +1060,18 @@ class TestTheStoreUsesTheTokenTheUserConfigured:
|
||||
/plugins/store/github-status reported `authenticated: true` with a
|
||||
rate_limit of 5000 while /starlark/repository/browse reported a limit of
|
||||
60 -- the store going blank was that 60 running out.
|
||||
|
||||
The managers are attributes web_interface/app.py hangs on the blueprint
|
||||
when it is imported, so they exist only once some earlier test has
|
||||
imported the app. Every patch here passes create=True: these tests must
|
||||
not depend on which test ran before them.
|
||||
"""
|
||||
|
||||
def test_the_store_managers_token_is_used(self):
|
||||
from web_interface.blueprints import api_v3 as mod
|
||||
|
||||
with patch.object(mod.api_v3, 'plugin_store_manager',
|
||||
MagicMock(github_token='ghp_configured')):
|
||||
MagicMock(github_token='ghp_configured'), create=True):
|
||||
assert mod._starlark_github_token() == 'ghp_configured'
|
||||
|
||||
def test_a_hand_edited_config_key_still_works(self):
|
||||
@@ -1064,8 +1080,8 @@ class TestTheStoreUsesTheTokenTheUserConfigured:
|
||||
cfg = MagicMock()
|
||||
cfg.load_config.return_value = {'github_token': 'ghp_by_hand'}
|
||||
with patch.object(mod.api_v3, 'plugin_store_manager',
|
||||
MagicMock(github_token=None)), \
|
||||
patch.object(mod.api_v3, 'config_manager', cfg):
|
||||
MagicMock(github_token=None), create=True), \
|
||||
patch.object(mod.api_v3, 'config_manager', cfg, create=True):
|
||||
assert mod._starlark_github_token() == 'ghp_by_hand'
|
||||
|
||||
def test_no_token_anywhere_is_not_an_error(self):
|
||||
@@ -1074,8 +1090,8 @@ class TestTheStoreUsesTheTokenTheUserConfigured:
|
||||
cfg = MagicMock()
|
||||
cfg.load_config.return_value = {}
|
||||
with patch.object(mod.api_v3, 'plugin_store_manager',
|
||||
MagicMock(github_token=None)), \
|
||||
patch.object(mod.api_v3, 'config_manager', cfg):
|
||||
MagicMock(github_token=None), create=True), \
|
||||
patch.object(mod.api_v3, 'config_manager', cfg, create=True):
|
||||
assert mod._starlark_github_token() is None
|
||||
|
||||
def test_an_unreadable_config_does_not_take_the_store_down(self):
|
||||
@@ -1084,8 +1100,8 @@ class TestTheStoreUsesTheTokenTheUserConfigured:
|
||||
cfg = MagicMock()
|
||||
cfg.load_config.side_effect = OSError("config.json is unreadable")
|
||||
with patch.object(mod.api_v3, 'plugin_store_manager',
|
||||
MagicMock(github_token=None)), \
|
||||
patch.object(mod.api_v3, 'config_manager', cfg):
|
||||
MagicMock(github_token=None), create=True), \
|
||||
patch.object(mod.api_v3, 'config_manager', cfg, create=True):
|
||||
assert mod._starlark_github_token() is None
|
||||
|
||||
def test_browse_hands_the_token_to_the_repository(self, client):
|
||||
@@ -1099,7 +1115,7 @@ class TestTheStoreUsesTheTokenTheUserConfigured:
|
||||
repo.return_value.get_rate_limit_info.return_value = {'remaining': 4999}
|
||||
|
||||
with patch.object(mod.api_v3, 'plugin_store_manager',
|
||||
MagicMock(github_token='ghp_configured')), \
|
||||
MagicMock(github_token='ghp_configured'), create=True), \
|
||||
patch('web_interface.blueprints.api_v3._get_tronbyte_repository_class',
|
||||
return_value=repo):
|
||||
client.get('/api/v3/starlark/repository/browse')
|
||||
@@ -1189,15 +1205,13 @@ class TestStandaloneRenderUsesTheDeviceLocation:
|
||||
SCHEMA = {"schema": [{"typeOf": "location", "id": "location"}]}
|
||||
|
||||
@pytest.fixture
|
||||
def app_dir(self, tmp_path, monkeypatch):
|
||||
def app_dir(self, starlark_apps_dir, monkeypatch):
|
||||
from web_interface.blueprints import api_v3 as module
|
||||
apps_dir = tmp_path / "starlark-apps"
|
||||
apps_dir = starlark_apps_dir
|
||||
app_dir = apps_dir / "weather"
|
||||
app_dir.mkdir(parents=True)
|
||||
(app_dir / "weather.star").write_text("# app")
|
||||
(app_dir / "schema.json").write_text(json.dumps(self.SCHEMA))
|
||||
monkeypatch.setattr(module, '_STARLARK_APPS_DIR', apps_dir)
|
||||
monkeypatch.setattr(module, '_STARLARK_MANIFEST_FILE', apps_dir / 'manifest.json')
|
||||
(apps_dir / 'manifest.json').write_text(json.dumps(
|
||||
{'apps': {'weather': {'star_file': 'weather.star'}}}))
|
||||
config_manager = MagicMock()
|
||||
|
||||
Reference in New Issue
Block a user