diff --git a/server/plugins/dockerdisc/config.json b/server/plugins/dockerdisc/config.json
index 3b654589..b3182998 100644
--- a/server/plugins/dockerdisc/config.json
+++ b/server/plugins/dockerdisc/config.json
@@ -544,13 +544,13 @@
"name": [
{
"language_code": "en_us",
- "string": "Docker Host MAC Address (Fallback)"
+ "string": "Docker Host MAC Address (Manual)"
}
],
"description": [
{
"language_code": "en_us",
- "string": "Manual fallback physical MAC address of the Docker host, used if auto-detecting it via the Socket Proxy /info endpoint fails. The host must already exist as a device in NetAlertX (found via ARP/Nmap) - this plugin never creates it."
+ "string": "Manually configured physical MAC address of the Docker host. When set, it is used immediately - no Socket Proxy /info call is made. Leave blank to auto-detect the host via hostname matching instead. The host must already exist as a device in NetAlertX (found via ARP/Nmap) - this plugin never creates it."
}
]
}
@@ -620,7 +620,7 @@
"description": [
{
"language_code": "en_us",
- "string": "One entry per Docker host to track. Each entry pairs a read-only Docker Socket Proxy URL with that host's device (auto-detected, or entered manually as a fallback). Every container found on a host is listed under that host's own Device Details -> Plugins -> DOCKERDISC tab - the host device must already exist in NetAlertX (via ARP/Nmap); this plugin never creates devices."
+ "string": "One entry per Docker host to track. Each entry pairs a read-only Docker Socket Proxy URL with that host's device, matched either by a manually configured MAC (used immediately when set) or by auto-detected hostname. Every container found on a host is listed under that host's own Device Details -> Plugins -> DOCKERDISC tab - the host device must already exist in NetAlertX (via ARP/Nmap); this plugin never creates devices."
}
]
},
diff --git a/test/backend/test_device_instance.py b/test/backend/test_device_instance.py
index 8ad4cf88..6ddd2c1c 100644
--- a/test/backend/test_device_instance.py
+++ b/test/backend/test_device_instance.py
@@ -60,5 +60,37 @@ class TestGetAllByName(unittest.TestCase):
self.assertEqual(results, [])
+class TestGetByMac(unittest.TestCase):
+ """devMac is declared COLLATE NOCASE at the column level (unlike
+ devName), so getByMac() relies on the schema rather than applying its
+ own COLLATE clause - this exercises that guarantee against a real
+ SQLite connection, not a mock."""
+
+ def setUp(self):
+ self.conn = make_db()
+ insert_device_from_dict(self.conn, make_device_dict("aa:bb:cc:dd:ee:ff"))
+ self.conn.commit()
+
+ def _instance(self):
+ from models.device_instance import DeviceInstance
+ inst = DeviceInstance()
+
+ def _fetchone(q, p=()):
+ row = self.conn.execute(q, p).fetchone()
+ return dict(row) if row else None
+ inst._fetchone = _fetchone
+ return inst
+
+ def test_case_insensitive_match(self):
+ inst = self._instance()
+ result = inst.getByMac("AA:BB:CC:DD:EE:FF")
+ self.assertIsNotNone(result)
+ self.assertEqual(result["devMac"], "aa:bb:cc:dd:ee:ff")
+
+ def test_no_match_returns_none(self):
+ inst = self._instance()
+ self.assertIsNone(inst.getByMac("00:00:00:00:00:00"))
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/test/db_test_helpers.py b/test/db_test_helpers.py
index 646f8526..66763ac2 100644
--- a/test/db_test_helpers.py
+++ b/test/db_test_helpers.py
@@ -28,7 +28,7 @@ from db.db_history import ensure_deviceshistory_table, ensure_deviceshistory_tri
CREATE_DEVICES = """
CREATE TABLE IF NOT EXISTS Devices (
- devMac TEXT PRIMARY KEY,
+ devMac TEXT PRIMARY KEY COLLATE NOCASE,
devName TEXT,
devOwner TEXT,
devType TEXT,
diff --git a/test/plugins/test_dockerdisc.py b/test/plugins/test_dockerdisc.py
index 7f425091..582e834a 100644
--- a/test/plugins/test_dockerdisc.py
+++ b/test/plugins/test_dockerdisc.py
@@ -445,20 +445,18 @@ def test_lookup_device_mac_not_found():
assert dockerdisc.lookup_device_mac("aa:bb:cc:dd:ee:ff") is False
-def test_lookup_device_mac_found_regardless_of_stored_mac_case():
- """The plugin always passes a lowercase, normalize_mac()'d value in -
- this only guards that lookup_device_mac() doesn't do anything of its
- own (e.g. an exact-string comparison) that would undo whatever
- case-insensitivity DeviceInstance.getByMac() provides. The real
- guarantee is schema-level - Devices.devMac is declared
- `COLLATE NOCASE` (server/db/schema/app.sql), which is exactly why
- getByMac() itself doesn't need to apply it explicitly (unlike
- getAllByName(), which does - devName has no such column collation;
- see that method's docstring and test/backend/test_device_instance.py).
- This test's stub can't exercise real SQLite collation, only that this
- function's own logic is agnostic to it."""
- with patch.object(dockerdisc, "DeviceInstance", _stub_device_instance(get_by_mac={"devMac": "AA:BB:CC:DD:EE:FF"})):
- assert dockerdisc.lookup_device_mac("aa:bb:cc:dd:ee:ff") is True
+def test_lookup_device_mac_passes_mac_through_unchanged():
+ """lookup_device_mac() must not do any of its own case massaging - it
+ delegates entirely to DeviceInstance.getByMac(), which relies on
+ Devices.devMac's schema-level `COLLATE NOCASE` (server/db/schema/app.sql)
+ for case-insensitive matching. That guarantee is exercised against a
+ real SQLite connection in test/backend/test_device_instance.py's
+ TestGetByMac; a mocked DeviceInstance can't exercise real collation, so
+ this test only checks that the mac argument reaches getByMac() as-is."""
+ stub = _stub_device_instance(get_by_mac={"devMac": "aa:bb:cc:dd:ee:ff"})
+ with patch.object(dockerdisc, "DeviceInstance", stub):
+ dockerdisc.lookup_device_mac("AA:BB:CC:DD:EE:FF")
+ stub.return_value.getByMac.assert_called_once_with("AA:BB:CC:DD:EE:FF")
# ---------------------------------------------------------------------------