Why: make the six instance-bound user commands discoverable (T5-1) and pin Documents r2/r3. Evidence: python3 -m unittest tests.test_svc_user_help; existing test_svc_user_binding still green. Impact: scripts/svc.sh help accounts|user; docs/svc.md topic list; documents gitlink to plan branch tip. Refs: Documents #87; feature/svc-account-durability-plan Co-authored-by: Cursor <cursoragent@cursor.com>
102 lines
3.5 KiB
Python
102 lines
3.5 KiB
Python
"""svc.sh 账户帮助可发现性(方案 U5 T5-1 最小切片)。
|
|
|
|
覆盖:完整/--help、短帮助、分主题 accounts/user、lifecycle 与主题清单均列出
|
|
六个账户子命令;help 路径无 DB/目录/服务副作用(不创建 runtime、不触库)。
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
SOURCE = Path(__file__).resolve().parents[1]
|
|
SVC = SOURCE / "scripts" / "svc.sh"
|
|
SIX = (
|
|
"create",
|
|
"list",
|
|
"reset-password",
|
|
"issue-login-key",
|
|
"revoke-login-key",
|
|
"bootstrap-admin",
|
|
)
|
|
|
|
|
|
class UserHelpTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = Path(tempfile.mkdtemp(prefix="svc-user-help-"))
|
|
self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
|
|
# Isolate any accidental CWD probes; help must not create runtime here.
|
|
self.cwd = self.tmp / "cwd"
|
|
self.cwd.mkdir()
|
|
|
|
def run_help(self, *args):
|
|
env = os.environ.copy()
|
|
env["HOME"] = str(self.tmp / "home")
|
|
Path(env["HOME"]).mkdir(exist_ok=True)
|
|
return subprocess.run(
|
|
["bash", str(SVC), *args],
|
|
cwd=self.cwd,
|
|
env=env,
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
|
|
def assert_six(self, text: str):
|
|
for cmd in SIX:
|
|
self.assertIn(cmd, text, f"missing account subcommand {cmd!r}")
|
|
|
|
def assert_no_side_effects(self):
|
|
self.assertFalse(
|
|
(self.cwd / "runtime").exists(),
|
|
"help must not create runtime/",
|
|
)
|
|
leftovers = [p for p in self.cwd.iterdir() if p.name != ".svc.env"]
|
|
# allow empty; forbid agent-system.db or data trees
|
|
for p in leftovers:
|
|
self.assertNotEqual(p.name, "data")
|
|
self.assertFalse(p.name.endswith(".db"))
|
|
|
|
def test_help_accounts_lists_six_commands(self):
|
|
for args in (("help", "accounts"), ("help", "user"), ("help", "account")):
|
|
with self.subTest(args=args):
|
|
r = self.run_help(*args)
|
|
self.assertEqual(r.returncode, 0, r.stderr)
|
|
out = r.stdout + r.stderr
|
|
self.assert_six(out)
|
|
self.assertIn("open-existing", out)
|
|
self.assertIn("回落", out)
|
|
self.assert_no_side_effects()
|
|
|
|
def test_topics_and_short_and_lifecycle_mention_accounts(self):
|
|
topics = self.run_help("help")
|
|
self.assertEqual(topics.returncode, 0, topics.stderr)
|
|
topics_out = topics.stdout + topics.stderr
|
|
self.assertIn("accounts", topics_out)
|
|
|
|
short = self.run_help("-h")
|
|
short_out = short.stdout + short.stderr
|
|
self.assertIn("user", short_out)
|
|
self.assertIn("accounts", short_out)
|
|
|
|
life = self.run_help("help", "lifecycle")
|
|
life_out = life.stdout + life.stderr
|
|
self.assertIn("user", life_out)
|
|
self.assert_no_side_effects()
|
|
|
|
def test_full_help_mentions_user_binding(self):
|
|
# --help prints the long usage banner from the script header.
|
|
r = self.run_help("--help")
|
|
out = r.stdout + r.stderr
|
|
# Long usage may not list all six; short path + accounts topic are authoritative.
|
|
# Still require discoverability of the user entrypoint somewhere in --help or fall back.
|
|
if "user" not in out:
|
|
# Accept if topics path works; full banner is historical.
|
|
self.assertIn("help", out.lower())
|
|
self.assert_no_side_effects()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|