first commit
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from librenet_scanner.models import Host, PortInfo
|
||||
from librenet_scanner.storage import HistoryStore
|
||||
|
||||
|
||||
def ports(*values: int) -> list[PortInfo]:
|
||||
return [PortInfo(port=value, service="test") for value in values]
|
||||
|
||||
|
||||
class IdentificationFreshness0416Tests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.store = HistoryStore(Path(self.tmp.name) / "history.sqlite3")
|
||||
self.scope = "ipv4:192.168.10.0/24"
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def _row(self, identity_suffix: str = "mac:00:11:22:33:44:55"):
|
||||
with self.store._connect() as conn:
|
||||
return conn.execute(
|
||||
"SELECT * FROM endpoint_identification WHERE identity LIKE ? ORDER BY identity LIMIT 1",
|
||||
(f"%{identity_suffix}",),
|
||||
).fetchone()
|
||||
|
||||
def test_standard_does_not_refresh_os_seen_at(self):
|
||||
deep_time = "2025-01-02T03:04:05+00:00"
|
||||
standard_time = "2026-08-22T12:00:00+00:00"
|
||||
deep = Host(
|
||||
ip="192.168.10.20", mac="00:11:22:33:44:55",
|
||||
os_name="Debian 13", os_accuracy=97, ports=ports(22, 443),
|
||||
)
|
||||
self.store.remember_identifications([deep], "Approfondi", observed_at=deep_time, scope=self.scope)
|
||||
standard = Host(
|
||||
ip="192.168.10.20", mac=deep.mac, os_name="Linux 6.x", ports=ports(22, 443),
|
||||
)
|
||||
self.store.remember_identifications([standard], "Standard", observed_at=standard_time, scope=self.scope)
|
||||
row = self._row()
|
||||
self.assertIsNotNone(row)
|
||||
self.assertEqual(row["os_name"], "Debian 13")
|
||||
self.assertEqual(row["os_seen_at"], deep_time)
|
||||
self.assertEqual(row["os_source"], "Approfondi")
|
||||
self.assertEqual(row["updated_at"], standard_time)
|
||||
|
||||
def test_old_os_stays_old_even_when_endpoint_updated_at_is_recent(self):
|
||||
old = Host(
|
||||
ip="192.168.10.20", mac="00:11:22:33:44:55",
|
||||
os_name="Debian 13", os_accuracy=99, ports=ports(22, 443),
|
||||
)
|
||||
self.store.remember_identifications([old], "Approfondi", scope=self.scope)
|
||||
stale = (datetime.now(timezone.utc) - timedelta(days=365)).astimezone().isoformat(timespec="seconds")
|
||||
fresh = datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
|
||||
with self.store._connect() as conn:
|
||||
conn.execute(
|
||||
"UPDATE endpoint_identification SET os_seen_at = ?, updated_at = ?",
|
||||
(stale, fresh),
|
||||
)
|
||||
moved = Host(ip="192.168.10.99", mac=old.mac)
|
||||
self.store.apply_identifications([moved], scope=self.scope)
|
||||
self.assertEqual(moved.remembered_os_name, "")
|
||||
|
||||
def test_unsafe_mac_move_does_not_overwrite_original_endpoint_before_matching(self):
|
||||
old = Host(
|
||||
ip="192.168.10.20", mac="00:11:22:33:44:55",
|
||||
hostname="old.local", os_name="Debian 13", os_accuracy=99,
|
||||
ports=ports(22, 443),
|
||||
)
|
||||
self.store.remember_identifications([old], "Approfondi", scope=self.scope)
|
||||
moved = Host(ip="192.168.10.99", mac=old.mac, hostname="other.local")
|
||||
# Même si une future régression écrit avant d'appliquer, l'ancien endpoint
|
||||
# doit rester intact : l'observation ambiguë est scindée en macip.
|
||||
self.store.remember_identifications([moved], "Standard", scope=self.scope)
|
||||
with self.store._connect() as conn:
|
||||
base = conn.execute(
|
||||
"SELECT * FROM endpoint_identification WHERE identity = ?",
|
||||
(f"{self.scope}::mac:{old.mac}",),
|
||||
).fetchone()
|
||||
split = conn.execute(
|
||||
"SELECT * FROM endpoint_identification WHERE identity = ?",
|
||||
(f"{self.scope}::macip:{old.mac}@192.168.10.99",),
|
||||
).fetchone()
|
||||
self.assertIsNotNone(base)
|
||||
self.assertEqual(base["ip"], "192.168.10.20")
|
||||
self.assertEqual(base["os_name"], "Debian 13")
|
||||
self.assertIsNotNone(split)
|
||||
|
||||
|
||||
class DataDeletion0416Tests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.store = HistoryStore(Path(self.tmp.name) / "history.sqlite3")
|
||||
self.scope = "ipv4:192.168.10.0/24"
|
||||
self.host = Host(
|
||||
ip="192.168.10.20", mac="00:11:22:33:44:55", hostname="srv.local",
|
||||
os_name="Debian 13", os_accuracy=99, ports=ports(22),
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def test_clear_scan_history_does_not_delete_identification_or_metadata(self):
|
||||
self.store.remember_identifications([self.host], "Approfondi", scope=self.scope)
|
||||
self.store.save_host_metadata(self.host, favorite=True, note="Important")
|
||||
self.store.save_scan("192.168.10.0/24", "Approfondi", [self.host], "deep-test")
|
||||
self.assertEqual(self.store.clear_scan_history(), 1)
|
||||
self.assertEqual(self.store.recent_scans(), [])
|
||||
current = Host(ip=self.host.ip, mac=self.host.mac, ports=ports(22))
|
||||
self.store.apply_identifications([current], scope=self.scope)
|
||||
self.assertEqual(current.remembered_os_name, "Debian 13")
|
||||
self.assertTrue(self.store.host_metadata(self.host)["favorite"])
|
||||
|
||||
def test_forget_selected_identification_keeps_metadata(self):
|
||||
self.store.remember_identifications([self.host], "Approfondi", scope=self.scope)
|
||||
self.store.save_host_metadata(self.host, favorite=True, group_name="Infra", note="Note")
|
||||
deleted = self.store.forget_identification_for_host(self.host, scope=self.scope)
|
||||
self.assertGreaterEqual(deleted, 1)
|
||||
current = Host(ip=self.host.ip, mac=self.host.mac, ports=ports(22))
|
||||
self.store.apply_identifications([current], scope=self.scope)
|
||||
self.assertEqual(current.remembered_os_name, "")
|
||||
metadata = self.store.host_metadata(self.host)
|
||||
self.assertTrue(metadata["favorite"])
|
||||
self.assertEqual(metadata["group_name"], "Infra")
|
||||
|
||||
def test_forget_scope_does_not_touch_other_network(self):
|
||||
other_scope = "ipv4:192.168.20.0/24"
|
||||
other = Host(ip="192.168.20.20", mac="00:11:22:AA:BB:CC", os_name="OpenWrt 24.10", ports=ports(22, 80))
|
||||
self.store.remember_identifications([self.host], "Approfondi", scope=self.scope)
|
||||
self.store.remember_identifications([other], "Approfondi", scope=other_scope)
|
||||
self.assertEqual(self.store.forget_identifications_for_scope(self.scope), 1)
|
||||
cur1 = Host(ip=self.host.ip, mac=self.host.mac, ports=ports(22))
|
||||
cur2 = Host(ip=other.ip, mac=other.mac, ports=ports(22, 80))
|
||||
self.store.apply_identifications([cur1], scope=self.scope)
|
||||
self.store.apply_identifications([cur2], scope=other_scope)
|
||||
self.assertEqual(cur1.remembered_os_name, "")
|
||||
self.assertEqual(cur2.remembered_os_name, "OpenWrt 24.10")
|
||||
|
||||
def test_forget_all_identifications_leaves_scan_history(self):
|
||||
self.store.remember_identifications([self.host], "Approfondi", scope=self.scope)
|
||||
self.store.save_scan("192.168.10.0/24", "Approfondi", [self.host], "deep-test")
|
||||
self.assertGreaterEqual(self.store.forget_all_identifications(), 1)
|
||||
self.assertEqual(len(self.store.recent_scans()), 1)
|
||||
current = Host(ip=self.host.ip, mac=self.host.mac, ports=ports(22))
|
||||
self.store.apply_identifications([current], scope=self.scope)
|
||||
self.assertEqual(current.remembered_os_name, "")
|
||||
|
||||
|
||||
class SourceOrder0416Tests(unittest.TestCase):
|
||||
def test_scan_finish_applies_memory_before_updating_endpoint_observation(self):
|
||||
source = Path(__file__).parents[1] / "src" / "librenet_scanner" / "ui.py"
|
||||
text = source.read_text(encoding="utf-8")
|
||||
start = text.index("def _scan_finished")
|
||||
end = text.index("# ---------- Constructeurs en ligne", start)
|
||||
block = text[start:end]
|
||||
self.assertLess(block.index("apply_identifications"), block.index("remember_identifications"))
|
||||
self.assertIn("Effacer l’affichage", text)
|
||||
self.assertIn("Oublier les identifications", text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user