File: test_protocol.py

package info (click to toggle)
librouteros 3.4.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 380 kB
  • sloc: python: 1,579; makefile: 141; sh: 4
file content (160 lines) | stat: -rw-r--r-- 6,134 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# -*- coding: UTF-8 -*-

import pytest
from unittest.mock import MagicMock, patch

from librouteros.protocol import Encoder, Decoder, ApiProtocol, AsyncApiProtocol
from librouteros.connections import SocketTransport, AsyncSocketTransport
from librouteros.exceptions import (
    ProtocolError,
    FatalError,
)


class Test_Decoder:
    def setup_method(self):
        self.decoder = Decoder()
        self.decoder.encoding = "ASCII"

    @pytest.mark.parametrize(
        "length,expected",
        (
            (b"x", 0),  # 120
            (b"\xbf", 1),  # 191
            (b"\xdf", 2),  # 223
            (b"\xef", 3),  # 239
        ),
    )
    def test_determineLength(self, length, expected):
        assert self.decoder.determineLength(length) == expected

    def test_determineLength_raises(self, bad_first_length_bytes):
        with pytest.raises(ProtocolError) as error:
            self.decoder.determineLength(bad_first_length_bytes)
        assert str(bad_first_length_bytes) in str(error.value)

    def test_decodeLength(self, valid_word_length):
        result = self.decoder.decodeLength(valid_word_length.encoded)
        assert result == valid_word_length.integer

    def test_decodeLength_raises(self, bad_length_bytes):
        with pytest.raises(ProtocolError) as error:
            self.decoder.decodeLength(bad_length_bytes)
        assert str(bad_length_bytes) in str(error.value)


class Test_Encoder:
    def setup_method(self):
        self.encoder = Encoder()
        self.encoder.encoding = "ASCII"

    def test_encodeLength(self, valid_word_length):
        result = self.encoder.encodeLength(valid_word_length.integer)
        assert result == valid_word_length.encoded

    def test_encodeLength_raises_if_lenghth_is_too_big(self):
        """Length must be < 268435456"""
        invalid = 268435456
        with pytest.raises(ProtocolError):
            self.encoder.encodeLength(invalid)

    def test_non_ASCII_word_encoding(self):
        """When encoding is ASCII, word may only contain ASCII characters."""
        self.encoder.encoding = "ASCII"
        with pytest.raises(UnicodeEncodeError):
            self.encoder.encodeWord("łą")

    @patch.object(Encoder, "encodeLength", return_value=b"")
    def test_utf_8_word_encoding(self, enc_len_mock):
        """
        Assert that len is taken from bytes, not utf8 word itself.

        len('ł') == 1
        'ł'.encode(encoding='UTF8') == 2
        """
        self.encoder.encoding = "utf-8"
        word = "ł"
        self.encoder.encodeWord(word)
        enc_len_mock.assert_called_once_with(len(word.encode(self.encoder.encoding)))

    @patch.object(Encoder, "encodeWord", return_value=b"")
    def test_encodeSentence(self, encodeWord_mock):
        r"""
        Assert that:
            \x00 is appended to the sentence
            encodeWord is called == len(sentence)
        """
        encoded = self.encoder.encodeSentence("first", "second")
        assert encodeWord_mock.call_count == 2
        assert encoded[-1:] == b"\x00"


class Test_ApiProtocol:
    def setup_method(self):
        self.protocol = ApiProtocol(
            transport=MagicMock(spec=SocketTransport),
            encoding="utf-8",
        )
        self.async_protocol = AsyncApiProtocol(
            transport=MagicMock(spec=AsyncSocketTransport),
            encoding="utf-8",
        )

    @pytest.mark.asyncio
    @patch.object(Encoder, "encodeSentence")
    async def test_writeSentence_calls_encodeSentence(self, encodeSentence_mock):
        self.protocol.writeSentence("/ip/address/print", "=key=value")
        encodeSentence_mock.assert_called_once_with("/ip/address/print", "=key=value")

        # async
        await self.async_protocol.writeSentence("/ip/address/print", "=key=value")
        encodeSentence_mock.assert_called_with("/ip/address/print", "=key=value")

    @pytest.mark.asyncio
    @patch.object(Encoder, "encodeSentence")
    async def test_writeSentence_calls_transport_write(self, encodeSentence_mock):
        """Assert that write is called with encoded sentence."""
        self.protocol.writeSentence("/ip/address/print", "=key=value")
        self.protocol.transport.write.assert_called_once_with(encodeSentence_mock.return_value)

        # async
        await self.async_protocol.writeSentence("/ip/address/print", "=key=value")
        self.async_protocol.transport.write.assert_called_once_with(encodeSentence_mock.return_value)

    @pytest.mark.asyncio
    @patch("librouteros.protocol.iter", return_value=("!fatal", "reason"))
    async def test_readSentence_raises_FatalError(self, iter_mock):
        """Assert that FatalError is raised with its reason."""
        with pytest.raises(FatalError) as error:
            self.protocol.readSentence()
        assert str(error.value) == "reason"
        assert self.protocol.transport.close.call_count == 1

    @pytest.mark.asyncio
    @patch("librouteros.protocol.AsyncApiProtocol.readWord", side_effect=["!fatal", "reason", ""])
    async def test_async_readSentence_raises_FatalError(self, readWord_mock):
        """Assert that FatalError is raised with its reason."""
        with pytest.raises(FatalError) as error:
            await self.async_protocol.readSentence()
        assert str(error.value) == "reason"
        assert self.async_protocol.transport.close.call_count == 1

    @pytest.mark.asyncio
    async def test_decoding_ignores_character_errors(self):
        word = b"\x11\xfb\x95" + "łąć".encode("utf-8")
        length = Encoder.encodeLength(len(word))
        self.protocol.transport.read.side_effect = (length, b"", word)
        assert self.protocol.readWord() == "\x11łąć"

        # async
        self.async_protocol.transport.read.side_effect = (length, b"", word)
        assert await self.async_protocol.readWord() == "\x11łąć"

    @pytest.mark.asyncio
    async def test_close(self):
        self.protocol.close()
        self.protocol.transport.close.assert_called_once_with()

        # async
        await self.async_protocol.close()
        self.async_protocol.transport.close.assert_called_once_with()