sirenamc/stackline
Stackline, a team task board you run from the web or from Claude/ChatGPT. FastAPI + DataK3 + appid + Models + MCP.
A remix template · 0 remixes · Remix sirenamc/stackline
tests/test_plan.py
"""A whole board from one sentence, the example board, and the public demo's reset."""
from datetime import timedelta
import pytest
from boards.application.use_cases import Boards, NotFound
from boards.domain.model import Forbidden, Invalid, Person
from conftest import MEMBER, OWNER, STRANGER, TODAY, VIEWER, FakeAI, MemoryBoards
DEADLINE = TODAY + timedelta(days=10)
def _cards(board):
return {c["title"]: c for lst in board["lists"] for c in lst["cards"]}
def test_set_up_board_from_a_written_plan(svc):
s, _ = svc
board = s.set_up_board(OWNER, "Site", deadline=DEADLINE.isoformat(), people=[MEMBER.email],
steps=[{"title": "Copy", "days_before": 6, "assignee": MEMBER.email},
{"title": "Design", "days_before": 3, "after": [1]},
{"title": "Launch", "list": "doing", "after": [1, 2],
"due_date": DEADLINE.isoformat(), "assignee": "me"}])
cards = _cards(board)
assert cards["Copy"]["due_date"] == (DEADLINE - timedelta(days=6)).isoformat()
assert cards["Copy"]["assignee"] == MEMBER.email
assert cards["Launch"]["list"] == "Doing" and cards["Launch"]["assignee"] == OWNER.email
assert set(cards["Launch"]["blocked_by"]) == {cards["Copy"]["number"], cards["Design"]["number"]}
assert board["role"] == "owner"
assert {m["email"] for m in board["members"]} == {OWNER.email, MEMBER.email}
blocking = s.what_is_blocking(MEMBER, cards["Launch"]["number"])
assert {c["title"] for c in blocking["blocking"]} == {"Copy", "Design"}
assert any(e["action"] == "blocked" for e in s.board_activity(OWNER, board["id"]))
def test_set_up_board_lets_the_model_draft_the_plan(svc):
s, _ = svc
board = s.set_up_board(MEMBER, "Release", goal="ship the app", deadline=DEADLINE.isoformat())
cards = _cards(board)
assert list(cards) == ["Draft", "Review", "Ship"]
assert cards["Ship"]["due_date"] == DEADLINE.isoformat()
assert s.assistant.planned == ("ship the app", DEADLINE.isoformat(), TODAY.isoformat())
@pytest.mark.parametrize("steps, error", [
([{"title": "A"}, {"title": "B", "after": [2]}], "earlier steps"),
([{"title": "A", "assignee": "zed@x.io"}], "isn't in people"),
([{"title": "A", "list": "Someday"}], "list must be"),
([{"title": ""}], "needs a title"),
([], "no steps"),
([{"title": f"s{i}"} for i in range(16)], "15 steps"),
([{"title": "A"}, {"title": "B", "after": ["first"]}], "whole number"),
([{"title": "A", "days_before": 10 ** 9}], "out of range"),
(["fine", 42], "object with a title"),
])
def test_a_bad_plan_changes_nothing(svc, steps, error):
s, _ = svc
before = len(s.my_boards(OWNER)), len(s.repo.cards), len(s.repo.events)
with pytest.raises(Invalid, match=error):
s.set_up_board(OWNER, "Broken", deadline=DEADLINE.isoformat(), steps=steps)
assert (len(s.my_boards(OWNER)), len(s.repo.cards), len(s.repo.events)) == before
def test_example_board_has_late_cards_and_a_blocked_launch(svc):
s, _ = svc
board = s.start_example(STRANGER)
cards = _cards(board)
assert len(cards) == 9 and board["role"] == "owner"
assert sorted(c["title"] for c in s.overdue(STRANGER, board["id"])) == [
"Set up the domain and email", "Write the home page copy"]
launch = s.what_is_blocking(STRANGER, cards["Launch the site"]["number"])
assert not launch["ready"] and len(launch["blocking"]) >= 3
assert board["id"] not in {b["id"] for b in s.my_boards(OWNER)}
def _demo():
return Boards(MemoryBoards(), FakeAI(), today=lambda: TODAY,
demo_accounts=(OWNER.email, MEMBER.email, VIEWER.email))
def test_demo_reset_tells_the_roles_story_and_can_run_again():
s = _demo()
s.reset_demo(VIEWER)
first = sorted(b["title"] for b in s.my_boards(OWNER))
s.add_card(OWNER, "Ops", "mess left by a visitor")
s.reset_demo(MEMBER)
assert sorted(b["title"] for b in s.my_boards(OWNER)) == first == ["Launch", "Ops"]
assert "mess left by a visitor" not in str(s.show_board(OWNER, "Ops"))
# the viewer reads Launch, can't change it, and doesn't see Ops at all
assert [b["title"] for b in s.my_boards(VIEWER)] == ["Launch"]
with pytest.raises(NotFound):
s.show_board(VIEWER, "Ops")
some_card = _cards(s.show_board(VIEWER, "Launch"))["Launch the site"]["number"]
with pytest.raises(Forbidden):
s.move_card(VIEWER, some_card, "Done")
assert s.my_plate(MEMBER)["overdue"]
def test_only_demo_accounts_reset_the_demo(svc):
s, _ = svc
with pytest.raises(Forbidden):
s.reset_demo(OWNER) # no demo configured on this copy
with pytest.raises(Forbidden):
_demo().reset_demo(Person("someone@else.io"))