first commit
This commit is contained in:
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user