Completed
Push — develop ( 7d38f7...827c8b )
by Jace
02:04
created

MockList   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 7
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 2
c 3
b 0
f 0
dl 0
loc 7
rs 10
1
# pylint: disable=missing-docstring,no-self-use,attribute-defined-outside-init,protected-access,misplaced-comparison-constant
2
3
import logging
4
from unittest.mock import Mock
5
6
import pytest
7
8
import yorm
9
from yorm.bases import Mappable
10
from yorm.mapper import Mapper
11
from yorm.types import String, Integer, Boolean, List, Dictionary
12
13
from . import strip
14
15
16
class MockMapper(Mapper):
17
    """Mapped file with stubbed file IO."""
18
19
    def __init__(self, obj, path, attrs):
20
        super().__init__(obj, path, attrs)
21
        self._mock_file = None
22
        self._mock_modified = True
23
        self.exists = True
24
25
    def _read(self):
26
        text = self._mock_file
27
        logging.debug("Mock read:\n%s", text.strip())
28
        return text
29
30
    def _write(self, text):
31
        logging.debug("Mock write:\n%s", text.strip())
32
        self._mock_file = text
33
        self.modified = True
34
35
    @property
36
    def modified(self):
37
        return self._mock_modified
38
39
    @modified.setter
40
    def modified(self, changes):
41
        self._mock_modified = changes
42
43
44
# CLASSES ######################################################################
45
46
@yorm.attr(all=Integer)
47
class IntegerList(List):
48
    """List of integers."""
49
50
51
@yorm.attr(status=Boolean)
52
class StatusDictionary(Dictionary):
53
    """Dictionary of statuses."""
54
55
56
class SampleMappable(Mappable):
57
    """Sample mappable class with hard-coded settings."""
58
59
    def __init__(self):
60
        self.__mapper__ = None
61
62
        logging.debug("Initializing sample...")
63
        self.var1 = None
64
        self.var2 = None
65
        self.var3 = None
66
        self.var4 = None
67
        self.var5 = None
68
        logging.debug("Sample initialized")
69
70
        path = "mock/path/to/sample.yml"
71
        attrs = {'var1': String,
72
                 'var2': Integer,
73
                 'var3': Boolean,
74
                 'var4': IntegerList,
75
                 'var5': StatusDictionary}
76
        self.__mapper__ = MockMapper(self, path, attrs)
77
        self.__mapper__.save()
78
79
    def __repr__(self):
80
        return "<sample {}>".format(id(self))
81
82
83
# TESTS ########################################################################
84
85
86
class TestMappable:
87
    """Unit tests for the `Mappable` class."""
88
89
    def setup_method(self, _):
90
        """Create an mappable instance for tests."""
91
        self.sample = SampleMappable()
92
93
    def test_init(self):
94
        """Verify files are created after initialized."""
95
        text = self.sample.__mapper__._read()
96
        assert strip("""
97
        var1: ''
98
        var2: 0
99
        var3: false
100
        var4:
101
        -
102
        var5:
103
          status: false
104
        """) == text
105
106
    def test_set(self):
107
        """Verify the file is written to after setting an attribute."""
108
        self.sample.var1 = "abc123"
109
        self.sample.var2 = 1
110
        self.sample.var3 = True
111
        self.sample.var4 = [42]
112
        self.sample.var5 = {'status': True}
113
        text = self.sample.__mapper__._read()
114
        assert strip("""
115
        var1: abc123
116
        var2: 1
117
        var3: true
118
        var4:
119
        - 42
120
        var5:
121
          status: true
122
        """) == text
123
124
    def test_set_converted(self):
125
        """Verify conversion occurs when setting attributes."""
126
        self.sample.var1 = 42
127
        self.sample.var2 = "1"
128
        self.sample.var3 = 'off'
129
        self.sample.var4 = None
130
        self.sample.var5 = {'status': 1}
131
        text = self.sample.__mapper__._read()
132
        assert strip("""
133
        var1: 42
134
        var2: 1
135
        var3: false
136
        var4:
137
        -
138
        var5:
139
          status: true
140
        """) == text
141
142
    def test_set_error(self):
143
        """Verify an exception is raised when a value cannot be converted."""
144
        with pytest.raises(ValueError):
145
            self.sample.var2 = "abc"
146
147
    def test_get(self):
148
        """Verify the file is read from before getting an attribute."""
149
        text = strip("""
150
        var1: def456
151
        var2: 42
152
        var3: off
153
        """)
154
        self.sample.__mapper__._write(text)
155
        assert"def456" == self.sample.var1
156
        assert 42 == self.sample.var2
