geenforce: order backup revisions in Python, not in MySQL

The reports table 500'd on every load: the backup lookup ordered with
ORDER BY lastseenat DESC NULLS LAST, which SQLite accepts and MySQL
rejects outright. Every test passed and the real database refused the
query - the tests run on SQLite, so the dialect difference was invisible.

Sorting in Python removes the dependency for nothing: the rows are one per
host per kind. The regression test pins which revision wins, including
that one never confirmed does not, and says why the sort lives here so it
does not get helpfully moved back into SQL.
This commit is contained in:
cproudlock
2026-08-12 16:49:58 -04:00
parent 52eb10f5ca
commit f1f573862d
2 changed files with 55 additions and 2 deletions

View File

@@ -151,3 +151,45 @@ def test_hosts_below_libversion_reports_the_floor(db):
'gea-shopfloor-cmm', 'runtime', '2.6')
assert hosts == ['A', 'B']
assert floor == '2.3'
# -- backup state on the reports table ---------------------------------------
def test_backup_state_prefers_the_newest_confirmed_revision(db):
"""A host with several revisions shows the most recently CONFIRMED one, and
a revision that has never been confirmed (lastseenat NULL) never wins.
Ordering happens in Python on purpose. Doing it in SQL needs
ORDER BY ... NULLS LAST, which SQLite accepts and MySQL rejects outright -
so it passed every test here and returned a 500 against the real database.
"""
from datetime import timedelta
from plugins.backups.models import BackupRevision
from plugins.geenforce.api.routes import _attach_backup_state
from shopdb.api import Asset, AssetType
assettype = AssetType.query.first()
if not assettype:
assettype = AssetType(assettype='computer')
db.session.add(assettype)
db.session.flush()
asset = Asset(assetnumber='PC-1', name='PC-1',
assettypeid=assettype.assettypeid)
db.session.add(asset)
db.session.flush()
now = datetime.now(timezone.utc).replace(tzinfo=None)
for kind, lastseen in (('ntlars', now - timedelta(days=9)),
('udc', now),
('files', None)):
db.session.add(BackupRevision(
assetid=asset.assetid, backupkind=kind, contenthash=kind * 8,
sourcehostname='WJPC01', lastseenat=lastseen, createdat=now))
db.session.flush()
facts = {'wjpc01': {'backupkind': None, 'backuplastseen': None}}
_attach_backup_state(facts, {'WJPC01'})
assert facts['wjpc01']['backupkind'] == 'udc'
assert facts['wjpc01']['backuplastseen'] is not None