Hard delete applications, nest installer packages, detect PE versions
Soft delete left orphaned executables on disk and let a deleted name collide with a fresh import, so delete is now a hard delete that removes the associated files. The destructive paths (delete, revert-to-record) require a typed OK because neither can be undone. Installers keep their filename at import and nest per application/version, which stops repeat imports doubling up. Version now comes from the same PE detection the endpoints agent uses; when no version can be read the caller is asked for one rather than silently storing a blank. Applications also surface Version and a monitorable toggle, with launch path and process name required only while monitorable is ticked. Fleet shows the PC alias, matching PC Details.
This commit is contained in:
110
tests/test_plugins/test_endpoints_fleet_alias.py
Normal file
110
tests/test_plugins/test_endpoints_fleet_alias.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""The Fleet list must carry the asset alias.
|
||||
|
||||
The alias is what people actually call a PC ("Bay 3 Controller"); the hostname
|
||||
is what DNS calls it. The PCs list has shown both for a long time, but the
|
||||
Fleet list query never selected Asset.name, so the column could not be built
|
||||
even though the data existed - it was missing rather than broken.
|
||||
|
||||
These pin the field on the payload and its inclusion in search, so the column
|
||||
cannot quietly lose its source again.
|
||||
"""
|
||||
|
||||
LIST_URL = '/api/endpoints/'
|
||||
|
||||
|
||||
def _endpoint(db, hostname='fleetbox', alias='Bay 3 Controller'):
|
||||
"""An Asset + Computer + EndpointState triple: one row on the fleet list.
|
||||
|
||||
The list joins through Computer and filters on Asset.isactive, so all three
|
||||
are required for a row to appear at all.
|
||||
"""
|
||||
from shopdb.api import Asset, AssetType
|
||||
from plugins.computers.models import Computer
|
||||
from plugins.endpoints.models import EndpointState
|
||||
|
||||
assettype = AssetType.query.filter_by(assettype='computer').first()
|
||||
if assettype is None:
|
||||
assettype = AssetType(assettype='computer', pluginname='computers',
|
||||
tablename='computers')
|
||||
db.session.add(assettype)
|
||||
db.session.flush()
|
||||
|
||||
asset = Asset(assetnumber=hostname, name=alias,
|
||||
assettypeid=assettype.assettypeid, isactive=True)
|
||||
db.session.add(asset)
|
||||
db.session.flush()
|
||||
db.session.add(Computer(assetid=asset.assetid, hostname=hostname))
|
||||
db.session.add(EndpointState(assetid=asset.assetid, hostname=hostname))
|
||||
db.session.commit()
|
||||
return asset.assetid
|
||||
|
||||
|
||||
def _rows(response):
|
||||
return response.get_json()['data']
|
||||
|
||||
|
||||
def test_the_fleet_list_carries_the_alias(client, db, auth_headers):
|
||||
_endpoint(db)
|
||||
|
||||
response = client.get(LIST_URL, headers=auth_headers)
|
||||
|
||||
assert response.status_code == 200, response.get_json()
|
||||
rows = _rows(response)
|
||||
assert len(rows) == 1
|
||||
# Exposed as 'alias', not 'name': the row already has a hostname, and
|
||||
# 'name' beside 'hostname' reads as a duplicate of the same thing.
|
||||
assert rows[0]['alias'] == 'Bay 3 Controller'
|
||||
|
||||
|
||||
def test_an_endpoint_without_an_alias_still_lists(client, db, auth_headers):
|
||||
# The column renders '-' for these. A null must not drop the row or 500.
|
||||
_endpoint(db, hostname='namelessbox', alias=None)
|
||||
|
||||
response = client.get(LIST_URL, headers=auth_headers)
|
||||
|
||||
assert response.status_code == 200, response.get_json()
|
||||
rows = _rows(response)
|
||||
assert len(rows) == 1
|
||||
assert rows[0]['alias'] is None
|
||||
assert rows[0]['hostname'] == 'namelessbox'
|
||||
|
||||
|
||||
def test_the_alias_is_searchable(client, db, auth_headers):
|
||||
# Someone looking for a PC types what it is called, not its hostname.
|
||||
_endpoint(db, hostname='pc-a', alias='Bay 3 Controller')
|
||||
_endpoint(db, hostname='pc-b', alias='Paint Booth')
|
||||
|
||||
response = client.get(LIST_URL + '?search=Bay 3', headers=auth_headers)
|
||||
|
||||
assert response.status_code == 200, response.get_json()
|
||||
rows = _rows(response)
|
||||
assert [r['hostname'] for r in rows] == ['pc-a']
|
||||
|
||||
|
||||
def test_searching_by_hostname_still_works(client, db, auth_headers):
|
||||
# The alias clause was added to an existing OR; prove it did not replace
|
||||
# what was already there.
|
||||
_endpoint(db, hostname='pc-a', alias='Bay 3 Controller')
|
||||
_endpoint(db, hostname='pc-b', alias='Paint Booth')
|
||||
|
||||
response = client.get(LIST_URL + '?search=pc-b', headers=auth_headers)
|
||||
|
||||
assert response.status_code == 200, response.get_json()
|
||||
rows = _rows(response)
|
||||
assert [r['hostname'] for r in rows] == ['pc-b']
|
||||
|
||||
|
||||
def test_the_row_keeps_its_other_columns(client, db, auth_headers):
|
||||
# The alias was added mid-tuple in the query, so every following column
|
||||
# shifted position. If that unpacking is wrong the values silently swap
|
||||
# rather than raising.
|
||||
_endpoint(db, hostname='fleetbox', alias='Bay 3 Controller')
|
||||
|
||||
response = client.get(LIST_URL, headers=auth_headers)
|
||||
|
||||
row = _rows(response)[0]
|
||||
assert row['hostname'] == 'fleetbox'
|
||||
assert row['assetnumber'] == 'fleetbox'
|
||||
assert row['alias'] == 'Bay 3 Controller'
|
||||
assert row['status'] in ('never', 'quiet', 'reporting')
|
||||
assert 'computerid' in row and row['computerid'] is not None
|
||||
Reference in New Issue
Block a user