"""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 assert statuses['Cyan Toner Level'] == 'ok'