# Path: librouteros/api.py
# class Api:
# 
#     def __init__(self, protocol: ApiProtocol):
#         self.protocol = protocol
# 
#     def __call__(self, cmd: str, **kwargs: typing.Any) -> ResponseIter:
#         """
#         Call Api with given command.
#         Yield each row.
# 
#         :param cmd: Command word. eg. /ip/address/print
#         :param kwargs: Dictionary with optional arguments.
#         """
#         words = (compose_word(key, value) for key, value in kwargs.items())
#         self.protocol.writeSentence(cmd, *words)
#         yield from self.readResponse()
# 
#     def rawCmd(self, cmd: str, *words: str) -> ResponseIter:
#         """
#         Call Api with given command and raw words.
#         End user is responsible to properly format each api word argument.
#         :param cmd: Command word. eg. /ip/address/print
#         :param args: Iterable with optional plain api arguments.
#         """
#         self.protocol.writeSentence(cmd, *words)
#         yield from self.readResponse()
# 
#     def readSentence(self) -> typing.Tuple[str, ReplyDict]:
#         """
#         Read one sentence and parse words.
# 
#         :returns: Reply word, dict with attribute words.
#         """
#         reply_word, words = self.protocol.readSentence()
#         return reply_word, dict(parse_word(word) for word in words)
# 
#     def readResponse(self) -> Response:
#         """
#         Yield each sentence untill !done is received.
# 
#         :throws TrapError: If one !trap is received.
#         :throws MultiTrapError: If > 1 !trap is received.
#         """
#         traps = []
#         reply_word = None
#         response = []
#         while reply_word != '!done':
#             reply_word, words = self.readSentence()
#             if reply_word == '!trap':
#                 traps.append(TrapError(**words))
#             elif reply_word in ('!re', '!done') and words:
#                 response.append(words)
# 
#         if len(traps) > 1:
#             raise MultiTrapError(*traps)
#         if len(traps) == 1:
#             raise traps[0]
#         return response
# 
#     def close(self) -> None:
#         self.protocol.close()
# 
#     def path(self, *path: str):
#         return Path(
#             path='',
#             api=self,
#         ).join(*path)
# 
# Path: librouteros/protocol.py
# def parse_word(word: str) -> typing.Tuple[str, typing.Any]:
#     """
#     Split given attribute word to key, value pair.
# 
#     Values are casted to python equivalents.
# 
#     :param word: API word.
#     :returns: Key, value pair.
#     """
#     mapping = {'yes': True, 'true': True, 'no': False, 'false': False}
#     _, key, value = word.split('=', 2)
#     try:
#         value = int(value)                # type: ignore
#     except ValueError:
#         value = mapping.get(value, value) # type: ignore
#     return (key, value)
# 
# def compose_word(key: str, value: typing.Any) -> str:
#     """
#     Create a attribute word from key, value pair.
#     Values are casted to api equivalents.
#     """
#     return f'={key}={cast_to_api(value)}'
# 
# class ApiProtocol(Encoder, Decoder):
# 
#     def __init__(self, transport: SocketTransport, encoding: str):
#         self.transport = transport
#         self.encoding = encoding
# 
#     @staticmethod
#     def log(direction_string: str, *sentence: str) -> None:
#         for word in sentence:
#             LOGGER.debug(f'{direction_string} {word!r}')
#         LOGGER.debug(f'{direction_string} EOS')
# 
#     def writeSentence(self, cmd: str, *words: str) -> None:
#         """
#         Write encoded sentence.
# 
#         :param cmd: Command word.
#         :param words: Aditional words.
#         """
#         encoded = self.encodeSentence(cmd, *words)
#         self.log('<---', cmd, *words)
#         self.transport.write(encoded)
# 
#     def readSentence(self) -> typing.Tuple[str, typing.Tuple[str, ...]]:
#         """
#         Read every word untill empty word (NULL byte) is received.
# 
#         :return: Reply word, tuple with read words.
#         """
#         sentence = tuple(word for word in iter(self.readWord, ''))
#         self.log('--->', *sentence)
#         reply_word, words = sentence[0], sentence[1:]
#         if reply_word == '!fatal':
#             self.transport.close()
#             raise FatalError(words[0])
#         return reply_word, words
# 
#     def readWord(self) -> str:
#         byte = self.transport.read(1)
#         # Early return check for null byte
#         if byte == b'\x00':
#             return ''
#         to_read = self.determineLength(byte)
#         byte += self.transport.read(to_read)
#         length = self.decodeLength(byte)
#         word = self.transport.read(length)
#         return word.decode(encoding=self.encoding, errors='ignore')
# 
#     def close(self) -> None:
#         self.transport.close()


import pytest
from unittest.mock import MagicMock, patch
from librouteros.api import (
    Api,
)
from librouteros.protocol import (
    parse_word,
    compose_word,
    ApiProtocol,
)



@pytest.mark.parametrize('word,pair', (
    ('=dynamic=true', ('dynamic', True)),
    ('=dynamic=false', ('dynamic', False)),
))
def test_bool_parse_word(word, pair):
    """
    Test for parsing legacy bool values.

    Older routeros versions accept yes/true/no/false as values,
    but only return true/false.
    """
    assert parse_word(word) == pair


def test_parse_word(word_pair):
    assert parse_word(word_pair.word) == word_pair.pair


def test_compose_word(word_pair):
    assert compose_word(*word_pair.pair) == word_pair.word


class Test_Api:

    def setup(self):
        self.api = Api(protocol=MagicMock())


