chore(home): mirror from kalinamall (9883e6a) with papatramp URLs
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
"""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
|
||||
|
||||
from app.models import Host
|
||||
from app.services.agent_poll import build_agent_poll_payload
|
||||
from app.services.agent_update import (
|
||||
AGENT_UPDATE_SUCCESS,
|
||||
AgentUpdateNotAllowedError,
|
||||
process_agent_update_ingest,
|
||||
request_agent_update,
|
||||
)
|
||||
from app.services.agent_update_settings import upsert_agent_update_settings
|
||||
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,
|
||||
mode="sac",
|
||||
recommended_ssh_version="2.1.0-SAC",
|
||||
fallback_after_minutes=10,
|
||||
)
|
||||
assert cfg.mode == "sac"
|
||||
assert cfg.recommended_ssh_version == "2.1.0-SAC"
|
||||
assert cfg.fallback_after_minutes == 10
|
||||
|
||||
|
||||
def test_request_agent_update_sets_pending(db_session):
|
||||
upsert_agent_update_settings(db_session, mode="sac", recommended_ssh_version="2.1.0-SAC")
|
||||
host = Host(
|
||||
hostname="linux-1",
|
||||
os_family="linux",
|
||||
product="ssh-monitor",
|
||||
product_version="2.0.0-SAC",
|
||||
agent_instance_id="agent-uuid-1",
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
request_agent_update(db_session, host)
|
||||
assert host.pending_agent_update is True
|
||||
assert host.pending_update_target_version == "2.1.0-SAC"
|
||||
assert host.agent_update_state == "requested"
|
||||
|
||||
|
||||
def test_request_agent_update_blocked_in_gpo_mode(db_session):
|
||||
upsert_agent_update_settings(db_session, mode="gpo")
|
||||
host = Host(
|
||||
hostname="linux-2",
|
||||
os_family="linux",
|
||||
product="ssh-monitor",
|
||||
agent_instance_id="agent-uuid-2",
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
try:
|
||||
request_agent_update(db_session, host)
|
||||
raise AssertionError("expected AgentUpdateNotAllowedError")
|
||||
except AgentUpdateNotAllowedError:
|
||||
pass
|
||||
|
||||
|
||||
def test_poll_payload_includes_update_and_config(db_session):
|
||||
upsert_agent_update_settings(db_session, mode="sac")
|
||||
host = Host(
|
||||
hostname="win-1",
|
||||
os_family="windows",
|
||||
product="rdp-login-monitor",
|
||||
agent_instance_id="agent-uuid-3",
|
||||
pending_agent_update=True,
|
||||
pending_update_target_version="2.1.0-SAC",
|
||||
agent_config={"ServerDisplayName": "Test PC"},
|
||||
agent_config_revision=3,
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
payload = build_agent_poll_payload(db_session, host)
|
||||
assert payload["config_revision"] == 3
|
||||
assert payload["config"]["settings"]["ServerDisplayName"] == "Test PC"
|
||||
assert payload["update"]["requested"] is True
|
||||
assert payload["update"]["target_version"] == "2.1.0-SAC"
|
||||
assert payload["update"]["source"] == "sac"
|
||||
|
||||
|
||||
def test_ingest_agent_update_success_clears_pending(db_session):
|
||||
host = Host(
|
||||
hostname="linux-3",
|
||||
os_family="linux",
|
||||
product="ssh-monitor",
|
||||
agent_instance_id="agent-uuid-4",
|
||||
pending_agent_update=True,
|
||||
pending_update_target_version="2.1.0-SAC",
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
process_agent_update_ingest(
|
||||
db_session,
|
||||
host,
|
||||
AGENT_UPDATE_SUCCESS,
|
||||
{"product_version": "2.1.0-SAC"},
|
||||
)
|
||||
assert host.pending_agent_update is False
|
||||
assert host.product_version == "2.1.0-SAC"
|
||||
assert host.agent_update_state == "success"
|
||||
|
||||
|
||||
def test_api_request_agent_update(jwt_headers, client, db_session):
|
||||
upsert_agent_update_settings(db_session, mode="sac", recommended_ssh_version="2.1.0-SAC")
|
||||
host = Host(
|
||||
hostname="api-linux",
|
||||
os_family="linux",
|
||||
product="ssh-monitor",
|
||||
agent_instance_id="agent-api-1",
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
response = client.post(f"/api/v1/hosts/{host.id}/actions/request-agent-update", headers=jwt_headers)
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["pending_agent_update"] is True
|
||||
assert body["target_version"] == "2.1.0-SAC"
|
||||
|
||||
|
||||
def test_api_agent_poll_extended(auth_headers, client, db_session):
|
||||
upsert_agent_update_settings(db_session, mode="sac")
|
||||
host = Host(
|
||||
hostname="poll-host",
|
||||
os_family="linux",
|
||||
product="ssh-monitor",
|
||||
agent_instance_id="poll-agent-id",
|
||||
pending_agent_update=True,
|
||||
pending_update_target_version="2.1.0-SAC",
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/agent/commands",
|
||||
params={"agent_instance_id": "poll-agent-id"},
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["update"]["requested"] is True
|
||||
assert body["commands"] == []
|
||||
|
||||
|
||||
def test_api_patch_agent_config(jwt_headers, client, db_session):
|
||||
host = Host(
|
||||
hostname="cfg-host",
|
||||
os_family="windows",
|
||||
product="rdp-login-monitor",
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1/hosts/{host.id}/agent-config",
|
||||
json={"settings": {"ServerDisplayName": "UNMS Kalina", "GetInventory": True}},
|
||||
headers=jwt_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["config_revision"] == 1
|
||||
assert body["settings"]["ServerDisplayName"] == "UNMS Kalina"
|
||||
|
||||
|
||||
def test_api_agent_update_fallback_ssh(jwt_headers, client, db_session, monkeypatch):
|
||||
monkeypatch.setenv("SAC_LINUX_ADMIN_USER", "root")
|
||||
monkeypatch.setenv("SAC_LINUX_ADMIN_PASSWORD", "pw")
|
||||
from app.config import get_settings
|
||||
|
||||
get_settings.cache_clear()
|
||||
|
||||
host = Host(
|
||||
hostname="fb-linux",
|
||||
os_family="linux",
|
||||
product="ssh-monitor",
|
||||
ipv4="10.10.36.9",
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
with patch("app.services.agent_update.run_ssh_monitor_update") as mock_update:
|
||||
from app.services.ssh_connect import SshCommandResult
|
||||
|
||||
mock_update.return_value = SshCommandResult(
|
||||
ok=True,
|
||||
message="updated",
|
||||
target="fb-linux",
|
||||
agent_version="2.1.0-SAC",
|
||||
)
|
||||
response = client.post(
|
||||
f"/api/v1/hosts/{host.id}/actions/agent-update-fallback",
|
||||
headers=jwt_headers,
|
||||
)
|
||||
|
||||
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"
|
||||
|
||||
|
||||
def test_clear_stale_running_remote_actions(db_session):
|
||||
from app.services.host_remote_actions import clear_stale_running_remote_actions
|
||||
|
||||
host = Host(
|
||||
hostname="stale-win",
|
||||
os_family="windows",
|
||||
product="rdp-login-monitor",
|
||||
agent_update_state="running",
|
||||
remote_action={"status": "running", "message": "Подключение…"},
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
cleared = clear_stale_running_remote_actions(db_session, reason="test reset")
|
||||
assert cleared == ["stale-win"]
|
||||
db_session.refresh(host)
|
||||
assert host.agent_update_state == "failed"
|
||||
assert host.remote_action["status"] == "failed"
|
||||
assert host.remote_action["ok"] is False
|
||||
|
||||
|
||||
def test_get_remote_action_status_ignores_stale_running_payload(db_session):
|
||||
from app.services.host_remote_actions import get_remote_action_status
|
||||
|
||||
host = Host(
|
||||
hostname="done-linux",
|
||||
os_family="linux",
|
||||
product="ssh-monitor",
|
||||
agent_update_state="success",
|
||||
remote_action={
|
||||
"status": "running",
|
||||
"message": "Выполняется обновление… (лог с хоста)",
|
||||
"output": "=== Script update completed successfully ===",
|
||||
"ok": True,
|
||||
"finished_at": "2026-07-08T02:21:11+00:00",
|
||||
},
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
status = get_remote_action_status(host)
|
||||
assert status["active"] is False
|
||||
assert status["agent_update_state"] == "success"
|
||||
|
||||
|
||||
def test_cancel_host_remote_job_api(jwt_headers, client, db_session):
|
||||
host = Host(
|
||||
hostname="cancel-me",
|
||||
os_family="windows",
|
||||
product="rdp-login-monitor",
|
||||
agent_update_state="running",
|
||||
remote_action={"status": "running", "title": "Обновление через WinRM"},
|
||||
)
|
||||
db_session.add(host)
|
||||
db_session.commit()
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1/hosts/{host.id}/actions/remote-job/cancel",
|
||||
headers=jwt_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["active"] is False
|
||||
assert body["status"] == "failed"
|
||||
Reference in New Issue
Block a user