Match the optional identifiers in every list's search box

This commit is contained in:
2026-08-17 13:18:26 -04:00
parent fbd99041b0
commit 49f68081c3
6 changed files with 109 additions and 0 deletions

View File

@@ -375,6 +375,11 @@ def list_computers():
Asset.assetnumber.ilike(pattern),
Asset.name.ilike(pattern),
Asset.serialnumber.ilike(pattern),
# The optional identifiers too (ADR-001). Global search matches
# these, and a tag read off the machine has to find it here as
# well - this box is where someone holding the label looks.
Asset.gaugelabreference.ilike(pattern),
Asset.maintenancereference.ilike(pattern),
Computer.hostname.ilike(pattern),
ComputerType.computertype.ilike(pattern)
)

View File

@@ -189,6 +189,11 @@ def list_machines():
Asset.assetnumber.ilike(pattern),
Asset.name.ilike(pattern),
Asset.serialnumber.ilike(pattern),
# The optional identifiers too (ADR-001). Global search matches
# these, and a tag read off the machine has to find it here as
# well - this box is where someone holding the label looks.
Asset.gaugelabreference.ilike(pattern),
Asset.maintenancereference.ilike(pattern),
MachineType.machinetype.ilike(pattern),
Vendor.vendor.ilike(pattern)
)

View File

@@ -179,6 +179,11 @@ def list_tools():
Asset.assetnumber.ilike(pattern),
Asset.name.ilike(pattern),
Asset.serialnumber.ilike(pattern),
# The optional identifiers too (ADR-001). Global search matches
# these, and a tag read off the machine has to find it here as
# well - this box is where someone holding the label looks.
Asset.gaugelabreference.ilike(pattern),
Asset.maintenancereference.ilike(pattern),
MeasuringToolType.name.ilike(pattern),
))
if type_id := request.args.get('typeid', type=int):

View File

@@ -223,6 +223,11 @@ def list_network_devices():
Asset.assetnumber.ilike(pattern),
Asset.name.ilike(pattern),
Asset.serialnumber.ilike(pattern),
# The optional identifiers too (ADR-001). Global search matches
# these, and a tag read off the machine has to find it here as
# well - this box is where someone holding the label looks.
Asset.gaugelabreference.ilike(pattern),
Asset.maintenancereference.ilike(pattern),
NetworkDevice.hostname.ilike(pattern),
NetworkDeviceType.networkdevicetype.ilike(pattern),
Vendor.vendor.ilike(pattern)

View File

@@ -254,6 +254,11 @@ def list_printers():
Asset.assetnumber.ilike(pattern),
Asset.name.ilike(pattern),
Asset.serialnumber.ilike(pattern),
# The optional identifiers too (ADR-001). Global search matches
# these, and a tag read off the machine has to find it here as
# well - this box is where someone holding the label looks.
Asset.gaugelabreference.ilike(pattern),
Asset.maintenancereference.ilike(pattern),
Printer.hostname.ilike(pattern),
Printer.windowsname.ilike(pattern),
PrinterType.printertype.ilike(pattern),

View File

@@ -0,0 +1,84 @@
"""Every asset list's search box matches the optional identifiers (ADR-001).
Settings offers `gaugelabreference` and `maintenancereference` on machines, PCs,
printers, network devices and measuring tools. Both were shown on the form and
the detail page and neither was matched by any list's `?search=`, so someone
holding a physical tag could read it, type it into the list they were already
looking at, and get nothing - while global search found it.
One parametrized test over all five lists on purpose. The search clause is
copy-pasted five times, once per plugin, which is exactly how all five came to
omit a field their forms collect; a per-plugin test would drift the same way.
"""
import pytest
from shopdb.core.models import Asset, AssetType
# (list endpoint, create endpoint, assettype)
LISTS = [
('/api/machines', '/api/machines', 'machine'),
('/api/computers', '/api/computers', 'computer'),
('/api/printers', '/api/printers', 'printer'),
('/api/network', '/api/network', 'network_device'),
('/api/measuringtools', '/api/measuringtools', 'measuring_tool'),
]
def _seed(client, db, auth_headers, createpath, assettype, assetnumber):
"""Create one record of this type carrying both identifiers."""
if not AssetType.query.filter_by(assettype=assettype).first():
db.session.add(AssetType(assettype=assettype, pluginname=assettype,
tablename=assettype, description=assettype))
db.session.commit()
resp = client.post(createpath, json={'assetnumber': assetnumber},
headers=auth_headers)
assert resp.status_code == 201, (createpath, resp.get_json())
asset = Asset.query.filter_by(assetnumber=assetnumber).first()
asset.gaugelabreference = f'ZZG{assetnumber}'
asset.maintenancereference = f'ZZM{assetnumber}'
db.session.commit()
return asset
def _numbers(client, auth_headers, listpath, query):
"""Asset numbers a list returns for a search term."""
resp = client.get(f'{listpath}?search={query}', headers=auth_headers)
assert resp.status_code == 200, (listpath, resp.get_json())
found = []
for row in resp.get_json()['data']:
# Lists differ in shape; the asset number is the field all of them
# carry, either directly or on a nested asset.
found.append(row.get('assetnumber')
or (row.get('asset') or {}).get('assetnumber'))
return [f for f in found if f]
@pytest.mark.parametrize('listpath,createpath,assettype', LISTS)
def test_a_list_search_finds_a_gauge_lab_reference(client, db, auth_headers,
listpath, createpath, assettype):
assetnumber = f'ZZ{assettype[:4].upper()}01'
_seed(client, db, auth_headers, createpath, assettype, assetnumber)
assert assetnumber in _numbers(client, auth_headers, listpath,
f'ZZG{assetnumber}')
@pytest.mark.parametrize('listpath,createpath,assettype', LISTS)
def test_a_list_search_finds_a_maintenance_reference(client, db, auth_headers,
listpath, createpath, assettype):
assetnumber = f'ZZ{assettype[:4].upper()}02'
_seed(client, db, auth_headers, createpath, assettype, assetnumber)
assert assetnumber in _numbers(client, auth_headers, listpath,
f'ZZM{assetnumber}')
@pytest.mark.parametrize('listpath,createpath,assettype', LISTS)
def test_a_list_search_still_excludes_a_non_match(client, db, auth_headers,
listpath, createpath, assettype):
"""Widening the clause must not turn the search box into a pass-through."""
assetnumber = f'ZZ{assettype[:4].upper()}03'
_seed(client, db, auth_headers, createpath, assettype, assetnumber)
assert _numbers(client, auth_headers, listpath, 'ZZNOTHINGMATCHESTHIS') == []