Display role vocabulary, migration commit fix, stale enforcement reports

This commit is contained in:
2026-08-13 09:30:40 -04:00
parent d327ea1a5c
commit 5dcc7e30af
16 changed files with 490 additions and 46 deletions

View File

@@ -227,3 +227,39 @@ def test_report_without_a_subtype_stores_none(db):
stored = ManifestEnforcementReport.query.filter_by(
hostname='WJPC77', iscurrent=True).one()
assert stored.subtype is None
def test_reported_subtype_is_normalised_to_the_role_vocabulary(db):
"""display-type.txt is hand-edited, so its casing drifts across the fleet.
Normalising on the way in keeps the fleet table reading one vocabulary
instead of whatever each kiosk happened to be typed as.
"""
from plugins.geenforce.service import record_enforcement_report
for index, reported in enumerate(('lobby', 'LOBBY', ' Lobby ')):
host = f'WJKIOSKCASE{index}'
record_enforcement_report({'hostname': host,
'scopename': 'gea-shopfloor-display',
'subtype': reported, 'counts': {}})
db.session.flush()
stored = ManifestEnforcementReport.query.filter_by(
hostname=host, iscurrent=True).one()
assert stored.subtype == 'Lobby', reported
def test_reported_subtype_keeps_an_unknown_value_verbatim(db):
"""An unrecognised subtype is a typo on a kiosk or a role nobody declared.
Both are worth SEEING in the fleet table, so the value is stored as sent
rather than blanked or guessed at.
"""
from plugins.geenforce.service import record_enforcement_report
record_enforcement_report({'hostname': 'WJKIOSKODD', 'scopename': 'gea-shopfloor-display',
'subtype': 'Lobbby', 'counts': {}})
db.session.flush()
stored = ManifestEnforcementReport.query.filter_by(
hostname='WJKIOSKODD', iscurrent=True).one()
assert stored.subtype == 'Lobbby'

View File

@@ -185,3 +185,103 @@ def test_report_missing_hostname_rejected(client, db, app, auth_headers):
resp = client.post('/api/geenforce/report', json={'scopename': 'x'},
headers={'X-API-Key': secret})
assert resp.status_code == 400
# -- going quiet ------------------------------------------------------------
#
# A report records how one cycle went; nothing ages it. A PC switched off after
# a clean run therefore kept 'ok' indefinitely and read as healthy - which is
# how a machine offline for over a day still showed 'ok'.
def _age_report(db, hostname, minutes):
"""Push a stored report's server-side receivedat back in time."""
from datetime import timedelta
from plugins.geenforce.models import ManifestEnforcementReport
from plugins.geenforce.service import _utcnow
(ManifestEnforcementReport.query
.filter_by(hostname=hostname, iscurrent=True)
.update({'receivedat': _utcnow() - timedelta(minutes=minutes)}))
db.session.commit()
def _post_clean_report(client, secret, hostname):
return client.post('/api/geenforce/report', json={
'hostname': hostname, 'scopename': 'gea-shopfloor-cmm',
'appliedversion': 1, 'enforcerversion': '2.6',
'counts': {'installed': 1, 'skipped': 0, 'failed': 0, 'filtered': 0},
'results': [{'name': 'Alpha', 'action': 'installed'}],
}, headers={'X-API-Key': secret})
def test_a_pc_reporting_now_is_not_stale(client, db, app, auth_headers):
_seed_and_publish(app)
secret = _token(client, auth_headers, ['geenforce.report'])
_post_clean_report(client, secret, 'WJCMM01')
row = client.get('/api/geenforce/reports',
headers=auth_headers).get_json()['data'][0]
assert row['status'] == 'ok'
assert row['isstale'] is False
assert row['staleafterminutes'] == 30
def test_a_pc_that_stopped_reporting_goes_stale(client, db, app, auth_headers):
_seed_and_publish(app)
secret = _token(client, auth_headers, ['geenforce.report'])
_post_clean_report(client, secret, 'WJCMM01')
_age_report(db, 'WJCMM01', minutes=60 * 26) # offline over a day
row = client.get('/api/geenforce/reports',
headers=auth_headers).get_json()['data'][0]
assert row['isstale'] is True
# The REPORTED status is left alone - it is still a true record of the last
# cycle, and the UI shows it in the tooltip behind the stale badge.
assert row['status'] == 'ok'
def test_stale_boundary_is_the_configured_threshold(client, db, app, auth_headers):
_seed_and_publish(app)
secret = _token(client, auth_headers, ['geenforce.report'])
_post_clean_report(client, secret, 'WJCMM01')
_age_report(db, 'WJCMM01', minutes=29)
row = client.get('/api/geenforce/reports',
headers=auth_headers).get_json()['data'][0]
assert row['isstale'] is False, 'inside the window is not stale'
_age_report(db, 'WJCMM01', minutes=31)
row = client.get('/api/geenforce/reports',
headers=auth_headers).get_json()['data'][0]
assert row['isstale'] is True, 'past the window is stale'
def test_stale_threshold_is_a_setting(client, db, app, auth_headers):
from shopdb.api import Setting
_seed_and_publish(app)
secret = _token(client, auth_headers, ['geenforce.report'])
_post_clean_report(client, secret, 'WJCMM01')
_age_report(db, 'WJCMM01', minutes=45)
Setting.set('geenforce_reportstaleminutes', '120', valuetype='integer',
category='geenforce')
db.session.commit()
row = client.get('/api/geenforce/reports',
headers=auth_headers).get_json()['data'][0]
assert row['isstale'] is False
assert row['staleafterminutes'] == 120
def test_zero_disables_the_stale_check(client, db, app, auth_headers):
from shopdb.api import Setting
_seed_and_publish(app)
secret = _token(client, auth_headers, ['geenforce.report'])
_post_clean_report(client, secret, 'WJCMM01')
_age_report(db, 'WJCMM01', minutes=60 * 24 * 7)
Setting.set('geenforce_reportstaleminutes', '0', valuetype='integer',
category='geenforce')
db.session.commit()
row = client.get('/api/geenforce/reports',
headers=auth_headers).get_json()['data'][0]
assert row['isstale'] is False
assert row['staleafterminutes'] == 0