"""The two endpoints switches are declared, seeded, and have a control. Both were readable by their enforcement code before they were declared: plugin.endpoints.revokeddeniessharedkey agentauth.revoked_denies_shared_key plugin.endpoints.bootstrapquerytoken bootstrap._query_token_allowed An undeclared key has no row until somebody writes one, and until it was declared the settings API upserted it with valuetype 'string' - so bool(Setting.get(key, True)) read the text 'false' as True and the switch did nothing. Declaring it fixes both halves: the row is seeded at plugin install/enable, and a PUT lands as a real boolean. The UI half is checked by reading the pages. A switch with no control on an air-gapped box can only be changed with SQL, which is not a setting. """ from pathlib import Path import pytest from plugins.endpoints.api import agentauth, bootstrap from plugins.endpoints.plugin import EndpointsPlugin from shopdb.core.models import Setting REPO = Path(__file__).resolve().parents[2] VIEWS = REPO / 'plugins' / 'endpoints' / 'frontend' / 'views' REVOKED_KEY = agentauth.REVOKED_DENIES_SHARED_KEY_SETTING QUERY_KEY = bootstrap.QUERY_TOKEN_SETTING def _declared(): return {entry['key']: entry for entry in EndpointsPlugin().get_settings_defaults()} @pytest.mark.parametrize('key,value', [(REVOKED_KEY, 'false'), (QUERY_KEY, 'true')]) def test_the_switch_is_declared_with_todays_behaviour(key, value): entry = _declared().get(key) assert entry is not None, key + ' is read but never declared' assert entry['valuetype'] == 'boolean' assert entry['category'] == 'plugin.endpoints' # The declared default must be what the code does with no row at all, or # seeding a site silently changes its behaviour. assert entry['value'] == value @pytest.mark.parametrize('key', [REVOKED_KEY, QUERY_KEY]) def test_the_description_names_what_turning_it_on_costs(key): """An operator on the shop floor decides from this text alone.""" description = _declared()[key]['description'] assert len(description) > 120, description if key == REVOKED_KEY: # The revoke + blockreenroll rollback onto an older agent stops working. assert 'rollback' in description assert 'enrolls again' in description else: assert 're-issued' in description or 're-issue' in description def test_the_declared_defaults_match_what_the_code_reads_with_no_row(db): """No Setting rows at all: the readers fall back to these same values.""" assert Setting.query.filter_by(key=REVOKED_KEY).first() is None assert Setting.query.filter_by(key=QUERY_KEY).first() is None assert agentauth.revoked_denies_shared_key() is False assert bootstrap._query_token_allowed() is True def test_the_upgrade_pass_seeds_them_on_a_site_that_installed_earlier(app, db): """install/enable fire only on a state transition, so a fleet already running this plugin gets these rows from `flask plugin upgrade-all`, which calls sync_all_plugin_settings -> _seed_plugin_settings. This drives _seed_plugin_settings on a manager built for this test rather than the process-wide plugin_manager singleton, and that is deliberate. create_app() re-points that singleton, eight test modules build their own app, and whatever ran last on this xdist worker owns it: the symptoms are "no such table: settings" (seeding against another app's in-memory SQLite) and "'NoneType' object has no attribute 'get_all'" (registry left None). Both land on whichever test follows, so they move with the split - this one passed locally and failed on CI for exactly that reason. init_app is not called here either: it re-registers the plugin blueprints, and twice on one app raises "The name 'backups' is already registered". Setting _app and _db is all the seeding routine reads. """ from shopdb.plugins import PluginManager from plugins.endpoints.plugin import EndpointsPlugin assert Setting.query.filter_by(key=QUERY_KEY).first() is None manager = PluginManager() manager._app = app manager._db = db assert manager._seed_plugin_settings(EndpointsPlugin()) is True for key, value in ((REVOKED_KEY, False), (QUERY_KEY, True)): row = Setting.query.filter_by(key=key).one() assert row.valuetype == 'boolean' assert row.category == 'plugin.endpoints' assert row.get_typed_value() is value def test_a_put_lands_as_a_boolean_and_flips_the_query_token(client, db, auth_headers): """End to end for bootstrapquerytoken through the API the page uses. The declaration is what makes the upsert boolean; as a string row the read below would stay True whatever was saved. """ saved = client.put('/api/settings/' + QUERY_KEY, json={'value': False}, headers=auth_headers) assert saved.status_code == 200, saved.get_json() body = saved.get_json()['data'] assert body['value'] is False assert body['valuetype'] == 'boolean' assert body['category'] == 'plugin.endpoints' assert bootstrap._query_token_allowed() is False back = client.put('/api/settings/' + QUERY_KEY, json={'value': True}, headers=auth_headers) assert back.get_json()['data']['value'] is True assert bootstrap._query_token_allowed() is True def test_the_settings_api_lists_the_declared_switch(client, db, auth_headers): """The agent-key page reads GET /api/settings/ to render the box.""" client.put('/api/settings/' + QUERY_KEY, json={'value': True}, headers=auth_headers) resp = client.get('/api/settings/' + QUERY_KEY, headers=auth_headers) assert resp.status_code == 200, resp.get_json() assert resp.get_json()['data']['value'] is True def test_the_enrollment_page_carries_the_eviction_checkbox(): page = (VIEWS / 'EnrollmentSettings.vue').read_text(encoding='utf-8') assert 'toggle-revokeddenies' in page, 'no checkbox for the eviction switch' assert 'onRevokedDeniesToggle' in page # It sits with the two switches it belongs beside. assert 'toggle-sharedkey' in page and 'toggle-autoenroll' in page # The operator has to be told what it breaks before clicking it. assert 'rollback' in page assert 'enrolls again' in page def test_the_agent_key_page_carries_the_query_token_control(): page = (VIEWS / 'AgentKeySettings.vue').read_text(encoding='utf-8') assert QUERY_KEY in page, 'the page does not touch the setting' assert 'toggle-querytoken' in page assert 'onQueryTokenToggle' in page # Turning it off breaks commands already handed out, so say so. assert 're-issue' in page