<filename>tests/test_sync_tool.py
import unittest
from nextactions.synctool import SyncTool
from nextactions.trello import Trello
from nextactions.board import Board
from nextactions.list import List
from nextactions.config import Config
from unittest.mock import MagicMock, call, patch
from nextactions.card import Card


class TestSyncTool(unittest.TestCase):

    def setUp(self):
        self.config = Config()
        self.config.set('gtd_board_id', "123")
        self.trello = Trello(self.config)
        self.board = Board(self.trello, {'id': "123", 'name': "GTD"})
        self.list = List(self.trello, {'id': "456", 'name': "Next Actions"})
        self.trello.getBoardById = MagicMock(return_value=self.board)
        self.board.getListByName = MagicMock(return_value=self.list)
        self.sync_tool = SyncTool(self.config, self.trello)

    def testGetNextActionCards(self):
        self.board.getCards = MagicMock(return_value=[])
        self.assertEqual(self.sync_tool.getNextActionCards(), [])
        self.trello.getBoardById.assert_called_once_with("123")
        self.board.getCards.assert_called_once()

    @patch('nextactions.card.Card')
    @patch('nextactions.card.Card')
    def testGetNextActionCardsReturnsOnlyAutoGeneratedCards(self, normal,
                                                            auto):
        normal.isAutoGenerated.return_value = False
        auto.isAutoGenerated.return_value = True
        self.board.getCards = MagicMock(return_value=[auto, normal])
        results = self.sync_tool.getNextActionCards()
        self.assertEqual(results, [auto])

    @patch('nextactions.card.Card')
    def testReset(self, card):
        self.sync_tool.getNextActionCards = MagicMock(return_value=[card])
        archived = self.sync_tool.reset()
        card.archive.assert_called_once()
        self.assertEqual(archived, [card])

    @patch('nextactions.board.Board')
    @patch('nextactions.list.List')
    @patch('nextactions.card.Card')
    def testGetProjectBoards(self, card, project_list, project_board):
        self.board.getListByName = MagicMock(return_value=project_list)
        card.getProjectBoard.return_value = project_board
        project_list.getCards.return_value = [card]
        self.assertEqual(self.sync_tool.getProjectBoards(), [project_board])

    def testGetTopTodoCardsForNonExistentList(self):
        self.board.getListByName = MagicMock(return_value=None)
        self.sync_tool.getProjectBoards = MagicMock(return_value=[self.board])
        self.assertEqual(self.sync_tool.getTopTodoCards(), [])

    @patch('nextactions.list.List')
    def testGetTopTodoCardsForEmptyList(self, project_list):
        self.board.getListByName = MagicMock(return_value=project_list)
        project_list.getTopCard.return_value = None
        self.sync_tool.getProjectBoards = MagicMock(return_value=[self.board])
        self.assertEqual(self.sync_tool.getTopTodoCards(), [])

    @patch('nextactions.card.Card', autospec=True)
    def testGetTopTodoCards(self, card):
        boards = [self.board, self.board]
        self.sync_tool.getProjectBoards = MagicMock(return_value=boards)
        self.list.getCards = MagicMock(return_value=[card])
        self.assertEqual(self.sync_tool.getTopTodoCards(), [card, card])

    @patch('nextactions.card.Card', autospec=True)
    def testSyncCard(self, card):
        card.board_id = "123"
        card.name = "Card"
        card.url = "URL"
        self.list.createCard = MagicMock()
        self.sync_tool.syncCard(card)
        self.list.createCard.assert_called_once_with(
            "GTD - Card",
            "URL\n\n" + Card.AUTO_GENERATED_TEXT
        )

    @patch('nextactions.card.Card', autospec=True)
    def testSyncArchivesOldNextActions(self, card):
        self.sync_tool.getNextActionCards = MagicMock(return_value=[card])
        self.trello.getOwnedCards = MagicMock(return_value=[])
        self.sync_tool.getTopTodoCards = MagicMock(return_value=[])
        card.archive = MagicMock()

        created, archived = self.sync_tool.sync()
        card.archive.assert_called_once()
        self.assertEqual(created, [])
        self.assertEqual(archived, [card])

    @patch('nextactions.card.Card')
    @patch('nextactions.card.Card')
    def testSyncIgnoresExistingNextActions(self, card, next_action_card):
        next_action_card.linksTo.return_value = True
        self.sync_tool.getNextActionCards = MagicMock(
            return_value=[next_action_card]
        )
        self.trello.getOwnedCards = MagicMock(return_value=[])
        self.sync_tool.getTopTodoCards = MagicMock(return_value=[card])
        next_action_card.archive = MagicMock()
        self.sync_tool.syncCard = MagicMock()

        created, archived = self.sync_tool.sync()
        next_action_card.archive.assert_not_called()
        self.sync_tool.syncCard.assert_not_called()
        self.assertEqual(created, [])
        self.assertEqual(archived, [])

    @patch('nextactions.card.Card')
    @patch('nextactions.card.Card')
    def testSyncCreatesNextActionCards(self, card1, card2):
        self.sync_tool.getNextActionCards = MagicMock(return_value=[])
        self.trello.getOwnedCards = MagicMock(return_value=[card1])
        self.sync_tool.getTopTodoCards = MagicMock(return_value=[card2])
        self.sync_tool.syncCard = MagicMock()

        created, archived = self.sync_tool.sync()
        self.sync_tool.syncCard.assert_has_calls([call(card1), call(card2)])
        self.assertEqual(created, [card1, card2])
        self.assertEqual(archived, [])

