Wave one complete. Three cards, no new data and no migrations. Printer supplies reuses the existing low-supplies query and its five-minute cache; a Zabbix round-trip per printer on every dashboard load would make this the slowest page in the app. One row per printer listing every depleted cartridge, criticals first - a row per cartridge would report one printer three times and read as three problems, and showing only the worst class would hide a low cartridge behind a critical one on the same machine when whoever walks out there wants to carry both. While there: the low-supplies REPORT itself was including healthy cartridges. A printer with one empty black and three full colour ones listed all four, so the reader had to find the problem inside the row. It now lists only what needs replacing, and the test that asserted the old behaviour now asserts the new. Expiring warranties keeps already-expired entries on the list rather than dropping them the day they lapse, which is how they get missed. Horizon is warranty_expiringdays, default 90, because that suits a site budgeting quarterly and nobody else. Mis-numbered bays promotes check-shared-machines out of a CLI command nobody will remember to run - it found seven bays that had been wrong for weeks. It reports only numbers with NO child assets, so part markers legitimately sharing an operation stay silent: that distinction is the whole card, and without it it would list correct data beside faults and be ignored. Printers also loses its dead component-named widget; notifications, network and machines still have theirs.
145 lines
5.2 KiB
Python
145 lines
5.2 KiB
Python
"""End-to-end test of ZabbixService against the mock Zabbix JSON-RPC server.
|
|
|
|
Exercises the real HTTP path: Bearer auth, host.get by IP, tag-filtered
|
|
item.get, supply parsing, ping, and the low-supplies roll-up.
|
|
"""
|
|
|
|
import socket
|
|
|
|
import pytest
|
|
|
|
from tools.mock_zabbix import serve_in_thread
|
|
|
|
|
|
def _free_port():
|
|
sock = socket.socket()
|
|
sock.bind(('127.0.0.1', 0))
|
|
port = sock.getsockname()[1]
|
|
sock.close()
|
|
return port
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_zabbix(app):
|
|
"""Boot the mock server and point app config at it."""
|
|
port = _free_port()
|
|
server = serve_in_thread(port, 'testtoken')
|
|
app.config['ZABBIX_ENABLED'] = True
|
|
app.config['ZABBIX_URL'] = f'http://127.0.0.1:{port}'
|
|
app.config['ZABBIX_TOKEN'] = 'testtoken'
|
|
yield
|
|
server.shutdown()
|
|
|
|
|
|
def test_service_reachable_and_configured(app, db, mock_zabbix):
|
|
from plugins.printers.services import ZabbixService
|
|
with app.app_context():
|
|
service = ZabbixService()
|
|
assert service.isconfigured
|
|
assert service.isreachable
|
|
|
|
|
|
def test_gethostid_by_ip(app, db, mock_zabbix):
|
|
from plugins.printers.services import ZabbixService
|
|
with app.app_context():
|
|
service = ZabbixService()
|
|
assert service.gethostidbyip('10.20.30.40') == '10501'
|
|
assert service.gethostidbyip('1.2.3.4') is None
|
|
|
|
|
|
def test_supplies_parsed_with_color_and_status_filter(app, db, mock_zabbix):
|
|
from plugins.printers.services import ZabbixService
|
|
with app.app_context():
|
|
supplies = ZabbixService().getsuppliesbyip('10.20.30.40')
|
|
# disabled item (status=1) dropped, three active remain
|
|
names = {s['name'] for s in supplies}
|
|
assert 'Disabled Drum Level' not in names
|
|
assert len(supplies) == 3
|
|
black = next(s for s in supplies if s['name'].startswith('Black'))
|
|
assert black['color'] == 'black'
|
|
assert black['level'] == 4
|
|
|
|
|
|
def test_ping_status(app, db, mock_zabbix):
|
|
from plugins.printers.services import ZabbixService
|
|
with app.app_context():
|
|
assert ZabbixService().getpingstatus('10.20.30.40') == '1'
|
|
|
|
|
|
def test_bad_token_returns_no_data(app, db):
|
|
"""Wrong token -> API errors -> service returns nothing, fails soft."""
|
|
port = _free_port()
|
|
server = serve_in_thread(port, 'rightsecret')
|
|
app.config['ZABBIX_ENABLED'] = True
|
|
app.config['ZABBIX_URL'] = f'http://127.0.0.1:{port}'
|
|
app.config['ZABBIX_TOKEN'] = 'wrongsecret'
|
|
try:
|
|
with app.app_context():
|
|
assert ZabbixService_gethost(app) is None
|
|
finally:
|
|
server.shutdown()
|
|
|
|
|
|
def ZabbixService_gethost(app):
|
|
from plugins.printers.services import ZabbixService
|
|
return ZabbixService().gethostidbyip('10.20.30.40')
|
|
|
|
|
|
def test_low_supplies_rollup_flags_waste_and_toner(app, db, mock_zabbix):
|
|
"""The mock host has a 4% black toner and a 97%-full waste -> both flagged."""
|
|
from shopdb.core.models import (
|
|
Vendor, Model, Asset, AssetType, Communication, CommunicationType
|
|
)
|
|
from plugins.printers.models import Printer
|
|
from plugins.printers.api.asset_routes import _get_low_supplies_data
|
|
from shopdb.extensions import cache
|
|
|
|
with app.app_context():
|
|
# an HP printer at the mock's known IP
|
|
vendor = Vendor(vendor='HP')
|
|
db.session.add(vendor)
|
|
db.session.flush()
|
|
model = Model(modelnumber='HP M454', vendorid=vendor.vendorid)
|
|
db.session.add(model)
|
|
|
|
atype = AssetType.query.filter_by(assettype='printer').first()
|
|
if not atype:
|
|
atype = AssetType(assettype='printer', pluginname='printers',
|
|
tablename='printers')
|
|
db.session.add(atype)
|
|
db.session.flush()
|
|
asset = Asset(assetnumber='PRN-1', name='Test Printer',
|
|
assettypeid=atype.assettypeid)
|
|
db.session.add(asset)
|
|
db.session.flush()
|
|
|
|
printer = Printer(assetid=asset.assetid, vendorid=vendor.vendorid,
|
|
modelnumberid=model.modelnumberid)
|
|
db.session.add(printer)
|
|
|
|
comtype = CommunicationType.query.filter_by(comtype='IP').first()
|
|
if not comtype:
|
|
comtype = CommunicationType(comtype='IP')
|
|
db.session.add(comtype)
|
|
db.session.flush()
|
|
db.session.add(Communication(assetid=asset.assetid,
|
|
comtypeid=comtype.comtypeid,
|
|
ipaddress='10.20.30.40', isprimary=True))
|
|
db.session.commit()
|
|
|
|
cache.delete('printers_low_supplies')
|
|
data = _get_low_supplies_data()
|
|
|
|
assert data['summary']['total_checked'] == 1
|
|
assert len(data['printers']) == 1
|
|
row = data['printers'][0]
|
|
statuses = {s['name']: s['status'] for s in row['supplies']}
|
|
# 4% black toner is critical
|
|
assert statuses['Black Toner Level'] == 'critical'
|
|
# 97%-full waste (HP, non-inverted) -> 3% remaining -> critical
|
|
assert statuses['Waste Cartridge Level'] == 'critical'
|
|
# 60% cyan is FINE, so it is not listed at all. The report answers
|
|
# "what needs replacing"; including healthy cartridges made the reader
|
|
# hunt for the problem inside the row.
|
|
assert 'Cyan Toner Level' not in statuses
|