Files
KeelrLeek dbfd1720c5 Harbor as of 2026-10-05: the coordination hub for the Open Sea's decomp team
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.
2026-10-05 10:44:23 -05:00

135 lines
5.0 KiB
Python

"""Shared test fixtures: temporary projects under R:\\Workspace\\Data\\openseas-chat\\test-tmp (never C:)."""
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
import urllib.error
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from harbor import projects # noqa: E402
from harbor.core import Harbor # noqa: E402
TMP_ROOT = Path(os.environ.get("HARBOR_TEST_TMP", r"R:\Workspace\Data\openseas-chat\test-tmp"))
LEGACY_LINE = re.compile(r'^\[([^\]]+)\]\s+([A-Za-z]+)\s*(?:→|->)\s*([A-Za-z]+):\s?(.*)$')
ROSTER = [{"name": n, "role": r} for n, r in (("Claude", "Integration"), ("Codex", "Decompilation"),
("DeepSeek", "Decompilation"), ("OpenCode", "Decompilation"),
("Kimi", "Review"))] + [{"name": "User", "kind": "human"}]
PROJECT_TOML = """name = "{name}"
title = "Test {name}"
database = '{db}'
mirror = '{mirror}'
coordinators = ["Claude"]
reviewers = ["Claude"]
[identities]
"worktree-deepseek" = "DeepSeek"
[attention]
ack_overdue = 30
item_stale = 120
agent_quiet = 60
""" + "".join(f'\n[[agents]]\nname = "{a["name"]}"\n' + (f'kind = "{a["kind"]}"\n' if a.get("kind") else "")
for a in ROSTER)
class Clock:
def __init__(self, t=1_790_000_000.0):
self.t = t
def __call__(self):
return self.t
def temp_dir():
TMP_ROOT.mkdir(parents=True, exist_ok=True)
fence = TMP_ROOT / ".harbor.toml" # tests never join a project marked above this folder
if not fence.exists():
fence.write_text("# Test folders belong to no Harbor project above this one.\nboundary = true\n", encoding="utf-8")
return Path(tempfile.mkdtemp(dir=TMP_ROOT))
def make_home(folder, names=("test",)):
"""A Harbor home with registered projects; returns {name: Project}."""
out = {}
for name in names:
pdir = folder / "projects"
pdir.mkdir(parents=True, exist_ok=True)
mirror = folder / name / "agents-chat.md"
mirror.parent.mkdir(parents=True, exist_ok=True)
mirror.write_text("# chat\n\n---\n", encoding="utf-8")
(pdir / f"{name}.toml").write_text(PROJECT_TOML.format(name=name, db=folder / name / "harbor.db",
mirror=mirror), encoding="utf-8")
out[name] = projects.load_named(name, home=folder)
(folder / "config.toml").write_text(f'default_project = "{names[0]}"\n', encoding="utf-8")
return out
class HarborCase(unittest.TestCase):
def setUp(self):
self.dir = temp_dir()
self.project = make_home(self.dir)["test"]
self.mirror = self.project.mirror
self.clock = Clock()
self.h = Harbor(self.project, clock=self.clock)
def tearDown(self):
self.h.close()
shutil.rmtree(self.dir, ignore_errors=True)
def env_for(home, **extra):
env = {k: v for k, v in os.environ.items() if not k.startswith("HARBOR_")}
env.update(HARBOR_HOME=str(home), PYTHONDONTWRITEBYTECODE="1", HARBOR_NO_TOASTS="1", **extra)
return env
def run_cli(home, *args, cwd=None, stdin=None, **env):
return subprocess.run([sys.executable, "-B", "-m", "harbor", *args], cwd=cwd or home, env=env_for(home, **env),
input=stdin, capture_output=True, text=True, encoding="utf-8", timeout=60,
**({} if cwd else {}))
def run_module(home, *args, cwd=None, stdin=None, **env):
environment = env_for(home, **env)
environment["PYTHONPATH"] = str(ROOT)
return subprocess.run([sys.executable, "-B", "-m", "harbor", *args], cwd=cwd or home, env=environment,
input=stdin, capture_output=True, text=True, encoding="utf-8", timeout=60)
def http(port, path, body=None, headers=None, browser=True, token=None):
h = {}
if body is not None:
h["Content-Type"] = "application/json"
if browser:
h.update({"Sec-Fetch-Site": "same-origin", "X-Harbor-Token": token or "",
"Origin": f"http://127.0.0.1:{port}"})
h.update(headers or {})
data = json.dumps(body).encode("utf-8") if body is not None else None
request = urllib.request.Request(f"http://127.0.0.1:{port}{path}", data=data, headers=h)
try:
with urllib.request.urlopen(request, timeout=10) as response:
raw = response.read()
kind = response.headers.get("Content-Type", "")
return response.status, (json.loads(raw or b"null") if "json" in kind else raw.decode("utf-8", "replace"))
except urllib.error.HTTPError as error:
return error.code, json.loads(error.read() or b"null")
def wait_for_port(port, seconds=15):
deadline = time.monotonic() + seconds
while time.monotonic() < deadline:
try:
urllib.request.urlopen(f"http://127.0.0.1:{port}/participants", timeout=1).read()
return True
except OSError:
time.sleep(0.1)
return False