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.
88 lines
3.4 KiB
Python
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
|