forked from Keelrleek/Harbor
Messages, threads, acknowledgements, claims, bands, tasks, review items and stop flags for a team of AI agents; the chat page and board; host hooks and MCP server; autopilot; tools/watch.py. Put under version control after the weekend's changes so they have history and can be rolled back. Local only.
156 lines
7.9 KiB
Python
156 lines
7.9 KiB
Python
"""Installer tests against a fake user home (HARBOR_INSTALL_HOME) - real host configs are never touched."""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import tomllib
|
|
import unittest
|
|
|
|
import yaml
|
|
|
|
from helpers import make_home, temp_dir
|
|
from harbor import install
|
|
|
|
CODEX_CONFIG = '''model = "gpt"
|
|
notify = ["x.exe", "turn-ended"]
|
|
|
|
[features]
|
|
|
|
[mcp_servers.node_repl]
|
|
command = "node"
|
|
|
|
[tui.model_availability_nux]
|
|
seen = true
|
|
'''
|
|
DSH_PATCH = '''# Your patch layer
|
|
- id: ui-chat
|
|
name: "@deepseek-ai/dsh-client-ui-chat"
|
|
config:
|
|
transcriptView: standard
|
|
- id: tools
|
|
config:
|
|
mode: !!js process.env.DSH_TOOLS_MODE
|
|
'''
|
|
|
|
|
|
class Installer(unittest.TestCase):
|
|
def setUp(self):
|
|
self.dir = temp_dir()
|
|
self.home = self.dir / "user"
|
|
(self.home / ".codex").mkdir(parents=True)
|
|
(self.home / ".codex" / "config.toml").write_text(CODEX_CONFIG, encoding="utf-8")
|
|
(self.home / ".codex" / "AGENTS.md").write_text("# My rules\n\nBe brief.\n", encoding="utf-8")
|
|
(self.home / ".config" / "opencode").mkdir(parents=True)
|
|
(self.home / ".config" / "opencode" / "opencode.jsonc").write_text('{\n "$schema": "https://opencode.ai/config.json"\n}', encoding="utf-8")
|
|
(self.home / ".dsh" / "profiles" / "desktop").mkdir(parents=True)
|
|
(self.home / ".dsh" / "profiles" / "desktop" / "cordis.patch.yml").write_text(DSH_PATCH, encoding="utf-8")
|
|
self.harbor_home = self.dir / "harbor-home"
|
|
make_home(self.harbor_home)
|
|
self.saved = {k: os.environ.get(k) for k in ("HARBOR_INSTALL_HOME", "HARBOR_HOME", "HARBOR_PROJECT", "APPDATA")}
|
|
os.environ.update(HARBOR_INSTALL_HOME=str(self.home), HARBOR_HOME=str(self.harbor_home),
|
|
APPDATA=str(self.dir / "appdata"))
|
|
os.environ.pop("HARBOR_PROJECT", None)
|
|
self.cwd = os.getcwd()
|
|
os.chdir(self.dir) # resolve projects from the test folder, not from wherever the suite runs
|
|
|
|
def tearDown(self):
|
|
os.chdir(self.cwd)
|
|
for k, v in self.saved.items():
|
|
if v is None:
|
|
os.environ.pop(k, None)
|
|
else:
|
|
os.environ[k] = v
|
|
shutil.rmtree(self.dir, ignore_errors=True)
|
|
|
|
def read(self, *parts):
|
|
return (self.home.joinpath(*parts)).read_text(encoding="utf-8")
|
|
|
|
def test_codex_install_is_idempotent_and_reversible(self):
|
|
install.install("codex", python="py -3.12")
|
|
config = tomllib.loads(self.read(".codex", "config.toml"))
|
|
server = config["mcp_servers"]["harbor"]
|
|
self.assertEqual(server["env"]["HARBOR_AGENT"], "Codex")
|
|
self.assertEqual(server["args"][-3:], ["--as", "Codex", "mcp"])
|
|
self.assertEqual(config["mcp_servers"]["node_repl"]["command"], "node") # untouched
|
|
self.assertTrue(config["tui"]["model_availability_nux"]["seen"])
|
|
hooks = json.loads(self.read(".codex", "hooks.json"))["hooks"]
|
|
self.assertEqual(sorted(hooks), ["PreToolUse", "SessionStart", "Stop", "UserPromptSubmit"])
|
|
self.assertIn("hook pre-tool --host codex", hooks["PreToolUse"][0]["hooks"][0]["command"])
|
|
self.assertIn("Be brief.", self.read(".codex", "AGENTS.md"))
|
|
self.assertIn("you are **Codex**", self.read(".codex", "AGENTS.md"))
|
|
once = self.read(".codex", "config.toml")
|
|
report = install.install("codex", python="py -3.12")
|
|
self.assertEqual(self.read(".codex", "config.toml"), once)
|
|
self.assertTrue(all(line.startswith("unchanged") for line in report if line.startswith(("unchanged", "update", "create"))))
|
|
install.install("codex", remove=True)
|
|
self.assertEqual(self.read(".codex", "config.toml"), CODEX_CONFIG)
|
|
self.assertEqual(self.read(".codex", "AGENTS.md"), "# My rules\n\nBe brief.\n")
|
|
self.assertFalse((self.home / ".codex" / "hooks.json").exists())
|
|
backups = list((self.harbor_home / "backups" / "integrations").rglob("config.toml"))
|
|
self.assertTrue(backups)
|
|
|
|
def test_codex_keeps_other_hooks(self):
|
|
(self.home / ".codex" / "hooks.json").write_text(json.dumps(
|
|
{"hooks": {"Stop": [{"hooks": [{"type": "command", "command": "mine.exe"}]}]}}), encoding="utf-8")
|
|
install.install("codex", python="py -3.12")
|
|
stop = json.loads(self.read(".codex", "hooks.json"))["hooks"]["Stop"]
|
|
self.assertEqual(len(stop), 2)
|
|
install.install("codex", remove=True)
|
|
self.assertEqual(json.loads(self.read(".codex", "hooks.json")),
|
|
{"hooks": {"Stop": [{"hooks": [{"type": "command", "command": "mine.exe"}]}]}})
|
|
|
|
def test_opencode(self):
|
|
install.install("opencode", python="py -3.12")
|
|
config = json.loads(self.read(".config", "opencode", "opencode.jsonc"))
|
|
self.assertEqual(config["$schema"], "https://opencode.ai/config.json")
|
|
self.assertEqual(config["mcp"]["harbor"]["environment"], {"HARBOR_AGENT": "OpenCode", "HARBOR_HOST": "opencode"})
|
|
plugin = self.read(".config", "opencode", "plugin", "harbor.js")
|
|
self.assertIn('const AGENT = "OpenCode";', plugin)
|
|
self.assertIn('const COMMAND = ["py", "-3.12", "-B",', plugin)
|
|
self.assertIn("export const HarborPlugin", plugin)
|
|
install.install("opencode", remove=True)
|
|
self.assertEqual(json.loads(self.read(".config", "opencode", "opencode.jsonc")),
|
|
{"$schema": "https://opencode.ai/config.json"})
|
|
self.assertFalse((self.home / ".config" / "opencode" / "plugin" / "harbor.js").exists())
|
|
|
|
def test_deepseek(self):
|
|
install.install("deepseek", python="py -3.12")
|
|
patch = self.read(".dsh", "profiles", "desktop", "cordis.patch.yml")
|
|
self.assertTrue(patch.startswith(DSH_PATCH.rstrip("\n")))
|
|
rows = install._TolerantLoader.load(patch)
|
|
inserted = rows[-1]["insert"]
|
|
self.assertEqual([r["id"] for r in inserted], ["mcp-harbor", "hooks-harbor"])
|
|
self.assertEqual(inserted[0]["config"]["env"], {"HARBOR_AGENT": "DeepSeek", "HARBOR_HOST": "deepseek"})
|
|
hooks = json.loads((self.harbor_home / "integrations" / "deepseek" / "hooks.json").read_text(encoding="utf-8"))
|
|
self.assertIn("--host deepseek", hooks["hooks"]["Stop"][0]["hooks"][0]["command"])
|
|
install.install("deepseek", remove=True)
|
|
self.assertEqual(self.read(".dsh", "profiles", "desktop", "cordis.patch.yml"), DSH_PATCH)
|
|
|
|
def test_kimi_plugin_is_bound_to_a_project(self):
|
|
install.install("kimi", project="test", python="py -3.12", register=False)
|
|
folder = self.harbor_home / "integrations" / "kimi" / "harbor-test"
|
|
manifest = json.loads((folder / "kimi.plugin.json").read_text(encoding="utf-8"))
|
|
self.assertEqual(manifest["mcpServers"]["harbor"]["env"], {"HARBOR_AGENT": "Kimi", "HARBOR_PROJECT": "test", "HARBOR_HOST": "kimi"})
|
|
self.assertEqual([h["event"] for h in manifest["hooks"]], ["UserPromptSubmit", "PreToolUse", "Stop"])
|
|
self.assertTrue(all("--project test" in h["command"] for h in manifest["hooks"]))
|
|
self.assertTrue((folder / "skills" / "harbor-test" / "SKILL.md").exists())
|
|
|
|
def test_refusals(self):
|
|
with self.assertRaises(install.InstallError):
|
|
install.install("codex", agent="Nobody")
|
|
(self.home / ".config" / "opencode" / "opencode.jsonc").write_text('{ // mine\n}', encoding="utf-8")
|
|
with self.assertRaises(install.InstallError):
|
|
install.install("opencode")
|
|
(self.home / ".codex" / "config.toml").write_text(CODEX_CONFIG + "\n[mcp_servers.harbor]\ncommand='x'\n",
|
|
encoding="utf-8")
|
|
with self.assertRaises(install.InstallError):
|
|
install.install("codex")
|
|
|
|
def test_dry_run_writes_nothing(self):
|
|
report = install.install("deepseek", dry_run=True, python="py -3.12")
|
|
self.assertTrue(any(line.startswith("update") for line in report))
|
|
self.assertEqual(self.read(".dsh", "profiles", "desktop", "cordis.patch.yml"), DSH_PATCH)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|