c8b0663508
批量添加替代CSV并支持去重;脚本库页内嵌执行历史;推送历史记录 rsync 权限策略; 脚本执行不再依赖 Agent 在线;服务器同步日志与相关单测补齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
66 lines
1.8 KiB
Python
66 lines
1.8 KiB
Python
"""Script dispatch must use SSH readiness, not Agent is_online heartbeat."""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from server.application.services.script_service import ScriptService
|
|
from server.domain.models import Server
|
|
|
|
|
|
def _make_service() -> ScriptService:
|
|
return ScriptService(
|
|
script_repo=MagicMock(),
|
|
execution_repo=MagicMock(),
|
|
credential_repo=MagicMock(),
|
|
server_repo=MagicMock(),
|
|
audit_repo=MagicMock(),
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_ssh_when_agent_offline():
|
|
"""Servers without Agent heartbeat should still receive SSH script exec."""
|
|
service = _make_service()
|
|
server = Server(
|
|
id=7,
|
|
name="srv-7",
|
|
domain="10.0.0.7",
|
|
port=22,
|
|
username="root",
|
|
auth_method="password",
|
|
password="enc",
|
|
is_online=False,
|
|
connectivity="ok",
|
|
)
|
|
service.server_repo.get_by_id = AsyncMock(return_value=server)
|
|
|
|
mock_exec = AsyncMock(
|
|
return_value={"status": "ok", "stdout": "ok\n", "stderr": "", "exit_code": 0},
|
|
)
|
|
with patch.object(service, "_call_ssh_exec", mock_exec):
|
|
results = await service._dispatch_to_servers([7], "echo ok", timeout=30)
|
|
|
|
assert results["7"]["exit_code"] == 0
|
|
mock_exec.assert_awaited_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dispatch_skips_missing_ssh_credentials():
|
|
service = _make_service()
|
|
server = Server(
|
|
id=8,
|
|
name="srv-8",
|
|
domain="10.0.0.8",
|
|
port=22,
|
|
username="root",
|
|
auth_method="key",
|
|
is_online=True,
|
|
)
|
|
service.server_repo.get_by_id = AsyncMock(return_value=server)
|
|
|
|
results = await service._dispatch_to_servers([8], "echo ok", timeout=30)
|
|
|
|
assert results["8"]["exit_code"] == -1
|
|
assert "SSH" in results["8"]["stderr"]
|