Files
bench/tests/test_boards_sync.py
T
istosandClaude Opus 5 58b658389b sync: ride the resolved remote, and say when there is none
REMOTE/UPSTREAM were module constants, so a team-mode board whose remote
is named anything but origin synced nothing at all — silently, with a
healthy header. The remote is now resolved per use (config's answer, the
one PRs already honour) and threaded through fetch, publish, replay and
integrate; every message names whatever was resolved.

The silence is the other half. Where _converge()/push_now() returned a
bare 'no-origin' that both callers discarded, _remote() now _note()s at
stalled level first: no remote at all names both fixes (add one, or set
BOARD_GIT_REMOTE), and a BOARD_GIT_REMOTE naming a remote this checkout
does not have stalls naming it rather than quietly using another. It
clears with a recovery line like the offline path, and install() asks at
startup so the chip is there from first paint, not the second beat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:33:39 +02:00

717 lines
29 KiB
Python

"""Boards sync through origin/main (task 19): a move pushes, a beat pulls,
a lost race is a toast, and a human's unpushed commit is never published.
Everything runs against real clones of a real bare upstream — the whole
point of the card is what git actually does under a race, so nothing here
is mocked except the SSE fan-out (captured, to read what the board said).
python3 -m unittest discover -s tests -v
"""
from __future__ import annotations
import importlib
import os
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO / "manager" / "core"))
import config # noqa: E402
import github # noqa: E402
import state # noqa: E402
import sync # noqa: E402
import taskfiles # noqa: E402
import watch # noqa: E402
FILENAME = "07-shared-card.md"
CARD = ("# 07 — A card two boards can reach\n\n"
"**Status:** Backlog\n"
"**Priority:** High\n"
"**Type:** Feature\n\n"
"Body text long enough that git sees a rename rather than a delete\n"
"and an add when the file moves between two stage directories, which\n"
"is what turns a same-card race into a conflict it can report.\n")
def git(cwd: Path, *args: str) -> subprocess.CompletedProcess:
return subprocess.run(["git", "-C", str(cwd), *args],
capture_output=True, text=True)
class TwoBoards(unittest.TestCase):
"""One bare upstream, two clones — 'ada' and 'elena', each with its own
board. config.REPO/TASKS point at whichever board is acting."""
def setUp(self):
# resolve(): macOS tempdirs sit behind the /var → /private/var
# symlink and git reports the resolved path.
self.tmp = Path(tempfile.mkdtemp(prefix="bench-sync-")).resolve()
self.addCleanup(shutil.rmtree, self.tmp, True)
self.origin = self.tmp / "origin.git"
subprocess.run(["git", "init", "-q", "--bare", "-b", "main", str(self.origin)],
check=True, capture_output=True)
self.ada = self._clone("ada")
for slug in config.STAGE_DIRS:
(self.ada / "tasks" / slug).mkdir(parents=True)
(self.ada / "tasks" / "backlog" / FILENAME).write_text(CARD, encoding="utf-8")
(self.ada / "code.txt").write_text("shipped\n", encoding="utf-8")
git(self.ada, "add", "-A")
git(self.ada, "commit", "-q", "-m", "root")
git(self.ada, "push", "-q", "origin", "main")
self.elena = self._clone("elena")
# REPO/TASKS are patched here (not just assigned by use()) so the
# checkout under test is restored for every other test module.
self.patch(SYNC=True, COMMIT_MOVES=True, FETCH_TIMEOUT=10.0,
SESSIONS_DIR=self.tmp / "sessions",
REPO=self.ada, TASKS=self.ada / "tasks")
self.use(self.ada)
state.BOARD_EVENTS.clear()
state.EXPECTED_MOVES.clear()
state.COMMIT_HOOKS.clear()
self.addCleanup(state.COMMIT_HOOKS.clear)
sync.ARRIVED.clear()
sync._NOTES.clear()
self.addCleanup(sync._NOTES.clear)
self.addCleanup(sync.ARRIVED.clear)
self.broadcasts: list[dict] = []
self.addCleanup(setattr, state, "broadcast", state.broadcast)
state.broadcast = self.broadcasts.append
def _clone(self, who: str) -> Path:
path = self.tmp / who
subprocess.run(["git", "clone", "-q", str(self.origin), str(path)],
check=True, capture_output=True)
git(path, "config", "user.name", who)
git(path, "config", "user.email", f"{who}@example.com")
return path
def patch(self, **values) -> None:
for attr, value in values.items():
self.addCleanup(setattr, config, attr, getattr(config, attr))
setattr(config, attr, value)
# — acting as one board or the other —
def use(self, board: Path) -> None:
config.REPO, config.TASKS = board, board / "tasks"
def move(self, board: Path, source: str, target: str) -> None:
self.use(board)
taskfiles.move_task(FILENAME, source, target)
def stage_of(self, board: Path) -> str | None:
for slug in config.STAGE_DIRS:
if (board / "tasks" / slug / FILENAME).is_file():
return slug
return None
def card(self, board: Path) -> str:
stage = self.stage_of(board)
return (board / "tasks" / stage / FILENAME).read_text(encoding="utf-8")
def head(self, board: Path) -> str:
return git(board, "rev-parse", "HEAD").stdout.strip()
def origin_head(self) -> str:
return git(self.origin, "rev-parse", "main").stdout.strip()
def summaries(self) -> list[str]:
return [e["summary"] for e in state.BOARD_EVENTS]
def toasts(self) -> list[str]:
return [b["message"] for b in self.broadcasts if b.get("type") == "toast"]
def sig(self, board: Path) -> dict[str, set[str]]:
self.use(board)
return {slug: {p.name for p in (board / "tasks" / slug).glob("*.md")}
for slug in config.STAGE_DIRS
if (board / "tasks" / slug).is_dir()}
# — a move reaching the other board —
def test_a_move_pushes_and_the_other_board_pulls_it(self):
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "ok")
self.use(self.elena)
self.assertEqual(sync.pull_now(), "pulled")
self.assertEqual(self.stage_of(self.elena), "to-do")
self.assertEqual(self.card(self.elena), self.card(self.ada),
"both boards hold the same bytes")
self.assertIn("**Assignee:** ada", self.card(self.elena))
def test_the_pull_attributes_the_move_to_its_author(self):
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.use(self.elena)
before = self.sig(self.elena)
sync.pull_now()
after = self.sig(self.elena)
# Two boards are two processes: elena's has no memory of ada moving
# anything, so nothing but the pull can attribute this.
state.EXPECTED_MOVES.clear()
state.BOARD_EVENTS.clear()
watch.narrate(before, after)
moves = [e for e in state.BOARD_EVENTS if e["kind"] == "move"]
self.assertEqual(len(moves), 1)
self.assertEqual(moves[0]["actor"], "ada",
"a move that arrived over origin is not 'disk'")
self.assertEqual((moves[0]["from"], moves[0]["to"]), ("backlog", "to-do"))
def test_an_archive_publishes_itself_and_empties_the_other_board(self):
"""Task 44: an archive is a board-made move like any other, so it
commits, pushes and reaches the teammate — where the card is simply
gone from every column rather than lingering as one it never saw
leave."""
self.use(self.ada)
taskfiles.archive_task(FILENAME, "backlog")
self.assertEqual(sync.push_now(), "ok")
self.use(self.elena)
self.assertEqual(sync.pull_now(), "pulled")
self.assertIsNone(self.stage_of(self.elena), "out of every column")
self.assertTrue((self.elena / "tasks" / "archive" / FILENAME).is_file(),
"never deleted: a fresh clone still has the card")
def test_attribution_is_consumed_once_and_expires(self):
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.use(self.elena)
sync.pull_now()
self.assertEqual(sync.arrived_actor(FILENAME), "ada")
self.assertEqual(sync.arrived_actor(FILENAME), "",
"the next hand-move on this disk is not ada's")
def test_a_pull_that_brings_nothing_says_nothing(self):
self.use(self.elena)
state.BOARD_EVENTS.clear()
self.assertEqual(sync.pull_now(), "up-to-date")
self.assertEqual(self.summaries(), [])
def test_the_commit_hook_publishes_without_being_asked(self):
sync.install()
self.assertIn(sync.on_commit, state.COMMIT_HOOKS)
before = self.origin_head()
self.move(self.ada, "backlog", "to-do")
for _ in range(100): # the push runs off-thread
if self.origin_head() != before:
break
time.sleep(0.05)
self.assertEqual(self.origin_head(), self.head(self.ada),
"the move published itself")
# — the same-card race —
def test_losing_a_race_undoes_the_move_and_names_who_took_the_card(self):
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "ok")
# elena never saw ada's move: her board still shows the card in
# backlog/ and she moves it somewhere else entirely.
self.move(self.elena, "backlog", "in-progress")
self.assertEqual(sync.push_now(), "pulled")
self.assertEqual(self.stage_of(self.elena), "to-do",
"the loser's board holds the winner's version")
self.assertEqual(self.card(self.elena), self.card(self.ada))
self.assertIn("**Assignee:** ada", self.card(self.elena))
self.assertEqual(self.origin_head(), self.head(self.elena))
self.assertIn("07 claimed by ada — your move was undone", self.toasts())
self.assertIn("07 claimed by ada — your move was undone", self.summaries())
def test_exactly_one_claim_survives_when_both_claim_the_same_card(self):
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.move(self.elena, "backlog", "to-do") # same target, other name
sync.push_now()
self.use(self.ada)
sync.pull_now()
self.assertEqual(self.card(self.ada), self.card(self.elena),
"both boards converge on the same file bytes")
self.assertEqual(self.card(self.elena).count("**Assignee:**"), 1)
self.assertIn("**Assignee:** ada", self.card(self.elena))
self.assertEqual(self.origin_head(), self.head(self.ada))
self.assertEqual(self.origin_head(), self.head(self.elena))
def test_the_winner_keeps_moving_after_the_loser_gave_way(self):
"""Convergence is not a dead end: the board that lost re-reads the
card and can move it on, and that move publishes normally."""
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.move(self.elena, "backlog", "in-progress")
sync.push_now()
self.move(self.elena, "to-do", "in-progress")
self.assertEqual(sync.push_now(), "ok")
self.use(self.ada)
sync.pull_now()
self.assertEqual(self.stage_of(self.ada), "in-progress")
def test_a_race_on_two_different_cards_keeps_both_moves(self):
other = "08-another-card.md"
(self.ada / "tasks" / "backlog" / other).write_text(
CARD.replace("# 07", "# 08"), encoding="utf-8")
git(self.ada, "add", "-A")
git(self.ada, "commit", "-q", "-m", "board: 08 → backlog (ada)")
git(self.ada, "push", "-q", "origin", "main")
self.use(self.elena)
sync.pull_now()
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.use(self.elena)
taskfiles.move_task(other, "backlog", "to-do")
self.assertEqual(sync.push_now(), "pulled")
self.assertTrue((self.elena / "tasks" / "to-do" / other).is_file(),
"elena's own move survives a rebase it does not collide with")
self.assertTrue((self.elena / "tasks" / "to-do" / FILENAME).is_file())
self.assertEqual(self.toasts(), [], "nothing was undone")
def test_a_drag_started_before_the_card_moved_underneath_is_refused(self):
"""The mid-drag race. The browser sends the stage it picked the card
up from, so a move that arrived meanwhile makes the drop stale — and
a stale drop must fail, not resurrect the card in two places."""
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.use(self.elena)
sync.pull_now()
with self.assertRaises(ValueError) as caught:
taskfiles.move_task(FILENAME, "backlog", "in-progress")
self.assertIn("no longer in backlog/", str(caught.exception))
self.assertEqual(self.stage_of(self.elena), "to-do")
self.assertFalse((self.elena / "tasks" / "in-progress" / FILENAME).exists())
# — the piggyback guard —
def test_a_human_commit_on_main_stops_the_push(self):
self.use(self.ada)
(self.ada / "code.txt").write_text("my unpushed experiment\n", encoding="utf-8")
git(self.ada, "commit", "-qam", "wip: not ready for anyone else")
before = self.origin_head()
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "stray")
self.assertEqual(self.origin_head(), before, "origin never saw it")
self.assertNotIn("wip: not ready for anyone else",
git(self.origin, "log", "--format=%s", "main").stdout)
warnings = [s for s in self.summaries() if "not a board commit" in s]
self.assertEqual(len(warnings), 1)
self.assertIn("wip: not ready for anyone else", warnings[0])
self.assertEqual(sync.status()["state"], "stalled")
def test_the_guard_warns_once_however_often_the_beat_runs(self):
self.use(self.ada)
git(self.ada, "commit", "-q", "--allow-empty", "-m", "wip: mine")
self.move(self.ada, "backlog", "to-do")
for _ in range(4):
sync.push_now()
sync.pull_now()
self.assertEqual(len([s for s in self.summaries() if "not a board commit" in s]), 1)
def test_a_human_commit_also_blocks_the_rebase_and_says_so(self):
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.use(self.elena)
(self.elena / "code.txt").write_text("elena's experiment\n", encoding="utf-8")
git(self.elena, "commit", "-qam", "wip: elena's own work")
before = self.head(self.elena)
self.assertEqual(sync.pull_now(), "diverged")
self.assertEqual(self.head(self.elena), before,
"nothing was rebased over the human's commit")
self.assertEqual(self.stage_of(self.elena), "backlog")
self.assertTrue(any("diverged" in s for s in self.summaries()))
self.assertEqual(sync.status()["state"], "stalled")
def test_the_guard_stands_down_once_the_human_commit_is_gone(self):
self.use(self.ada)
git(self.ada, "commit", "-q", "--allow-empty", "-m", "wip: mine")
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "stray")
git(self.ada, "push", "-q", "origin", "main") # the human pushes it themselves
self.assertEqual(sync.push_now(), "nothing")
self.assertEqual(sync.status()["state"], "ok")
# — offline —
def test_an_unreachable_origin_is_quiet_and_catches_up(self):
self.use(self.ada)
git(self.ada, "remote", "set-url", "origin", str(self.tmp / "gone.git"))
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "offline")
for _ in range(3):
self.assertEqual(sync.pull_now(), "offline")
self.assertEqual(len([s for s in self.summaries() if "unreachable" in s]), 1,
"one quiet note, not one per beat")
self.assertEqual(sync.status()["state"], "offline")
self.assertEqual(self.stage_of(self.ada), "to-do",
"the board kept working while origin was gone")
git(self.ada, "remote", "set-url", "origin", str(self.origin))
self.assertEqual(sync.pull_now(), "up-to-date")
self.assertEqual(self.origin_head(), self.head(self.ada),
"the queued commit went out on the next reachable beat")
self.assertTrue(any("caught up" in s for s in self.summaries()))
self.assertEqual(sync.status()["state"], "ok")
self.use(self.elena)
sync.pull_now()
self.assertEqual(self.stage_of(self.elena), "to-do")
# — which remote this board rides (task 37) —
def test_a_remote_named_otherwise_syncs_exactly_the_same(self):
"""The bug this card came from: sync hardcoded `origin`, so a
checkout whose remote is called anything else synced nothing at
all — silently, with a healthy header."""
self.use(self.ada)
git(self.ada, "remote", "rename", "origin", "upstream")
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "ok")
self.assertEqual(self.origin_head(), self.head(self.ada))
self.assertEqual(sync.status()["state"], "ok")
self.assertTrue(any("upstream/main" in s for s in self.summaries()),
"the ticker names the remote it actually rode")
self.use(self.elena)
self.assertEqual(sync.pull_now(), "pulled")
self.assertEqual(self.stage_of(self.elena), "to-do")
def test_the_configured_remote_wins_over_the_first_one_listed(self):
"""BOARD_GIT_REMOTE is what PRs already honoured; sync honours the
same answer, so the two halves of team mode cannot disagree about
where this board's work goes."""
self.use(self.ada)
git(self.ada, "remote", "rename", "origin", "fork")
git(self.ada, "remote", "add", "backup", str(self.tmp / "elsewhere.git"))
self.patch(GIT_REMOTE="fork")
self.assertEqual(config.git_remotes()[0], "backup",
"auto-detection alone would pick the wrong one here")
self.assertEqual(github.remote(), "fork")
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "ok")
self.assertEqual(self.origin_head(), self.head(self.ada))
def test_no_remote_at_all_stalls_the_board_and_names_both_fixes(self):
self.use(self.ada)
git(self.ada, "remote", "remove", "origin")
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "no-remote")
self.assertEqual(sync.pull_now(), "no-remote")
self.assertEqual(sync.status()["state"], "stalled")
stalled = [s for s in self.summaries() if "no remote to sync through" in s]
self.assertEqual(len(stalled), 1, "narrated once, not once per beat")
self.assertIn("git remote add", stalled[0])
self.assertIn("BOARD_GIT_REMOTE", stalled[0])
self.assertIn(stalled[0], sync.status()["detail"])
def test_the_stall_clears_when_a_remote_appears(self):
self.use(self.ada)
git(self.ada, "remote", "remove", "origin")
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "no-remote")
git(self.ada, "remote", "add", "origin", str(self.origin))
# never fetched from it, so this is the full converge: fetch, then push
self.assertEqual(sync.push_now(), "up-to-date")
self.assertEqual(sync.status()["state"], "ok")
self.assertTrue(any("converging again" in s for s in self.summaries()),
"the ticker closes the loop, as the offline path does")
self.assertEqual(self.origin_head(), self.head(self.ada))
def test_a_named_remote_that_does_not_exist_stalls_naming_it(self):
"""A typo in BOARD_GIT_REMOTE is likelier than no remote at all, and
quietly using origin instead would hide it."""
self.use(self.ada)
self.patch(GIT_REMOTE="typo")
before = self.origin_head()
self.move(self.ada, "backlog", "to-do")
self.assertEqual(sync.push_now(), "no-remote")
self.assertEqual(self.origin_head(), before, "origin was not used behind our back")
self.assertEqual(sync.status()["state"], "stalled")
stalled = [s for s in self.summaries() if "BOARD_GIT_REMOTE names 'typo'" in s]
self.assertEqual(len(stalled), 1)
self.assertIn("origin", stalled[0], "it says which remotes this checkout has")
def test_the_missing_remote_is_on_the_header_from_startup(self):
"""Not on the second beat: the condition is true before the first
converge, and a board that never started syncing must not render
like one that is."""
self.use(self.ada)
git(self.ada, "remote", "remove", "origin")
sync.install()
self.assertEqual(sync.status()["state"], "stalled")
self.assertEqual(len([s for s in self.summaries()
if "no remote to sync through" in s]), 1)
def test_the_gate_off_says_nothing_about_a_missing_remote(self):
self.patch(SYNC=False)
self.use(self.ada)
git(self.ada, "remote", "remove", "origin")
sync.install()
self.assertEqual(sync.push_now(), "off")
self.assertEqual(sync.pull_now(), "off")
self.assertEqual(self.summaries(), [])
self.assertEqual(sync.status(), {"enabled": False, "state": "off", "detail": ""})
# — never pulling into a checkout that is not ready —
def test_uncommitted_changes_stall_the_pull_loudly(self):
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.use(self.elena)
(self.elena / "code.txt").write_text("half-finished\n", encoding="utf-8")
before = self.head(self.elena)
self.assertEqual(sync.pull_now(), "dirty")
self.assertEqual(self.head(self.elena), before)
self.assertEqual((self.elena / "code.txt").read_text(encoding="utf-8"),
"half-finished\n")
self.assertTrue(any("uncommitted changes" in s for s in self.summaries()))
self.assertEqual(sync.status()["state"], "stalled")
git(self.elena, "checkout", "--", "code.txt")
self.assertEqual(sync.pull_now(), "pulled")
self.assertEqual(sync.status()["state"], "ok")
def test_untracked_files_do_not_stall_anything(self):
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.use(self.elena)
(self.elena / "scratch.txt").write_text("mine\n", encoding="utf-8")
self.assertEqual(sync.pull_now(), "pulled")
def test_a_checkout_off_main_pauses_sync(self):
self.move(self.ada, "backlog", "to-do")
sync.push_now()
self.use(self.elena)
git(self.elena, "checkout", "-q", "-b", "side")
before = self.head(self.elena)
self.assertEqual(sync.pull_now(), "not-on-main")
self.assertEqual(self.head(self.elena), before)
self.assertTrue(any("not main" in s for s in self.summaries()))
# — the gate —
def test_the_gate_off_does_not_touch_the_network(self):
self.patch(SYNC=False)
self.use(self.ada)
# A remote that never answers: anything that fetched or pushed here
# would hang instead of returning at once.
git(self.ada, "config", "protocol.ext.allow", "always")
git(self.ada, "remote", "set-url", "origin", "ext::sleep 30")
started = time.monotonic()
self.assertEqual(sync.push_now(), "off")
self.assertEqual(sync.pull_now(), "off")
sync.on_commit(FILENAME)
self.assertLess(time.monotonic() - started, 2)
self.assertEqual(self.summaries(), [])
self.assertEqual(sync.status(), {"enabled": False, "state": "off", "detail": ""})
def test_the_gate_off_leaves_moves_exactly_as_they_were(self):
self.patch(SYNC=False, COMMIT_MOVES=False)
before = self.origin_head()
self.move(self.ada, "backlog", "to-do")
self.assertEqual(self.stage_of(self.ada), "to-do")
self.assertEqual(self.head(self.ada), before, "no commit, no push")
self.assertEqual(self.origin_head(), before)
self.assertNotIn("**Assignee:**", self.card(self.ada))
def test_a_registered_hook_is_inert_with_the_gate_off(self):
sync.install()
self.patch(SYNC=False)
before = self.origin_head()
self.move(self.ada, "backlog", "to-do")
time.sleep(0.2)
self.assertEqual(self.origin_head(), before)
def test_the_watcher_still_says_disk_for_a_plain_hand_move(self):
self.use(self.ada)
before = self.sig(self.ada)
shutil.move(str(self.ada / "tasks" / "backlog" / FILENAME),
str(self.ada / "tasks" / "to-do" / FILENAME))
state.BOARD_EVENTS.clear()
watch.narrate(before, self.sig(self.ada))
self.assertEqual(state.BOARD_EVENTS[0]["actor"], "disk")
class TheGateImpliesCommitMoves(unittest.TestCase):
"""BOARD_SYNC=1 turns BOARD_COMMIT_MOVES on: there is nothing to publish
until moves commit themselves."""
def reload(self, **env) -> None:
saved = {k: os.environ.get(k) for k in ("BOARD_SYNC", "BOARD_COMMIT_MOVES")}
def restore():
for key, value in saved.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
importlib.reload(config)
self.addCleanup(restore)
for key in saved:
os.environ.pop(key, None)
os.environ.update(env)
importlib.reload(config)
def test_sync_on_implies_commit_moves(self):
self.reload(BOARD_SYNC="1")
self.assertTrue(config.SYNC)
self.assertTrue(config.COMMIT_MOVES)
def test_both_are_off_by_default(self):
self.reload()
self.assertFalse(config.SYNC)
self.assertFalse(config.COMMIT_MOVES)
self.assertEqual(config.SYNC_INTERVAL, 30.0)
def test_commit_moves_alone_stays_alone(self):
self.reload(BOARD_COMMIT_MOVES="1")
self.assertTrue(config.COMMIT_MOVES)
self.assertFalse(config.SYNC)
class TheStrayCommitTest(unittest.TestCase):
"""The piggyback guard reads commit subjects — the one thing standing
between a human's private work and origin."""
def test_board_commits_pass(self):
self.assertEqual(sync._stray(["abc board: 07 → to-do (ada)",
"def board: 08 → done (elena)"]), "")
def test_the_oldest_stray_is_the_one_named(self):
self.assertEqual(
sync._stray(["abc board: 07 → to-do (ada)", "def wip: older", "aaa wip: oldest"]),
"aaa wip: oldest")
def test_a_commit_merely_mentioning_the_board_is_still_stray(self):
self.assertEqual(sync._stray(["abc fix the board: really"]),
"abc fix the board: really")
class TheRemoteResolver(unittest.TestCase):
"""One answer to "which remote is this board's", in config — the module
that already owns the setting. Resolved on demand: config is imported
everywhere and must not shell out at import."""
def setUp(self):
self.tmp = Path(tempfile.mkdtemp(prefix="bench-remote-")).resolve()
self.addCleanup(shutil.rmtree, self.tmp, True)
subprocess.run(["git", "init", "-q", "-b", "main", str(self.tmp)],
check=True, capture_output=True)
for attr in ("REPO", "GIT_REMOTE"):
self.addCleanup(setattr, config, attr, getattr(config, attr))
config.REPO, config.GIT_REMOTE = self.tmp, ""
def test_a_checkout_with_no_remotes_resolves_to_nothing(self):
self.assertEqual(config.git_remotes(), [])
self.assertIsNone(config.git_remote())
def test_the_first_remote_when_the_setting_is_empty(self):
git(self.tmp, "remote", "add", "upstream", "https://example.invalid/x.git")
self.assertEqual(config.git_remotes(), ["upstream"])
self.assertEqual(config.git_remote(), "upstream")
def test_the_setting_wins_and_is_taken_exactly_as_named(self):
git(self.tmp, "remote", "add", "origin", "https://example.invalid/x.git")
config.GIT_REMOTE = "fork"
self.assertEqual(config.git_remote(), "fork",
"a configured name is never swapped for another")
def test_somewhere_that_is_not_a_repo_answers_without_raising(self):
config.REPO = self.tmp / "not-a-checkout"
self.assertEqual(config.git_remotes(), [])
self.assertIsNone(config.git_remote())
class TheSyncChip(unittest.TestCase):
"""board.html is a single file with no frontend runner — these are the
source-level invariants of the surface this card adds."""
@classmethod
def setUpClass(cls):
cls.html = (REPO / "manager" / "core" / "board.html").read_text(encoding="utf-8")
def test_a_server_toast_reaches_the_person(self):
self.assertIn("msg.type === 'toast'", self.html)
self.assertIn("toast(msg.message, !!msg.error)", self.html)
def test_the_chip_hides_itself_while_sync_is_healthy(self):
self.assertIn("if (!s || !s.enabled || s.state === 'ok') { el.hidden = true;", self.html)
self.assertIn(".livechip[hidden]{display:none}", self.html,
"the chip's own display:flex would beat the UA's [hidden]")
def test_a_refused_move_re_reads_the_board(self):
"""What makes the mid-drag race safe in the browser: the drop sends
the stage it started from, and a rejected move reloads disk state
rather than leaving the stale card on screen."""
self.assertIn("const { file, from } = JSON.parse(e.dataTransfer.getData("
"'application/json'));", self.html)
self.assertIn("if (!res.ok) { toast(data.error || 'move failed', true); "
"await loadState(); return false; }", self.html)
def test_sync_events_have_a_glyph_and_a_filter(self):
self.assertIn("sync: '⇅'", self.html)
self.assertIn("new Set(['move', 'new', 'agent', 'sync'])", self.html)
if __name__ == "__main__":
unittest.main()