mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-10-02 02:35:05 -04:00
BE: Stats Logging for Down Devices corrected #1818
This commit is contained in:
1 parent
6d5760405b
commit
fa5c62c9c3
3 files changed
+107
-4
No files matched your search
@@ -578,11 +578,11 @@ def print_scan_stats(db):
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM CurrentScan) AS devices_detected,
|
||||
(SELECT COUNT(*) FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Devices WHERE devMac = scanMac)) AS new_devices,
|
||||
(SELECT COUNT(*) FROM DevicesView WHERE devAlertDown != 0 AND devIsSleeping = 0 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)) AS down_alerts,
|
||||
(SELECT COUNT(*) FROM DevicesView WHERE devAlertDown != 0 AND devIsSleeping = 0 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE scanMac = devMac)) AS down_alerts,
|
||||
(SELECT COUNT(*) FROM DevicesView
|
||||
WHERE devAlertDown != 0 AND devCanSleep = 0
|
||||
AND devPresentLastScan = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)
|
||||
AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE scanMac = devMac)
|
||||
) AS new_down_alerts,
|
||||
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0) AS new_connections,
|
||||
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 1 AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac)) AS disconnections,
|
||||
|
||||
@@ -95,8 +95,8 @@ CREATE_EVENTS = """
|
||||
|
||||
CREATE_CURRENT_SCAN = """
|
||||
CREATE TABLE IF NOT EXISTS CurrentScan (
|
||||
scanMac TEXT,
|
||||
scanLastIP TEXT,
|
||||
scanMac TEXT COLLATE NOCASE,
|
||||
scanLastIP TEXT COLLATE NOCASE,
|
||||
scanVendor TEXT,
|
||||
scanSourcePlugin TEXT,
|
||||
scanName TEXT,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Regression coverage for GitHub issue #1818: print_scan_stats() (device_handling.py)
|
||||
undercounted "down_alerts"/"new_down_alerts" whenever a device's stored MAC and its
|
||||
CurrentScan MAC differed only by case.
|
||||
|
||||
DevicesView exposes devMac as LOWER(IFNULL(devMac, '')) - a function-derived VIEW
|
||||
column, not a bare table column. print_scan_stats()'s down-alert subqueries compared
|
||||
it against CurrentScan.scanMac as `devMac = scanMac` (DevicesView column on the LEFT),
|
||||
which - unlike a bare COLLATE NOCASE column comparison - loses NOCASE collation and
|
||||
becomes a case-sensitive byte comparison. Reversing the operand order to
|
||||
`scanMac = devMac` restores NOCASE collation (SQLite gives precedence to the left
|
||||
operand's column collation; scanMac is a real COLLATE-free CurrentScan column but the
|
||||
comparison still resolves via devMac's declared NOCASE affinity once devMac is on the
|
||||
right, verified empirically against a real SQLite instance built from the actual
|
||||
DevicesView definition - see server/scan/device_handling.py print_scan_stats()).
|
||||
|
||||
This function is diagnostic/verbose-log-only (mylog("verbose"/"trace", ...), no other
|
||||
consumer) - it does not drive real "Device Down" notifications, which go through
|
||||
session_events.insert_events() -> presence.current_scan_presence_condition() instead
|
||||
and were already safe (scanMac on the left there). This test covers the diagnostic
|
||||
counter only.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from db_test_helpers import ( # noqa: E402
|
||||
DummyDB,
|
||||
make_device_dict,
|
||||
insert_device_from_dict,
|
||||
make_current_scan_dict,
|
||||
insert_current_scan_row_from_dict,
|
||||
)
|
||||
|
||||
import scan.device_handling as device_handling # noqa: E402
|
||||
from scan.device_handling import print_scan_stats # noqa: E402
|
||||
|
||||
|
||||
def _run_stats(conn, monkeypatch):
|
||||
"""Call print_scan_stats() and return the logged down_alerts/new_down_alerts
|
||||
counts, parsed straight out of its own mylog("verbose", ...) calls - this
|
||||
exercises the real function/query rather than a re-derived copy of it."""
|
||||
logged = {}
|
||||
|
||||
def fake_mylog(level, parts):
|
||||
text = "".join(parts) if isinstance(parts, list) else str(parts)
|
||||
if "Down Alerts" in text and "New Down Alerts" not in text:
|
||||
logged["down_alerts"] = int(text.rsplit(":", 1)[1].strip())
|
||||
elif "New Down Alerts" in text:
|
||||
logged["new_down_alerts"] = int(text.rsplit(":", 1)[1].strip())
|
||||
|
||||
monkeypatch.setattr(device_handling, "mylog", fake_mylog)
|
||||
|
||||
db = DummyDB(conn)
|
||||
print_scan_stats(db)
|
||||
return logged
|
||||
|
||||
|
||||
class TestScanStatsMacCase:
|
||||
def test_matching_mac_different_case_is_not_a_false_down_alert(self, scan_db, monkeypatch):
|
||||
"""Device stored lowercase, present this cycle under an uppercase MAC
|
||||
(e.g. a plugin like FREEBOX reporting uppercase) must not be counted
|
||||
as down - this is the exact scenario from issue #1818."""
|
||||
device = make_device_dict(
|
||||
mac="54:ef:44:9a:b6:7f",
|
||||
devAlertDown=1,
|
||||
devCanSleep=0,
|
||||
devPresentLastScan=1,
|
||||
)
|
||||
insert_device_from_dict(scan_db, device)
|
||||
|
||||
current_scan = make_current_scan_dict(mac="54:EF:44:9A:B6:7F")
|
||||
insert_current_scan_row_from_dict(scan_db, current_scan)
|
||||
|
||||
stats = _run_stats(scan_db, monkeypatch)
|
||||
|
||||
assert stats["down_alerts"] == 0
|
||||
assert stats["new_down_alerts"] == 0
|
||||
|
||||
def test_truly_absent_device_is_still_counted_as_down(self, scan_db, monkeypatch):
|
||||
"""Sanity check: a device genuinely missing from CurrentScan this
|
||||
cycle must still be counted - the fix must not mask real down alerts.
|
||||
|
||||
An unrelated CurrentScan row is seeded too: print_scan_stats() GROUPs
|
||||
BY scanSourcePlugin, so an empty CurrentScan produces zero result rows
|
||||
and print_scan_stats() falls back to its hardcoded all-zero stub - that
|
||||
would make this assertion pass for the wrong reason."""
|
||||
device = make_device_dict(
|
||||
mac="54:ef:44:9a:b6:7f",
|
||||
devAlertDown=1,
|
||||
devCanSleep=0,
|
||||
devPresentLastScan=1,
|
||||
)
|
||||
insert_device_from_dict(scan_db, device)
|
||||
|
||||
other_scan = make_current_scan_dict(mac="11:22:33:44:55:66")
|
||||
insert_current_scan_row_from_dict(scan_db, other_scan)
|
||||
|
||||
stats = _run_stats(scan_db, monkeypatch)
|
||||
|
||||
assert stats["down_alerts"] == 1
|
||||
assert stats["new_down_alerts"] == 1
|
||||
Reference in new issue
Block a user