Files
LOGAR/tests/test_server.py
T
2026-09-30 17:33:50 +02:00

787 lines
35 KiB
Python

import os
import sys
import json
import socket
import struct
import sqlite3
import unittest
import urllib.request
import uuid
import warnings
from datetime import datetime, timezone, timedelta
warnings.filterwarnings("ignore")
# Ensure parent directory and src directory are in path to import Server
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
import Server
import pgpy
class TestServerComponent(unittest.TestCase):
def setUp(self):
self.test_db = f"test_server_state_{uuid.uuid4().hex[:8]}.db"
self.test_config = f"test_server_config_{uuid.uuid4().hex[:8]}.json"
def tearDown(self):
if os.path.exists(self.test_db):
try:
os.remove(self.test_db)
except Exception:
pass
if os.path.exists(self.test_config):
try:
os.remove(self.test_config)
except Exception:
pass
def test_first_run_config_and_keypair_generation(self):
config = Server.load_or_init_config(self.test_config)
self.assertTrue(os.path.exists(self.test_config))
self.assertIn("server_fingerprint", config)
self.assertIn("public_key", config)
self.assertIn("private_key", config)
self.assertIn("auth_token", config)
self.assertNotIn("site_name", config)
# Verify keypair
priv_key, _ = pgpy.PGPKey.from_blob(config["private_key"])
pub_key, _ = pgpy.PGPKey.from_blob(config["public_key"])
self.assertEqual(str(pub_key.fingerprint), config["server_fingerprint"])
def test_create_client_config(self):
Server.load_or_init_config(self.test_config)
client_out = "test_client_out.json"
try:
client_conf = Server.create_client_config(
server_host="10.0.0.1",
server_port=9443,
output_path=client_out,
config_path=self.test_config
)
self.assertTrue(os.path.exists(client_out))
self.assertEqual(client_conf["server_host"], "10.0.0.1")
self.assertEqual(client_conf["server_port"], 9443)
# Verify no machine name or site_name is included
self.assertNotIn("server_name", client_conf)
self.assertNotIn("name", client_conf)
self.assertNotIn("site_name", client_conf)
finally:
if os.path.exists(client_out):
os.remove(client_out)
def test_direct_hermes_anomaly_and_12h_window(self):
Server.init_db(self.test_db)
log_entry = {
"server": "app-worker-01.corp.local",
"signature": "PostgresConnWarning",
"severity": "WARNING",
"message": "Connection to database pool near capacity: 85%",
"os_type": "linux"
}
payload = {
"server": "app-worker-01.corp.local",
"logs": [log_entry]
}
# Run 1: WARNING is immediately VERIFIED for Hermes AI reasoning
res1 = Server.process_ingested_logs(payload, self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res1["status"], "success")
self.assertEqual(res1["promoted_verified"], 1)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT run_count, status FROM active_issues WHERE signature = ?", ("PostgresConnWarning",))
row = c.fetchone()
conn.close()
self.assertEqual(row[0], 1)
self.assertEqual(row[1], "VERIFIED")
# Runs 2 to 4: Increments consecutive run count while remaining VERIFIED
for run_idx in range(2, 5):
res = Server.process_ingested_logs(payload, self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res["status"], "success")
self.assertEqual(res["promoted_verified"], 0)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT run_count, status FROM active_issues WHERE signature = ?", ("PostgresConnWarning",))
row = c.fetchone()
conn.close()
self.assertEqual(row[0], 4)
self.assertEqual(row[1], "VERIFIED")
def test_error_immediate_pass(self):
Server.init_db(self.test_db)
log_entry = {
"server": "app-worker-01.corp.local",
"signature": "KernelPanicCritical",
"severity": "ERROR",
"message": "Kernel panic - not syncing: Fatal hardware error",
"os_type": "linux"
}
payload = {
"server": "app-worker-01.corp.local",
"logs": [log_entry]
}
# Run 1: ERROR must immediately promote to VERIFIED
res = Server.process_ingested_logs(payload, self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res["status"], "success")
self.assertEqual(res["promoted_verified"], 1)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT run_count, status, severity FROM active_issues WHERE signature = ?", ("KernelPanicCritical",))
row = c.fetchone()
conn.close()
self.assertIsNotNone(row)
self.assertEqual(row[0], 1)
self.assertEqual(row[1], "VERIFIED")
self.assertEqual(row[2], "ERROR")
def test_server_severity_filtering(self):
Server.init_db(self.test_db)
payload = {
"server": "app-worker-01.corp.local",
"logs": [
{"server": "app-worker-01", "signature": "SigInfo", "severity": "INFO", "message": "Info msg", "os_type": "linux"},
{"server": "app-worker-01", "signature": "SigWarn", "severity": "WARNING", "message": "Warn msg", "os_type": "linux"},
{"server": "app-worker-01", "signature": "SigErr", "severity": "ERROR", "message": "Err msg", "os_type": "linux"},
{"server": "app-worker-01", "signature": "SigDebug", "severity": "DEBUG", "message": "Debug msg", "os_type": "linux"},
{"server": "app-worker-01", "signature": "SigTrace", "severity": "TRACE", "message": "Trace msg", "os_type": "linux"}
]
}
res = Server.process_ingested_logs(payload, self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res["status"], "success")
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT signature, status FROM active_issues ORDER BY signature")
rows = dict(c.fetchall())
conn.close()
self.assertIn("SigInfo", rows)
self.assertIn("SigWarn", rows)
self.assertIn("SigErr", rows)
self.assertNotIn("SigDebug", rows)
self.assertNotIn("SigTrace", rows)
# All actionable issues (ERROR, WARNING, INFO) are promoted to VERIFIED immediately for Hermes reasoning
self.assertEqual(rows["SigErr"], "VERIFIED")
self.assertEqual(rows["SigWarn"], "VERIFIED")
self.assertEqual(rows["SigInfo"], "VERIFIED")
def test_license_schema_and_pki_generation(self):
secret = "test-secret-12345"
Server.init_db(self.test_db, enrollment_secret=secret, max_seats=5)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT max_seats, enrollment_secret FROM license_config WHERE id = 1")
row = c.fetchone()
conn.close()
self.assertIsNotNone(row)
self.assertEqual(row[0], 5)
self.assertEqual(row[1], secret)
# Test Dynamic PKI
test_cert_dir = "test_certs_pki"
try:
ca_cert, ca_key, ca_pem, ca_key_pem = Server.enrollment.generate_ca_if_needed(cert_dir=test_cert_dir)
self.assertIn("BEGIN CERTIFICATE", ca_pem)
self.assertIn("BEGIN RSA PRIVATE KEY", ca_key_pem)
srv_cert, srv_key, srv_pem, srv_key_pem = Server.enrollment.generate_server_cert_if_needed(
ca_cert, ca_key, hostnames=["127.0.0.1", "localhost"], cert_dir=test_cert_dir
)
self.assertIn("BEGIN CERTIFICATE", srv_pem)
client_cert_pem, client_key_pem = Server.enrollment.issue_client_cert("node-test-1", ca_cert, ca_key)
self.assertIn("BEGIN CERTIFICATE", client_cert_pem)
self.assertIn("BEGIN RSA PRIVATE KEY", client_key_pem)
fp = Server.enrollment.calculate_cert_fingerprint(client_cert_pem)
self.assertEqual(len(fp), 64)
finally:
import shutil
if os.path.exists(test_cert_dir):
shutil.rmtree(test_cert_dir, ignore_errors=True)
def test_enrollment_endpoint_and_seat_quota(self):
from fastapi import HTTPException
secret = "super-secret-enrollment"
max_seats = 2
Server.init_db(self.test_db, enrollment_secret=secret, max_seats=max_seats)
test_cert_dir = "test_certs_enroll"
try:
ca_cert, ca_key, ca_pem, _ = Server.enrollment.generate_ca_if_needed(cert_dir=test_cert_dir)
Server.SERVER_STATE["config"] = {"db_path": self.test_db, "cert_dir": test_cert_dir}
Server.SERVER_STATE["ca_cert"] = ca_cert
Server.SERVER_STATE["ca_key"] = ca_key
Server.SERVER_STATE["ca_cert_pem"] = ca_pem
# 1. Invalid secret should raise 403
bad_req = Server.ClientEnrollRequest(
client_id="client-1",
hostname="host-1",
os="linux",
enrollment_secret="wrong-secret"
)
with self.assertRaises(HTTPException) as cm:
Server.enroll_client(bad_req)
self.assertEqual(cm.exception.status_code, 403)
# 2. Valid enrollment for client 1
req1 = Server.ClientEnrollRequest(
client_id="client-1",
hostname="host-1",
os="linux",
enrollment_secret=secret
)
resp1 = Server.enroll_client(req1)
self.assertIn("client_cert", resp1)
self.assertIn("client_key", resp1)
self.assertTrue(resp1["ca_cert"].strip().startswith(ca_pem.strip()))
# 3. Valid enrollment for client 2
req2 = Server.ClientEnrollRequest(
client_id="client-2",
hostname="host-2",
os="windows",
enrollment_secret=secret
)
resp2 = Server.enroll_client(req2)
self.assertIn("client_cert", resp2)
# 4. Seat quota exhausted: client 3 should raise 403
req3 = Server.ClientEnrollRequest(
client_id="client-3",
hostname="host-3",
os="linux",
enrollment_secret=secret
)
with self.assertRaises(HTTPException) as cm:
Server.enroll_client(req3)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("License seat limit reached", cm.exception.detail)
# 5. Re-enrollment for existing client 1 should succeed
resp1_re = Server.enroll_client(req1)
self.assertIn("client_cert", resp1_re)
# 6. Revoked client should be rejected
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE clients SET status = 'revoked' WHERE client_id = 'client-1'")
conn.commit()
conn.close()
with self.assertRaises(HTTPException) as cm:
Server.enroll_client(req1)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("revoked", cm.exception.detail)
finally:
import shutil
if os.path.exists(test_cert_dir):
shutil.rmtree(test_cert_dir, ignore_errors=True)
def test_cert_validity_and_hub_pki_renewal(self):
test_cert_dir = "test_certs_renew"
try:
ca_cert, ca_key, ca_pem, _ = Server.enrollment.generate_ca_if_needed(cert_dir=test_cert_dir)
srv_cert, srv_key, srv_pem, _ = Server.enrollment.generate_server_cert_if_needed(
ca_cert, ca_key, hostnames=["127.0.0.1"], cert_dir=test_cert_dir
)
# 1. Freshly generated certificates should NOT be expiring soon with standard 30-day threshold
self.assertFalse(Server.enrollment.is_cert_expiring_soon(ca_pem, threshold_days=30))
self.assertFalse(Server.enrollment.is_cert_expiring_soon(srv_pem, threshold_days=30))
# 2. Huge threshold (e.g. 5000 days) should flag expiration
self.assertTrue(Server.enrollment.is_cert_expiring_soon(srv_pem, threshold_days=5000))
# 3. check_and_renew_hub_pki with standard threshold should report no renewal needed
ca_renewed, srv_renewed = Server.enrollment.check_and_renew_hub_pki(cert_dir=test_cert_dir, threshold_days=30)
self.assertFalse(ca_renewed)
self.assertFalse(srv_renewed)
# 4. In-flight reload of SSLContext
ssl_ctx = Server.init_mtls_server_context(cert_dir=test_cert_dir)
Server.SERVER_STATE["ssl_ctx"] = ssl_ctx
Server.SERVER_STATE["config"] = {"db_path": self.test_db, "cert_dir": test_cert_dir, "tcp_host": "127.0.0.1"}
# Trigger rotation using high threshold
rotated = Server.check_and_rotate_server_certs(cert_dir=test_cert_dir, hostnames=["127.0.0.1"], threshold_days=5000)
self.assertTrue(rotated)
# Check that backup files were generated
bak_files = [f for f in os.listdir(test_cert_dir) if f.endswith(".bak")]
self.assertGreater(len(bak_files), 0)
finally:
import shutil
if os.path.exists(test_cert_dir):
shutil.rmtree(test_cert_dir, ignore_errors=True)
def test_user_auth_and_session(self):
"""Tests admin password hashing, verification, session tokens, and password change."""
Server.init_db(self.test_db)
# Verify default admin credentials
self.assertTrue(Server.verify_user_credentials("admin", "admin_logar_2026!", db_path=self.test_db))
self.assertFalse(Server.verify_user_credentials("admin", "wrong_password", db_path=self.test_db))
self.assertFalse(Server.verify_user_credentials("nonexistent", "password", db_path=self.test_db))
# Test session token generation & validation
token = Server.generate_session_token("admin")
self.assertEqual(Server.verify_session_token(token), "admin")
self.assertIsNone(Server.verify_session_token("invalid.token.structure"))
# Test changing password
Server.change_user_password("admin", "new_secure_pass123", db_path=self.test_db)
self.assertFalse(Server.verify_user_credentials("admin", "admin_logar_2026!", db_path=self.test_db))
self.assertTrue(Server.verify_user_credentials("admin", "new_secure_pass123", db_path=self.test_db))
def test_hermes_tokens_and_auth(self):
"""Tests Hermes Bearer token creation, verification, revocation, and deletion."""
Server.init_db(self.test_db)
# Test default Hermes token was created on init
tokens = Server.list_hermes_tokens(db_path=self.test_db)
self.assertEqual(len(tokens), 1)
self.assertEqual(tokens[0]["status"], "active")
default_token = tokens[0]["token"]
self.assertTrue(Server.verify_hermes_token(default_token, db_path=self.test_db))
# Test creating new token
new_tok = Server.create_hermes_token(name="Hermes Autonomous Agent 01", db_path=self.test_db)
self.assertIn("token", new_tok)
self.assertEqual(new_tok["name"], "Hermes Autonomous Agent 01")
self.assertTrue(Server.verify_hermes_token(new_tok["token"], db_path=self.test_db))
# Test revoking token
self.assertTrue(Server.revoke_hermes_token(new_tok["token"], db_path=self.test_db))
self.assertFalse(Server.verify_hermes_token(new_tok["token"], db_path=self.test_db))
# Test deleting token
self.assertTrue(Server.delete_hermes_token(new_tok["token"], db_path=self.test_db))
tokens_after = Server.list_hermes_tokens(db_path=self.test_db)
self.assertEqual(len(tokens_after), 1)
def test_client_profile_export(self):
"""Tests client profile dictionary generation and export formatting."""
cfg = Server.load_or_init_config(self.test_config)
profile = Server.generate_client_profile_dict(
server_host="logar-hub.corp.internal",
server_port=9443,
client_id="worker-node-101",
config_dict=cfg
)
self.assertEqual(profile["server_host"], "logar-hub.corp.internal")
self.assertEqual(profile["server_port"], 9443)
self.assertEqual(profile["client_id"], "worker-node-101")
self.assertEqual(profile["server_fingerprint"], cfg["server_fingerprint"])
self.assertEqual(profile["server_public_key"], cfg["public_key"])
self.assertEqual(profile["enrollment_secret"], cfg["enrollment_secret"])
self.assertEqual(profile["format"], "LOGAR_CLIENT_PROFILE")
def test_client_seat_management_and_relative_time(self):
"""Tests client listing, seat approval, revocation, deletion, and relative timestamp helper."""
Server.init_db(self.test_db, enrollment_secret="test-sec-123", max_seats=5)
Server.SERVER_STATE["config"] = {"db_path": self.test_db}
# Populate test clients
conn = sqlite3.connect(self.test_db)
now = datetime.now(timezone.utc)
five_min_ago = (now - timedelta(minutes=5)).strftime("%Y-%m-%d %H:%M:%S")
two_hours_ago = (now - timedelta(hours=2)).strftime("%Y-%m-%d %H:%M:%S")
conn.execute("INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, last_seen) VALUES ('c-win', 'WIN-PROD-01', 'windows', 'FP_W1', 'active', ?)", (five_min_ago,))
conn.execute("INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, last_seen) VALUES ('c-lin', 'LIN-PROD-01', 'linux', 'FP_L1', 'active', ?)", (two_hours_ago,))
conn.commit()
conn.close()
# 1. Test listing
client_data = Server.list_clients()
self.assertEqual(client_data["active_seats"], 2)
self.assertEqual(client_data["max_seats"], 5)
self.assertEqual(len(client_data["clients"]), 2)
self.assertEqual(client_data["clients"][0]["last_activity_relative"], "5m ago")
# 2. Test revoking
rev_req = Server.ClientActionRequest(client_id="c-win")
rev_res = Server.revoke_client(rev_req)
self.assertEqual(rev_res["status"], "revoked")
client_data = Server.list_clients()
self.assertEqual(client_data["active_seats"], 1)
# 3. Test approving / reactivating
app_res = Server.approve_client(rev_req)
self.assertEqual(app_res["status"], "active")
client_data = Server.list_clients()
self.assertEqual(client_data["active_seats"], 2)
# 4. Test deleting client record
del_res = Server.delete_client(rev_req)
self.assertEqual(del_res["status"], "deleted")
client_data = Server.list_clients()
self.assertEqual(len(client_data["clients"]), 1)
self.assertEqual(client_data["active_seats"], 1)
# 5. Test format_relative_time edge cases
self.assertEqual(Server.format_relative_time(None), "Never")
self.assertEqual(Server.format_relative_time(""), "Never")
self.assertEqual(Server.format_relative_time("invalid-date-string"), "invalid-date-str")
now_fresh = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
rel = Server.format_relative_time(now_fresh)
self.assertTrue(rel.endswith("s ago") or rel == "Just now", f"Unexpected relative time: {rel}")
def test_integrated_local_log_collector_zero_seats(self):
"""Tests that local hub log collection runs without license, registering a 0-seat built-in client."""
Server.init_db(self.test_db, enrollment_secret="test-sec-456", max_seats=0)
Server.SERVER_STATE["config"] = {
"db_path": self.test_db,
"evaluation_window_hours": 12,
"min_persistence_runs": 1
}
# Run cycle
state = {}
processed = Server.run_local_log_collection_cycle(self.test_db, window_hours=12, min_runs=1, state=state)
# Verify local hub client entry exists in clients table
machine_id = Server.get_machine_identifier()
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT client_id, cert_fingerprint, status FROM clients WHERE client_id = ?", (machine_id,))
row = c.fetchone()
conn.close()
self.assertIsNotNone(row)
self.assertEqual(row[0], machine_id)
self.assertEqual(row[1], "LOCAL-HOST-BUILTIN")
self.assertEqual(row[2], "active")
# Verify active seat count is STILL 0 (exempt from seat accounting)
client_data = Server.list_clients()
self.assertEqual(client_data["active_seats"], 0)
self.assertEqual(len(client_data["clients"]), 1)
def test_two_factor_auth_and_totp_flow(self):
"""Tests TOTP secret generation, token verification, URI generation, and enable/disable lifecycle."""
Server.init_db(self.test_db)
# 1. Test TOTP helpers
secret = Server.generate_totp_secret()
self.assertTrue(len(secret) >= 16)
token = Server.get_totp_token(secret)
self.assertEqual(len(token), 6)
self.assertTrue(token.isdigit())
self.assertTrue(Server.verify_totp_code(secret, token))
self.assertFalse(Server.verify_totp_code(secret, "000000" if token != "000000" else "999999"))
self.assertFalse(Server.verify_totp_code(secret, "invalid"))
# 2. Test OTPAuth URI
uri = Server.get_totp_uri("admin", secret, issuer="LOGAR Fleet Hub")
self.assertTrue(uri.startswith("otpauth://totp/"))
self.assertIn("secret=" + secret, uri)
self.assertIn("LOGAR", uri)
# 3. Test initial user 2FA status
info = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertFalse(info["enabled"])
self.assertEqual(info["secret"], "")
# 4. Test enabling 2FA
enabled = Server.enable_user_totp("admin", secret, db_path=self.test_db)
self.assertTrue(enabled)
info_active = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertTrue(info_active["enabled"])
self.assertEqual(info_active["secret"], secret)
# 5. Test disabling 2FA
disabled = Server.disable_user_totp("admin", db_path=self.test_db)
self.assertTrue(disabled)
info_disabled = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertFalse(info_disabled["enabled"])
def test_persistent_session_secret_and_rate_limiting(self):
"""Tests DB persistence of session secret and sliding window rate limiting."""
Server.init_db(self.test_db)
# 1. Session secret persistence
sec1 = Server.get_session_secret(self.test_db)
self.assertTrue(len(sec1) >= 32)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT value FROM server_meta WHERE key = 'session_secret'")
row = c.fetchone()
conn.close()
self.assertIsNotNone(row)
self.assertEqual(row[0], sec1)
# Token generated with test_db
token = Server.generate_session_token("admin", db_path=self.test_db)
self.assertEqual(Server.verify_session_token(token, db_path=self.test_db), "admin")
# 2. Rate limiting
test_ip = "10.0.0.99"
Server.reset_failed_ip_attempts(test_ip, scope="login", db_path=self.test_db)
self.assertTrue(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="login", db_path=self.test_db))
Server.record_failed_ip_attempt(test_ip, scope="login", db_path=self.test_db)
Server.record_failed_ip_attempt(test_ip, scope="login", db_path=self.test_db)
self.assertTrue(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="login", db_path=self.test_db))
Server.record_failed_ip_attempt(test_ip, scope="login", db_path=self.test_db)
self.assertFalse(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="login", db_path=self.test_db))
# Different scope ('enroll') should still be allowed
self.assertTrue(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="enroll", db_path=self.test_db))
Server.reset_failed_ip_attempts(test_ip, scope="login", db_path=self.test_db)
self.assertTrue(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="login", db_path=self.test_db))
def test_ingest_queue_manager_and_batch_flush(self):
"""Tests IngestQueueManager buffering, capacity bounds, and atomic batch flushing."""
Server.init_db(self.test_db)
qm = Server.IngestQueueManager(flush_interval=1.0, batch_size=5, max_queue=50)
# Initial stats
stats = qm.get_stats()
self.assertEqual(stats["queue_size"], 0)
self.assertEqual(stats["total_ingested"], 0)
self.assertEqual(stats["total_flushed"], 0)
# Push 3 payloads
for i in range(3):
payload = {
"server": f"node-{i}.corp.local",
"logs": [
{"server": f"node-{i}", "signature": f"Sig_{i}", "severity": "WARNING", "message": f"Log msg {i}", "os_type": "linux"}
]
}
pushed = qm.push(payload)
self.assertTrue(pushed)
stats = qm.get_stats()
self.assertEqual(stats["queue_size"], 3)
self.assertEqual(stats["total_ingested"], 3)
# Flush to database
res_flush = qm.flush(db_path=self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res_flush["flushed_runs"], 3)
self.assertEqual(res_flush["processed_logs"], 3)
# Verify DB records
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT COUNT(*) FROM ingest_runs")
run_count = c.fetchone()[0]
c.execute("SELECT COUNT(*) FROM active_issues WHERE status = 'VERIFIED'")
issue_count = c.fetchone()[0]
conn.close()
self.assertEqual(run_count, 3)
self.assertEqual(issue_count, 3)
# Stats after flush
stats = qm.get_stats()
self.assertEqual(stats["queue_size"], 0)
self.assertEqual(stats["total_flushed"], 3)
def test_enterprise_db_feature_gating(self):
"""Tests feature check helpers and DB storage info under standard and enterprise licensed states."""
Server.init_db(self.test_db)
# 1. Standard / Unlicensed state
self.assertFalse(Server.is_feature_licensed("enterprise_db", db_path=self.test_db))
info = Server.get_storage_engine_info(db_path=self.test_db)
self.assertEqual(info["engine"], "sqlite")
self.assertFalse(info["feature_unlocked"])
self.assertIn("SQLite 3 (WAL Mode)", info["backend_display"])
# 2. Unlock enterprise_db feature in DB
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE license_config SET features = ? WHERE id = 1", (json.dumps(["enterprise_db", "ai_heuristics"]),))
conn.commit()
conn.close()
self.assertTrue(Server.is_feature_licensed("enterprise_db", db_path=self.test_db))
self.assertTrue(Server.is_feature_licensed("ai_heuristics", db_path=self.test_db))
self.assertFalse(Server.is_feature_licensed("unknown_feat", db_path=self.test_db))
info_unlocked = Server.get_storage_engine_info(db_path=self.test_db)
self.assertTrue(info_unlocked["feature_unlocked"])
self.assertIn("enterprise_db", info_unlocked["licensed_features"])
def test_postgres_storage_endpoints_authorization(self):
"""Tests REST API security gating on DB engine management endpoints."""
Server.init_db(self.test_db)
Server.SERVER_STATE["config"] = {"db_path": self.test_db, "storage_engine": "sqlite"}
# 1. Test direct authorization & endpoint logic
stats = Server.api_db_stats()
self.assertIn("queue", stats)
self.assertIn("storage", stats)
self.assertFalse(stats["enterprise_db_licensed"])
# 2. Test Connection without enterprise_db -> HTTPException 403
pg_req = Server.DbConnectionTestRequest(host="localhost", port=5432, database="logar", user="postgres", password="")
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_test_connection(pg_req)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
# 3. Switch backend without enterprise_db -> HTTPException 403
switch_req = Server.DbSwitchBackendRequest(db_type="postgres", host="localhost", port=5432, database="logar", user="postgres", password="")
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_switch_backend(switch_req)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
# 4. Flush buffer endpoint executes successfully
flush_res = Server.api_db_flush_buffer()
self.assertEqual(flush_res["status"], "success")
# 5. Optional HTTP client verification if starlette testclient dependencies are present
try:
from fastapi.testclient import TestClient
client = TestClient(Server.app)
# Unauthenticated request to /api/db/stats -> 401
res = client.get("/api/db/stats")
self.assertEqual(res.status_code, 401)
# Authenticated request with session cookie
token = Server.generate_session_token("admin", db_path=self.test_db)
client.cookies.set(Server.SESSION_COOKIE_NAME, token)
res_auth = client.get("/api/db/stats")
self.assertEqual(res_auth.status_code, 200)
data = res_auth.json()
self.assertIn("queue", data)
self.assertIn("storage", data)
self.assertFalse(data["enterprise_db_licensed"])
# 403 checks via HTTP client
res_test = client.post("/api/db/test-connection", json={"host": "localhost", "port": 5432, "database": "logar", "user": "postgres", "password": ""})
self.assertEqual(res_test.status_code, 403)
res_switch = client.post("/api/db/switch-backend", json={"db_type": "postgres", "host": "localhost", "port": 5432, "database": "logar", "user": "postgres", "password": ""})
self.assertEqual(res_switch.status_code, 403)
res_flush = client.post("/api/db/flush-buffer")
self.assertEqual(res_flush.status_code, 200)
except (ImportError, RuntimeError, Exception):
# Starlette TestClient may raise RuntimeError when httpx/httpx2 is missing in lightweight runner environments
pass
def test_db_encryption_at_rest_and_full_config_api(self):
"""Tests OpenPGP data-at-rest encryption/decryption and full DB/Cache configuration API."""
Server.init_db(self.test_db)
priv_armored, pub_armored, fp = Server.generate_server_keypair("test-db-node")
priv_key, _ = pgpy.PGPKey.from_blob(priv_armored)
pub_key, _ = pgpy.PGPKey.from_blob(pub_armored)
Server.SERVER_STATE["config"] = {
"db_path": self.test_db,
"storage_engine": "sqlite",
"cache_mode": "direct",
"encrypt_at_rest": False,
"private_key": priv_armored,
"public_key": pub_armored,
"server_fingerprint": fp
}
Server.SERVER_STATE["private_key_obj"] = priv_key
Server.SERVER_STATE["public_key_obj"] = pub_key
# 1. Test OpenPGP encryption & decryption roundtrip
raw_msg = "Critical database corruption: segment 0x89FA damaged"
encrypted_msg = Server.encrypt_field_for_storage(raw_msg, force=True)
self.assertTrue(encrypted_msg.startswith("-----BEGIN PGP MESSAGE-----"))
self.assertNotIn("Critical database corruption", encrypted_msg)
decrypted_msg = Server.decrypt_field_from_storage(encrypted_msg)
self.assertEqual(decrypted_msg, raw_msg)
# 2. Test that without enterprise_db license, buffering, encryption at rest, and postgres are blocked
cfg_unlicensed_buf = Server.DbFullConfigRequest(
storage_engine="sqlite",
cache_mode="buffered",
encrypt_at_rest=False
)
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_update_config(cfg_unlicensed_buf)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
cfg_unlicensed_enc = Server.DbFullConfigRequest(
storage_engine="sqlite",
cache_mode="direct",
encrypt_at_rest=True
)
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_update_config(cfg_unlicensed_enc)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
pg_req = Server.DbFullConfigRequest(
storage_engine="postgres",
cache_mode="direct",
pg_config=Server.DbConnectionTestRequest(host="db.corp.internal", port=5432, database="logar", user="postgres", password="")
)
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_update_config(pg_req)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
# 3. Standard local SQLite configuration succeeds when unlicensed
cfg_standard = Server.DbFullConfigRequest(
storage_engine="sqlite",
cache_mode="direct",
encrypt_at_rest=False
)
res_std = Server.api_db_update_config(cfg_standard)
self.assertEqual(res_std["status"], "success")
# 4. Unlock enterprise_db feature in license_config
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE license_config SET features = ? WHERE id = 1", (json.dumps(["enterprise_db"]),))
conn.commit()
conn.close()
# 5. Test updating config with enterprise_db unlocked (direct -> buffered, encrypt_at_rest = True)
cfg_req = Server.DbFullConfigRequest(
storage_engine="sqlite",
cache_mode="buffered",
flush_interval=4.0,
batch_size=200,
max_queue=25000,
encrypt_at_rest=True
)
res = Server.api_db_update_config(cfg_req)
self.assertEqual(res["status"], "success")
self.assertEqual(Server.SERVER_STATE["config"]["cache_mode"], "buffered")
self.assertTrue(Server.SERVER_STATE["config"]["encrypt_at_rest"])
self.assertEqual(Server.INGEST_QUEUE.flush_interval, 4.0)
self.assertEqual(Server.INGEST_QUEUE.batch_size, 200)
# 6. Verify storage engine info output
info = Server.get_storage_engine_info(db_path=self.test_db)
self.assertEqual(info["cache_mode"], "buffered")
self.assertTrue(info["encrypt_at_rest"])
self.assertEqual(info["flush_interval"], 4.0)
if __name__ == "__main__":
unittest.main()