first commit

This commit is contained in:
Mateusz Gruszczyński
2026-08-13 15:58:52 +02:00
commit adfdb0b86c
100 changed files with 6216 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
import unittest
from app.policy import PolicyEngine
def event(src, dst, severity=1):
return {
"src_ip": src,
"dest_ip": dst,
"alert": {"severity": severity, "signature_id": 1234, "signature": "test"},
}
class PolicyTests(unittest.TestCase):
def test_observation_mode_selects_candidate_but_does_not_block(self):
p = PolicyEngine(False, 1, "192.168.100.0/24", "")
d = p.evaluate(event("192.168.100.10", "9.9.9.9"))
self.assertFalse(d.should_block)
self.assertEqual(d.target, "9.9.9.9")
self.assertIn("observation", d.reason)
def test_blocks_public_remote_when_enabled(self):
p = PolicyEngine(True, 1, "192.168.100.0/24", "")
d = p.evaluate(event("192.168.100.10", "9.9.9.9"))
self.assertTrue(d.should_block)
self.assertEqual(d.target, "9.9.9.9")
def test_inbound_selects_source(self):
p = PolicyEngine(True, 1, "192.168.100.0/24", "")
d = p.evaluate(event("9.9.9.9", "192.168.100.10"))
self.assertTrue(d.should_block)
self.assertEqual(d.target, "9.9.9.9")
def test_private_remote_is_never_blocked(self):
p = PolicyEngine(True, 1, "192.168.100.0/24", "")
d = p.evaluate(event("192.168.100.10", "10.10.10.10"))
self.assertFalse(d.should_block)
def test_never_block_list_wins(self):
p = PolicyEngine(True, 1, "192.168.100.0/24", "9.9.9.9/32")
d = p.evaluate(event("192.168.100.10", "9.9.9.9"))
self.assertFalse(d.should_block)
self.assertIn("NEVER_BLOCK", d.reason)
def test_severity_threshold(self):
p = PolicyEngine(True, 1, "192.168.100.0/24", "")
d = p.evaluate(event("192.168.100.10", "9.9.9.9", severity=2))
self.assertFalse(d.should_block)
if __name__ == "__main__":
unittest.main()
+58
View File
@@ -0,0 +1,58 @@
import json
import threading
import unittest
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from app.routeros import RouterOSClient
class Handler(BaseHTTPRequestHandler):
last_put = None
def do_GET(self):
data = b"[]"
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def do_PUT(self):
length = int(self.headers.get("Content-Length", "0"))
Handler.last_put = json.loads(self.rfile.read(length))
data = json.dumps(Handler.last_put).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def log_message(self, fmt, *args):
return
class RouterOSTests(unittest.TestCase):
def test_put_address_list_entry(self):
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
client = RouterOSClient(
f"http://127.0.0.1:{server.server_port}",
"user",
"secret",
False,
"IDS-BLOCK",
2,
)
result = client.block_ip("9.9.9.9", "1h", "test")
self.assertTrue(result.success)
self.assertEqual(Handler.last_put["list"], "IDS-BLOCK")
self.assertEqual(Handler.last_put["address"], "9.9.9.9")
finally:
server.shutdown()
server.server_close()
if __name__ == "__main__":
unittest.main()
+38
View File
@@ -0,0 +1,38 @@
import os
import tempfile
import unittest
from app.store import AlertStore
class StoreTests(unittest.TestCase):
def test_insert_and_read(self):
with tempfile.TemporaryDirectory() as td:
path = os.path.join(td, "alerts.db")
store = AlertStore(path)
event = {
"timestamp": "2026-08-13T10:00:00+00:00",
"event_type": "alert",
"src_ip": "192.168.100.10",
"src_port": 12345,
"dest_ip": "9.9.9.9",
"dest_port": 443,
"proto": "TCP",
"alert": {
"signature_id": 1000001,
"signature": "LOCAL TZSP PIPELINE TEST",
"category": "Test",
"severity": 1,
"action": "allowed",
},
}
store.insert_alert(event, False, "9.9.9.9", "observation")
rows = store.recent(10)
self.assertEqual(len(rows), 1)
self.assertEqual(rows[0]["signature_id"], 1000001)
self.assertEqual(store.summary()["total_alerts"], 1)
store.close()
if __name__ == "__main__":
unittest.main()
+29
View File
@@ -0,0 +1,29 @@
import unittest
from app.tzsp import TZSPError, decode_tzsp
class TZSPTests(unittest.TestCase):
def test_decodes_ethernet_frame(self):
frame = b"\xaa" * 60
packet = decode_tzsp(b"\x01\x00\x00\x01\x01" + frame)
self.assertEqual(packet.encapsulation, 1)
self.assertEqual(packet.frame, frame)
def test_skips_tags(self):
frame = b"\xbb" * 60
data = b"\x01\x00\x00\x01" + b"\x0a\x02\x12\x34" + b"\x00" + b"\x01" + frame
packet = decode_tzsp(data)
self.assertEqual(packet.frame, frame)
def test_rejects_missing_end_tag(self):
with self.assertRaises(TZSPError):
decode_tzsp(b"\x01\x00\x00\x01\x0a\x01\xff")
def test_rejects_non_packet_type(self):
with self.assertRaises(TZSPError):
decode_tzsp(b"\x01\x03\x00\x01\x01\x00")
if __name__ == "__main__":
unittest.main()
+27
View File
@@ -0,0 +1,27 @@
import unittest
from app.webui import DASHBOARD
class WebUITests(unittest.TestCase):
def test_dashboard_is_english(self):
self.assertIn('<html lang="en">', DASHBOARD)
self.assertIn("System status", DASHBOARD)
self.assertIn("Recent alerts", DASHBOARD)
self.assertIn("TZSP datagrams", DASHBOARD)
for polish_text in (
"Ładowanie",
"Tryb DEV",
"Brak alertów",
"Ostatnie alerty",
"Źródło",
"Blokady RouterOS",
):
self.assertNotIn(polish_text, DASHBOARD)
def test_dashboard_uses_status_endpoint(self):
self.assertIn("fetch('/api/status')", DASHBOARD)
if __name__ == "__main__":
unittest.main()