feat: lazily find-or-create the Bestellungen Deck board and status stacks

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V57jSQPqwkGG8BuAXg59X5
This commit is contained in:
Jonny 2026-09-13 16:16:14 +02:00
parent dfe76db184
commit 2313c3c490
2 changed files with 89 additions and 0 deletions

View File

@ -485,6 +485,39 @@ async def _deck_request(method: str, path: str, json_body: dict = None):
_deck_cache: dict = {}
_DECK_BOARD_TITLE = "Bestellungen"
_DECK_STACK_TITLES = ["Offen", "In Arbeit", "Erledigt"]
async def _ensure_deck_board() -> dict:
"""Finds (or lazily creates) the 'Bestellungen' board and its three
status stacks. Result is cached in _deck_cache after the first call -
board/stack ids don't change at runtime, and finding/creating them
costs several sequential Deck API calls."""
if "board_id" in _deck_cache:
return _deck_cache
boards = await _deck_request("GET", "/boards")
board = next((b for b in boards if b["title"] == _DECK_BOARD_TITLE), None)
if board is None:
board = await _deck_request("POST", "/boards", {"title": _DECK_BOARD_TITLE, "color": "0082C9"})
board_id = board["id"]
stacks = await _deck_request("GET", f"/boards/{board_id}/stacks")
existing_by_title = {s["title"]: s["id"] for s in stacks}
stack_ids = {}
for order, title in enumerate(_DECK_STACK_TITLES):
if title in existing_by_title:
stack_ids[title] = existing_by_title[title]
else:
created = await _deck_request("POST", f"/boards/{board_id}/stacks", {"title": title, "order": order})
stack_ids[title] = created["id"]
_deck_cache["board_id"] = board_id
_deck_cache["stacks"] = stack_ids
return _deck_cache
def _decode_mime_words(value: str) -> str:
parts = decode_header(value)

View File

@ -85,3 +85,59 @@ async def test_deck_request_raises_on_error_status():
with patch.object(main.aiohttp, "ClientSession", return_value=fake_session):
with pytest.raises(RuntimeError):
await main._deck_request("GET", "/boards")
@pytest.mark.asyncio
async def test_ensure_deck_board_creates_board_and_stacks_when_missing():
titles_created = []
async def fake_deck_request(method, path, json_body=None):
if (method, path) == ("GET", "/boards"):
return []
if (method, path) == ("POST", "/boards"):
return {"id": 10, "title": "Bestellungen"}
if (method, path) == ("GET", "/boards/10/stacks"):
return []
if (method, path) == ("POST", "/boards/10/stacks"):
titles_created.append(json_body["title"])
return {"id": 100 + len(titles_created), "title": json_body["title"]}
raise AssertionError(f"unexpected call {method} {path}")
with patch.object(main, "_deck_request", new=AsyncMock(side_effect=fake_deck_request)):
board = await main._ensure_deck_board()
assert board["board_id"] == 10
assert titles_created == ["Offen", "In Arbeit", "Erledigt"]
assert set(board["stacks"].keys()) == {"Offen", "In Arbeit", "Erledigt"}
@pytest.mark.asyncio
async def test_ensure_deck_board_reuses_existing_board_and_stacks():
async def fake_deck_request(method, path, json_body=None):
if (method, path) == ("GET", "/boards"):
return [{"id": 10, "title": "Bestellungen"}]
if (method, path) == ("GET", "/boards/10/stacks"):
return [
{"id": 1, "title": "Offen", "cards": []},
{"id": 2, "title": "In Arbeit", "cards": []},
{"id": 3, "title": "Erledigt", "cards": []},
]
raise AssertionError(f"unexpected call {method} {path}")
with patch.object(main, "_deck_request", new=AsyncMock(side_effect=fake_deck_request)) as mock_req:
board = await main._ensure_deck_board()
assert board == {"board_id": 10, "stacks": {"Offen": 1, "In Arbeit": 2, "Erledigt": 3}}
assert mock_req.call_count == 2
@pytest.mark.asyncio
async def test_ensure_deck_board_caches_after_first_lookup():
main._deck_cache["board_id"] = 10
main._deck_cache["stacks"] = {"Offen": 1, "In Arbeit": 2, "Erledigt": 3}
with patch.object(main, "_deck_request", new=AsyncMock()) as mock_req:
board = await main._ensure_deck_board()
mock_req.assert_not_called()
assert board["board_id"] == 10