mobiledev/taskflow

TaskFlow, a team task board you run from the web or from Claude and ChatGPT. FastAPI + DataK3 + appid + Models + MCP.

A remix template · 0 remixes · Remix mobiledev/taskflow

tests/fakes.py

"""In-memory stand-ins for DataK3 and Dodil Models, so the rules test without a Dodil account."""
import copy
import itertools
from datetime import datetime, timedelta

_clock = itertools.count()


class MemoryRepo:
    def __init__(self):
        self.boards, self.cards, self.events, self.claimed = {}, {}, [], set()

    # Copies in and out, like a real database: a use case that forgets to save loses its change.
    def get(self, board_id):
        return copy.deepcopy(self.boards.get(board_id))

    def boards_for(self, email):
        return [copy.deepcopy(b) for b in self.boards.values()
                if b.owner_email == email or email in b.members]

    def save_board(self, board):
        self.boards[board.id] = copy.deepcopy(board)

    def remove_membership(self, board_id, email):
        self.boards[board_id].members.pop(email, None)

    def get_card(self, card_id):
        return copy.deepcopy(self.cards.get(card_id))

    def cards_on(self, board_id):
        return [copy.deepcopy(c) for c in self.cards.values() if c.board_id == board_id]

    def save_card(self, card):
        self.cards[card.id] = copy.deepcopy(card)

    def delete_card(self, card_id):
        self.cards.pop(card_id, None)

    def delete_board(self, board_id):
        self.boards.pop(board_id, None)
        self.cards = {k: c for k, c in self.cards.items() if c.board_id != board_id}
        self.events = [e for e in self.events if e["board_id"] != board_id]

    def claim_card_number(self, board_id, at_least):
        n = at_least
        while (board_id, n) in self.claimed:
            n += 1
        self.claimed.add((board_id, n))
        return n

    def log(self, board_id, card_id, actor, action, changes=None):
        at = (datetime(2026, 1, 1) + timedelta(seconds=next(_clock))).isoformat()
        self.events.append({"board_id": board_id, "card_id": card_id, "at": at,
                            "actor": actor, "action": action, "changes": changes or {}})

    def activity(self, board_id, card_id=None, limit=50):
        rows = [e for e in self.events if e["board_id"] == board_id
                and (card_id is None or e["card_id"] == card_id)]
        return [{k: e[k] for k in ("at", "actor", "action", "changes")}
                for e in reversed(rows)][:limit]


class FakeAI:
    def checklist(self, title, description):
        return [f"plan {title}", f"do {title}", "check it"]

    def summarize(self, facts):
        return "SUMMARY: " + facts.splitlines()[0]

    def embed(self, texts):
        # Bag of words over a tiny vocabulary: close enough to rank "invoice" next to "invoice".
        vocab = ["invoice", "bill", "pay", "logo", "design", "launch", "test"]
        return [[float(w in t.lower()) for w in vocab] for t in texts]