1# -*- coding: utf-8 -*-
2"""
3test_events.py
4~~~~~~~~~~~~~~
5
6Specific tests for any function that is logically self-contained as part of
7events.py.
8"""
9import inspect
10import sys
11
12from hypothesis import given
13from hypothesis.strategies import (
14    integers, lists, tuples
15)
16import pytest
17
18import h2.errors
19import h2.events
20import h2.settings
21
22
23# We define a fairly complex Hypothesis strategy here. We want to build a list
24# of two tuples of (Setting, value). For Setting we want to make sure we can
25# handle settings that the rest of hyper knows nothing about, so we want to
26# use integers from 0 to (2**16-1). For values, they're from 0 to (2**32-1).
27# Define that strategy here for clarity.
28SETTINGS_STRATEGY = lists(
29    tuples(
30        integers(min_value=0, max_value=2**16-1),
31        integers(min_value=0, max_value=2**32-1),
32    )
33)
34
35
36class TestRemoteSettingsChanged(object):
37    """
38    Validate the function of the RemoteSettingsChanged event.
39    """
40    @given(SETTINGS_STRATEGY)
41    def test_building_settings_from_scratch(self, settings_list):
42        """
43        Missing old settings are defaulted to None.
44        """
45        settings_dict = dict(settings_list)
46        e = h2.events.RemoteSettingsChanged.from_settings(
47            old_settings={},
48            new_settings=settings_dict,
49        )
50
51        for setting, new_value in settings_dict.items():
52            assert e.changed_settings[setting].setting == setting
53            assert e.changed_settings[setting].original_value is None
54            assert e.changed_settings[setting].new_value == new_value
55
56    @given(SETTINGS_STRATEGY, SETTINGS_STRATEGY)
57    def test_only_reports_changed_settings(self,
58                                           old_settings_list,
59                                           new_settings_list):
60        """
61        Settings that were not changed are not reported.
62        """
63        old_settings_dict = dict(old_settings_list)
64        new_settings_dict = dict(new_settings_list)
65        e = h2.events.RemoteSettingsChanged.from_settings(
66            old_settings=old_settings_dict,
67            new_settings=new_settings_dict,
68        )
69
70        assert len(e.changed_settings) == len(new_settings_dict)
71        assert (
72            sorted(list(e.changed_settings.keys())) ==
73            sorted(list(new_settings_dict.keys()))
74        )
75
76    @given(SETTINGS_STRATEGY, SETTINGS_STRATEGY)
77    def test_correctly_reports_changed_settings(self,
78                                                old_settings_list,
79                                                new_settings_list):
80        """
81        Settings that are changed are correctly reported.
82        """
83        old_settings_dict = dict(old_settings_list)
84        new_settings_dict = dict(new_settings_list)
85        e = h2.events.RemoteSettingsChanged.from_settings(
86            old_settings=old_settings_dict,
87            new_settings=new_settings_dict,
88        )
89
90        for setting, new_value in new_settings_dict.items():
91            original_value = old_settings_dict.get(setting)
92            assert e.changed_settings[setting].setting == setting
93            assert e.changed_settings[setting].original_value == original_value
94            assert e.changed_settings[setting].new_value == new_value
95
96
97class TestEventReprs(object):
98    """
99    Events have useful representations.
100    """
101    example_request_headers = [
102        (':authority', 'example.com'),
103        (':path', '/'),
104        (':scheme', 'https'),
105        (':method', 'GET'),
106    ]
107    example_informational_headers = [
108        (':status', '100'),
109        ('server', 'fake-serv/0.1.0')
110    ]
111    example_response_headers = [
112        (':status', '200'),
113        ('server', 'fake-serv/0.1.0')
114    ]
115
116    def test_requestreceived_repr(self):
117        """
118        RequestReceived has a useful debug representation.
119        """
120        e = h2.events.RequestReceived()
121        e.stream_id = 5
122        e.headers = self.example_request_headers
123
124        assert repr(e) == (
125            "<RequestReceived stream_id:5, headers:["
126            "(':authority', 'example.com'), "
127            "(':path', '/'), "
128            "(':scheme', 'https'), "
129            "(':method', 'GET')]>"
130        )
131
132    def test_responsereceived_repr(self):
133        """
134        ResponseReceived has a useful debug representation.
135        """
136        e = h2.events.ResponseReceived()
137        e.stream_id = 500
138        e.headers = self.example_response_headers
139
140        assert repr(e) == (
141            "<ResponseReceived stream_id:500, headers:["
142            "(':status', '200'), "
143            "('server', 'fake-serv/0.1.0')]>"
144        )
145
146    def test_trailersreceived_repr(self):
147        """
148        TrailersReceived has a useful debug representation.
149        """
150        e = h2.events.TrailersReceived()
151        e.stream_id = 62
152        e.headers = self.example_response_headers
153
154        assert repr(e) == (
155            "<TrailersReceived stream_id:62, headers:["
156            "(':status', '200'), "
157            "('server', 'fake-serv/0.1.0')]>"
158        )
159
160    def test_informationalresponsereceived_repr(self):
161        """
162        InformationalResponseReceived has a useful debug representation.
163        """
164        e = h2.events.InformationalResponseReceived()
165        e.stream_id = 62
166        e.headers = self.example_informational_headers
167
168        assert repr(e) == (
169            "<InformationalResponseReceived stream_id:62, headers:["
170            "(':status', '100'), "
171            "('server', 'fake-serv/0.1.0')]>"
172        )
173
174    def test_datareceived_repr(self):
175        """
176        DataReceived has a useful debug representation.
177        """
178        e = h2.events.DataReceived()
179        e.stream_id = 888
180        e.data = b"abcdefghijklmnopqrstuvwxyz"
181        e.flow_controlled_length = 88
182
183        assert repr(e) == (
184            "<DataReceived stream_id:888, flow_controlled_length:88, "
185            "data:6162636465666768696a6b6c6d6e6f7071727374>"
186        )
187
188    def test_windowupdated_repr(self):
189        """
190        WindowUpdated has a useful debug representation.
191        """
192        e = h2.events.WindowUpdated()
193        e.stream_id = 0
194        e.delta = 2**16
195
196        assert repr(e) == "<WindowUpdated stream_id:0, delta:65536>"
197
198    def test_remotesettingschanged_repr(self):
199        """
200        RemoteSettingsChanged has a useful debug representation.
201        """
202        e = h2.events.RemoteSettingsChanged()
203        e.changed_settings = {
204            h2.settings.SettingCodes.INITIAL_WINDOW_SIZE:
205                h2.settings.ChangedSetting(
206                    h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**16, 2**15
207                ),
208        }
209
210        assert repr(e) == (
211            "<RemoteSettingsChanged changed_settings:{ChangedSetting("
212            "setting=SettingCodes.INITIAL_WINDOW_SIZE, original_value=65536, "
213            "new_value=32768)}>"
214        )
215
216    def test_pingreceived_repr(self):
217        """
218        PingReceived has a useful debug representation.
219        """
220        e = h2.events.PingReceived()
221        e.ping_data = b'abcdefgh'
222
223        assert repr(e) == "<PingReceived ping_data:6162636465666768>"
224
225    def test_pingackreceived_repr(self):
226        """
227        PingAckReceived has a useful debug representation.
228        """
229        e = h2.events.PingAckReceived()
230        e.ping_data = b'abcdefgh'
231
232        assert repr(e) == "<PingAckReceived ping_data:6162636465666768>"
233
234    def test_streamended_repr(self):
235        """
236        StreamEnded has a useful debug representation.
237        """
238        e = h2.events.StreamEnded()
239        e.stream_id = 99
240
241        assert repr(e) == "<StreamEnded stream_id:99>"
242
243    def test_streamreset_repr(self):
244        """
245        StreamEnded has a useful debug representation.
246        """
247        e = h2.events.StreamReset()
248        e.stream_id = 919
249        e.error_code = h2.errors.ErrorCodes.ENHANCE_YOUR_CALM
250        e.remote_reset = False
251
252        assert repr(e) == (
253            "<StreamReset stream_id:919, "
254            "error_code:ErrorCodes.ENHANCE_YOUR_CALM, remote_reset:False>"
255        )
256
257    def test_pushedstreamreceived_repr(self):
258        """
259        PushedStreamReceived has a useful debug representation.
260        """
261        e = h2.events.PushedStreamReceived()
262        e.pushed_stream_id = 50
263        e.parent_stream_id = 11
264        e.headers = self.example_request_headers
265
266        assert repr(e) == (
267            "<PushedStreamReceived pushed_stream_id:50, parent_stream_id:11, "
268            "headers:["
269            "(':authority', 'example.com'), "
270            "(':path', '/'), "
271            "(':scheme', 'https'), "
272            "(':method', 'GET')]>"
273        )
274
275    def test_settingsacknowledged_repr(self):
276        """
277        SettingsAcknowledged has a useful debug representation.
278        """
279        e = h2.events.SettingsAcknowledged()
280        e.changed_settings = {
281            h2.settings.SettingCodes.INITIAL_WINDOW_SIZE:
282                h2.settings.ChangedSetting(
283                    h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**16, 2**15
284                ),
285        }
286
287        assert repr(e) == (
288            "<SettingsAcknowledged changed_settings:{ChangedSetting("
289            "setting=SettingCodes.INITIAL_WINDOW_SIZE, original_value=65536, "
290            "new_value=32768)}>"
291        )
292
293    def test_priorityupdated_repr(self):
294        """
295        PriorityUpdated has a useful debug representation.
296        """
297        e = h2.events.PriorityUpdated()
298        e.stream_id = 87
299        e.weight = 32
300        e.depends_on = 8
301        e.exclusive = True
302
303        assert repr(e) == (
304            "<PriorityUpdated stream_id:87, weight:32, depends_on:8, "
305            "exclusive:True>"
306        )
307
308    @pytest.mark.parametrize("additional_data,data_repr", [
309        (None, "None"),
310        (b'some data', "736f6d652064617461")
311    ])
312    def test_connectionterminated_repr(self, additional_data, data_repr):
313        """
314        ConnectionTerminated has a useful debug representation.
315        """
316        e = h2.events.ConnectionTerminated()
317        e.error_code = h2.errors.ErrorCodes.INADEQUATE_SECURITY
318        e.last_stream_id = 33
319        e.additional_data = additional_data
320
321        assert repr(e) == (
322            "<ConnectionTerminated error_code:ErrorCodes.INADEQUATE_SECURITY, "
323            "last_stream_id:33, additional_data:%s>" % data_repr
324        )
325
326    def test_alternativeserviceavailable_repr(self):
327        """
328        AlternativeServiceAvailable has a useful debug representation.
329        """
330        e = h2.events.AlternativeServiceAvailable()
331        e.origin = b"example.com"
332        e.field_value = b'h2=":8000"; ma=60'
333
334        assert repr(e) == (
335            '<AlternativeServiceAvailable origin:example.com, '
336            'field_value:h2=":8000"; ma=60>'
337        )
338
339    def test_unknownframereceived_repr(self):
340        """
341        UnknownFrameReceived has a useful debug representation.
342        """
343        e = h2.events.UnknownFrameReceived()
344        assert repr(e) == '<UnknownFrameReceived>'
345
346
347def all_events():
348    """
349    Generates all the classes (i.e., events) defined in h2.events.
350    """
351    for _, obj in inspect.getmembers(sys.modules['h2.events']):
352
353        # We are only interested in objects that are defined in h2.events;
354        # objects that are imported from other modules are not of interest.
355        if hasattr(obj, '__module__') and (obj.__module__ != 'h2.events'):
356            continue
357
358        if inspect.isclass(obj):
359            yield obj
360
361
362@pytest.mark.parametrize('event', all_events())
363def test_all_events_subclass_from_event(event):
364    """
365    Every event defined in h2.events subclasses from h2.events.Event.
366    """
367    assert (event is h2.events.Event) or issubclass(event, h2.events.Event)
368