Dashboard cards, backup last-seen
This commit is contained in:
123
tests/test_plugins/test_geenforce_dashboard.py
Normal file
123
tests/test_plugins/test_geenforce_dashboard.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""GE-Enforce dashboard card: what is broken on the floor right now.
|
||||
|
||||
The reports table has always held enforcement failures - the failing entry, its
|
||||
exit code, the engine's message - and the only way to see any of it was to open
|
||||
one PC's report modal, one PC at a time. A bay returned 500 to every collector
|
||||
report for a day and a half before anyone looked. This card is the fix, so
|
||||
these tests pin the behaviour that makes it useful rather than decorative.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from plugins.geenforce.models import (ManifestEnforcementReport,
|
||||
ManifestEnforcementResult)
|
||||
|
||||
URL = '/api/geenforce/dashboard/failures'
|
||||
|
||||
|
||||
def _report(db, hostname, iscurrent=True, results=(), scopename='gea-shopfloor-cmm',
|
||||
receivedat=None):
|
||||
report = ManifestEnforcementReport(
|
||||
hostname=hostname, scopename=scopename, phase='runtime',
|
||||
status='failed', iscurrent=iscurrent,
|
||||
receivedat=receivedat or datetime(2026, 8, 11, 12, 0, 0))
|
||||
db.session.add(report)
|
||||
db.session.flush()
|
||||
for entryname, action, exitcode, message in results:
|
||||
db.session.add(ManifestEnforcementResult(
|
||||
reportid=report.reportid, entryname=entryname, action=action,
|
||||
exitcode=exitcode, message=message))
|
||||
db.session.commit()
|
||||
return report
|
||||
|
||||
|
||||
def test_a_failed_entry_is_listed_with_what_a_person_needs(client, db, auth_headers):
|
||||
"""Per ENTRY, not per report. "Three PCs failed" is a number; "Install
|
||||
OpenText failed with exit 1603 on WJSF1234" is something to act on."""
|
||||
_report(db, 'WJSF1234', results=[
|
||||
('Install OpenText', 'failed', 1603, 'Fatal error during installation')])
|
||||
|
||||
rows = client.get(URL, headers=auth_headers).get_json()['data']
|
||||
assert len(rows) == 1
|
||||
assert rows[0]['hostname'] == 'WJSF1234'
|
||||
assert rows[0]['entryname'] == 'Install OpenText'
|
||||
assert rows[0]['exitcode'] == 1603
|
||||
assert 'Fatal error' in rows[0]['message']
|
||||
|
||||
|
||||
def test_only_failures_appear(client, db, auth_headers):
|
||||
"""A cycle installs, skips and filters far more than it fails. Listing
|
||||
anything but failures is how a card becomes wallpaper."""
|
||||
_report(db, 'WJSF1234', results=[
|
||||
('Install OpenText', 'failed', 1603, 'boom'),
|
||||
('Install Acrobat', 'installed', 0, ''),
|
||||
('Set FMS host', 'skipped', 0, ''),
|
||||
('CMM settings', 'filtered', 0, 'PCTypes filter')])
|
||||
|
||||
rows = client.get(URL, headers=auth_headers).get_json()['data']
|
||||
assert [r['entryname'] for r in rows] == ['Install OpenText']
|
||||
|
||||
|
||||
def test_a_fixed_failure_disappears_on_its_own(client, db, auth_headers):
|
||||
"""Superseded reports are not current. A failure that has since been fixed
|
||||
must clear itself rather than needing someone to dismiss it - otherwise the
|
||||
card accumulates history and stops meaning "right now"."""
|
||||
_report(db, 'WJSF1234', iscurrent=False, results=[
|
||||
('Install OpenText', 'failed', 1603, 'boom')])
|
||||
_report(db, 'WJSF1234', iscurrent=True, results=[
|
||||
('Install OpenText', 'installed', 0, '')])
|
||||
|
||||
assert client.get(URL, headers=auth_headers).get_json()['data'] == []
|
||||
|
||||
|
||||
def test_the_row_links_to_the_pc_when_shopdb_knows_it(client, db, auth_headers):
|
||||
"""A card that names a PC without linking to it is a worse report."""
|
||||
from plugins.computers.models import Computer
|
||||
from shopdb.core.models import Asset, AssetType
|
||||
|
||||
atype = AssetType.query.filter_by(assettype='computer').first()
|
||||
if not atype:
|
||||
atype = AssetType(assettype='computer')
|
||||
db.session.add(atype)
|
||||
db.session.flush()
|
||||
asset = Asset(assetnumber='WJSF1234',
|
||||
assettypeid=atype.assettypeid, statusid=1)
|
||||
db.session.add(asset)
|
||||
db.session.flush()
|
||||
comp = Computer(assetid=asset.assetid, hostname='WJSF1234')
|
||||
db.session.add(comp)
|
||||
db.session.commit()
|
||||
|
||||
_report(db, 'WJSF1234', results=[('Install OpenText', 'failed', 1603, 'x')])
|
||||
rows = client.get(URL, headers=auth_headers).get_json()['data']
|
||||
assert rows[0]['computerid'] == comp.computerid
|
||||
|
||||
|
||||
def test_a_pc_shopdb_does_not_know_still_reports_its_failure(client, db,
|
||||
auth_headers):
|
||||
"""The failure is real even when the inventory is behind. Dropping the row
|
||||
would hide exactly the bay most likely to be misconfigured."""
|
||||
_report(db, 'GHOSTPC', results=[('Install OpenText', 'failed', 1603, 'x')])
|
||||
rows = client.get(URL, headers=auth_headers).get_json()['data']
|
||||
assert len(rows) == 1
|
||||
assert rows[0]['computerid'] is None
|
||||
|
||||
|
||||
def test_the_card_is_permission_gated(client, db, member_headers):
|
||||
"""A dashboard card must not become a way around RBAC."""
|
||||
assert client.get(URL, headers=member_headers).status_code == 403
|
||||
|
||||
|
||||
def test_the_widget_declares_data_and_a_renderer_not_a_component(app):
|
||||
"""The old contract named a Vue component per widget, which cannot survive
|
||||
a lean build - which is why five plugins declared widgets pointing at
|
||||
components nobody wrote. A plugin declares data and shape; core renders."""
|
||||
from plugins.geenforce.plugin import GeEnforcePlugin
|
||||
|
||||
widget = GeEnforcePlugin().get_dashboard_widgets()[0]
|
||||
assert 'component' not in widget
|
||||
assert widget['render'] == 'exceptions'
|
||||
assert widget['permission'] == 'geenforce.manage'
|
||||
# Empty cards must shrink, or the board becomes wallpaper.
|
||||
assert widget['empty'] == 'hide'
|
||||
assert widget['map']['link'] == '/pcs/{computerid}'
|
||||
Reference in New Issue
Block a user