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)