Files
shopdb-flask/tests/test_plugins/test_endpoint_applications_model.py
Cam Proudlock 6680c3c5de Merge the wilm delivery work into main
Ben Baker's installed-software collection, software delivery (run_script and
update_app), agentless adoption tool and command-queue hardening, with his
authorship preserved.

Reviewed before promotion. Fixed: the endpoints plugin tests were never collected
by pytest, the SBOM snapshot table's key would exhaust on MySQL, ingest did not
truncate two columns strict MySQL would reject, and update_app's version branch
pinned a digest for a file that route never serves. Agent packaging moved off
require_role('admin') onto a new endpoints.agentpackage permission.

Contract 0.27.0. Full suite 2231, frontend 314.
2026-08-30 19:41:15 -04:00

88 lines
3.4 KiB
Python

"""The installed-software snapshot table: it must actually accept inserts.
This table is rewritten wholesale on every endpoint check-in - delete the
asset's rows, insert the current inventory - so its surrogate key climbs at
(endpoints x installed apps) per report and never reuses a value. On MySQL a
32-bit key exhausts and inserts stop working permanently.
Widening it to BigInteger broke every insert on SQLite, because SQLite only
autoincrements a column typed exactly INTEGER PRIMARY KEY. Neither fault was
caught by the suite, for the same reason: nothing inserted into this table at
all. This does.
"""
from shopdb.api import db
from plugins.endpoints.models.endpoint import EndpointApplication
def test_rows_insert_and_the_key_autoincrements(app):
with app.app_context():
db.create_all()
first = EndpointApplication(assetid=1, displayname='Probe', displayversion='1.0')
second = EndpointApplication(assetid=1, displayname='Probe 2', displayversion='2.0')
db.session.add_all([first, second])
db.session.commit()
assert first.endpointapplicationid is not None
assert second.endpointapplicationid is not None
assert second.endpointapplicationid != first.endpointapplicationid
def test_the_key_is_wide_enough_for_a_table_that_is_rewritten_every_cycle(app):
"""BIGINT on MySQL, INTEGER on SQLite - both halves matter.
Dropping the variant breaks SQLite inserts; dropping the BigInteger breaks
MySQL years later, which is worse because it is silent until it is not.
"""
from sqlalchemy.dialects import mysql, sqlite
with app.app_context():
column = EndpointApplication.__table__.c.endpointapplicationid
assert 'BIGINT' in str(column.type.compile(dialect=mysql.dialect()))
assert 'INTEGER' in str(column.type.compile(dialect=sqlite.dialect()))
def test_over_long_registry_values_are_clamped_not_fatal(app):
"""One malformed Uninstall entry must not cost the whole check-in.
On MySQL in strict mode an over-long value raises Data too long and fails the
transaction, taking telemetry and events down with it - so that endpoint goes
silent until somebody uninstalls the offending program. SQLite does not
enforce declared lengths, so the suite would never have shown it.
"""
from plugins.endpoints.plugin import EndpointsPlugin
with app.app_context():
db.create_all()
warnings = []
stored = EndpointsPlugin()._replace_applications(
assetid=4242,
applications=[{
'displayname': 'A' * 400,
'displayversion': 'V' * 400,
'publisher': 'P' * 400,
}],
warnings=warnings,
)
db.session.commit()
assert stored == 1
row = EndpointApplication.query.filter_by(assetid=4242).one()
assert len(row.displayname) == 255
assert len(row.displayversion) == 128
assert len(row.publisher) == 255
def test_a_blank_value_becomes_null_rather_than_an_empty_string(app):
from plugins.endpoints.plugin import EndpointsPlugin
with app.app_context():
db.create_all()
EndpointsPlugin()._replace_applications(
assetid=4243,
applications=[{'displayname': 'Thing', 'publisher': ' '}],
warnings=[],
)
db.session.commit()
row = EndpointApplication.query.filter_by(assetid=4243).one()
assert row.publisher is None