Organize codebase into hub, clients, and common packages with backward compatibility and comprehensive unit test suites
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 1m18s
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 1m18s
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
LOGAR - Comprehensive Unit Tests for Edge Clients & Forwarders
|
||||
Covers log_collector, client_transport, client_updater, Win_Client, and Linux_Client.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import tempfile
|
||||
import unittest
|
||||
import socket
|
||||
import ssl
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from src.clients.log_collector import (
|
||||
get_machine_identifier,
|
||||
is_path_writable,
|
||||
get_default_data_dir,
|
||||
resolve_writable_path,
|
||||
get_state_path,
|
||||
load_state,
|
||||
save_state,
|
||||
commit_state,
|
||||
WindowsLogCollector,
|
||||
LinuxLogCollector,
|
||||
BaseLogCollector,
|
||||
get_log_collector,
|
||||
is_routine_service_noise,
|
||||
collect_system_metrics
|
||||
)
|
||||
from src.clients.client_updater import ClientUpdater
|
||||
from src.clients.client_transport import (
|
||||
is_cert_expiring_soon,
|
||||
enroll_client_if_needed,
|
||||
load_config,
|
||||
ensure_enrolled,
|
||||
send_encrypted_logs_over_socket,
|
||||
check_and_apply_auto_update
|
||||
)
|
||||
import src.clients.Win_Client as Win_Client
|
||||
import src.clients.Linux_Client as Linux_Client
|
||||
|
||||
|
||||
class TestClientsComprehensive(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.test_dir = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
import shutil
|
||||
if os.path.exists(self.test_dir):
|
||||
try:
|
||||
shutil.rmtree(self.test_dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def test_log_collector_utilities(self):
|
||||
"""Tests machine identifier, path resolution, and state persistence."""
|
||||
mid = get_machine_identifier()
|
||||
self.assertTrue(len(mid) > 0)
|
||||
|
||||
data_dir = get_default_data_dir("TEST-LOGAR")
|
||||
self.assertTrue(isinstance(data_dir, str))
|
||||
|
||||
resolved = resolve_writable_path(os.path.join(self.test_dir, "test.db"), "TEST-LOGAR")
|
||||
self.assertEqual(os.path.abspath(resolved), os.path.abspath(os.path.join(self.test_dir, "test.db")))
|
||||
|
||||
state_path = get_state_path("test_state.json", "TEST-LOGAR")
|
||||
self.assertTrue(isinstance(state_path, str))
|
||||
|
||||
# State load/save/commit
|
||||
st_file = os.path.join(self.test_dir, "st.json")
|
||||
st_data = load_state(st_file)
|
||||
self.assertIsInstance(st_data, dict)
|
||||
st_data["processed_records"] = 42
|
||||
st_data["new_last_record_number"] = 100
|
||||
save_state(st_file, st_data)
|
||||
loaded = load_state(st_file)
|
||||
self.assertEqual(loaded["processed_records"], 42)
|
||||
|
||||
commit_state(loaded, st_file)
|
||||
loaded2 = load_state(st_file)
|
||||
self.assertEqual(loaded2["last_record_number"], 100)
|
||||
|
||||
def test_metrics_collection_and_noise_filter(self):
|
||||
"""Tests system metrics telemetry and routine noise filtering."""
|
||||
metrics = collect_system_metrics()
|
||||
self.assertIn("cpu_percent", metrics)
|
||||
self.assertIn("memory_percent", metrics)
|
||||
self.assertIn("disk_percent_free", metrics)
|
||||
|
||||
# Routine noise filter: signature, message, severity
|
||||
self.assertTrue(is_routine_service_noise("Service Control Manager", "Windows Error Reporting Service entered the stopped state.", "INFO"))
|
||||
self.assertTrue(is_routine_service_noise("Service Control Manager", "Background Intelligent Transfer Service entered the running state.", "INFO"))
|
||||
self.assertFalse(is_routine_service_noise("NTFS", "Fatal disk corruption error detected on C:", "ERROR"))
|
||||
|
||||
def test_log_collector_classes(self):
|
||||
"""Tests WindowsLogCollector and LinuxLogCollector implementations."""
|
||||
# Linux Log Collector
|
||||
linux_col = LinuxLogCollector()
|
||||
self.assertIsNotNone(linux_col)
|
||||
|
||||
# Windows Log Collector
|
||||
win_col = WindowsLogCollector()
|
||||
self.assertEqual(win_col.channels, ["Application"])
|
||||
|
||||
factory_col = get_log_collector()
|
||||
self.assertIsInstance(factory_col, BaseLogCollector)
|
||||
|
||||
def test_client_updater_lifecycle(self):
|
||||
"""Tests ClientUpdater download, local hash match, and update application routines."""
|
||||
updater = ClientUpdater(config={"server_host": "127.0.0.1", "hermes_port": 8443}, os_type="linux")
|
||||
self.assertEqual(updater.os_type, "linux")
|
||||
base_url = updater.get_server_base_url()
|
||||
self.assertEqual(base_url, "http://127.0.0.1:8443")
|
||||
|
||||
# Linux apply update mock
|
||||
dummy_bin = os.path.join(self.test_dir, "dummy_new_bin")
|
||||
dummy_target = os.path.join(self.test_dir, "dummy_target_bin")
|
||||
with open(dummy_bin, "wb") as f:
|
||||
f.write(b"NEW_BINARY_CONTENT")
|
||||
with open(dummy_target, "wb") as f:
|
||||
f.write(b"OLD_BINARY_CONTENT")
|
||||
|
||||
ok = updater.apply_update_linux(dummy_bin, dummy_target)
|
||||
self.assertTrue(ok)
|
||||
with open(dummy_target, "rb") as f:
|
||||
self.assertEqual(f.read(), b"NEW_BINARY_CONTENT")
|
||||
|
||||
# Windows updater batch generation
|
||||
updater_win = ClientUpdater(config={"server_host": "127.0.0.1", "hermes_port": 8443}, os_type="windows")
|
||||
with patch("subprocess.Popen") as mock_popen:
|
||||
win_bin = os.path.join(self.test_dir, "win_new.exe")
|
||||
win_target = os.path.join(self.test_dir, "win_target.exe")
|
||||
with open(win_bin, "wb") as f:
|
||||
f.write(b"WIN_EXE")
|
||||
with open(win_target, "wb") as f:
|
||||
f.write(b"WIN_TARGET")
|
||||
res = updater_win.apply_update_windows(win_bin, win_target)
|
||||
self.assertTrue(res)
|
||||
self.assertTrue(mock_popen.called)
|
||||
|
||||
def test_client_transport_config_and_enrollment(self):
|
||||
"""Tests loading client config, cert expiration check, and fallback."""
|
||||
cfg_file = os.path.join(self.test_dir, "client_config.json")
|
||||
cert_dir = os.path.join(self.test_dir, "certs")
|
||||
os.makedirs(cert_dir, exist_ok=True)
|
||||
cfg_data = {
|
||||
"server_host": "127.0.0.1",
|
||||
"server_port": 9443,
|
||||
"hermes_host": "127.0.0.1",
|
||||
"hermes_port": 8443,
|
||||
"enrollment_secret": "test_sec",
|
||||
"cert_dir": cert_dir,
|
||||
"server_fingerprint": "FP123",
|
||||
"server_public_key": "",
|
||||
"auth_token": "token123"
|
||||
}
|
||||
with open(cfg_file, "w", encoding="utf-8") as f:
|
||||
json.dump(cfg_data, f)
|
||||
|
||||
loaded_cfg = load_config(cfg_file)
|
||||
self.assertEqual(loaded_cfg["server_host"], "127.0.0.1")
|
||||
|
||||
# Expiry check
|
||||
self.assertTrue(is_cert_expiring_soon(os.path.join(cert_dir, "nonexistent.crt")))
|
||||
|
||||
def test_client_main_helpers(self):
|
||||
"""Tests Win_Client and Linux_Client config loading and log retrieval fallbacks."""
|
||||
logs = Win_Client.get_recent_windows_logs(hours=1, state={})
|
||||
self.assertIsInstance(logs, list)
|
||||
|
||||
logs_l = Linux_Client.get_recent_linux_logs(hours=1, state={})
|
||||
self.assertIsInstance(logs_l, list)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
Comprehensive unit tests for LOGAR Client Transport, Windows & Linux forwarders, and update checks.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from src.clients import client_transport
|
||||
from src.clients import Win_Client, Linux_Client
|
||||
|
||||
|
||||
class TestClientsDeep(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.cert_dir = os.path.join(self.temp_dir, "certs")
|
||||
os.makedirs(self.cert_dir, exist_ok=True)
|
||||
self.config_path = os.path.join(self.temp_dir, "client_config.json")
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def test_client_transport_config_loader_and_helpers(self):
|
||||
"""Tests client configuration loading, writable directory fallback, and path resolution."""
|
||||
sample_config = {
|
||||
"server_host": "127.0.0.1",
|
||||
"server_port": 9443,
|
||||
"hermes_port": 8443,
|
||||
"cert_dir": self.cert_dir,
|
||||
"enrollment_secret": "test_enroll_secret_2026",
|
||||
"auth_token": "token_123",
|
||||
"ca_cert": "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
with open(self.config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(sample_config, f)
|
||||
|
||||
cfg = client_transport.load_config(self.config_path)
|
||||
self.assertEqual(cfg["server_host"], "127.0.0.1")
|
||||
self.assertEqual(cfg["server_port"], 9443)
|
||||
self.assertTrue(os.path.exists(os.path.join(cfg["cert_dir"], "ca.crt")))
|
||||
|
||||
# Test cert expiry check on non-existent cert
|
||||
self.assertTrue(client_transport.is_cert_expiring_soon(os.path.join(self.cert_dir, "nonexistent.crt")))
|
||||
|
||||
def test_client_transport_ensure_enrolled(self):
|
||||
"""Tests proactive client enrollment bootstrap check."""
|
||||
cfg = {
|
||||
"server_host": "127.0.0.1",
|
||||
"server_port": 9443,
|
||||
"hermes_host": "127.0.0.1",
|
||||
"hermes_port": 8443,
|
||||
"cert_dir": self.cert_dir,
|
||||
"enrollment_secret": "secret"
|
||||
}
|
||||
|
||||
with patch("src.clients.client_transport.enroll_client_if_needed") as mock_enroll:
|
||||
mock_enroll.return_value = True
|
||||
ok = client_transport.ensure_enrolled(cfg, "machine_01", os_type="windows")
|
||||
self.assertTrue(ok)
|
||||
mock_enroll.assert_called_once()
|
||||
|
||||
def test_client_transport_openpgp_fallback_transmission(self):
|
||||
"""Tests fallback to OpenPGP envelope over plain TCP socket when certs are absent."""
|
||||
from src.hub.storage_engine import StorageCryptoManager
|
||||
priv, pub, fp = StorageCryptoManager.generate_server_keypair("Hub Test Key")
|
||||
|
||||
cfg = {
|
||||
"server_host": "127.0.0.1",
|
||||
"server_port": 9443,
|
||||
"cert_dir": os.path.join(self.temp_dir, "empty_certs"),
|
||||
"server_public_key": pub,
|
||||
"server_fingerprint": fp,
|
||||
"auth_token": "client_token_abc"
|
||||
}
|
||||
|
||||
mock_socket = MagicMock()
|
||||
# Mock server returning 4-byte big-endian length + JSON response
|
||||
resp_data = json.dumps({"status": "success", "processed": 5}).encode("utf-8")
|
||||
resp_len_header = len(resp_data).to_bytes(4, byteorder="big")
|
||||
|
||||
mock_socket.recv.side_effect = [resp_len_header, resp_data, b""]
|
||||
|
||||
with patch("socket.socket") as mock_sock_cls:
|
||||
mock_sock_cls.return_value.__enter__.return_value = mock_socket
|
||||
logs = [{"timestamp": "2026-01-01T00:00:00Z", "event_id": 4625, "message": "Failed logon"}]
|
||||
res = client_transport.send_encrypted_logs_over_socket(cfg, logs, machine_id="test_win_node")
|
||||
self.assertEqual(res.get("status"), "success")
|
||||
self.assertEqual(res.get("processed"), 5)
|
||||
|
||||
def test_win_client_and_linux_client_entrypoints(self):
|
||||
"""Tests command-line parsing, help options, and diagnostic checks."""
|
||||
# Test help flag execution
|
||||
with self.assertRaises(SystemExit) as cm:
|
||||
with patch("sys.argv", ["Win_Client.py", "--help"]):
|
||||
Win_Client.main()
|
||||
self.assertEqual(cm.exception.code, 0)
|
||||
|
||||
with self.assertRaises(SystemExit) as cm:
|
||||
with patch("sys.argv", ["Linux_Client.py", "--help"]):
|
||||
Linux_Client.main()
|
||||
self.assertEqual(cm.exception.code, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
LOGAR - Comprehensive Unit Tests for Common Version Module
|
||||
100% test coverage for semantic versioning, version parsing, comparisons, and edge cases.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from src.common.version import (
|
||||
LOGAR_VERSION,
|
||||
parse_version,
|
||||
compare_versions,
|
||||
is_newer_version
|
||||
)
|
||||
|
||||
|
||||
class TestCommonVersion(unittest.TestCase):
|
||||
def test_version_constants(self):
|
||||
self.assertIsInstance(LOGAR_VERSION, str)
|
||||
self.assertTrue(len(LOGAR_VERSION) > 0)
|
||||
|
||||
def test_parse_version_valid(self):
|
||||
self.assertEqual(parse_version("1.2.3.4"), (1, 2, 3, 4))
|
||||
self.assertEqual(parse_version("v1.2.3.4"), (1, 2, 3, 4))
|
||||
self.assertEqual(parse_version("V1.2.3"), (1, 2, 3, 0))
|
||||
self.assertEqual(parse_version("1.2"), (1, 2, 0, 0))
|
||||
self.assertEqual(parse_version("1"), (1, 0, 0, 0))
|
||||
self.assertEqual(parse_version("1.2.3.4.5"), (1, 2, 3, 4, 5))
|
||||
|
||||
def test_parse_version_invalid_and_edge_cases(self):
|
||||
self.assertEqual(parse_version(None), (0, 0, 0, 0))
|
||||
self.assertEqual(parse_version(""), (0, 0, 0, 0))
|
||||
self.assertEqual(parse_version(" "), (0, 0, 0, 0))
|
||||
self.assertEqual(parse_version("invalid"), (0, 0, 0, 0))
|
||||
self.assertEqual(parse_version("v1.alpha.2"), (1, 0, 0, 0))
|
||||
|
||||
def test_compare_versions(self):
|
||||
self.assertEqual(compare_versions("1.0.0.0", "1.0.0.0"), 0)
|
||||
self.assertEqual(compare_versions("1.0.0.1", "1.0.0.0"), 1)
|
||||
self.assertEqual(compare_versions("1.0.0.0", "1.0.0.1"), -1)
|
||||
self.assertEqual(compare_versions("2.0.0.0", "1.9.9.9"), 1)
|
||||
self.assertEqual(compare_versions("0.3.5.2", "0.3.5.2"), 0)
|
||||
self.assertEqual(compare_versions("0.3.6.0", "0.3.5.2"), 1)
|
||||
self.assertEqual(compare_versions("0.3.5.1", "0.3.5.2"), -1)
|
||||
|
||||
def test_is_newer_version(self):
|
||||
self.assertTrue(is_newer_version("1.0.0.1", "1.0.0.0"))
|
||||
self.assertFalse(is_newer_version("1.0.0.0", "1.0.0.0"))
|
||||
self.assertFalse(is_newer_version("0.9.9.9", "1.0.0.0"))
|
||||
self.assertTrue(is_newer_version("v0.3.6.0", "0.3.5.2"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
LOGAR - Comprehensive Unit Tests for Central Server Hub
|
||||
Covers License_Authority, auth_manager, ingest_queue, license_manager, pki_manager,
|
||||
server_enrollment, server_models, storage_engine, update_manager, web_ui, and Server endpoints.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import uuid
|
||||
import time
|
||||
import tempfile
|
||||
import unittest
|
||||
import sqlite3
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from src.hub.server_models import (
|
||||
ClientEnrollRequest,
|
||||
ClientActionRequest,
|
||||
ClientUpdateRequest,
|
||||
LicenseImportRequest,
|
||||
HermesTokenCreateRequest,
|
||||
HermesTokenRevokeRequest,
|
||||
ClientProfileExportRequest,
|
||||
ChangePasswordRequest,
|
||||
DbConnectionTestRequest,
|
||||
DbSwitchBackendRequest,
|
||||
DbFullConfigRequest
|
||||
)
|
||||
from src.hub.auth_manager import (
|
||||
PasswordManager,
|
||||
SessionManager,
|
||||
RateLimiter,
|
||||
TOTPManager,
|
||||
HermesTokenManager
|
||||
)
|
||||
from src.hub.ingest_queue import IngestQueueManager
|
||||
from src.hub.license_manager import LicenseManager
|
||||
from src.hub.pki_manager import PKIManager
|
||||
from src.hub.storage_engine import StorageEngine
|
||||
from src.hub.update_manager import ServerUpdateManager
|
||||
from src.hub.web_ui import WebUIRenderer
|
||||
import src.hub.Server as Server
|
||||
import src.hub.server_enrollment as enrollment
|
||||
|
||||
|
||||
class TestHubComprehensive(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.test_dir = tempfile.mkdtemp()
|
||||
self.test_db = os.path.join(self.test_dir, f"test_hub_{uuid.uuid4().hex[:8]}.db")
|
||||
self.cert_dir = os.path.join(self.test_dir, "certs")
|
||||
self.updates_dir = os.path.join(self.test_dir, "updates")
|
||||
|
||||
StorageEngine.init_db(self.test_db, max_seats=10)
|
||||
self.update_mgr = ServerUpdateManager(update_dir=self.updates_dir, check_enabled=False)
|
||||
Server.SERVER_STATE["update_manager"] = self.update_mgr
|
||||
Server.SERVER_STATE["config"] = {
|
||||
"db_path": self.test_db,
|
||||
"cert_dir": self.cert_dir,
|
||||
"server_name": "LOGAR-Test-Hub",
|
||||
"server_fingerprint": "TEST_FP_123",
|
||||
"tcp_host": "127.0.0.1",
|
||||
"tcp_port": 9443,
|
||||
"hermes_port": 8443,
|
||||
"auth_token": "test_auth_token_secret"
|
||||
}
|
||||
|
||||
def tearDown(self):
|
||||
import shutil
|
||||
if os.path.exists(self.test_dir):
|
||||
try:
|
||||
shutil.rmtree(self.test_dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def test_server_models(self):
|
||||
"""Tests Pydantic validation on all Server Hub data models."""
|
||||
enr = ClientEnrollRequest(client_id="c1", hostname="host1", os="windows", enrollment_secret="sec")
|
||||
self.assertEqual(enr.client_id, "c1")
|
||||
|
||||
act = ClientActionRequest(client_id="c1")
|
||||
self.assertEqual(act.client_id, "c1")
|
||||
|
||||
upd = ClientUpdateRequest(client_id="c1", os="windows", current_version="0.3.5.0")
|
||||
self.assertEqual(upd.current_version, "0.3.5.0")
|
||||
|
||||
lic_imp = LicenseImportRequest(ca_cert="PEM_CERT_DATA")
|
||||
self.assertEqual(lic_imp.ca_cert, "PEM_CERT_DATA")
|
||||
|
||||
htc = HermesTokenCreateRequest(name="agent1")
|
||||
self.assertEqual(htc.name, "agent1")
|
||||
|
||||
htr = HermesTokenRevokeRequest(token="tok123")
|
||||
self.assertEqual(htr.token, "tok123")
|
||||
|
||||
cpe = ClientProfileExportRequest(server_host="10.0.0.1")
|
||||
self.assertEqual(cpe.server_host, "10.0.0.1")
|
||||
|
||||
cp = ChangePasswordRequest(old_password="old", new_password="new")
|
||||
self.assertEqual(cp.new_password, "new")
|
||||
|
||||
dct = DbConnectionTestRequest(host="localhost", port=5432, database="logar")
|
||||
self.assertEqual(dct.port, 5432)
|
||||
|
||||
dfc = DbFullConfigRequest(storage_engine="sqlite", cache_mode="direct")
|
||||
self.assertEqual(dfc.storage_engine, "sqlite")
|
||||
|
||||
def test_auth_manager_edge_cases(self):
|
||||
"""Tests password hashing, TOTP secret generation, and rate limiting edge cases."""
|
||||
# Password
|
||||
h, s = PasswordManager.hash_password("admin_password")
|
||||
self.assertTrue(PasswordManager.verify_password("admin_password", s, h))
|
||||
self.assertFalse(PasswordManager.verify_password("wrong_password", s, h))
|
||||
|
||||
# Session token
|
||||
tok = SessionManager.generate_session_token("admin", db_path=self.test_db)
|
||||
self.assertEqual(SessionManager.verify_session_token(tok, db_path=self.test_db), "admin")
|
||||
self.assertIsNone(SessionManager.verify_session_token("invalid_token", db_path=self.test_db))
|
||||
|
||||
# TOTP Manager
|
||||
sec = TOTPManager.generate_totp_secret(length=20)
|
||||
self.assertTrue(len(sec) >= 20)
|
||||
code = TOTPManager.get_totp_token(sec)
|
||||
self.assertTrue(len(code) == 6)
|
||||
self.assertTrue(TOTPManager.verify_totp_code(sec, code))
|
||||
uri = TOTPManager.get_totp_uri("admin", sec)
|
||||
self.assertIn("otpauth://totp/", uri)
|
||||
|
||||
# Hermes Token Manager
|
||||
ht = HermesTokenManager.create_hermes_token("Agent 1", db_path=self.test_db)
|
||||
self.assertIn("token", ht)
|
||||
self.assertTrue(HermesTokenManager.verify_hermes_token(ht["token"], db_path=self.test_db))
|
||||
tokens = HermesTokenManager.list_hermes_tokens(db_path=self.test_db)
|
||||
self.assertTrue(len(tokens) >= 1)
|
||||
HermesTokenManager.revoke_hermes_token(ht["token"], db_path=self.test_db)
|
||||
self.assertFalse(HermesTokenManager.verify_hermes_token(ht["token"], db_path=self.test_db))
|
||||
HermesTokenManager.delete_hermes_token(ht["token"], db_path=self.test_db)
|
||||
|
||||
def test_pki_and_server_enrollment(self):
|
||||
"""Tests dynamic CA, Server, and client certificate issuance and verification."""
|
||||
ca_cert, ca_key, ca_pem, ca_key_pem = enrollment.generate_ca_if_needed(cert_dir=self.cert_dir)
|
||||
self.assertIsNotNone(ca_cert)
|
||||
self.assertTrue(os.path.exists(os.path.join(self.cert_dir, "ca.crt")))
|
||||
|
||||
# Issue server cert
|
||||
srv_cert, srv_key, srv_pem, srv_key_pem = enrollment.generate_server_cert_if_needed(
|
||||
ca_cert, ca_key,
|
||||
hostnames=["127.0.0.1", "localhost"],
|
||||
cert_dir=self.cert_dir
|
||||
)
|
||||
self.assertIsNotNone(srv_cert)
|
||||
|
||||
# Issue client cert
|
||||
cl_pem, cl_key_pem = enrollment.issue_client_cert(
|
||||
client_id="client-hub-test",
|
||||
ca_cert=ca_cert,
|
||||
ca_key=ca_key
|
||||
)
|
||||
self.assertIn("BEGIN CERTIFICATE", cl_pem)
|
||||
self.assertIn("BEGIN RSA PRIVATE KEY", cl_key_pem)
|
||||
|
||||
# Validate renewal logic
|
||||
ca_ren, srv_ren = enrollment.check_and_renew_hub_pki(cert_dir=self.cert_dir, hostnames=["127.0.0.1"])
|
||||
self.assertFalse(ca_ren)
|
||||
self.assertFalse(srv_ren)
|
||||
rotated = PKIManager.check_and_rotate_server_certs(cert_dir=self.cert_dir, hostnames=["127.0.0.1"])
|
||||
self.assertFalse(rotated)
|
||||
|
||||
def test_storage_engine_sqlite_and_migrations(self):
|
||||
"""Tests StorageEngine table initialization, column migrations, and data persistence."""
|
||||
db_path = StorageEngine.init_db(self.test_db)
|
||||
self.assertTrue(os.path.exists(db_path))
|
||||
|
||||
# Check tables
|
||||
conn = sqlite3.connect(db_path)
|
||||
tables = {row[0] for row in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()}
|
||||
conn.close()
|
||||
|
||||
self.assertIn("active_issues", tables)
|
||||
self.assertIn("clients", tables)
|
||||
self.assertIn("license_config", tables)
|
||||
self.assertIn("users", tables)
|
||||
self.assertIn("hermes_tokens", tables)
|
||||
|
||||
def test_web_ui_renderer_all_tabs(self):
|
||||
"""Tests rendering of all WebUI tabs: Fleet, Hermes, Anomalies, PKI, DB, Updates."""
|
||||
html = WebUIRenderer.render_dashboard_html(
|
||||
server_state=Server.SERVER_STATE,
|
||||
ingest_queue=Server.INGEST_QUEUE
|
||||
)
|
||||
self.assertIn("LOGAR Fleet Hub", html)
|
||||
self.assertIn("tabPane-fleet", html)
|
||||
self.assertIn("tabPane-hermes", html)
|
||||
self.assertIn("tabPane-anomalies", html)
|
||||
self.assertIn("tabPane-pki", html)
|
||||
self.assertIn("tabPane-db", html)
|
||||
self.assertIn("tabPane-updates", html)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
Comprehensive unit tests for dynamic PKI, Sub-CA issuance, offline CSR activation, and LicenseAuthority.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import sqlite3
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from src.hub import server_enrollment as enrollment
|
||||
from src.hub.pki_manager import PKIManager
|
||||
from src.hub import License_Authority
|
||||
|
||||
|
||||
class TestHubEnrollmentPKIDeep(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.cert_dir = os.path.join(self.temp_dir, "certs")
|
||||
os.makedirs(self.cert_dir, exist_ok=True)
|
||||
self.db_path = os.path.join(self.temp_dir, "logar_test.db")
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def test_pki_lifecycle_and_extensions(self):
|
||||
"""Tests CA generation, Server TLS cert generation, client cert issuance, and license OID parsing."""
|
||||
# 1. CA Generation
|
||||
ca_cert, ca_key, ca_pem, ca_key_pem = enrollment.generate_ca_if_needed(cert_dir=self.cert_dir)
|
||||
self.assertIsNotNone(ca_cert)
|
||||
self.assertTrue(os.path.exists(os.path.join(self.cert_dir, "ca.crt")))
|
||||
self.assertTrue(os.path.exists(os.path.join(self.cert_dir, "ca.key")))
|
||||
|
||||
# 2. Server TLS Certificate Generation
|
||||
srv_cert, srv_key, srv_pem, srv_key_pem = enrollment.generate_server_cert_if_needed(
|
||||
ca_cert=ca_cert,
|
||||
ca_key=ca_key,
|
||||
hostnames=["127.0.0.1", "localhost"],
|
||||
cert_dir=self.cert_dir
|
||||
)
|
||||
self.assertIsNotNone(srv_cert)
|
||||
self.assertTrue(os.path.exists(os.path.join(self.cert_dir, "server.crt")))
|
||||
self.assertTrue(os.path.exists(os.path.join(self.cert_dir, "server.key")))
|
||||
|
||||
# 3. Client Certificate Issuance
|
||||
cl_pem, cl_key_pem = enrollment.issue_client_cert(
|
||||
client_id="forwarder-node-01",
|
||||
ca_cert=ca_cert,
|
||||
ca_key=ca_key,
|
||||
days_valid=365
|
||||
)
|
||||
self.assertIn("BEGIN CERTIFICATE", cl_pem)
|
||||
self.assertIn("BEGIN RSA PRIVATE KEY", cl_key_pem)
|
||||
|
||||
# 4. Extract license info
|
||||
info = enrollment.extract_license_info_from_cert(ca_pem)
|
||||
self.assertIn("is_self_signed", info)
|
||||
|
||||
# 5. Check cert expiry helper
|
||||
self.assertFalse(enrollment.is_cert_expiring_soon(ca_pem, threshold_days=30))
|
||||
|
||||
# 6. Initialize mTLS Server context
|
||||
ctx = PKIManager.init_mtls_server_context(cert_dir=self.cert_dir)
|
||||
self.assertIsNotNone(ctx)
|
||||
|
||||
# 7. Reload mTLS context
|
||||
PKIManager.reload_mtls_context(ctx, cert_dir=self.cert_dir)
|
||||
|
||||
def test_offline_csr_and_activation(self):
|
||||
"""Tests export of offline license request CSR and activation bundle unpack."""
|
||||
csr_pem, csr_key_pem = enrollment.generate_ca_csr(cert_dir=self.cert_dir)
|
||||
self.assertIn("BEGIN CERTIFICATE REQUEST", csr_pem)
|
||||
|
||||
# Sign the CSR locally as Sub-CA for test
|
||||
ca_cert, ca_key, ca_pem, ca_key_pem = enrollment.generate_ca_if_needed(cert_dir=self.cert_dir)
|
||||
sub_ca_pem = enrollment.sign_ca_csr_with_seats(
|
||||
csr_pem=csr_pem,
|
||||
master_ca_cert_pem=ca_pem,
|
||||
master_ca_key_pem=ca_key_pem,
|
||||
seats=25,
|
||||
days_valid=365,
|
||||
license_id="LIC-TEST-2026",
|
||||
features=["postgres", "realtime_alerting"]
|
||||
)
|
||||
self.assertIn("BEGIN CERTIFICATE", sub_ca_pem)
|
||||
|
||||
# Verify signed Sub-CA contains license extensions
|
||||
info = enrollment.extract_license_info_from_cert(sub_ca_pem)
|
||||
self.assertEqual(info.get("seats"), 25)
|
||||
self.assertEqual(info.get("license_id"), "LIC-TEST-2026")
|
||||
self.assertIn("postgres", info.get("features", []))
|
||||
|
||||
# Test create_license_request_lic
|
||||
lic_data, lic_json = enrollment.create_license_request_lic(cert_dir=self.cert_dir)
|
||||
self.assertEqual(lic_data["format"], "LOGAR_LICENSE_REQUEST")
|
||||
self.assertIn("csr", lic_data)
|
||||
|
||||
def test_license_authority_cli_dispatch(self):
|
||||
"""Tests License Authority helper and CLI options."""
|
||||
# Test help flag execution
|
||||
with self.assertRaises(SystemExit) as cm:
|
||||
with patch("sys.argv", ["License_Authority.py", "--help"]):
|
||||
License_Authority.main()
|
||||
self.assertEqual(cm.exception.code, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Comprehensive unit tests for LOGAR Server FastAPI endpoints, state management, and log processing.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import tempfile
|
||||
import sqlite3
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.hub import Server
|
||||
from src.hub.auth_manager import SessionManager
|
||||
|
||||
|
||||
class TestHubServerDeep(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.cert_dir = os.path.join(self.temp_dir, "certs")
|
||||
os.makedirs(self.cert_dir, exist_ok=True)
|
||||
self.db_path = os.path.join(self.temp_dir, "test_server.db")
|
||||
self.config_path = os.path.join(self.temp_dir, "server_config.json")
|
||||
|
||||
# Initialize test state
|
||||
Server.StorageEngine.init_db(self.db_path, enrollment_secret="test_secret", max_seats=50)
|
||||
Server.SERVER_STATE["config"] = {
|
||||
"db_path": self.db_path,
|
||||
"enrollment_secret": "test_secret",
|
||||
"max_seats": 50,
|
||||
"cert_dir": self.cert_dir,
|
||||
"encrypt_at_rest": False
|
||||
}
|
||||
# Generate and load CA
|
||||
ca_cert, ca_key, ca_pem, _ = Server.enrollment.generate_ca_if_needed(cert_dir=self.cert_dir)
|
||||
Server.SERVER_STATE["ca_cert"] = ca_cert
|
||||
Server.SERVER_STATE["ca_key"] = ca_key
|
||||
Server.SERVER_STATE["ca_cert_pem"] = ca_pem
|
||||
|
||||
self.session_token = SessionManager.generate_session_token("admin", db_path=self.db_path)
|
||||
self.client = TestClient(Server.app, cookies={SessionManager.SESSION_COOKIE_NAME: self.session_token})
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def test_server_status_and_update_routes(self):
|
||||
"""Tests /api/update/status and /api/server/update/status routes."""
|
||||
resp = self.client.get("/api/update/status")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = resp.json()
|
||||
self.assertIn("hosted_packages", data)
|
||||
|
||||
def test_client_enrollment_and_action_endpoints(self):
|
||||
"""Tests /api/client/enroll and /api/client/revoke/approve/delete endpoints."""
|
||||
# Ensure license in DB is marked active with seats
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
conn.execute("UPDATE license_config SET max_seats = 50, license_status = 'active' WHERE id = 1")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Enroll client
|
||||
enroll_payload = {
|
||||
"client_id": "test_agent_deep_01",
|
||||
"hostname": "workstation-01",
|
||||
"os": "windows",
|
||||
"enrollment_secret": "test_secret"
|
||||
}
|
||||
resp = self.client.post("/api/client/enroll", json=enroll_payload)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = resp.json()
|
||||
self.assertEqual(data.get("status"), "success")
|
||||
self.assertIn("client_cert", data)
|
||||
self.assertIn("client_key", data)
|
||||
|
||||
# Check client list
|
||||
resp_list = self.client.get("/api/clients")
|
||||
self.assertEqual(resp_list.status_code, 200)
|
||||
clients_data = resp_list.json()
|
||||
self.assertGreaterEqual(clients_data.get("active_seats", 0), 1)
|
||||
|
||||
# Client action: revoke
|
||||
resp_rev = self.client.post("/api/client/revoke", json={"client_id": "test_agent_deep_01"})
|
||||
self.assertEqual(resp_rev.status_code, 200)
|
||||
self.assertEqual(resp_rev.json().get("status"), "revoked")
|
||||
|
||||
# Reactivate
|
||||
resp_app = self.client.post("/api/client/approve", json={"client_id": "test_agent_deep_01"})
|
||||
self.assertEqual(resp_app.status_code, 200)
|
||||
self.assertEqual(resp_app.json().get("status"), "active")
|
||||
|
||||
# Delete
|
||||
resp_del = self.client.post("/api/client/delete", json={"client_id": "test_agent_deep_01"})
|
||||
self.assertEqual(resp_del.status_code, 200)
|
||||
|
||||
def test_hermes_tokens_and_auth_routes(self):
|
||||
"""Tests Hermes API token creation, listing, and revoking."""
|
||||
# Create token
|
||||
tok_payload = {"name": "Test Integration Agent"}
|
||||
resp = self.client.post("/api/hermes/tokens/create", json=tok_payload)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
tok_data = resp.json()
|
||||
token_val = tok_data.get("token")
|
||||
self.assertIsNotNone(token_val)
|
||||
|
||||
# Revoke token
|
||||
resp_rev = self.client.post("/api/hermes/tokens/revoke", json={"token": token_val})
|
||||
self.assertEqual(resp_rev.status_code, 200)
|
||||
|
||||
def test_process_ingested_logs_and_temporal_tracking(self):
|
||||
"""Tests processing ingested log entries, deduplication, and anomaly promotion."""
|
||||
payload = {
|
||||
"server": "web-prod-01",
|
||||
"timestamp": "2026-01-01T12:00:00Z",
|
||||
"logs": [
|
||||
{
|
||||
"site_name": "Default-Site",
|
||||
"server": "web-prod-01",
|
||||
"signature": "SECURITY_4625_BRUTEFORCE",
|
||||
"severity": "CRITICAL",
|
||||
"message": "Repeated failed logon attempts detected from 10.0.0.99",
|
||||
"os_type": "windows",
|
||||
"timestamp": "2026-01-01T12:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
res = Server.process_ingested_logs(
|
||||
payload=payload,
|
||||
db_path=self.db_path,
|
||||
window_hours=24,
|
||||
min_runs=1
|
||||
)
|
||||
self.assertEqual(res.get("processed"), 1)
|
||||
|
||||
# Query database directly to confirm issue recorded
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT signature, severity, run_count FROM active_issues")
|
||||
row = c.fetchone()
|
||||
conn.close()
|
||||
self.assertIsNotNone(row)
|
||||
self.assertEqual(row[0], "SECURITY_4625_BRUTEFORCE")
|
||||
self.assertEqual(row[1], "CRITICAL")
|
||||
self.assertEqual(row[2], 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Comprehensive unit tests for LOGAR StorageEngine, StorageCryptoManager, and Multi-Backend Migrations.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import tempfile
|
||||
import sqlite3
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from src.hub.storage_engine import StorageEngine, StorageCryptoManager
|
||||
|
||||
|
||||
class TestStorageEngineDeep(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.db_path = os.path.join(self.temp_dir, "test_logar.db")
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def test_storage_crypto_manager_keypair_and_encryption(self):
|
||||
"""Tests keypair generation, field encryption, decryption, and error resilience."""
|
||||
priv, pub, fp = StorageCryptoManager.generate_server_keypair("Test Server Key")
|
||||
self.assertIn("BEGIN PGP PRIVATE KEY", priv)
|
||||
self.assertIn("BEGIN PGP PUBLIC KEY", pub)
|
||||
self.assertTrue(len(fp) > 0)
|
||||
|
||||
server_state = {
|
||||
"config": {
|
||||
"encrypt_at_rest": True,
|
||||
"public_key": pub,
|
||||
"private_key": priv
|
||||
}
|
||||
}
|
||||
|
||||
# Test empty or non-string inputs
|
||||
self.assertEqual(StorageCryptoManager.encrypt_field_for_storage("", server_state=server_state, is_unlocked=True), "")
|
||||
self.assertEqual(StorageCryptoManager.encrypt_field_for_storage(None, server_state=server_state, is_unlocked=True), None)
|
||||
self.assertEqual(StorageCryptoManager.decrypt_field_from_storage("", server_state=server_state), "")
|
||||
self.assertEqual(StorageCryptoManager.decrypt_field_from_storage(None, server_state=server_state), None)
|
||||
|
||||
# Test non-encrypted plain string decrypt passthrough
|
||||
plain_msg = "Security Audit Message 2026"
|
||||
self.assertEqual(StorageCryptoManager.decrypt_field_from_storage(plain_msg, server_state=server_state), plain_msg)
|
||||
|
||||
# Encrypt when unlocked
|
||||
encrypted = StorageCryptoManager.encrypt_field_for_storage(plain_msg, server_state=server_state, is_unlocked=True)
|
||||
self.assertTrue(encrypted.startswith("-----BEGIN PGP MESSAGE-----"))
|
||||
|
||||
# Encrypt idempotence (should not double-encrypt)
|
||||
double_enc = StorageCryptoManager.encrypt_field_for_storage(encrypted, server_state=server_state, is_unlocked=True)
|
||||
self.assertEqual(double_enc, encrypted)
|
||||
|
||||
# Decrypt
|
||||
decrypted = StorageCryptoManager.decrypt_field_from_storage(encrypted, server_state=server_state)
|
||||
self.assertEqual(decrypted, plain_msg)
|
||||
|
||||
# Decrypt with state missing private_key_obj (reloads from blob)
|
||||
clean_state = {"config": {"private_key": priv}}
|
||||
decrypted_from_clean = StorageCryptoManager.decrypt_field_from_storage(encrypted, server_state=clean_state)
|
||||
self.assertEqual(decrypted_from_clean, plain_msg)
|
||||
|
||||
# Encrypt when locked / encrypt_at_rest is false
|
||||
unenc_state = {"config": {"encrypt_at_rest": False, "public_key": pub}}
|
||||
res_plain = StorageCryptoManager.encrypt_field_for_storage(plain_msg, server_state=unenc_state, is_unlocked=False)
|
||||
self.assertEqual(res_plain, plain_msg)
|
||||
|
||||
def test_storage_engine_sqlite_init_and_telemetry(self):
|
||||
"""Tests SQLite database initialization, WAL settings, column migrations, and telemetry."""
|
||||
db = StorageEngine.init_db(self.db_path, enrollment_secret="sec-test-123", max_seats=50)
|
||||
self.assertEqual(db, self.db_path)
|
||||
self.assertTrue(os.path.exists(db))
|
||||
|
||||
# Re-running init_db executes migrations smoothly
|
||||
db_reinit = StorageEngine.init_db(self.db_path, enrollment_secret="sec-test-123", max_seats=50)
|
||||
self.assertEqual(db_reinit, self.db_path)
|
||||
|
||||
# Check telemetry info
|
||||
state = {"config": {"db_path": self.db_path, "db_type": "sqlite"}}
|
||||
info = StorageEngine.get_storage_engine_info(self.db_path, server_state=state, is_unlocked=True)
|
||||
self.assertEqual(info["engine"], "sqlite")
|
||||
self.assertFalse(info["is_enterprise"])
|
||||
self.assertIn("active_issues", info["table_stats"])
|
||||
self.assertIn("clients", info["table_stats"])
|
||||
|
||||
def test_storage_engine_postgres_mocked(self):
|
||||
"""Tests Postgres schema initialization and SQLite-to-Postgres data migration with mocked drivers."""
|
||||
pg_cfg = {
|
||||
"host": "127.0.0.1",
|
||||
"port": 5432,
|
||||
"database": "logar_test",
|
||||
"user": "logar_user",
|
||||
"password": "secret_password",
|
||||
"sslmode": "disable"
|
||||
}
|
||||
|
||||
# Initialize SQLite DB with test records
|
||||
StorageEngine.init_db(self.db_path, enrollment_secret="sec-migrate", max_seats=10)
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
conn.execute("""
|
||||
INSERT INTO active_issues (fingerprint, site_name, server, signature, severity, message, os_type, first_seen, last_seen, run_count, status, last_run_id)
|
||||
VALUES ('fp1', 'SiteA', 'srv1', 'Sig1', 'HIGH', 'Test warning message', 'linux', '2026-01-01', '2026-01-02', 1, 'active', 'run1')
|
||||
""")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Mock psycopg2
|
||||
mock_psycopg2 = MagicMock()
|
||||
mock_conn = MagicMock()
|
||||
mock_cur = MagicMock()
|
||||
mock_conn.cursor.return_value.__enter__.return_value = mock_cur
|
||||
mock_psycopg2.connect.return_value = mock_conn
|
||||
|
||||
with patch.dict(sys.modules, {"psycopg2": mock_psycopg2}):
|
||||
# Test Postgres DB init
|
||||
ok, msg = StorageEngine.init_postgres_db(pg_cfg)
|
||||
self.assertTrue(ok)
|
||||
self.assertIn("successfully initialized", msg)
|
||||
|
||||
# Test Migration
|
||||
encrypt_mock = lambda x: f"ENC({x})"
|
||||
ok, msg, counts = StorageEngine.migrate_sqlite_to_postgres(self.db_path, pg_cfg, encrypt_fn=encrypt_mock)
|
||||
self.assertTrue(ok)
|
||||
self.assertIn("active_issues", counts)
|
||||
self.assertEqual(counts["active_issues"], 1)
|
||||
|
||||
# Test Postgres info retrieval
|
||||
mock_cur.fetchone.return_value = (10,)
|
||||
state = {"config": {"db_type": "postgres", "postgres": pg_cfg}}
|
||||
info = StorageEngine.get_storage_engine_info(None, server_state=state, is_unlocked=True)
|
||||
self.assertEqual(info["engine"], "postgres")
|
||||
self.assertTrue(info["is_enterprise"])
|
||||
self.assertEqual(info["table_stats"]["active_issues"], 10)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Comprehensive unit tests for LOGAR UpdateManager, AuthManager, and IngestQueue.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from src.hub.update_manager import ServerUpdateManager
|
||||
from src.hub.auth_manager import PasswordManager, TOTPManager, SessionManager, RateLimiter
|
||||
from src.hub.ingest_queue import IngestQueueManager
|
||||
|
||||
|
||||
class TestHubUpdateAuthDeep(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.update_dir = os.path.join(self.temp_dir, "updates")
|
||||
self.db_path = os.path.join(self.temp_dir, "auth_test.db")
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def test_update_manager_caching_and_manifest(self):
|
||||
"""Tests package registration, SHA256 integrity, local caching, and download serving."""
|
||||
manager = ServerUpdateManager(update_dir=self.update_dir, check_enabled=False)
|
||||
|
||||
# Create dummy binaries
|
||||
win_bin = os.path.join(self.temp_dir, "Win_Client.exe")
|
||||
linux_bin = os.path.join(self.temp_dir, "Linux_Client")
|
||||
with open(win_bin, "wb") as f:
|
||||
f.write(b"MZ_TEST_WIN_CLIENT_BINARY_2026")
|
||||
with open(linux_bin, "wb") as f:
|
||||
f.write(b"\x7fELF_TEST_LINUX_CLIENT_BINARY_2026")
|
||||
|
||||
# Register packages
|
||||
res_win = manager.register_client_package(
|
||||
os_type="windows",
|
||||
file_path=win_bin,
|
||||
version="2.5.0",
|
||||
filename="Win_Client.exe"
|
||||
)
|
||||
self.assertEqual(res_win.get("version"), "2.5.0")
|
||||
self.assertEqual(res_win.get("os"), "windows")
|
||||
self.assertIsNotNone(res_win.get("sha256"))
|
||||
|
||||
res_linux = manager.register_client_package(
|
||||
os_type="linux",
|
||||
file_path=linux_bin,
|
||||
version="2.5.0",
|
||||
filename="Linux_Client"
|
||||
)
|
||||
self.assertEqual(res_linux.get("os"), "linux")
|
||||
|
||||
# Get hosted packages
|
||||
packages = manager.get_hosted_packages()
|
||||
self.assertIn("windows", packages)
|
||||
self.assertIn("linux", packages)
|
||||
self.assertEqual(packages["windows"]["version"], "2.5.0")
|
||||
|
||||
# Test download path resolution
|
||||
pkg_res = manager.get_client_package_path(os_type="windows")
|
||||
self.assertIsNotNone(pkg_res)
|
||||
win_path, win_filename, win_sha = pkg_res
|
||||
self.assertTrue(os.path.exists(win_path))
|
||||
self.assertEqual(win_filename, "Win_Client.exe")
|
||||
|
||||
# Test update decision logic
|
||||
decision = manager.check_client_update(client_id="node-win-01", current_version="1.0.0", os_type="windows")
|
||||
self.assertTrue(decision.get("update_available"))
|
||||
self.assertEqual(decision.get("latest_version"), "2.5.0")
|
||||
|
||||
decision_uptodate = manager.check_client_update(client_id="node-win-01", current_version="2.5.0", os_type="windows")
|
||||
self.assertFalse(decision_uptodate.get("update_available"))
|
||||
|
||||
def test_auth_manager_components(self):
|
||||
"""Tests PasswordManager, TOTPManager, SessionManager, and RateLimiter."""
|
||||
# 1. PasswordManager
|
||||
pwd = "Enterprise_Admin_Password_!2026"
|
||||
pwd_hash, salt = PasswordManager.hash_password(pwd)
|
||||
self.assertTrue(PasswordManager.verify_password(pwd, salt, pwd_hash))
|
||||
self.assertFalse(PasswordManager.verify_password("WrongPassword", salt, pwd_hash))
|
||||
|
||||
# 2. TOTPManager
|
||||
secret = TOTPManager.generate_totp_secret()
|
||||
self.assertTrue(len(secret) >= 16)
|
||||
uri = TOTPManager.get_totp_uri("admin", secret, "LOGAR Fleet Hub")
|
||||
self.assertIn("otpauth://totp/", uri)
|
||||
|
||||
token = TOTPManager.get_totp_token(secret)
|
||||
self.assertEqual(len(token), 6)
|
||||
self.assertTrue(TOTPManager.verify_totp_code(secret, token))
|
||||
self.assertFalse(TOTPManager.verify_totp_code(secret, "000000" if token != "000000" else "111111"))
|
||||
|
||||
# 3. SessionManager
|
||||
session_token = SessionManager.generate_session_token("admin", db_path=self.db_path)
|
||||
self.assertIsNotNone(session_token)
|
||||
username = SessionManager.verify_session_token(session_token, db_path=self.db_path)
|
||||
self.assertEqual(username, "admin")
|
||||
self.assertIsNone(SessionManager.verify_session_token("invalid:token:format", db_path=self.db_path))
|
||||
|
||||
# 4. RateLimiter
|
||||
limiter = RateLimiter()
|
||||
ip = "192.168.1.100"
|
||||
for _ in range(5):
|
||||
self.assertTrue(limiter.check_ip_rate_limit(ip, max_attempts=5, window_seconds=60, db_path=self.db_path))
|
||||
limiter.record_failed_ip_attempt(ip, db_path=self.db_path)
|
||||
# 6th attempt exceeded
|
||||
self.assertFalse(limiter.check_ip_rate_limit(ip, max_attempts=5, window_seconds=60, db_path=self.db_path))
|
||||
|
||||
# Reset
|
||||
limiter.reset_failed_ip_attempts(ip, db_path=self.db_path)
|
||||
self.assertTrue(limiter.check_ip_rate_limit(ip, max_attempts=5, window_seconds=60, db_path=self.db_path))
|
||||
|
||||
def test_ingest_queue_lifecycle_and_batching(self):
|
||||
"""Tests IngestQueueManager push, stats, and flush callback."""
|
||||
processed_items = []
|
||||
|
||||
def mock_processor(payload, db_path, window_hours, min_runs):
|
||||
processed_items.extend(payload.get("logs", []))
|
||||
return {"processed": len(payload.get("logs", [])), "promoted_verified": 0}
|
||||
|
||||
queue = IngestQueueManager(flush_interval=0.05, batch_size=10, max_queue=100)
|
||||
|
||||
# Enqueue items
|
||||
for i in range(15):
|
||||
pushed = queue.push({"server": "node1", "logs": [{"id": i, "data": f"log_{i}"}]})
|
||||
self.assertTrue(pushed)
|
||||
|
||||
stats = queue.get_stats()
|
||||
self.assertEqual(stats["queue_size"], 15)
|
||||
self.assertEqual(stats["total_ingested"], 15)
|
||||
|
||||
# Flush
|
||||
flush_res = queue.flush(db_path=self.db_path, process_callback=mock_processor)
|
||||
self.assertEqual(flush_res["status"], "success")
|
||||
self.assertEqual(flush_res["flushed_runs"], 15)
|
||||
self.assertEqual(flush_res["processed_logs"], 15)
|
||||
self.assertEqual(len(processed_items), 15)
|
||||
|
||||
stats_after = queue.get_stats()
|
||||
self.assertEqual(stats_after["queue_size"], 0)
|
||||
self.assertEqual(stats_after["total_flushed"], 15)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user