"""scripts/backfill_app_fingerprints.py against a real (SQLite) session. The unique index on (assetid, appfingerprint) turns two identical inventory rows on one asset into an IntegrityError mid-backfill. The script must keep the lowest id, delete the rest and report how many, and a failing batch must be rolled back rather than left half-applied. """ import importlib.util from pathlib import Path import pytest SCRIPT = Path(__file__).resolve().parent.parent / 'scripts' / 'backfill_app_fingerprints.py' @pytest.fixture def backfill(app): spec = importlib.util.spec_from_file_location('backfillfingerprints', SCRIPT) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def _asset(db, number): 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=number, assettypeid=atype.assettypeid, isactive=True) db.session.add(asset) db.session.commit() return asset.assetid def _row(db, assetid, name, version='1.0', publisher='Vendor', fingerprint=None): from plugins.endpoints.models import EndpointApplication row = EndpointApplication(assetid=assetid, displayname=name, displayversion=version, publisher=publisher, **{'appfingerprint': fingerprint}) db.session.add(row) db.session.commit() return row.endpointapplicationid def test_self_check_uses_fixed_expected_values(backfill, monkeypatch): assert backfill.verify_fingerprint_algorithm() is True monkeypatch.setattr(backfill, 'compute_fingerprint', lambda name, version, publisher: '0' * 64) with pytest.raises(ValueError): backfill.verify_fingerprint_algorithm() def test_duplicates_keep_the_lowest_id(backfill, db): from plugins.endpoints.models import EndpointApplication first = _asset(db, 'FP01') second = _asset(db, 'FP02') keep = _row(db, first, 'Chrome') _row(db, first, 'Chrome') _row(db, first, 'Chrome') other = _row(db, first, 'Office') elsewhere = _row(db, second, 'Chrome') summary = backfill.backfill_fingerprints(batch_size=2) assert summary['duplicatesremoved'] == 2 assert summary['failed'] == 0 rows = EndpointApplication.query.order_by( EndpointApplication.endpointapplicationid).all() assert [r.endpointapplicationid for r in rows] == [keep, other, elsewhere] assert all(r.appfingerprint for r in rows) def test_an_existing_higher_id_holder_gives_way(backfill, db): from plugins.endpoints.models import EndpointApplication assetid = _asset(db, 'FP03') fingerprint = backfill.compute_fingerprint('Chrome', '1.0', 'Vendor') older = _row(db, assetid, 'Chrome') _row(db, assetid, 'Chrome', fingerprint=fingerprint) summary = backfill.backfill_fingerprints() assert summary['duplicatesremoved'] == 1 rows = EndpointApplication.query.all() assert [(r.endpointapplicationid, r.appfingerprint) for r in rows] == \ [(older, fingerprint)] def test_a_failing_batch_is_rolled_back(backfill, db, monkeypatch): from plugins.endpoints.models import EndpointApplication assetid = _asset(db, 'FP04') _row(db, assetid, 'Chrome') _row(db, assetid, 'Office') def broken(apps): for app in apps: app.appfingerprint = 'x' * 64 raise RuntimeError('simulated failure') monkeypatch.setattr(backfill, '_fingerprint_batch', broken) summary = backfill.backfill_fingerprints() assert summary['failed'] == 2 assert summary['processed'] == 0 db.session.expire_all() assert EndpointApplication.query.filter( EndpointApplication.appfingerprint.isnot(None)).count() == 0