Match the optional identifiers in every list's search box
This commit is contained in:
@@ -375,6 +375,11 @@ def list_computers():
|
|||||||
Asset.assetnumber.ilike(pattern),
|
Asset.assetnumber.ilike(pattern),
|
||||||
Asset.name.ilike(pattern),
|
Asset.name.ilike(pattern),
|
||||||
Asset.serialnumber.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),
|
Computer.hostname.ilike(pattern),
|
||||||
ComputerType.computertype.ilike(pattern)
|
ComputerType.computertype.ilike(pattern)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -189,6 +189,11 @@ def list_machines():
|
|||||||
Asset.assetnumber.ilike(pattern),
|
Asset.assetnumber.ilike(pattern),
|
||||||
Asset.name.ilike(pattern),
|
Asset.name.ilike(pattern),
|
||||||
Asset.serialnumber.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),
|
MachineType.machinetype.ilike(pattern),
|
||||||
Vendor.vendor.ilike(pattern)
|
Vendor.vendor.ilike(pattern)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -179,6 +179,11 @@ def list_tools():
|
|||||||
Asset.assetnumber.ilike(pattern),
|
Asset.assetnumber.ilike(pattern),
|
||||||
Asset.name.ilike(pattern),
|
Asset.name.ilike(pattern),
|
||||||
Asset.serialnumber.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),
|
MeasuringToolType.name.ilike(pattern),
|
||||||
))
|
))
|
||||||
if type_id := request.args.get('typeid', type=int):
|
if type_id := request.args.get('typeid', type=int):
|
||||||
|
|||||||
@@ -223,6 +223,11 @@ def list_network_devices():
|
|||||||
Asset.assetnumber.ilike(pattern),
|
Asset.assetnumber.ilike(pattern),
|
||||||
Asset.name.ilike(pattern),
|
Asset.name.ilike(pattern),
|
||||||
Asset.serialnumber.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),
|
NetworkDevice.hostname.ilike(pattern),
|
||||||
NetworkDeviceType.networkdevicetype.ilike(pattern),
|
NetworkDeviceType.networkdevicetype.ilike(pattern),
|
||||||
Vendor.vendor.ilike(pattern)
|
Vendor.vendor.ilike(pattern)
|
||||||
|
|||||||
@@ -254,6 +254,11 @@ def list_printers():
|
|||||||
Asset.assetnumber.ilike(pattern),
|
Asset.assetnumber.ilike(pattern),
|
||||||
Asset.name.ilike(pattern),
|
Asset.name.ilike(pattern),
|
||||||
Asset.serialnumber.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.hostname.ilike(pattern),
|
||||||
Printer.windowsname.ilike(pattern),
|
Printer.windowsname.ilike(pattern),
|
||||||
PrinterType.printertype.ilike(pattern),
|
PrinterType.printertype.ilike(pattern),
|
||||||
|
|||||||
84
tests/test_core/test_list_search_identifiers.py
Normal file
84
tests/test_core/test_list_search_identifiers.py
Normal 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') == []
|
||||||
Reference in New Issue
Block a user