dmslanes/lanes
Lanes - team task boards with roles, history and an MCP endpoint for Claude and ChatGPT
A remix template · 0 remixes · Remix dmslanes/lanes
tests/test_extras.py
"""Extras: card dependencies and the AI use cases (a fake model stands in for Dodil Models)."""
import pytest
from app.domain.model import DomainError, Forbidden, NotFound, parse_checklist
from tests.test_rules import MEMBER, OWNER, STRANGER, VIEWER, svc, team # noqa: F401 (fixtures)
class FakeAI:
def __init__(self):
self.prompts = []
def complete(self, system, prompt):
self.prompts.append(prompt)
return "1. Draft the invoice\n- [ ] Check the amounts\n* Send it to Northwind\n\n"
def embed(self, texts):
# one axis per topic word: cards about money land close together
axes = ("invoice", "pay", "chair", "domain")
return [[float(any(w in t.lower() for w in (a, a + "s"))) + 0.01 for a in axes] for t in texts]
@pytest.fixture
def ai_svc(svc):
svc.ai = FakeAI()
return svc
def test_blocked_by_and_what_is_blocking(svc, team):
_, board = team
launch = svc.add_card(OWNER, board.id, "To do", "Launch the site")
copy = svc.add_card(OWNER, board.id, "To do", "Write the copy")
domain = svc.add_card(OWNER, board.id, "To do", "Renew the domain")
svc.block_card(MEMBER, launch.number, copy.number)
svc.block_card(MEMBER, copy.number, domain.number)
assert [c.number for c in svc.blockers(OWNER, "the launch")] == [copy.number, domain.number]
svc.move_card(OWNER, domain.number, "Done")
assert [c.number for c in svc.blockers(OWNER, launch.number)] == [copy.number]
assert svc.card_history(OWNER, launch.number)[-1].changes["blocked_by"] == [[], [copy.number]]
svc.unblock_card(MEMBER, launch.number, copy.number)
assert svc.blockers(OWNER, launch.number) == []
def test_dependency_rules(svc, team):
ws, board = team
a = svc.add_card(OWNER, board.id, "To do", "A")
b = svc.add_card(OWNER, board.id, "To do", "B")
svc.block_card(OWNER, a.number, b.number)
with pytest.raises(DomainError, match="loop"):
svc.block_card(OWNER, b.number, a.number)
with pytest.raises(DomainError):
svc.block_card(OWNER, a.number, a.number)
with pytest.raises(Forbidden):
svc.block_card(VIEWER, b.number, a.number)
other = svc.create_board(STRANGER, svc.create_workspace(STRANGER, "Else").id, "Theirs")
theirs = svc.add_card(STRANGER, other.id, "To do", "Hidden")
with pytest.raises(NotFound):
svc.block_card(OWNER, a.number, theirs.number) # can't even learn it exists
def test_checklist_appends_steps_and_respects_roles(ai_svc, team):
_, board = team
card = ai_svc.add_card(OWNER, board.id, "To do", "Send invoice", description="For October")
with pytest.raises(Forbidden):
ai_svc.card_to_checklist(VIEWER, card.number)
after = ai_svc.card_to_checklist(MEMBER, card.number)
assert after.description == ("For October\n\nChecklist:\n- [ ] Draft the invoice\n"
"- [ ] Check the amounts\n- [ ] Send it to Northwind")
assert "description" in ai_svc.card_history(OWNER, card.number)[-1].changes
def test_similar_cards_by_meaning_only_from_visible_boards(ai_svc, team):
_, board = team
invoice = ai_svc.add_card(OWNER, board.id, "To do", "Send invoice")
ai_svc.add_card(OWNER, board.id, "To do", "Order office chairs")
pay = ai_svc.add_card(OWNER, board.id, "To do", "Chase the unpaid invoices")
ws2 = ai_svc.create_workspace(STRANGER, "Else")
ai_svc.add_card(STRANGER, ai_svc.create_board(STRANGER, ws2.id, "X").id, "To do", "Their invoice")
ranked = ai_svc.similar_cards(VIEWER, invoice.number)
assert ranked[0][0].number == pay.number
assert all(c.workspace_id == invoice.workspace_id for c, _ in ranked)
def test_board_summary_reads_the_board(ai_svc, team):
_, board = team
ai_svc.add_card(OWNER, board.id, "Doing", "Renew the domain")
assert ai_svc.board_summary(VIEWER, board.id)
assert "#1 Renew the domain" in ai_svc.ai.prompts[-1]
with pytest.raises(NotFound):
ai_svc.board_summary(STRANGER, board.id)
def test_parse_checklist():
assert parse_checklist("1) One\n2. Two\n- [x] Three\n• Four\nOne") == ["One", "Two", "Three", "Four"]
def test_no_ai_configured_is_a_clear_error(svc, team):
_, board = team
card = svc.add_card(OWNER, board.id, "To do", "X")
with pytest.raises(DomainError, match="not configured"):
svc.card_to_checklist(OWNER, card.number)