feat: background remote agent updates survive SAC navigation (0.20.19)
This commit is contained in:
@@ -104,16 +104,32 @@ def mock_agent_git_release(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_remote_action_state():
|
||||
from app.services import host_remote_actions
|
||||
|
||||
with host_remote_actions._lock:
|
||||
host_remote_actions._running.clear()
|
||||
yield
|
||||
with host_remote_actions._lock:
|
||||
host_remote_actions._running.clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(db_session, monkeypatch):
|
||||
def client(db_session, db_engine, monkeypatch):
|
||||
monkeypatch.setenv("SAC_REMOTE_ACTION_INLINE", "1")
|
||||
monkeypatch.setattr("app.main.bootstrap_api_key", lambda: None)
|
||||
monkeypatch.setattr("app.main.bootstrap_users", lambda: None)
|
||||
|
||||
test_session_local = sessionmaker(bind=db_engine, autocommit=False, autoflush=False)
|
||||
monkeypatch.setattr("app.services.host_remote_actions.SessionLocal", test_session_local)
|
||||
|
||||
def override_get_db():
|
||||
session = test_session_local()
|
||||
try:
|
||||
yield db_session
|
||||
yield session
|
||||
finally:
|
||||
pass
|
||||
session.close()
|
||||
|
||||
fastapi_app.dependency_overrides[get_db] = override_get_db
|
||||
with TestClient(fastapi_app) as c:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for agent update settings, poll, request and ingest lifecycle."""
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import patch
|
||||
@@ -17,6 +18,21 @@ from app.services.ingest import ingest_event
|
||||
from tests.test_ingest import VALID_EVENT
|
||||
|
||||
|
||||
def wait_remote_job(client, host_id, headers, timeout=5.0):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
response = client.get(
|
||||
f"/api/v1/hosts/{host_id}/actions/remote-job",
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
if not body.get("active") and body.get("status") != "running":
|
||||
return body
|
||||
time.sleep(0.05)
|
||||
raise AssertionError("remote job did not finish in time")
|
||||
|
||||
|
||||
def test_agent_update_settings_sac_mode(db_session):
|
||||
cfg = upsert_agent_update_settings(
|
||||
db_session,
|
||||
@@ -203,6 +219,8 @@ def test_api_agent_update_fallback_ssh(jwt_headers, client, db_session, monkeypa
|
||||
headers=jwt_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["ok"] is True
|
||||
assert response.json()["product_version"] == "2.1.0-SAC"
|
||||
assert response.status_code == 202
|
||||
assert response.json()["status"] == "running"
|
||||
job = wait_remote_job(client, host.id, jwt_headers)
|
||||
assert job["ok"] is True
|
||||
assert job["product_version"] == "2.1.0-SAC"
|
||||
|
||||
@@ -4,6 +4,6 @@ from app.version import APP_NAME, APP_VERSION, APP_VERSION_LABEL
|
||||
|
||||
|
||||
def test_version_constants():
|
||||
assert APP_VERSION == "0.20.1"
|
||||
assert APP_VERSION == "0.20.19"
|
||||
assert APP_NAME == "Security Alert Center"
|
||||
assert APP_VERSION_LABEL == "Security Alert Center v.0.20.1"
|
||||
assert APP_VERSION_LABEL == "Security Alert Center v.0.20.19"
|
||||
|
||||
@@ -4,6 +4,7 @@ import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.config import get_settings
|
||||
from app.models import Event, Host, Problem
|
||||
@@ -138,13 +139,14 @@ def test_delete_host_removes_events_and_problems(client, db_session, jwt_headers
|
||||
)
|
||||
db_session.commit()
|
||||
|
||||
r = client.delete(f"/api/v1/hosts/{h.id}", headers=jwt_headers)
|
||||
host_id = h.id
|
||||
r = client.delete(f"/api/v1/hosts/{host_id}", headers=jwt_headers)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["hostname"] == "test-host"
|
||||
assert body["deleted_events"] == 1
|
||||
assert body["deleted_problems"] == 1
|
||||
assert db_session.get(Host, h.id) is None
|
||||
assert db_session.scalar(select(Host.id).where(Host.id == host_id)) is None
|
||||
|
||||
|
||||
def test_delete_host_404(client, jwt_headers):
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
"""Additional Linux admin API tests."""
|
||||
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def wait_remote_job(client, host_id, headers, timeout=5.0):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
response = client.get(
|
||||
f"/api/v1/hosts/{host_id}/actions/remote-job",
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
if not body.get("active") and body.get("status") != "running":
|
||||
return body
|
||||
time.sleep(0.05)
|
||||
raise AssertionError("remote job did not finish in time")
|
||||
|
||||
|
||||
def test_get_linux_admin_settings_env_default(jwt_headers, client, monkeypatch):
|
||||
monkeypatch.setenv("SAC_LINUX_ADMIN_USER", "")
|
||||
monkeypatch.setenv("SAC_LINUX_ADMIN_PASSWORD", "")
|
||||
@@ -137,7 +153,7 @@ def test_host_agent_update_success(jwt_headers, client, db_session, monkeypatch)
|
||||
db_session.commit()
|
||||
db_session.refresh(host)
|
||||
|
||||
with patch("app.api.v1.hosts.run_ssh_monitor_update") as mock_update:
|
||||
with patch("app.services.host_remote_actions.run_ssh_monitor_update") as mock_update:
|
||||
mock_update.return_value = SshCommandResult(
|
||||
ok=True,
|
||||
message="SSH OK (ubabuba), exit 0\nSUMMARY updated",
|
||||
@@ -150,9 +166,11 @@ def test_host_agent_update_success(jwt_headers, client, db_session, monkeypatch)
|
||||
headers=jwt_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.status_code == 202
|
||||
body = response.json()
|
||||
assert body["ok"] is True
|
||||
assert body["target"] == "ubabuba"
|
||||
if "product_version" in body:
|
||||
assert body["product_version"] is None or isinstance(body["product_version"], str)
|
||||
assert body["status"] == "running"
|
||||
job = wait_remote_job(client, host.id, jwt_headers)
|
||||
assert job["ok"] is True
|
||||
assert job["target"] == "ubabuba"
|
||||
if "product_version" in job:
|
||||
assert job["product_version"] is None or isinstance(job["product_version"], str)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlalchemy import inspect as sa_inspect
|
||||
|
||||
from app.models import Event, Host
|
||||
from app.services.winrm_connect import WinRmCmdResult
|
||||
|
||||
@@ -47,6 +49,7 @@ def test_qwinsta_via_winrm_on_client_host(jwt_headers, client, db_session, monke
|
||||
gw = Host(hostname="K6A-DC3", os_family="windows", product="rdp-login-monitor", ipv4="192.168.160.40")
|
||||
db_session.add_all([ws, gw])
|
||||
db_session.commit()
|
||||
ws_id = ws.id
|
||||
event = _flap_event(db_session, gw=gw, ws=ws)
|
||||
|
||||
qwinsta_out = " SESSIONNAME USERNAME ID STATE\r\n rdp-tcp#0 B26\\papatramp 2 Active\r\n"
|
||||
@@ -63,6 +66,10 @@ def test_qwinsta_via_winrm_on_client_host(jwt_headers, client, db_session, monke
|
||||
["Andrisonova-PC=OK"],
|
||||
)
|
||||
response = client.post(f"/api/v1/events/{event.id}/actions/qwinsta", headers=jwt_headers)
|
||||
mock_run.assert_called_once()
|
||||
called_identity = sa_inspect(mock_run.call_args.args[0]).identity
|
||||
assert called_identity is not None
|
||||
assert called_identity[0] == ws_id
|
||||
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
@@ -71,10 +78,6 @@ def test_qwinsta_via_winrm_on_client_host(jwt_headers, client, db_session, monke
|
||||
assert body["target"] == "Andrisonova-PC"
|
||||
assert "papatramp" in (body["result_stdout"] or "")
|
||||
|
||||
mock_run.assert_called_once()
|
||||
called_host = mock_run.call_args.args[0]
|
||||
assert called_host.hostname == "Andrisonova-PC"
|
||||
|
||||
|
||||
def test_qwinsta_client_not_in_hosts(jwt_headers, client, db_session, monkeypatch):
|
||||
monkeypatch.setenv("SAC_WIN_ADMIN_USER", r"B26\admin")
|
||||
|
||||
Reference in New Issue
Block a user