fix(kanban): update stale read-only docstring + board_exists early-out in board counts

The bridge module docstring still described the API as 'deliberately
read-only' but it now exposes full CRUD (tasks, boards, comments,
links, SSE). Updated to list the supported operations.

For _board_counts_for_slug (the hot path for the board-switcher badge),
added a board_exists() early-out that mirrors the agent's own helper
in plugin_api.py (path.exists() before connect()). This avoids a
redundant init_db()+connect() schema pass per board per list refresh.
connect() already handles auto-init for fresh databases via its
needs_init check, so the extra init_db was unnecessary overhead on
the hot path that scales linearly with board count.

Tests:
- test_board_counts_returns_empty_for_nonexistent_board: verifies the
  early-out (no connect() call, returns {})
- test_board_counts_returns_real_counts_for_populated_board: verifies
  actual per-status counts are returned for existing boards
This commit is contained in:
fxd-jason
2026-05-07 11:53:12 +08:00
committed by test
parent 697a7a10d1
commit a80b7695d8
2 changed files with 78 additions and 8 deletions
+15 -8
View File
@@ -1,9 +1,14 @@
"""Read-only Hermes Kanban bridge for the WebUI.
"""Hermes Kanban bridge for the WebUI.
This module exposes a small WebUI-native API under ``/api/kanban/*`` while
keeping Hermes Agent's ``hermes_cli.kanban_db`` as the only source of truth.
The first integration is deliberately read-only; write/move semantics can be
added in later focused PRs.
This module exposes a full CRUD API under ``/api/kanban/*`` while keeping
Hermes Agent's ``hermes_cli.kanban_db`` as the only source of truth.
Supported operations:
- Task CRUD (create, read, patch, bulk update, archive)
- Multi-board management (list, create, archive, switch)
- Task dependency links (create, delete)
- SSE live event stream for real-time updates
- Comments and worker dispatch integration
"""
from __future__ import annotations
@@ -702,10 +707,12 @@ def _board_meta_dict(meta):
def _board_counts_for_slug(slug):
"""Per-status task counts for a board, used to populate the board
switcher with a live "12 tasks" badge. Mirrors the agent dashboard's
``_board_counts`` helper. Best-effort — empty dict if the board's
sqlite is missing (which can happen on a freshly-created board before
the first task is added)."""
``_board_counts`` helper. Returns an empty dict for boards whose
sqlite file has not been materialized yet (freshly-created boards
with no tasks)."""
kb = _kb()
if not kb.board_exists(slug):
return {}
try:
conn = kb.connect(board=slug)
except Exception:
+63
View File
@@ -695,6 +695,69 @@ def test_list_boards_includes_default_when_only_default_exists(monkeypatch):
assert "default" in slugs
def test_board_counts_returns_empty_for_nonexistent_board(monkeypatch):
"""_board_counts_for_slug returns {} early for boards whose sqlite
file has not been materialized yet (board_exists returns False),
avoiding an unnecessary connect() call on the hot board-list path."""
fake_kanban = FakeKanbanDB()
connect_calls = []
orig_connect = fake_kanban.connect
def tracking_connect(*, board=None):
connect_calls.append(("connect", board))
return orig_connect(board=board)
fake_kanban.connect = tracking_connect
fake_hermes_cli = types.ModuleType("hermes_cli")
fake_hermes_cli.kanban_db = fake_kanban
monkeypatch.setitem(sys.modules, "hermes_cli", fake_hermes_cli)
monkeypatch.setitem(sys.modules, "hermes_cli.kanban_db", fake_kanban)
import api.kanban_bridge as bridge
bridge = importlib.reload(bridge)
counts = bridge._board_counts_for_slug("no-such-board")
assert counts == {}
# connect must NOT have been called — early-out via board_exists
assert connect_calls == []
def test_board_counts_returns_real_counts_for_populated_board(monkeypatch):
"""When a board has tasks, _board_counts_for_slug must return actual
per-status counts. The FakeConn needs to handle the board-counts SQL
pattern (which differs from the dashboard stats SQL)."""
fake_kanban = FakeKanbanDB()
fake_hermes_cli = types.ModuleType("hermes_cli")
fake_hermes_cli.kanban_db = fake_kanban
monkeypatch.setitem(sys.modules, "hermes_cli", fake_hermes_cli)
monkeypatch.setitem(sys.modules, "hermes_cli.kanban_db", fake_kanban)
import api.kanban_bridge as bridge
bridge = importlib.reload(bridge)
# Patch FakeConn.execute to handle the board-counts SQL:
# SELECT status, COUNT(*) AS n FROM tasks WHERE status != 'archived' GROUP BY status
orig_execute = FakeConn.execute
def patched_execute(self, sql, params=()):
if "SELECT status, COUNT(*) AS n FROM tasks" in sql and "GROUP BY status" in sql:
rows = []
grouped = {}
for task in self.tasks:
if task.status == "archived":
continue
grouped[task.status] = grouped.get(task.status, 0) + 1
for status, n in grouped.items():
rows.append(FakeRow(status=status, n=n))
return SimpleNamespace(fetchall=lambda: rows)
return orig_execute(self, sql, params)
FakeConn.execute = patched_execute
try:
counts = bridge._board_counts_for_slug("default")
# Default fake has t_1=ready, t_2=blocked
assert counts.get("ready") == 1
assert counts.get("blocked") == 1
finally:
FakeConn.execute = orig_execute
def test_create_board_payload_creates_and_optionally_switches(monkeypatch):
"""POST /boards must create a board and, when ``switch=true``, also set
it as the active board so subsequent requests resolve to it."""