Files
Harbor/tests/test_core.py
T

395 lines
22 KiB
Python

"""Harbor store tests (run: py -3.12 -B -m unittest discover -s R:\\Workspace\\Tools\\Harbor\\tests)."""
import shutil
import subprocess
import sys
import threading
import time
import unittest
from pathlib import Path
from helpers import LEGACY_LINE, ROSTER, Clock, HarborCase, make_home, temp_dir
from harbor import projects
from harbor.core import Harbor, HarborError, parse_scope
TOOLS = Path(r"R:\Workspace\Tools")
class Messages(HarborCase):
def test_send_inbox_ack(self):
r = self.h.send("Claude", ["DeepSeek"], "Please fix 0x529350", thread="fn:0x529350",
subject="AudioManagerTick", kind="request", ack_required=True)
self.assertEqual(r["targets"], ["DeepSeek"])
self.assertEqual([m["id"] for m in self.h.inbox("DeepSeek")], [r["id"]])
self.assertEqual(self.h.inbox("DeepSeek"), [])
self.assertEqual(len(self.h.pending_acks(sender="Claude")), 1)
self.h.ack("DeepSeek", [r["id"]], note="fixed")
self.assertEqual(self.h.pending_acks(sender="Claude"), [])
with self.assertRaises(HarborError):
self.h.ack("Kimi", [r["id"]])
def test_all_expands_to_everyone_but_sender(self):
r = self.h.send("Codex", "All", "hello")
self.assertEqual(sorted(r["targets"]), sorted(["Claude", "DeepSeek", "OpenCode", "Kimi", "User"]))
def test_reply_keeps_thread_and_people(self):
first = self.h.send("Claude", ["DeepSeek", "Kimi"], "question", thread="fn:0x50A720")
reply = self.h.send("DeepSeek", None, "answer", reply_to=first["id"])
self.assertEqual(reply["thread_id"], first["thread_id"])
self.assertEqual(sorted(reply["targets"]), ["Claude", "Kimi"])
broadcast = self.h.send("Codex", "All", "status for everyone")
answer = self.h.send("Kimi", None, "noted", reply_to=broadcast["id"])
self.assertEqual(answer["recipients"], ["All"])
def test_urgent_first_and_length_limits(self):
self.h.send("Claude", ["Kimi"], "normal one")
urgent = self.h.send("Claude", ["Kimi"], "urgent one", importance="urgent")
self.assertEqual(self.h.inbox("Kimi")[0]["id"], urgent["id"])
self.assertTrue(self.h.send("Claude", ["Kimi"], "word " * 3000)["id"])
with self.assertRaises(HarborError):
self.h.send("Claude", ["Kimi"], "x" * 20001)
def test_only_web_can_speak_as_the_human(self):
for origin in ("cli", "mcp", "hook", "cli-legacy"):
with self.assertRaises(HarborError):
self.h.send("User", "All", "resume everyone", origin=origin)
self.assertTrue(self.h.send("User", "All", "hi", origin="web")["id"])
def test_idempotent_send(self):
a = self.h.send("Claude", ["Kimi"], "once", idem="k1")
b = self.h.send("Claude", ["Kimi"], "once", idem="k1")
self.assertEqual(a["id"], b["id"])
self.assertEqual(len(self.h.inbox("Kimi")), 1)
def test_lint_compressed_text_and_flooding(self):
squashed = ("Root full222/ALL11 tick relocation audit proves SEVEN bad offsets; correctOffsets24ByteLayout "
"vs yourOffsetsWereWrong and stub23HarnessBug plus relocgen2Output and nativeBaseline6Cases")
self.assertTrue(any("compressed" in w for w in self.h.send("Codex", ["DeepSeek"], squashed)["warnings"]))
self.assertEqual(self.h.send("Codex", ["DeepSeek"], "Commit 4c392e33ae94858b fixed 0x45A970.")["warnings"], [])
for i in range(8):
self.h.send("Codex", "All", f"progress {i}")
self.assertTrue(any("in the last hour" in w for w in self.h.send("Codex", "All", "more")["warnings"]))
self.assertEqual(self.h.send("Codex", ["Claude"], "direct is fine")["warnings"], [])
def test_search_and_attachments(self):
self.h.send("Claude", ["Kimi"], "LayoutTextMesh zero-extends the glyph key")
self.h.send("Claude", ["Kimi"], "unrelated")
self.assertEqual(len(self.h.search("LayoutTextMesh glyph")), 1)
report = self.dir / "report.txt"
report.write_text("six cases match", encoding="utf-8")
r = self.h.send("DeepSeek", ["Claude"], "harness report attached", attachments=[report])
name = self.h.message(r["id"])["refs"]["attachments"][0]
self.assertTrue(name.endswith("-report.txt"))
self.assertEqual((self.project.attachments / name).read_text(encoding="utf-8"), "six cases match")
self.assertIn("Attachments: " + name, self.mirror.read_text(encoding="utf-8"))
with self.assertRaises(HarborError):
self.h.send("DeepSeek", ["Claude"], "x", attachments=[self.dir / "missing.txt"])
def test_receipts(self):
r = self.h.send("User", ["Codex", "Kimi"], "please both look", origin="web", ack_required=True)
self.h.inbox("Codex")
self.h.ack("Kimi", [r["id"]])
self.assertEqual(self.h.receipts([r["id"]])[r["id"]], {"read": ["Codex"], "acked": ["Kimi"], "waiting": []})
def test_threads_close_and_reopen(self):
r = self.h.send("Claude", ["Kimi"], "question", thread="fn:0x445310")
self.h.set_thread_state("Kimi", "fn:0x445310", "closed")
self.assertNotIn("fn:0x445310", [t["topic"] for t in self.h.threads(include_closed=False)])
self.h.send("Kimi", None, "one more thing", reply_to=r["id"])
self.assertEqual(self.h.thread("fn:0x445310")[0]["state"], "open")
def test_mirror_is_readable_by_old_tools(self):
self.h.send("Claude", ["DeepSeek"], "line one\n[2026-10-02 10:00Z] Codex → All: not a header\nline three",
kind="request", ack_required=True)
self.h.send("Claude", ["DeepSeek", "Kimi"], "to two people")
lines = self.mirror.read_text(encoding="utf-8").split("\n")
headers = [l for l in lines if LEGACY_LINE.match(l.strip())]
self.assertEqual(len(headers), 2)
self.assertIn("Claude → All:", headers[1])
self.assertIn("to DeepSeek,Kimi", headers[1])
if (TOOLS / "chat.py").exists():
out = subprocess.run([sys.executable, "-B", "-c",
"import sys; sys.path.insert(0, r'%s'); import chat, chat_common; "
"chat_common.CHAT = __import__('pathlib').Path(r'%s'); chat.CHAT = chat_common.CHAT; "
"chat.tail(5)" % (TOOLS, self.mirror)], capture_output=True, text=True,
encoding="utf-8")
self.assertIn("line three", out.stdout)
class Presence(HarborCase):
def test_doing_shows_in_briefs_and_digest(self):
self.h.doing("DeepSeek", "drafting 0x529350 AudioManagerTick")
self.assertIn("DeepSeek: drafting 0x529350", self.h.brief("Kimi"))
self.assertNotIn("Team now", self.h.brief("DeepSeek").split("DeepSeek: drafting")[0][-1:])
with self.assertRaises(HarborError):
self.h.doing("User", "x")
d = self.h.digest("1h")
self.assertEqual(next(a for a in d["agents"] if a["name"] == "DeepSeek")["doing"],
"drafting 0x529350 AudioManagerTick")
self.h.doing("DeepSeek", "")
self.assertIsNone(next(a for a in self.h.agents() if a["name"] == "DeepSeek")["doing"])
def test_wait_returns_on_message_stop_or_timeout(self):
self.h.clock = __import__("time").time
self.assertTrue(self.h.wait("Kimi", timeout=0.3, poll=0.05)["timed_out"])
def later():
time.sleep(0.4)
sender = Harbor(self.project)
sender.send("Claude", ["Kimi"], "your turn")
sender.close()
threading.Thread(target=later).start()
started = time.monotonic()
r = self.h.wait("Kimi", timeout=10, poll=0.05)
self.assertLess(time.monotonic() - started, 5)
self.assertEqual([m["body"] for m in r["messages"]], ["your turn"])
self.h.set_flag("stop:Kimi", "on", "Claude", "cli")
self.h.mark_read("Kimi")
self.assertEqual(self.h.wait("Kimi", timeout=10, poll=0.05)["stopped"], ["stop:Kimi"])
def test_digest_and_attention(self):
self.h.send("Codex", ["Claude"], "a")
self.h.send("User", ["Codex"], "status please", origin="web")
item = self.h.submit("DeepSeek", "thing", scope="fn:0x445310", commit="abc")
self.h.transition("Claude", item["id"], "in_review")
self.h.send("Claude", ["Kimi"], "please confirm", ack_required=True)
self.h.touch("OpenCode")
d = self.h.digest("1h")
codex = next(a for a in d["agents"] if a["name"] == "Codex")
self.assertEqual((codex["messages"], codex["threads"]), (1, 1))
self.assertEqual([e["to_state"] for e in d["item_events"]], ["submitted", "in_review"])
self.assertEqual(self.h.digest("1h")["to_human"], [])
self.h.send("Codex", ["User"], "here is the status")
self.assertEqual([m["body"] for m in self.h.digest("1h")["to_human"]], ["here is the status"])
self.assertEqual(self.h.attention(), [])
self.clock.t += 3 * 3600
kinds = sorted(a["kind"] for a in self.h.attention())
self.assertEqual(kinds, ["ack_overdue", "agent_quiet", "agent_quiet", "agent_quiet", "agent_quiet", "item_stale"])
self.h.set_flag("stop:all", "on", "Claude", "cli")
self.assertNotIn("agent_quiet", [a["kind"] for a in self.h.attention()])
with self.assertRaises(HarborError):
self.h.digest("yesterday-ish")
class Claims(HarborCase):
def test_claims_conflicts_ttl_and_bands(self):
self.h.claim("Claude", "range:0x440000-0x47FFFF", kind="band", on_behalf_of="DeepSeek")
self.h.claim("Claude", "range:0x480000-0x4FFFFF", kind="band", on_behalf_of="OpenCode")
with self.assertRaises(HarborError):
self.h.claim("Kimi", "range:0x400000-0x401000", kind="band")
d = self.h.claim("DeepSeek", "fn:0x445310", kind="draft")
with self.assertRaises(HarborError):
self.h.claim("OpenCode", "0x445310", kind="draft")
with self.assertRaises(HarborError):
self.h.claim("OpenCode", "fn:0x450000", kind="draft")
self.assertTrue(self.h.claim("OpenCode", "fn:0x490000", kind="draft")["id"])
self.assertTrue(self.h.claim("Kimi", "fn:0x445310", kind="review")["id"])
renewed = self.h.claim("DeepSeek", "fn:0x445310", kind="draft", ttl_hours=1)
self.assertEqual((renewed["renewed"], renewed["id"]), (True, d["id"]))
self.clock.t += 2 * 3600
self.assertEqual([c for c in self.h.active_claims(kind="draft") if c["scope"] == "fn:0x445310"], [])
over = self.h.claim("Claude", "fn:0x450010", kind="draft", override=True)
self.assertEqual(len(over["conflicts"]), 1)
self.assertEqual(self.h.release("Claude", scope="fn:0x450010"), [over["id"]])
self.assertTrue(self.h.claim("Codex", "task:write the README")["id"])
with self.assertRaises(HarborError):
self.h.claim("Kimi", "task:Write the README")
def test_scope_parsing(self):
self.assertEqual(parse_scope("0x445310")[0], "fn:0x445310")
self.assertEqual(parse_scope("range:0x440000-0x47ffff")[1:], (0x440000, 0x47FFFF))
with self.assertRaises(HarborError):
parse_scope("somewhere")
class Review(HarborCase):
def test_review_item_lifecycle(self):
item = self.h.submit("DeepSeek", "RouteCellCost readable", scope="fn:0x445310", commit="abc1234",
branch="deepseek/decomp")
self.assertEqual([m["kind"] for m in self.h.inbox("Claude")], ["request"])
with self.assertRaises(HarborError):
self.h.transition("DeepSeek", item["id"], "approved")
self.h.transition("Claude", item["id"], "in_review")
self.h.transition("Claude", item["id"], "changes_requested", note="movsx: signed read")
self.assertEqual(self.h.inbox("DeepSeek")[0]["kind"], "request")
self.h.transition("DeepSeek", item["id"], "submitted", commit="def5678")
self.h.transition("Kimi", item["id"], "approved")
with self.assertRaises(HarborError):
self.h.transition("Kimi", item["id"], "landed", commit="x")
with self.assertRaises(HarborError):
self.h.transition("Claude", item["id"], "landed")
done = self.h.transition("Claude", item["id"], "landed", commit="b4b44f0")
self.assertEqual((done["state"], done["commit_hash"]), ("landed", "def5678"))
with self.assertRaises(HarborError):
self.h.transition("Claude", item["id"], "held")
self.assertEqual([e["to_state"] for e in self.h.item(item["id"])[1]],
["submitted", "in_review", "changes_requested", "submitted", "approved", "landed"])
class Control(HarborCase):
def test_resume_one_agent_under_stop_all(self):
"""Resuming one agent whose own flag is already off still overrides a later 'stop everyone'."""
self.h.set_flag("stop:Claude", "off", "User", "web")
self.clock.t += 60
self.h.set_flag("stop:all", "on", "User", "web")
self.assertTrue(self.h.stopped("Claude"))
self.clock.t += 60
self.h.set_flag("stop:Claude", "off", "User", "web")
self.assertFalse(self.h.stopped("Claude"))
self.assertTrue(self.h.stopped("Codex"))
with self.assertRaises(HarborError):
self.h.set_flag("stop:Codex", "off", "Claude", "cli") # still the human's alone
def test_stop_and_resume_rules(self):
with self.assertRaises(HarborError):
self.h.set_flag("stop:all", "on", "User", "cli")
self.h.set_flag("stop:all", "on", "User", "web", reason="lets stop for now")
self.assertTrue(self.h.stopped("DeepSeek"))
self.assertIn("STOP", self.h.brief("DeepSeek"))
with self.assertRaises(HarborError):
self.h.set_flag("stop:all", "off", "Codex", "cli")
agent_msg = self.h.send("Kimi", "All", "resume please")
with self.assertRaises(HarborError):
self.h.set_flag("stop:all", "off", "Codex", "cli", relay_of=agent_msg["id"])
with self.assertRaises(HarborError):
self.h.set_flag("stop:all", "off", "Claude", "cli") # the user set it, not Claude
user_msg = self.h.send("User", "All", "okay resume everyone", origin="web")
with self.assertRaises(HarborError):
self.h.set_flag("stop:all", "off", "Codex", "cli", relay_of=user_msg["id"]) # only the human resumes
self.h.set_flag("stop:all", "off", "User", "web")
self.assertFalse(self.h.stopped("DeepSeek"))
self.h.set_flag("stop:Kimi", "on", "Kimi", "cli", reason="out of allowance")
with self.assertRaises(HarborError):
self.h.set_flag("stop:Kimi", "off", "Kimi", "cli")
with self.assertRaises(HarborError):
self.h.set_flag("stop:DeepSeek", "on", "Kimi", "cli")
self.h.set_flag("stop:Codex", "on", "Claude", "cli", reason="mirror the user's stop")
self.h.set_flag("stop:Codex", "off", "Claude", "cli", reason="set by mistake") # own stop, untouched
self.h.set_flag("stop:Codex", "on", "Claude", "cli")
self.h.set_flag("stop:Codex", "on", "User", "web")
with self.assertRaises(HarborError):
self.h.set_flag("stop:Codex", "off", "Claude", "cli") # the user has confirmed it
def test_brief_lists_what_needs_attention(self):
self.h.send("Claude", ["DeepSeek"], "please look", ack_required=True, importance="urgent")
self.h.claim("DeepSeek", "fn:0x445310", kind="draft", note="writing")
self.h.submit("DeepSeek", "thing", scope="fn:0x445310", commit="abc")
text = self.h.brief("DeepSeek")
for part in ("Unread: 1 (1 urgent)", "Waiting for your acknowledgement", "Your claims", "Your review items"):
self.assertIn(part, text)
self.assertIn("Review queue for you: 1 open", self.h.brief("Claude"))
self.assertNotIn("Review queue for you", self.h.brief("Codex"))
def test_legacy_import_is_idempotent_and_read(self):
legacy = self.dir / "old-chat.md"
legacy.write_text("# chat\n\n---\n\n[2026-10-01 13:32Z] User → All: rundown please\n\n"
"[2026-10-01 13:40Z] Codex → User: here it is\nsecond line\n\n"
"[2026-10-01 13:40Z] Codex → User: here it is\nsecond line\n\n"
"[2026-10-02 08:00Z] Kimi → Codex: next day\n", encoding="utf-8")
self.assertEqual(self.h.import_legacy(legacy), {"parsed": 4, "added": 4})
self.assertEqual(self.h.import_legacy(legacy)["added"], 0)
self.assertEqual(self.h.inbox("Codex"), [])
self.assertEqual(sorted(t["topic"] for t in self.h.threads(include_legacy=True)),
["legacy:2026-10-01", "legacy:2026-10-02"])
def test_backup_keeps_newest(self):
self.h.send("Claude", ["Kimi"], "keep me")
for i in range(3):
self.clock.t += 60
self.h.backup(keep=2)
copies = sorted(self.project.backups.glob("harbor-*.db"))
self.assertEqual(len(copies), 2)
import sqlite3
with sqlite3.connect(str(copies[-1])) as c:
self.assertEqual(c.execute("SELECT body FROM messages").fetchone()[0], "keep me")
class Projects(unittest.TestCase):
def setUp(self):
self.dir = temp_dir()
def tearDown(self):
shutil.rmtree(self.dir, ignore_errors=True)
def test_roster_comes_from_the_project(self):
p = projects.project_from_dict({"name": "garden", "human": "Kai", "coordinators": ["Lead"],
"database": str(self.dir / "g.db"),
"agents": [{"name": "Lead"}, {"name": "Helper"}]})
h = Harbor(p)
self.assertEqual(h.agent_names(), ["Lead", "Helper", "Kai"])
self.assertEqual(sorted(h.send("Lead", "All", "hi")["targets"]), ["Helper", "Kai"])
with self.assertRaises(HarborError):
h.send("Kai", "All", "pretend", origin="cli")
with self.assertRaises(HarborError):
h.send("Lead", ["Codex"], "not on this team")
item = h.submit("Helper", "first task") # reviewers default to Lead
self.assertEqual(h.inbox("Lead")[-1]["refs"]["item"], item["id"])
h.close()
smaller = projects.project_from_dict({"name": "garden", "human": "Kai", "database": str(self.dir / "g.db"),
"agents": [{"name": "Lead"}]})
h = Harbor(smaller)
self.assertEqual([a["name"] for a in h.agents()], ["Lead", "Kai"]) # Helper keeps history, inactive
h.close()
other = projects.project_from_dict({"name": "other", "database": str(self.dir / "g.db"),
"agents": [{"name": "Lead"}]})
with self.assertRaises(HarborError):
Harbor(other) # the database belongs to garden
def test_validation(self):
for bad in ({"name": "Bad Name"}, {"name": "x", "agents": [{"name": "All"}]},
{"name": "x", "agents": [{"name": "A"}, {"name": "A"}]},
{"name": "x", "coordinators": ["Ghost"], "agents": [{"name": "A"}]}):
with self.assertRaises(projects.ProjectError):
projects.project_from_dict(bad, home=self.dir)
def test_session_identity_for_two_sessions_of_one_app(self):
made = projects.project_from_dict({
"name": "pair", "identities": {"wt-codex": "Codex", "wt-codex2": "Codex2", "wt-deepseek": "DeepSeek"},
"agents": [{"name": "Codex", "host": "codex"}, {"name": "Codex2", "host": "codex"},
{"name": "DeepSeek", "host": "deepseek"}, {"name": "User", "kind": "human"}]}, home=self.dir)
two = self.dir / "wt-codex2" / "src"
two.mkdir(parents=True)
self.assertEqual(made.session_identity("Codex", two), "Codex2") # same app: the folder decides
self.assertEqual(made.session_identity("Codex", self.dir), "Codex") # no folder match: installed one
self.assertEqual(made.session_identity("DeepSeek", two), "DeepSeek") # another app's folder: ignored
self.assertEqual(made.session_identity(None, two), "Codex2") # nothing pinned: the folder
def test_resolution_order_and_identities(self):
made = make_home(self.dir, names=("first", "second"))
self.assertEqual(projects.resolve(home=self.dir, cwd=self.dir).name, "first") # home default
self.assertEqual(projects.resolve("second", home=self.dir).name, "second") # explicit
tree = self.dir / "repo" / "worktree-deepseek" / "src"
tree.mkdir(parents=True)
(self.dir / "repo" / ".harbor.toml").write_text('project = "second"\n', encoding="utf-8")
found = projects.resolve(home=self.dir, cwd=tree)
self.assertEqual(found.name, "second") # nearest marker
self.assertEqual(found.identity_for(tree), "DeepSeek")
self.assertIsNone(found.identity_for(self.dir))
with self.assertRaises(projects.ProjectError):
projects.resolve("missing", home=self.dir)
self.assertEqual(made["first"].ai_names, ["Claude", "Codex", "DeepSeek", "OpenCode", "Kimi"])
class Concurrency(unittest.TestCase):
def test_many_processes_write_without_loss(self):
d = temp_dir()
project = make_home(d)["test"]
Harbor(project).close()
root = Path(__file__).resolve().parents[1]
script = ("import sys; sys.path.insert(0, r'%s'); from harbor import projects; from harbor.core import Harbor; "
"h = Harbor(projects.load_named('test', home=r'%s')); "
"[h.send(sys.argv[1], ['Claude'], f'{sys.argv[1]} message {i}') for i in range(40)]" % (root, d))
senders = ["Codex", "DeepSeek", "OpenCode", "Kimi", "Codex", "DeepSeek"]
procs = [subprocess.Popen([sys.executable, "-B", "-c", script, s]) for s in senders]
self.assertEqual([p.wait(timeout=180) for p in procs], [0] * len(senders))
h = Harbor(project)
rows = h.conn.execute("SELECT COUNT(*) n, COUNT(DISTINCT id) u FROM messages").fetchone()
self.assertEqual((rows["n"], rows["u"]), (240, 240))
headers = [l for l in project.mirror.read_text(encoding="utf-8").split("\n") if LEGACY_LINE.match(l.strip())]
self.assertEqual(len(headers), 240)
self.assertEqual(h.unread_count("Claude")[0], 240)
h.close()
shutil.rmtree(d, ignore_errors=True)
if __name__ == "__main__":
unittest.main()