Dashboard cards: rebuild deployable dists

This commit is contained in:
2026-08-11 14:31:24 -04:00
parent 0ed8be75d9
commit 3b37603691
14 changed files with 507 additions and 62 deletions

View File

@@ -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

View File

@@ -118,3 +118,81 @@ def test_the_widget_uses_the_new_contract(app):
assert widget['render'] == 'exceptions'
assert widget['permission'] == 'computers.view'
assert widget['empty'] == 'hide'
# =============================================================================
# Machine numbers claimed by two PCs - a fault, unless the devices are modelled
# =============================================================================
SHAREDURL = '/api/computers/dashboard/sharedmachines'
def _machine(db, assetnumber):
mt = AssetType.query.filter_by(assettype='machine').first()
if not mt:
mt = AssetType(assettype='machine')
db.session.add(mt)
db.session.flush()
asset = Asset(assetnumber=assetnumber, assettypeid=mt.assettypeid,
statusid=1)
db.session.add(asset)
db.session.commit()
return asset
def _controls(db, pchostname, machineasset, label='collector:machine'):
from shopdb.core.models import AssetRelationship, RelationshipType
controls = RelationshipType.query.filter_by(relationshiptype='controls').first()
if not controls:
controls = RelationshipType(relationshiptype='controls', isdirectional=True)
db.session.add(controls)
db.session.flush()
comp = _pc(db, pchostname)
db.session.add(AssetRelationship(
sourceassetid=comp.assetid, targetassetid=machineasset.assetid,
relationshiptypeid=controls.relationshiptypeid, label=label,
isactive=True))
db.session.commit()
return comp
def test_one_pc_per_machine_is_not_a_finding(client, db, auth_headers):
machine = _machine(db, '3015')
_controls(db, 'ONLYPC', machine)
assert client.get(SHAREDURL, headers=auth_headers).get_json()['data'] == []
def test_two_pcs_on_one_number_is_reported(client, db, auth_headers):
machine = _machine(db, '2026')
_controls(db, 'PCONE', machine)
_controls(db, 'PCTWO', machine)
[row] = client.get(SHAREDURL, headers=auth_headers).get_json()['data']
assert row['machinenumber'] == '2026'
assert row['pccount'] == 2
assert 'PCONE' in row['pcs'] and 'PCTWO' in row['pcs']
def test_a_modelled_shared_number_is_not_a_finding(client, db, auth_headers):
"""Part markers legitimately share an operation number. Modelled correctly,
each device is its own asset filed partof the operation - so the operation
has child assets, and that is what tells a real fault from correct data."""
from shopdb.core.models import AssetRelationship, RelationshipType
operation = _machine(db, '0615')
_controls(db, 'MARKERPCA', operation)
_controls(db, 'MARKERPCB', operation)
partof = RelationshipType(relationshiptype='partof', isdirectional=True)
db.session.add(partof)
db.session.flush()
marker = _machine(db, 'MARKERPCA-PARTMARKER')
db.session.add(AssetRelationship(
sourceassetid=marker.assetid, targetassetid=operation.assetid,
relationshiptypeid=partof.relationshiptypeid,
label='collector:partmarker', isactive=True))
db.session.commit()
assert client.get(SHAREDURL, headers=auth_headers).get_json()['data'] == []
def test_shared_machines_is_permission_gated(client, db, member_headers):
assert client.get(SHAREDURL, headers=member_headers).status_code == 403