Files
Nexus/tests/test_btpanel_ssh_bootstrap.py
T
r 3195787461 fix(btpanel): 非 root SSH bootstrap 自动 sudo 提权
ubuntu 等用户免密 sudo 下宝塔已装但脚本未提权导致 ssh_command_failed;附 ssh_timeout 运维清单。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 10:40:46 +08:00

179 lines
5.6 KiB
Python

"""Tests for Baota SSH bootstrap state machine and remote script parsing."""
import json
import pytest
from server.infrastructure.btpanel.bootstrap_state import (
BOOTSTRAP_RETRY_SECONDS,
STATE_FAILED,
STATE_INSTALLING,
STATE_PENDING,
STATE_READY,
apply_bootstrap_failure,
apply_bootstrap_success,
is_eligible_for_background_bootstrap,
mark_bootstrap_pending,
merge_bootstrap_fields,
)
from server.infrastructure.btpanel.ssh_bootstrap import (
BootstrapRemoteError,
build_bootstrap_command,
classify_ssh_result,
parse_bootstrap_stdout,
wrap_sudo_nopasswd,
)
from server.domain.models import Server
class _Srv:
def __init__(self, extra_attrs=None):
self.extra_attrs = extra_attrs or {}
def test_mark_bootstrap_pending_sets_now():
extra = mark_bootstrap_pending({})
block = extra["bt_panel"]
assert block["bootstrap_state"] == STATE_PENDING
assert block["bootstrap_next_attempt_at"] is not None
assert block["auto_bootstrap"] is True
def test_apply_failure_bt_not_installed_goes_installing():
extra = apply_bootstrap_failure({}, error_code="bt_not_installed", permanent=False)
block = extra["bt_panel"]
assert block["bootstrap_state"] == STATE_INSTALLING
assert block["bootstrap_next_attempt_at"] is not None
assert block["bootstrap_last_error"] == "bt_not_installed"
def test_apply_failure_ssh_auth_is_permanent():
extra = apply_bootstrap_failure({}, error_code="ssh_auth_failed", permanent=True)
block = extra["bt_panel"]
assert block["bootstrap_state"] == STATE_FAILED
assert block.get("bootstrap_next_attempt_at") is None
def test_apply_success_sets_ready(monkeypatch):
monkeypatch.setattr(
"server.infrastructure.btpanel.credentials.encrypt_value",
lambda s: f"enc:{s}",
)
extra = apply_bootstrap_success(
{},
base_url="https://1.2.3.4:8888/abc",
api_key="token123",
verify_ssl=False,
actions=["enabled_api"],
)
block = extra["bt_panel"]
assert block["bootstrap_state"] == STATE_READY
assert block["base_url"] == "https://1.2.3.4:8888/abc"
assert block["api_key_enc"] == "enc:token123"
assert block.get("bootstrap_next_attempt_at") is None
def test_is_eligible_respects_next_attempt():
from datetime import datetime, timedelta, timezone
past = (datetime.now(timezone.utc) - timedelta(seconds=10)).isoformat().replace("+00:00", "Z")
future = (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat().replace("+00:00", "Z")
server_due = Server(
id=1, name="a", domain="1.2.3.4",
extra_attrs={"bt_panel": {
"bootstrap_state": STATE_PENDING,
"bootstrap_next_attempt_at": past,
"auto_bootstrap": True,
}},
)
server_later = Server(
id=2, name="b", domain="1.2.3.5",
extra_attrs={"bt_panel": {
"bootstrap_state": STATE_INSTALLING,
"bootstrap_next_attempt_at": future,
"auto_bootstrap": True,
}},
)
server_never_enrolled = Server(id=3, name="c", domain="1.2.3.6", extra_attrs={})
assert is_eligible_for_background_bootstrap(server_due) is True
assert is_eligible_for_background_bootstrap(server_later) is False
assert is_eligible_for_background_bootstrap(server_never_enrolled) is False
def test_classify_ssh_auth_permanent():
code, permanent = classify_ssh_result({
"status": "success",
"exit_code": 1,
"stderr": "Permission denied (publickey).",
})
assert code == "ssh_auth_failed"
assert permanent is True
def test_classify_api_json_permission_not_permanent():
code, permanent = classify_ssh_result({
"status": "success",
"exit_code": 1,
"stdout": "permission denied: '/www/server/panel/data/api.json'",
"stderr": "",
})
assert code == "ssh_permission_denied"
assert permanent is False
def test_parse_bootstrap_stdout_ok():
payload = {"ok": True, "base_url": "https://x:8888", "api_key": "t", "actions": []}
data = parse_bootstrap_stdout(json.dumps(payload))
assert data["base_url"] == "https://x:8888"
def test_parse_bootstrap_stdout_remote_error():
payload = {"ok": False, "error": "bt_not_installed", "message": "panel not installed"}
with pytest.raises(BootstrapRemoteError) as exc:
parse_bootstrap_stdout(json.dumps(payload))
assert exc.value.code == "bt_not_installed"
def test_build_bootstrap_command_includes_payload():
cmd = build_bootstrap_command(center_ip="10.0.0.1", panel_host="203.0.113.1")
assert "10.0.0.1" in cmd
assert "203.0.113.1" in cmd
assert "nexus_bt_bootstrap.py" in cmd
def test_wrap_sudo_nopasswd_root_unchanged():
inner = "echo hi"
assert wrap_sudo_nopasswd(inner, "root") == inner
def test_wrap_sudo_nopasswd_ubuntu():
inner = "echo hi"
wrapped = wrap_sudo_nopasswd(inner, "ubuntu")
assert wrapped.startswith("sudo -n bash -c ")
assert inner in wrapped
def test_classify_sudo_password_required():
code, permanent = classify_ssh_result({
"status": "success",
"exit_code": 1,
"stderr": "sudo: a password is required",
})
assert code == "ssh_sudo_required"
assert permanent is True
def test_classify_ssh_result_success_exit_zero():
ok_stdout = json.dumps(
{"ok": True, "base_url": "https://x:8888", "api_key": "t", "actions": [], "verify_ssl": False}
)
code, permanent = classify_ssh_result(
{"status": "success", "stdout": ok_stdout, "stderr": "", "exit_code": 0}
)
assert code == ""
assert permanent is False
def test_retry_interval_constant():
assert BOOTSTRAP_RETRY_SECONDS == 300