dashboard: convert the last dead widgets, and delete the one that had nothing
Three plugins still declared widgets naming Vue components nobody wrote. Converting them honestly meant three different answers, not one. notifications gets a real card: the active notifications themselves, not a count. "4 active" tells an admin nothing; knowing WHICH message the shop is looking at is the point, and it is how a stale one gets noticed and taken down. machines gets machines out of service - anything not In Use, excluding Inventory, because a spare on a shelf is stock rather than a problem. Someone is supposed to be chasing each of those and today they are visible only to whoever thinks to filter the list by status. network gets NOTHING, and its declaration is deleted rather than converted. Network devices carry no live status - no polling, no reachability check, nothing that can be wrong - so the only possible card is a count of how many exist, which is precisely the always-true number this dashboard exists to get away from. A comment records that, so the next person does not re-add it. If reachability is ever collected, that is the card. Also adds a contract test over every declared card: no component names, a valid renderer and severity, and - the one that matters - the endpoint must be a REAL route. A declaration pointing at a route nobody wrote is exactly how the old widgets rotted unnoticed for months, and now it fails the build instead.
This commit is contained in:
@@ -1,38 +1,65 @@
|
||||
"""Tests for the dashboard-widgets hook consumer (/api/dashboard/widgets).
|
||||
"""Every declared dashboard card obeys the contract.
|
||||
|
||||
Pins the wiring added for the BasePlugin.get_dashboard_widgets hook: the
|
||||
endpoint aggregates enabled plugins' widgets and skips disabled ones.
|
||||
|
||||
Plugin enabled-state is monkeypatched (not persisted) so these tests do not
|
||||
mutate the shared instance/plugins.json registry file.
|
||||
Five plugins once declared widgets naming Vue components that were never
|
||||
written, and nothing rendered - the frontend did not call the endpoint either,
|
||||
so the mismatch went unnoticed for months. These tests make that class of
|
||||
mistake impossible to repeat quietly: a declaration is checked against the
|
||||
contract, and its endpoint must actually exist.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
def _widget_plugins(client, headers):
|
||||
response = client.get('/api/dashboard/widgets', headers=headers)
|
||||
assert response.status_code == 200, response.get_json()
|
||||
widgets = response.get_json()['data']
|
||||
assert isinstance(widgets, list)
|
||||
return widgets, {w.get('plugin') for w in widgets}
|
||||
RENDERERS = ('exceptions', 'metric', 'list')
|
||||
|
||||
|
||||
def test_widgets_endpoint_aggregates_enabled_plugins(app, client, auth_headers,
|
||||
monkeypatch):
|
||||
"""An enabled plugin that implements the hook contributes a widget."""
|
||||
def _declared(app):
|
||||
pm = app.extensions['plugin_manager']
|
||||
monkeypatch.setattr(pm.registry, 'is_enabled', lambda name: True)
|
||||
|
||||
widgets, plugins = _widget_plugins(client, auth_headers)
|
||||
assert 'computers' in plugins # computers implements get_dashboard_widgets
|
||||
positions = [w.get('position', 99) for w in widgets]
|
||||
assert positions == sorted(positions)
|
||||
cards = []
|
||||
for name, plugin in pm.get_all_plugins().items():
|
||||
for card in plugin.get_dashboard_widgets() or []:
|
||||
cards.append((name, card))
|
||||
return cards
|
||||
|
||||
|
||||
def test_widgets_endpoint_skips_disabled_plugin(app, client, auth_headers,
|
||||
monkeypatch):
|
||||
"""A disabled plugin's widgets drop out of the aggregate."""
|
||||
pm = app.extensions['plugin_manager']
|
||||
monkeypatch.setattr(pm.registry, 'is_enabled', lambda name: name != 'computers')
|
||||
def test_there_are_cards_to_check(app):
|
||||
with app.app_context():
|
||||
assert _declared(app), 'no plugin declares a dashboard card'
|
||||
|
||||
_, plugins = _widget_plugins(client, auth_headers)
|
||||
assert 'computers' not in plugins
|
||||
|
||||
def test_no_card_names_a_frontend_component(app):
|
||||
"""The old contract. It cannot survive a lean build, where a plugin's
|
||||
component may never be staged into the bundle."""
|
||||
with app.app_context():
|
||||
named = [(p, c.get('id') or c.get('name')) for p, c in _declared(app)
|
||||
if 'component' in c]
|
||||
assert named == []
|
||||
|
||||
|
||||
def test_every_card_declares_the_fields_core_renders_from(app):
|
||||
with app.app_context():
|
||||
for pluginname, card in _declared(app):
|
||||
assert card.get('id'), pluginname
|
||||
assert card.get('title'), card
|
||||
assert card.get('endpoint'), card
|
||||
assert card.get('render') in RENDERERS, card
|
||||
assert card.get('severity') in ('critical', 'warning', 'info'), card
|
||||
|
||||
|
||||
def test_every_card_endpoint_is_a_real_route(app):
|
||||
"""A declaration pointing at a route nobody wrote is exactly how the old
|
||||
widgets rotted."""
|
||||
with app.app_context():
|
||||
rules = {str(rule) for rule in app.url_map.iter_rules()}
|
||||
for pluginname, card in _declared(app):
|
||||
endpoint = card['endpoint'].split('?')[0]
|
||||
assert endpoint in rules, '{}: {} is not a route'.format(
|
||||
pluginname, endpoint)
|
||||
|
||||
|
||||
def test_every_card_links_somewhere(app):
|
||||
"""A row that names a problem without linking to it is a worse report."""
|
||||
with app.app_context():
|
||||
for pluginname, card in _declared(app):
|
||||
if card['render'] == 'metric':
|
||||
continue
|
||||
assert (card.get('map') or {}).get('link'), card
|
||||
|
||||
Reference in New Issue
Block a user