# Path: librouteros/api.py
# class Api:
# 
#     def __init__(self, protocol: ApiProtocol):
#         self.protocol = protocol
# 
#     def __call__(self, cmd: str, **kwargs: typing.Any) -> ResponseIter:
#         """
#         Call Api with given command.
#         Yield each row.
# 
#         :param cmd: Command word. eg. /ip/address/print
#         :param kwargs: Dictionary with optional arguments.
#         """
#         words = (compose_word(key, value) for key, value in kwargs.items())
#         self.protocol.writeSentence(cmd, *words)
#         yield from self.readResponse()
# 
#     def rawCmd(self, cmd: str, *words: str) -> ResponseIter:
#         """
#         Call Api with given command and raw words.
#         End user is responsible to properly format each api word argument.
#         :param cmd: Command word. eg. /ip/address/print
#         :param args: Iterable with optional plain api arguments.
#         """
#         self.protocol.writeSentence(cmd, *words)
#         yield from self.readResponse()
# 
#     def readSentence(self) -> typing.Tuple[str, ReplyDict]:
#         """
#         Read one sentence and parse words.
# 
#         :returns: Reply word, dict with attribute words.
#         """
#         reply_word, words = self.protocol.readSentence()
#         return reply_word, dict(parse_word(word) for word in words)
# 
#     def readResponse(self) -> Response:
#         """
#         Yield each sentence untill !done is received.
# 
#         :throws TrapError: If one !trap is received.
#         :throws MultiTrapError: If > 1 !trap is received.
#         """
#         traps = []
#         reply_word = None
#         response = []
#         while reply_word != '!done':
#             reply_word, words = self.readSentence()
#             if reply_word == '!trap':
#                 traps.append(TrapError(**words))
#             elif reply_word in ('!re', '!done') and words:
#                 response.append(words)
# 
#         if len(traps) > 1:
#             raise MultiTrapError(*traps)
#         if len(traps) == 1:
#             raise traps[0]
#         return response
# 
#     def close(self) -> None:
#         self.protocol.close()
# 
#     def path(self, *path: str):
#         return Path(
#             path='',
#             api=self,
#         ).join(*path)
# 
# Path: librouteros/protocol.py
# def parse_word(word: str) -> typing.Tuple[str, typing.Any]:
#     """
#     Split given attribute word to key, value pair.
# 
#     Values are casted to python equivalents.
# 
#     :param word: API word.
#     :returns: Key, value pair.
#     """
#     mapping = {'yes': True, 'true': True, 'no': False, 'false': False}
#     _, key, value = word.split('=', 2)
#     try:
#         value = int(value)                # type: ignore
#     except ValueError:
#         value = mapping.get(value, value) # type: ignore
#     return (key, value)
# 
# def compose_word(key: str, value: typing.Any) -> str:
#     """
#     Create a attribute word from key, value pair.
#     Values are casted to api equivalents.
#     """
#     return f'={key}={cast_to_api(value)}'
# 
# class ApiProtocol(Encoder, Decoder):
# 
#     def __init__(self, transport: SocketTransport, encoding: str):
#         self.transport = transport
#         self.encoding = encoding
# 
#     @staticmethod
#     def log(direction_string: str, *sentence: str) -> None:
#         for word in sentence:
#             LOGGER.debug(f'{direction_string} {word!r}')
#         LOGGER.debug(f'{direction_string} EOS')
# 
#     def writeSentence(self, cmd: str, *words: str) -> None:
#         """
#         Write encoded sentence.
# 
#         :param cmd: Command word.
#         :param words: Aditional words.
#         """
#         encoded = self.encodeSentence(cmd, *words)
#         self.log('<---', cmd, *words)
#         self.transport.write(encoded)
# 
#     def readSentence(self) -> typing.Tuple[str, typing.Tuple[str, ...]]:
#         """
#         Read every word untill empty word (NULL byte) is received.
# 
#         :return: Reply word, tuple with read words.
#         """
#         sentence = tuple(word for word in iter(self.readWord, ''))
#         self.log('--->', *sentence)
#         reply_word, words = sentence[0], sentence[1:]
#         if reply_word == '!fatal':
#             self.transport.close()
#             raise FatalError(words[0])
#         return reply_word, words
# 
#     def readWord(self) -> str:
#         byte = self.transport.read(1)
#         # Early return check for null byte
#         if byte == b'\x00':
#             return ''
#         to_read = self.determineLength(byte)
#         byte += self.transport.read(to_read)
#         length = self.decodeLength(byte)
#         word = self.transport.read(length)
#         return word.decode(encoding=self.encoding, errors='ignore')
# 
#     def close(self) -> None:
#         self.transport.close()


# Path: tests/unit/test_api.py
import pytest
from unittest.mock import MagicMock, patch
from librouteros.api import (
    Api,
)
from librouteros.protocol import (
    parse_word,
    compose_word,
    ApiProtocol,
)




@pytest.mark.parametrize('word,pair', (
    ('=dynamic=true', ('dynamic', True)),
    ('=dynamic=false', ('dynamic', False)),
))
def test_bool_parse_word(word, pair):
    """
    Test for parsing legacy bool values.

    Older routeros versions accept yes/true/no/false as values,
    but only return true/false.
    """
    assert parse_word(word) == pair


def test_parse_word(word_pair):
    assert parse_word(word_pair.word) == word_pair.pair


def test_compose_word(word_pair):
    assert compose_word(*word_pair.pair) == word_pair.word


class Test_Api:

    def setup(self):
        self.api = Api(protocol=MagicMock())


    @patch.object(Api, 'readResponse')