diff --git a/test/web_interface/test_starlark_pixlet_routes.py b/test/web_interface/test_starlark_pixlet_routes.py index 35673f93..92102c48 100644 --- a/test/web_interface/test_starlark_pixlet_routes.py +++ b/test/web_interface/test_starlark_pixlet_routes.py @@ -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()