Files
Agent-System-Dev/tests/test_svc_user_binding.py
T
agent-exec-transport f84d7c724e
ci / svc-realcombo (pull_request) Successful in 2m55s
ci / test (pull_request) Successful in 9m8s
ci / test-pg (pull_request) Successful in 20m53s
agent-exec transport 8c38703572870841a8c0c81bef7c6773/e8f9fe72723f
2026-10-22 12:31:34 +00:00

181 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""svc.sh 账户 CLI 实例绑定(U1 规格 §6 T1–T5)。
覆盖:合法 manifest 下 user list 命中 data_root+open-existing(T1)、禁止覆盖
--data(T2)、删除 data 根后 OpenExisting 拒绝且不建空库(T3)、无 flock 拒绝
user/deploy(T4)。T5 身份回归由 test_svc_identity 同发现执行;此处仅冒烟。
桩与夹具复用 tests/test_svc_recover.py;manifest 辅助对齐 test_svc_identity。
"""
import importlib.util
import json
import os
import shutil
import sys
import unittest
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _port_alloc import free_test_port # noqa: E402
SOURCE = Path(__file__).resolve().parents[1]
_spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py")
tsr = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(tsr)
class UserBindingTests(tsr.SvcAutoRecoverTests):
def setUp(self):
super().setUp()
shutil.copy2(SOURCE / "scripts" / "svc_manifest.py",
self.root / "scripts" / "svc_manifest.py")
self.trace.mkdir(exist_ok=True)
def manifest(self, name):
p = self.runtime(name) / "manifest.json"
return json.loads(p.read_text(encoding="utf-8")) if p.exists() else None
def running(self, name):
pid = self.pid_of(name)
if not pid:
return False
try:
os.kill(pid, 0)
return True
except OSError:
return False
def touch_db(self, name):
data = Path(self.manifest(name)["identity"]["data_root"])
data.mkdir(parents=True, exist_ok=True)
(data / "agent-system.db").write_bytes(b"sqlite-stub")
def cli_trace(self):
f = self.trace / "cli.txt"
return f.read_text(encoding="utf-8") if f.exists() else ""
def clear_cli_trace(self):
f = self.trace / "cli.txt"
if f.exists():
f.unlink()
# ---- T1 合法 manifest:user list 绑定 data_root + --open-existing ----
def test_T1_user_list_binds_manifest_data(self):
port = free_test_port()
self.seed_agent("ub1", port)
self.start_ok("ub1")
self.svc("ub1", "stop")
root = self.manifest("ub1")["identity"]["data_root"]
self.touch_db("ub1")
self.clear_cli_trace()
result = self.svc("ub1", "user", "list")
self.assertIn("op", result.stdout)
trace = self.cli_trace()
self.assertIn("user list", trace)
self.assertIn("--open-existing", trace)
self.assertIn("--data %s" % root, trace)
self.assertNotIn("./data", trace)
# ---- T2 禁止覆盖 --data;未绑定默认目录拒绝 ----
def test_T2_reject_override_data_flag(self):
port = free_test_port()
self.seed_agent("ub2", port)
self.start_ok("ub2")
self.svc("ub2", "stop")
self.touch_db("ub2")
other = self.root / "evil-data"
other.mkdir()
(other / "agent-system.db").write_bytes(b"x")
result = self.svc("ub2", "user", "list", "--data", str(other), check=False)
self.assertNotEqual(result.returncode, 0)
self.assertTrue(any(k in result.stdout for k in ("禁止覆盖", "勿传")), result.stdout)
self.assertFalse((self.trace / "cli.txt").exists(), "拒绝后不得调用二进制")
def test_T2b_reject_literal_default_data(self):
d = self.runtime("ub2b")
(d / "bin").mkdir(parents=True)
shutil.copy2(self.stub, d / "bin" / "agent-system")
(d / "svc.env").write_text("".join("%s=%s\n" % kv for kv in [
("kind", "agent"), ("env", "ub2b"), ("pid", ""),
("addr", "127.0.0.1:9443"), ("tags", ""),
("source", "local@deadbee"), ("version", "stub"), ("started", ""),
("pidcheck", str(d / "bin" / "agent-system")),
("exec", "--addr 127.0.0.1:9443 --data ./data --plugins %s" % (d / "plugins")),
]))
env = dict(self.env, SVC_UB2B_DATA="./data")
result = self.svc("ub2b", "user", "list", check=False, env=env)
self.assertNotEqual(result.returncode, 0)
self.assertTrue(any(k in result.stdout for k in ("未绑定数据目录", "不回落")),
result.stdout)
# ---- T3 删除 data 根:OpenExisting 失败,不建空库 ----
def test_T3_missing_data_root_rejected(self):
port = free_test_port()
self.seed_agent("ub3", port)
self.start_ok("ub3")
self.svc("ub3", "stop")
root = Path(self.manifest("ub3")["identity"]["data_root"])
self.touch_db("ub3")
shutil.rmtree(root)
self.assertFalse(root.exists())
result = self.svc("ub3", "user", "create", "--owner", "x", "--name", "x",
check=False)
self.assertNotEqual(result.returncode, 0)
self.assertTrue(any(k in result.stdout for k in ("既有库不存在", "OpenExisting")),
result.stdout)
self.assertFalse(root.exists(), "拒绝后不得重建数据根")
self.assertFalse((root / "agent-system.db").exists())
# ---- T4 隐藏 flock:user/deploy 拒绝;已运行服务仍在 ----
def test_T4_no_flock_rejects_user_and_deploy(self):
port = free_test_port()
self.seed_agent("ub4", port)
self.start_ok("ub4")
self.assertTrue(self.running("ub4"))
self.touch_db("ub4")
# 构造不含 flock 的 PATH:链入常用外部命令,刻意排除 flock。
bin_dir = self.root / "nobin"
bin_dir.mkdir()
for name in (
"bash", "python3", "python", "dirname", "basename", "tr", "cksum",
"cut", "mkdir", "cat", "sed", "grep", "head", "rm", "mv", "cp",
"ls", "chmod", "uname", "date", "sleep", "kill", "ps", "awk",
"sort", "uniq", "env", "printf", "realpath", "readlink", "stat",
"tee", "wc", "find", "xargs", "touch", "ln", "mktemp", "id",
"git", "make", "tar", "gzip", "which", "command",
):
src = shutil.which(name)
if src:
dest = bin_dir / name
if not dest.exists():
dest.symlink_to(src)
self.assertIsNone(shutil.which("flock", path=str(bin_dir)))
env = dict(self.env, PATH=str(bin_dir))
r_user = self.svc("ub4", "user", "list", check=False, env=env)
self.assertNotEqual(r_user.returncode, 0)
self.assertIn("缺少 flock", r_user.stdout)
r_dep = self.svc("ub4", "deploy", "--dry-run", check=False, env=env)
self.assertNotEqual(r_dep.returncode, 0, r_dep.stdout)
self.assertIn("缺少 flock", r_dep.stdout)
self.assertTrue(self.running("ub4"), "拒绝后旧服务应仍在跑")
self.svc("ub4", "stop")
# ---- T5 recovery U1 身份冒烟(完整套件见 test_svc_identity)----
def test_T5_identity_regression_smoke(self):
port = free_test_port()
self.seed_agent("ub5", port)
self.start_ok("ub5")
iid = self.manifest("ub5")["instance_id"]
self.svc("ub5", "stop")
self.start_ok("ub5")
self.assertEqual(self.manifest("ub5")["instance_id"], iid)
self.svc("ub5", "stop")
if __name__ == "__main__":
unittest.main()