1import datetime as dt
2
3import pytest
4import pytz
5
6import stix2
7from stix2.exceptions import ExtraPropertiesError, STIXError
8from stix2.properties import (
9    BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty,
10    FloatProperty, HexProperty, IntegerProperty, ListProperty, Property,
11    StringProperty, TimestampProperty, TypeProperty,
12)
13
14
15def test_property():
16    p = Property()
17
18    assert p.required is False
19    assert p.clean('foo') == 'foo'
20    assert p.clean(3) == 3
21
22
23def test_basic_clean():
24    class Prop(Property):
25
26        def clean(self, value):
27            if value == 42:
28                return value
29            else:
30                raise ValueError("Must be 42")
31
32    p = Prop()
33
34    assert p.clean(42) == 42
35    with pytest.raises(ValueError):
36        p.clean(41)
37
38
39def test_property_default():
40    class Prop(Property):
41
42        def default(self):
43            return 77
44
45    p = Prop()
46
47    assert p.default() == 77
48
49
50def test_property_fixed():
51    p = Property(fixed="2.0")
52
53    assert p.clean("2.0")
54    with pytest.raises(ValueError):
55        assert p.clean("x") is False
56    with pytest.raises(ValueError):
57        assert p.clean(2.0) is False
58
59    assert p.default() == "2.0"
60    assert p.clean(p.default())
61
62
63def test_property_fixed_and_required():
64    with pytest.raises(STIXError):
65        Property(default=lambda: 3, required=True)
66
67
68def test_list_property():
69    p = ListProperty(StringProperty)
70
71    assert p.clean(['abc', 'xyz'])
72    with pytest.raises(ValueError):
73        p.clean([])
74
75
76def test_list_property_property_type_custom():
77    class TestObj(stix2.base._STIXBase):
78        _type = "test"
79        _properties = {
80            "foo": StringProperty(),
81        }
82    p = ListProperty(EmbeddedObjectProperty(type=TestObj))
83
84    objs_custom = [
85        TestObj(foo="abc", bar=123, allow_custom=True),
86        TestObj(foo="xyz"),
87    ]
88
89    assert p.clean(objs_custom)
90
91    dicts_custom = [
92        {"foo": "abc", "bar": 123},
93        {"foo": "xyz"},
94    ]
95
96    # no opportunity to set allow_custom=True when using dicts
97    with pytest.raises(ExtraPropertiesError):
98        p.clean(dicts_custom)
99
100
101def test_list_property_object_type():
102    class TestObj(stix2.base._STIXBase):
103        _type = "test"
104        _properties = {
105            "foo": StringProperty(),
106        }
107    p = ListProperty(TestObj)
108
109    objs = [TestObj(foo="abc"), TestObj(foo="xyz")]
110    assert p.clean(objs)
111
112    dicts = [{"foo": "abc"}, {"foo": "xyz"}]
113    assert p.clean(dicts)
114
115
116def test_list_property_object_type_custom():
117    class TestObj(stix2.base._STIXBase):
118        _type = "test"
119        _properties = {
120            "foo": StringProperty(),
121        }
122    p = ListProperty(TestObj)
123
124    objs_custom = [
125        TestObj(foo="abc", bar=123, allow_custom=True),
126        TestObj(foo="xyz"),
127    ]
128
129    assert p.clean(objs_custom)
130
131    dicts_custom = [
132        {"foo": "abc", "bar": 123},
133        {"foo": "xyz"},
134    ]
135
136    # no opportunity to set allow_custom=True when using dicts
137    with pytest.raises(ExtraPropertiesError):
138        p.clean(dicts_custom)
139
140
141def test_list_property_bad_element_type():
142    with pytest.raises(TypeError):
143        ListProperty(1)
144
145
146def test_list_property_bad_value_type():
147    class TestObj(stix2.base._STIXBase):
148        _type = "test"
149        _properties = {
150            "foo": StringProperty(),
151        }
152
153    list_prop = ListProperty(TestObj)
154    with pytest.raises(ValueError):
155        list_prop.clean([1])
156
157
158def test_string_property():
159    prop = StringProperty()
160
161    assert prop.clean('foobar')
162    assert prop.clean(1)
163    assert prop.clean([1, 2, 3])
164
165
166def test_type_property():
167    prop = TypeProperty('my-type')
168
169    assert prop.clean('my-type')
170    with pytest.raises(ValueError):
171        prop.clean('not-my-type')
172    assert prop.clean(prop.default())
173
174
175@pytest.mark.parametrize(
176    "value", [
177        2,
178        -1,
179        3.14,
180        False,
181    ],
182)
183def test_integer_property_valid(value):
184    int_prop = IntegerProperty()
185    assert int_prop.clean(value) is not None
186
187
188@pytest.mark.parametrize(
189    "value", [
190        -1,
191        -100,
192        -50 * 6,
193    ],
194)
195def test_integer_property_invalid_min_with_constraints(value):
196    int_prop = IntegerProperty(min=0, max=180)
197    with pytest.raises(ValueError) as excinfo:
198        int_prop.clean(value)
199    assert "minimum value is" in str(excinfo.value)
200
201
202@pytest.mark.parametrize(
203    "value", [
204        181,
205        200,
206        50 * 6,
207    ],
208)
209def test_integer_property_invalid_max_with_constraints(value):
210    int_prop = IntegerProperty(min=0, max=180)
211    with pytest.raises(ValueError) as excinfo:
212        int_prop.clean(value)
213    assert "maximum value is" in str(excinfo.value)
214
215
216@pytest.mark.parametrize(
217    "value", [
218        "something",
219        StringProperty(),
220    ],
221)
222def test_integer_property_invalid(value):
223    int_prop = IntegerProperty()
224    with pytest.raises(ValueError):
225        int_prop.clean(value)
226
227
228@pytest.mark.parametrize(
229    "value", [
230        2,
231        -1,
232        3.14,
233        False,
234    ],
235)
236def test_float_property_valid(value):
237    int_prop = FloatProperty()
238    assert int_prop.clean(value) is not None
239
240
241@pytest.mark.parametrize(
242    "value", [
243        "something",
244        StringProperty(),
245    ],
246)
247def test_float_property_invalid(value):
248    int_prop = FloatProperty()
249    with pytest.raises(ValueError):
250        int_prop.clean(value)
251
252
253@pytest.mark.parametrize(
254    "value", [
255        True,
256        False,
257        'True',
258        'False',
259        'true',
260        'false',
261        'TRUE',
262        'FALSE',
263        'T',
264        'F',
265        't',
266        'f',
267        1,
268        0,
269    ],
270)
271def test_boolean_property_valid(value):
272    bool_prop = BooleanProperty()
273
274    assert bool_prop.clean(value) is not None
275
276
277@pytest.mark.parametrize(
278    "value", [
279        'abc',
280        ['false'],
281        {'true': 'true'},
282        2,
283        -1,
284    ],
285)
286def test_boolean_property_invalid(value):
287    bool_prop = BooleanProperty()
288    with pytest.raises(ValueError):
289        bool_prop.clean(value)
290
291
292@pytest.mark.parametrize(
293    "value", [
294        '2017-01-01T12:34:56Z',
295    ],
296)
297def test_timestamp_property_valid(value):
298    ts_prop = TimestampProperty()
299    assert ts_prop.clean(value) == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
300
301
302def test_timestamp_property_invalid():
303    ts_prop = TimestampProperty()
304    with pytest.raises(TypeError):
305        ts_prop.clean(1)
306    with pytest.raises(ValueError):
307        ts_prop.clean("someday sometime")
308
309
310def test_binary_property():
311    bin_prop = BinaryProperty()
312
313    assert bin_prop.clean("TG9yZW0gSXBzdW0=")
314    with pytest.raises(ValueError):
315        bin_prop.clean("foobar")
316
317
318def test_hex_property():
319    hex_prop = HexProperty()
320
321    assert hex_prop.clean("4c6f72656d20497073756d")
322    with pytest.raises(ValueError):
323        hex_prop.clean("foobar")
324
325
326@pytest.mark.parametrize(
327    "value", [
328        ['a', 'b', 'c'],
329        ('a', 'b', 'c'),
330        'b',
331    ],
332)
333def test_enum_property_valid(value):
334    enum_prop = EnumProperty(value)
335    assert enum_prop.clean('b')
336
337
338def test_enum_property_clean():
339    enum_prop = EnumProperty(['1'])
340    assert enum_prop.clean(1) == '1'
341
342
343def test_enum_property_invalid():
344    enum_prop = EnumProperty(['a', 'b', 'c'])
345    with pytest.raises(ValueError):
346        enum_prop.clean('z')
347