135 lines
5.5 KiB
Python
135 lines
5.5 KiB
Python
import json
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import tomllib
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "scripts"
|
|
if str(SCRIPTS_DIR) not in sys.path:
|
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
|
|
import agent_install # noqa: E402
|
|
|
|
|
|
def make_project(root: Path) -> None:
|
|
(root / "pyproject.toml").write_text('[project]\nname = "test"\n', encoding="utf-8")
|
|
(root / "uv.lock").write_text("version = 1\n", encoding="utf-8")
|
|
(root / "config.example.json").write_text(json.dumps({"env": "nb-mcp", "collections": {}}), encoding="utf-8")
|
|
|
|
|
|
def fake_which(name: str) -> str | None:
|
|
return {
|
|
"git": "/usr/bin/git",
|
|
"uv": "/opt/bin/uv",
|
|
"nb": "/opt/bin/nb",
|
|
}.get(name)
|
|
|
|
|
|
class AgentInstallTests(unittest.TestCase):
|
|
def test_inspect_reports_required_tools_and_config_as_optional_before_prepare(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
make_project(root)
|
|
|
|
result = agent_install.inspect_project(root, which=fake_which, python_version=(3, 12, 4))
|
|
|
|
self.assertTrue(result["ok"])
|
|
checks = {check["name"]: check for check in result["checks"]}
|
|
self.assertTrue(checks["uv"]["ok"])
|
|
self.assertFalse(checks["config"]["ok"])
|
|
self.assertFalse(checks["config"]["required"])
|
|
|
|
def test_prepare_is_idempotent_and_never_overwrites_existing_config(self) -> None:
|
|
calls = []
|
|
|
|
def runner(command, **kwargs):
|
|
calls.append((command, kwargs))
|
|
return subprocess.CompletedProcess(command, 0, stdout="", stderr="")
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
make_project(root)
|
|
|
|
first = agent_install.prepare_project(root, runner=runner, which=fake_which, python_version=(3, 12, 4))
|
|
config_path = root / "config.json"
|
|
config_path.write_text(json.dumps({"env": "custom-env"}), encoding="utf-8")
|
|
second = agent_install.prepare_project(root, runner=runner, which=fake_which, python_version=(3, 12, 4))
|
|
|
|
preserved = json.loads(config_path.read_text(encoding="utf-8"))
|
|
|
|
self.assertTrue(first["configCreated"])
|
|
self.assertFalse(second["configCreated"])
|
|
self.assertEqual(preserved["env"], "custom-env")
|
|
self.assertEqual(calls[0][0], ["/opt/bin/uv", "sync", "--locked", "--python", "3.12"])
|
|
self.assertEqual(len(calls), 2)
|
|
|
|
def test_set_env_backs_up_config_before_changing_it(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
make_project(root)
|
|
original = {"env": "nb-mcp", "collections": {"customers": {"resource": "customers"}}}
|
|
(root / "config.json").write_text(json.dumps(original), encoding="utf-8")
|
|
|
|
result = agent_install.set_config_env(root, "nb-mcp-acme")
|
|
|
|
changed = json.loads((root / "config.json").read_text(encoding="utf-8"))
|
|
backup = json.loads((root / "config.json.pre-agent-install.bak").read_text(encoding="utf-8"))
|
|
|
|
self.assertTrue(result["changed"])
|
|
self.assertEqual(changed["env"], "nb-mcp-acme")
|
|
self.assertEqual(backup, original)
|
|
|
|
def test_client_config_uses_absolute_paths_and_is_read_only_by_default(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
config = agent_install.render_client_config(root, which=fake_which)
|
|
writable = agent_install.render_client_config(root, enable_writes=True, which=fake_which)
|
|
|
|
server = config["mcpServers"]["nocobase-crm"]
|
|
self.assertEqual(server["command"], "/opt/bin/uv")
|
|
self.assertEqual(server["env"]["NOCOBASE_MCP_NB_BIN"], "/opt/bin/nb")
|
|
self.assertIn(str(root.resolve()), server["args"])
|
|
self.assertNotIn("--enable-writes", server["args"])
|
|
self.assertIn("--enable-writes", writable["mcpServers"]["nocobase-crm"]["args"])
|
|
|
|
def test_doctor_checks_local_install_without_reading_nb_secrets(self) -> None:
|
|
commands = []
|
|
|
|
def runner(command, **kwargs):
|
|
commands.append(command)
|
|
return subprocess.CompletedProcess(command, 0, stdout="", stderr="")
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
make_project(root)
|
|
(root / "config.json").write_text(json.dumps({"env": "nb-mcp"}), encoding="utf-8")
|
|
|
|
result = agent_install.doctor_project(
|
|
root,
|
|
runner=runner,
|
|
which=fake_which,
|
|
python_version=(3, 12, 4),
|
|
)
|
|
|
|
self.assertTrue(result["ok"])
|
|
self.assertEqual(commands[0], ["/opt/bin/uv", "lock", "--check"])
|
|
self.assertNotIn("--show-secrets", json.dumps(result))
|
|
self.assertTrue(any("crm_health" in step for step in result["remainingVerification"]))
|
|
|
|
def test_release_manifest_version_matches_package_version(self) -> None:
|
|
root = Path(__file__).resolve().parent.parent
|
|
manifest = json.loads((root / "install-manifest.json").read_text(encoding="utf-8"))
|
|
package = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
|
|
|
|
version = package["project"]["version"]
|
|
self.assertEqual(manifest["version"], version)
|
|
self.assertEqual(manifest["distribution"]["releaseRef"], f"v{version}")
|
|
self.assertTrue(manifest["securityDefaults"]["readOnly"])
|
|
self.assertFalse(manifest["securityDefaults"]["uploads"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|