# Path: librouteros/connections.py
# class SocketTransport:
# 
#     def __init__(self, sock: socket):
#         self.sock = sock
# 
#     def write(self, data: bytes) -> None:
#         """
#         Write given bytes to socket. Loop as long as every byte in
#         string is written unless exception is raised.
#         """
#         self.sock.sendall(data)
# 
#     def read(self, length: int) -> bytes:
#         """
#         Read as many bytes from socket as specified in length.
#         Loop as long as every byte is read unless exception is raised.
#         """
#         data = bytearray()
#         while len(data) != length:
#             data += self.sock.recv((length - len(data)))
#             if not data:
#                 raise ConnectionClosed('Connection unexpectedly closed.')
#         return data
# 
#     def close(self) -> None:
#         self.sock.close()
# 
# Path: librouteros/exceptions.py
# class ProtocolError(LibRouterosError):
#     """Raised when e.g. encoding/decoding fails."""
# 
# class FatalError(ProtocolError):
#     """Exception raised when !fatal is received."""
# 
# class ConnectionClosed(LibRouterosError):
#     """Raised when connection have been closed."""


import pytest
from socket import error as SOCKET_ERROR, timeout as SOCKET_TIMEOUT, socket
from unittest.mock import MagicMock, patch, call
from librouteros.connections import SocketTransport
from librouteros.exceptions import (
    ProtocolError,
    FatalError,
    ConnectionClosed,
)

    def setup(self):
        self.transport = SocketTransport(sock=MagicMock(spec=socket))

    def test_calls_socket_close(self):
        self.transport.close()
        self.transport.sock.close.assert_called_once()

    def test_close_shutdown_exception(self):
        self.transport.sock.shutdown.side_effect = SOCKET_ERROR
        self.transport.close()
        self.transport.sock.close.assert_called_once_with()

    def test_close(self):
        self.transport.close()
        self.transport.sock.close.assert_called_once_with()

    def test_calls_sendall(self):
        self.transport.write(b'some message')
        self.transport.sock.sendall.assert_called_once_with(b'some message')

    @pytest.mark.parametrize("exception", (SOCKET_ERROR, SOCKET_TIMEOUT))
    def test_write_raises_socket_errors(self, exception):
        self.transport.sock.sendall.side_effect = exception
        with pytest.raises(exception):
            self.transport.write(b'some data')

    def test_read_raises_when_recv_returns_empty_byte_string(self):
        self.transport.sock.recv.return_value = b''
        with pytest.raises(ConnectionClosed):

# Path: librouteros/connections.py
# class SocketTransport:
# 
#     def __init__(self, sock: socket):
#         self.sock = sock
# 
#     def write(self, data: bytes) -> None:
#         """
#         Write given bytes to socket. Loop as long as every byte in
#         string is written unless exception is raised.
#         """
#         self.sock.sendall(data)
# 
#     def read(self, length: int) -> bytes:
#         """
#         Read as many bytes from socket as specified in length.
#         Loop as long as every byte is read unless exception is raised.
#         """
#         data = bytearray()
#         while len(data) != length:
#             data += self.sock.recv((length - len(data)))
#             if not data:
#                 raise ConnectionClosed('Connection unexpectedly closed.')
#         return data
# 
#     def close(self) -> None:
#         self.sock.close()
# 
# Path: librouteros/exceptions.py
# class ProtocolError(LibRouterosError):
#     """Raised when e.g. encoding/decoding fails."""
# 
# class FatalError(ProtocolError):
#     """Exception raised when !fatal is received."""
# 
# class ConnectionClosed(LibRouterosError):
#     """Raised when connection have been closed."""


# Path: tests/unit/test_connections.py
import pytest
from socket import error as SOCKET_ERROR, timeout as SOCKET_TIMEOUT, socket
from unittest.mock import MagicMock, patch, call
from librouteros.connections import SocketTransport
from librouteros.exceptions import (
    ProtocolError,
    FatalError,
    ConnectionClosed,
)


    def setup(self):
        self.transport = SocketTransport(sock=MagicMock(spec=socket))

    def test_calls_socket_close(self):
        self.transport.close()
        self.transport.sock.close.assert_called_once()

    def test_close_shutdown_exception(self):
        self.transport.sock.shutdown.side_effect = SOCKET_ERROR
        self.transport.close()
        self.transport.sock.close.assert_called_once_with()

    def test_close(self):
        self.transport.close()
        self.transport.sock.close.assert_called_once_with()

    def test_calls_sendall(self):
        self.transport.write(b'some message')
        self.transport.sock.sendall.assert_called_once_with(b'some message')

    @pytest.mark.parametrize("exception", (SOCKET_ERROR, SOCKET_TIMEOUT))
    def test_write_raises_socket_errors(self, exception):
        self.transport.sock.sendall.side_effect = exception
        with pytest.raises(exception):
            self.transport.write(b'some data')

    def test_read_raises_when_recv_returns_empty_byte_string(self):
        self.transport.sock.recv.return_value = b''
        with pytest.raises(ConnectionClosed):

            self.transport.read(3)