<filename>tests/test_sync_tool.py
import unittest
from nextactions.synctool import SyncTool
from nextactions.trello import Trello
from nextactions.board import Board
from nextactions.list import List
from nextactions.config import Config
from unittest.mock import MagicMock, call, patch
from nextactions.card import Card


class TestSyncTool(unittest.TestCase):

    def setUp(self):
        self.config = Config()
        self.config.set('gtd_board_id', "123")
        self.trello = Trello(self.config)
        self.board = Board(self.trello, {'id': "123", 'name': "GTD"})
        self.list = List(self.trello, {'id': "456", 'name': "Next Actions"})
        self.trello.getBoardById = MagicMock(return_value=self.board)
        self.board.getListByName = MagicMock(return_value=self.list)
        self.sync_tool = SyncTool(self.config, self.trello)

    def testGetNextActionCards(self):
        self.board.getCards = MagicMock(return_value=[])
        self.assertEqual(self.sync_tool.getNextActionCards(), [])
        self.trello.getBoardById.assert_called_once_with("123")
        self.board.getCards.assert_called_once()

    @patch('nextactions.card.Card')
    @patch('nextactions.card.Card')
    def testGetNextActionCardsReturnsOnlyAutoGeneratedCards(self, normal,
                                                            auto):
        normal.isAutoGenerated.return_value = False
        auto.isAutoGenerated.return_value = True
        self.board.getCards = MagicMock(return_value=[auto, normal])
        results = self.sync_tool.getNextActionCards()
        self.assertEqual(results, [auto])

    @patch('nextactions.card.Card')
    def testReset(self, card):
        self.sync_tool.getNextActionCards = MagicMock(return_value=[card])
        archived = self.sync_tool.reset()
        card.archive.assert_called_once()
        self.assertEqual(archived, [card])

    @patch('nextactions.board.Board')
    @patch('nextactions.list.List')
    @patch('nextactions.card.Card')
    def testGetProjectBoards(self, card, project_list, project_board):
        self.board.getListByName = MagicMock(return_value=project_list)
        card.getProjectBoard.return_value = project_board
        project_list.getCards.return_value = [card]
        self.assertEqual(self.sync_tool.getProjectBoards(), [project_board])

    def testGetTopTodoCardsForNonExistentList(self):
        self.board.getListByName = MagicMock(return_value=None)
        self.sync_tool.getProjectBoards = MagicMock(return_value=[self.board])
        self.assertEqual(self.sync_tool.getTopTodoCards(), [])

    @patch('nextactions.list.List')
    def testGetTopTodoCardsForEmptyList(self, project_list):
        self.board.getListByName = MagicMock(return_value=project_list)
        project_list.getTopCard.return_value = None
        self.sync_tool.getProjectBoards = MagicMock(return_value=[self.board])
        self.assertEqual(self.sync_tool.getTopTodoCards(), [])

    @patch('nextactions.card.Card', autospec=True)
    def testGetTopTodoCards(self, card):
        boards = [self.board, self.board]
        self.sync_tool.getProjectBoards = MagicMock(return_value=boards)
        self.list.getCards = MagicMock(return_value=[card])
        self.assertEqual(self.sync_tool.getTopTodoCards(), [card, card])

    @patch('nextactions.card.Card', autospec=True)
    def testSyncCard(self, card):
        card.board_id = "123"
        card.name = "Card"
        card.url = "URL"
        self.list.createCard = MagicMock()
        self.sync_tool.syncCard(card)
        self.list.createCard.assert_called_once_with(
            "GTD - Card",
            "URL\n\n" + Card.AUTO_GENERATED_TEXT
        )

    @patch('nextactions.card.Card', autospec=True)
    def testSyncArchivesOldNextActions(self, card):
        self.sync_tool.getNextActionCards = MagicMock(return_value=[card])
        self.trello.getOwnedCards = MagicMock(return_value=[])
        self.sync_tool.getTopTodoCards = MagicMock(return_value=[])
        card.archive = MagicMock()

        created, archived = self.sync_tool.sync()
        card.archive.assert_called_once()
        self.assertEqual(created, [])
        self.assertEqual(archived, [card])

    @patch('nextactions.card.Card')
    @patch('nextactions.card.Card')
    def testSyncIgnoresExistingNextActions(self, card, next_action_card):
        next_action_card.linksTo.return_value = True
        self.sync_tool.getNextActionCards = MagicMock(
            return_value=[next_action_card]
        )
        self.trello.getOwnedCards = MagicMock(return_value=[])
        self.sync_tool.getTopTodoCards = MagicMock(return_value=[card])
        next_action_card.archive = MagicMock()
        self.sync_tool.syncCard = MagicMock()

        created, archived = self.sync_tool.sync()
        next_action_card.archive.assert_not_called()
        self.sync_tool.syncCard.assert_not_called()
        self.assertEqual(created, [])
        self.assertEqual(archived, [])

    @patch('nextactions.card.Card')
    @patch('nextactions.card.Card')
    def testSyncCreatesNextActionCards(self, card1, card2):
        self.sync_tool.getNextActionCards = MagicMock(return_value=[])
        self.trello.getOwnedCards = MagicMock(return_value=[card1])
        self.sync_tool.getTopTodoCards = MagicMock(return_value=[card2])
        self.sync_tool.syncCard = MagicMock()

        created, archived = self.sync_tool.sync()
        self.sync_tool.syncCard.assert_has_calls([call(card1), call(card2)])
        self.assertEqual(created, [card1, card2])
        self.assertEqual(archived, [])