157
        assert False is self.sample.var3
158
159
    def test_error_invalid_yaml(self):
160
        """Verify an exception is raised on invalid YAML."""
161
        text = strip("""
162
        invalid: -
163
        """)
164
        self.sample.__mapper__._write(text)
165
        with pytest.raises(ValueError):
166
            print(self.sample.var1)
167
168
    def test_error_unexpected_yaml(self):
169
        """Verify an exception is raised on unexpected YAML."""
170
        text = strip("""
171
        not a dictionary
172
        """)
173
        self.sample.__mapper__._write(text)
174
        with pytest.raises(ValueError):
175
            print(self.sample.var1)
176
177
    def test_new(self):
178
        """Verify new attributes are added to the object."""
179
        self.sample.__mapper__.auto_track = True
180
        text = strip("""
181
        new: 42
182
        """)
183
        self.sample.__mapper__._write(text)
184
        assert 42 == self.sample.new
185
186
    def test_new_unknown(self):
187
        """Verify an exception is raised on new attributes w/ unknown types"""
188
        self.sample.__mapper__.auto_track = True
189
        text = strip("""
190
        new: !!timestamp 2001-12-15T02:59:43.1Z
191
        """)
192
        self.sample.__mapper__._write(text)
193
        with pytest.raises(ValueError):
194
            print(self.sample.var1)
195
196
197
class TestMappableTriggers:
198
199
    class MockDict(Mappable, dict):
200
        pass
201
202
    class MockList:
203
204
        def append(self, value):
205
            print(value)
206
207
        def insert(self, value):
208
            print(value)
209
210
    class Sample(MockDict, MockList):
211
212
        __mapper__ = Mock()
213
        __mapper__.attrs = {}
214
        __mapper__.load = Mock()
215
        __mapper__.save = Mock()
216
217
    def setup_method(self, _):
218
        """Create an mappable instance for tests."""
219
        self.sample = self.Sample()
220
        self.sample.__mapper__.load.reset_mock()
221
        self.sample.__mapper__.save.reset_mock()
222
        self.sample.__mapper__.auto_save_after_load = False
223
224
    def test_getattribute(self):
225
        with pytest.raises(AttributeError):
226
            getattr(self.sample, 'foo')
227
        assert 1 == self.sample.__mapper__.load.call_count
228
        assert 0 == self.sample.__mapper__.save.call_count
229
230
    def test_setattr(self):
231
        self.sample.__mapper__.attrs['foo'] = Mock()
232
        setattr(self.sample, 'foo', 'bar')
233
        assert 0 == self.sample.__mapper__.load.call_count
234
        assert 1 == self.sample.__mapper__.save.call_count
235
236
    def test_getitem(self):
237
        with pytest.raises(KeyError):
238
            print(self.sample['foo'])
239
        assert 1 == self.sample.__mapper__.load.call_count
240
        assert 0 == self.sample.__mapper__.save.call_count
241
242
    def test_setitem(self):
243
        self.sample['foo'] = 'bar'
244
        assert 0 == self.sample.__mapper__.load.call_count
245
        assert 1 == self.sample.__mapper__.save.call_count
246
247
    def test_delitem(self):
248
        self.sample['foo'] = 'bar'
249
        self.sample.__mapper__.save.reset_mock()
250
251
        del self.sample['foo']
252
        assert 0 == self.sample.__mapper__.load.call_count
253
        assert 1 == self.sample.__mapper__.save.call_count
254
255
    def test_append(self):
256
        self.sample.append('foo')
257
        assert 1 == self.sample.__mapper__.load.call_count
258
        assert 1 == self.sample.__mapper__.save.call_count
259
260
    def test_insert(self):
261
        self.sample.insert('foo')
262
        assert 1 == self.sample.__mapper__.load.call_count
263
        assert 1 == self.sample.__mapper__.save.call_count
264
265
    def test_iter(self):
266
        self.sample.append('foo')
267
        self.sample.append('bar')
268
        self.sample.__mapper__.load.reset_mock()
269
        self.sample.__mapper__.save.reset_mock()
270
        self.sample.__mapper__.auto_save_after_load = False
271
        self.sample.__mapper__.modified = True
272
273
        for item in self.sample:
274
            print(item)
275
        assert 1 == self.sample.__mapper__.load.call_count
276
        assert 0 == self.sample.__mapper__.save.call_count
277
278
    def test_handle_missing_mapper(self):
279
        sample = self.MockDict()
280
        sample.__mapper__ = None
281
        sample[0] = 0
282
        print(sample[0])
283
        assert None is sample.__mapper__
284