ghpsolutions/quadro
Quadro: team boards with roles, history and an MCP endpoint for Claude and ChatGPT
A remix template · 0 remixes · Remix ghpsolutions/quadro
tests/test_domain.py
import pytest
from boards.domain.model import (
Board, Card, Invalid, Person, Role, board_key, changes_between, check_blocker, clean_due,
clean_labels, clean_title, with_changes)
def _card(cid, blocked=()):
return Card(id=cid, board_id="b", number=1, list_id="l", title="t", position=1,
created_by="x", created_at="", updated_at="", blocked_by=tuple(blocked))
def test_board_keys_are_short_and_readable():
assert board_key("Ops") == "OPS"
assert board_key("Ops board") == "OPS"
assert board_key("Product launch 2026") == "PL2"
assert board_key("!!!") == "B"
def test_roles():
b = Board("b", "B", "B", "o@x.io", "", {"m@x.io": Role.MEMBER, "v@x.io": Role.VIEWER})
assert b.role_of(Person("o@x.io")) is Role.OWNER
assert b.role_of(Person("nobody@x.io")) is None
b.require_edit(Person("m@x.io"))
with pytest.raises(Exception):
b.require_edit(Person("v@x.io"))
with pytest.raises(Exception):
b.require_owner(Person("m@x.io"))
with pytest.raises(Invalid):
b.check_new_role(Person("o@x.io"), "m@x.io", Role.OWNER)
def test_blocker_loops_are_refused():
a, b, c = _card("a"), _card("b", ["a"]), _card("c", ["b"])
graph = {"a": (), "b": ("a",), "c": ("b",)}
with pytest.raises(Invalid, match="loop"):
check_blocker(a, c, graph) # a waits on c, which waits on b, which waits on a
with pytest.raises(Invalid, match="itself"):
check_blocker(a, a, graph)
check_blocker(c, a, graph) # fine: no loop
def test_cleaning():
assert clean_title(" Send invoice ") == "Send invoice"
with pytest.raises(Invalid):
clean_title(" ")
assert clean_due("2026-11-03") == "2026-11-03"
assert clean_due("") is None
with pytest.raises(Invalid):
clean_due("next friday")
assert clean_labels(["Infra", "infra", " Finance "]) == ("Infra", "Finance")
def test_changes_and_checklist():
before = _card("a")
after = with_changes(before, title="new", labels=("x",),
description="- [x] one\n- [ ] two\n* [X] three")
fields = {c["field"] for c in changes_between(before, after)}
assert fields == {"title", "labels", "description"}
assert after.checklist() == (2, 3)