Files
shopdb-flask/tests/test_plugins/test_printer_driver_resolution.py
cproudlock 2fb924f18e Let a printer driver carry the name Windows actually matches on
The driver form and the API both dropped `drivername` and `vendorid`. POST
returned 201 with both null, PUT accepted them and stored nothing, and no
screen offered either field - so there was no interface in the product that
could set them.

That is not a cosmetic gap. `drivername` is the exact string the INF declares,
and Add-PrinterDriver matches on it; `name` is ours to choose and means nothing
to Windows. With it null the shipped converger has nothing to match and
installs nothing, and the drift check that compares an observed driver name
against the expected one could never fire, because the expected value was never
stored. `vendorid` is how a universal driver (HP UPD, Xerox GPD) attaches to a
whole make instead of needing a near-duplicate row per model.

Both are now read on create and update, and the driver form gained the two
fields with hints saying what they are for - the INF name box says plainly that
leaving it blank means the converger cannot install that driver. A whitespace
value clears rather than stores: an empty box means "not known", and a blank
string would look set and match nothing.

Three tests. Verified by planting: drop the two fields from the create path
again and the round-trip test fails.

Scope: product
2026-09-03 15:16:33 -04:00

178 lines
8.2 KiB
Python

"""Which driver a printer installs with.
One row per model was unworkable: HP and Xerox universal drivers cover 41 of the
reference site's 44 printers, so binding a driver to a single model meant 21
near-duplicate rows pointing at one package - a table nobody keeps true, and the
reason 42 of 44 printers could not resolve a driver at all.
The order is most-specific-first, and each step exists for a printer that really
is on this floor: a plotter and a card printer need their own driver, everything
else takes its make's universal one.
"""
import pytest
from shopdb.core.models import Vendor, Model
from plugins.printers.models import Printer, PrinterDriver
from plugins.printers.api.asset_routes import _printer_driver
def _universal():
return (PrinterDriver.query
.filter(PrinterDriver.modelnumberid.is_(None),
PrinterDriver.isactive == True)
.order_by(PrinterDriver.name).all())
@pytest.fixture
def fleet(db):
hp = Vendor(vendor='HP')
xerox = Vendor(vendor='Xerox')
db.session.add_all([hp, xerox])
db.session.flush()
laserjet = Model(modelnumber='LaserJet M602', vendorid=hp.vendorid)
designjet = Model(modelnumber='DesignJet T1700', vendorid=hp.vendorid)
db.session.add_all([laserjet, designjet])
db.session.flush()
upd = PrinterDriver(name='HP Universal Print Driver', drivername='HP Universal Printing PS',
location=r'\\server\share\hp_upd', vendorid=hp.vendorid, isactive=True)
plotter = PrinterDriver(name='HP DesignJet T1700', drivername='HP DesignJet T1700dr V4',
location=r'\\server\share\designjet', vendorid=hp.vendorid,
modelnumberid=designjet.modelnumberid, isactive=True)
gpd = PrinterDriver(name='Xerox Global Print Driver PCL6',
drivername='Xerox Global Print Driver PCL6',
location=r'\\server\share\xerox', vendorid=xerox.vendorid, isactive=True)
db.session.add_all([upd, plotter, gpd])
db.session.commit()
return {'hp': hp, 'xerox': xerox, 'laserjet': laserjet, 'designjet': designjet,
'upd': upd, 'plotter': plotter, 'gpd': gpd}
def test_a_printer_takes_its_makes_universal_driver(fleet):
"""The case that covers most of a floor: no per-model row exists, and none
should have to."""
printer = Printer(vendorid=fleet['hp'].vendorid, modelnumberid=fleet['laserjet'].modelnumberid)
assert _printer_driver(printer, _universal()).name == 'HP Universal Print Driver'
def test_a_model_specific_driver_beats_the_universal_one(fleet):
"""A plotter is not a LaserJet. If the universal driver won here, the
DesignJet would be installed with a driver that cannot drive it."""
printer = Printer(vendorid=fleet['hp'].vendorid, modelnumberid=fleet['designjet'].modelnumberid)
assert _printer_driver(printer, _universal()).name == 'HP DesignJet T1700'
def test_vendors_do_not_bleed_into_each_other(fleet):
"""A Xerox must never resolve to the HP driver, whatever the ordering."""
printer = Printer(vendorid=fleet['xerox'].vendorid, modelnumberid=None)
assert _printer_driver(printer, _universal()).name == 'Xerox Global Print Driver PCL6'
def test_a_printer_with_no_vendor_resolves_to_nothing(fleet):
"""Better nothing than a guess: installing the wrong driver is worse than
reporting that a printer has none."""
printer = Printer(vendorid=None, modelnumberid=None)
assert _printer_driver(printer, _universal()) is None
def test_a_make_with_no_driver_row_resolves_to_nothing(db, fleet):
"""A vendor nobody has added a driver for is unresolved, not misresolved."""
zebra = Vendor(vendor='Zebra')
db.session.add(zebra)
db.session.commit()
printer = Printer(vendorid=zebra.vendorid, modelnumberid=None)
assert _printer_driver(printer, _universal()) is None
def test_the_pre_vendorid_naming_convention_still_resolves(db, fleet):
"""A site that populated printerdrivers before the column existed matched on
the vendor word in the driver's NAME. That must keep working, or an upgrade
silently takes every driver away."""
epson = Vendor(vendor='Epson')
db.session.add(epson)
db.session.flush()
db.session.add(PrinterDriver(name='Epson ColorWorks universal', drivername='EPSON TM-C3500',
location=r'\\server\share\epson', isactive=True))
db.session.commit()
# vendor attached, not just vendorid: the legacy path reads the vendor's
# NAME, which only exists through the relationship.
printer = Printer(vendorid=epson.vendorid, modelnumberid=None)
printer.vendor = epson
assert _printer_driver(printer, _universal()).name == 'Epson ColorWorks universal'
def test_a_row_that_names_a_vendor_is_not_matched_by_its_text(db, fleet):
"""A driver whose vendorid is set and does NOT match must not then be picked
up by the name convention: a mis-set vendor would resolve to the wrong
package, which is worse than resolving to none."""
brother = Vendor(vendor='Brother')
db.session.add(brother)
db.session.flush()
# Named for Brother, but bound to HP by id - the id is the truth.
db.session.add(PrinterDriver(name='Brother universal', drivername='Brother Universal',
location=r'\\server\share\brother',
vendorid=fleet['hp'].vendorid, isactive=True))
db.session.commit()
printer = Printer(vendorid=brother.vendorid, modelnumberid=None)
printer.vendor = brother
assert _printer_driver(printer, _universal()) is None
# --- the two fields the converger actually needs -----------------------------
#
# drivername is the exact string the INF declares, and it is what
# Add-PrinterDriver matches on - `name` is ours to choose and means nothing to
# Windows. vendorid attaches a universal driver (HP UPD, Xerox GPD) to a whole
# make. Neither was read from the request body, so both came back null on a
# 201, and there was no interface that could set them: the shipped converger
# had nothing to match and installed nothing, and driver-name drift could never
# be detected because the expected value was never stored.
def test_a_driver_keeps_the_inf_name_and_vendor_it_was_given(client, db,
auth_headers):
from plugins.printers.models import PrinterDriver
created = client.post('/api/printers/drivers', headers=auth_headers, json={
'name': 'HP universal',
'location': r'\\server\share\hpupd',
'drivername': 'HP Universal Printing PCL 6',
'vendorid': 1,
})
assert created.status_code == 201, created.get_json()
body = created.get_json()['data']
assert body['drivername'] == 'HP Universal Printing PCL 6'
assert body['vendorid'] == 1
stored = db.session.get(PrinterDriver, body['driverid'])
assert stored.drivername == 'HP Universal Printing PCL 6'
assert stored.vendorid == 1
def test_the_inf_name_and_vendor_can_be_changed_afterwards(client, db,
auth_headers):
created = client.post('/api/printers/drivers', headers=auth_headers, json={
'name': 'Xerox universal', 'location': r'\\server\share\xerox'})
driverid = created.get_json()['data']['driverid']
assert created.get_json()['data']['drivername'] is None
updated = client.put('/api/printers/drivers/%d' % driverid,
headers=auth_headers,
json={'drivername': 'Xerox Global Print Driver PCL6',
'vendorid': 2})
assert updated.status_code == 200, updated.get_json()
assert updated.get_json()['data']['drivername'] == \
'Xerox Global Print Driver PCL6'
assert updated.get_json()['data']['vendorid'] == 2
def test_a_blank_inf_name_clears_it_rather_than_storing_whitespace(
client, db, auth_headers):
"""An empty box means "not known", which the converger reads as "cannot
install this". A whitespace string would look set and match nothing."""
created = client.post('/api/printers/drivers', headers=auth_headers, json={
'name': 'Blank test', 'location': r'\\server\share\blank',
'drivername': ' '})
assert created.get_json()['data']['drivername'] is None