1# -*- coding: utf-8 -*-
2"""
3test_stream_reset
4~~~~~~~~~~~~~~~~~
5
6More complex tests that exercise stream resetting functionality to validate
7that connection state is appropriately maintained.
8
9Specifically, these tests validate that streams that have been reset accurately
10keep track of connection-level state.
11"""
12import pytest
13
14import h2.connection
15import h2.errors
16import h2.events
17
18
19class TestStreamReset(object):
20    """
21    Tests for resetting streams.
22    """
23    example_request_headers = [
24        (b':authority', b'example.com'),
25        (b':path', b'/'),
26        (b':scheme', b'https'),
27        (b':method', b'GET'),
28    ]
29    example_response_headers = [
30        (b':status', b'200'),
31        (b'server', b'fake-serv/0.1.0'),
32        (b'content-length', b'0')
33    ]
34
35    def test_reset_stream_keeps_header_state_correct(self, frame_factory):
36        """
37        A stream that has been reset still affects the header decoder.
38        """
39        c = h2.connection.H2Connection()
40        c.initiate_connection()
41        c.send_headers(stream_id=1, headers=self.example_request_headers)
42        c.reset_stream(stream_id=1)
43        c.send_headers(stream_id=3, headers=self.example_request_headers)
44        c.clear_outbound_data_buffer()
45
46        f = frame_factory.build_headers_frame(
47            headers=self.example_response_headers, stream_id=1
48        )
49        rst_frame = frame_factory.build_rst_stream_frame(
50            1, h2.errors.ErrorCodes.STREAM_CLOSED
51        )
52        events = c.receive_data(f.serialize())
53        assert not events
54        assert c.data_to_send() == rst_frame.serialize()
55
56        # This works because the header state should be intact from the headers
57        # frame that was send on stream 1, so they should decode cleanly.
58        f = frame_factory.build_headers_frame(
59            headers=self.example_response_headers, stream_id=3
60        )
61        event = c.receive_data(f.serialize())[0]
62
63        assert isinstance(event, h2.events.ResponseReceived)
64        assert event.stream_id == 3
65        assert event.headers == self.example_response_headers
66
67    @pytest.mark.parametrize('close_id,other_id', [(1, 3), (3, 1)])
68    def test_reset_stream_keeps_flow_control_correct(self,
69                                                     close_id,
70                                                     other_id,
71                                                     frame_factory):
72        """
73        A stream that has been reset does not affect the connection flow
74        control window.
75        """
76        c = h2.connection.H2Connection()
77        c.initiate_connection()
78        c.send_headers(stream_id=1, headers=self.example_request_headers)
79        c.send_headers(stream_id=3, headers=self.example_request_headers)
80
81        # Record the initial window size.
82        initial_window = c.remote_flow_control_window(stream_id=other_id)
83
84        f = frame_factory.build_headers_frame(
85            headers=self.example_response_headers, stream_id=close_id
86        )
87        c.receive_data(f.serialize())
88        c.reset_stream(stream_id=close_id)
89        c.clear_outbound_data_buffer()
90
91        f = frame_factory.build_data_frame(
92            data=b'some data',
93            stream_id=close_id
94        )
95        c.receive_data(f.serialize())
96
97        expected = frame_factory.build_rst_stream_frame(
98            stream_id=close_id,
99            error_code=h2.errors.ErrorCodes.STREAM_CLOSED,
100        ).serialize()
101        assert c.data_to_send() == expected
102
103        new_window = c.remote_flow_control_window(stream_id=other_id)
104        assert initial_window - len(b'some data') == new_window
105
106    @pytest.mark.parametrize('clear_streams', [True, False])
107    def test_reset_stream_automatically_resets_pushed_streams(self,
108                                                              frame_factory,
109                                                              clear_streams):
110        """
111        Resetting a stream causes RST_STREAM frames to be automatically emitted
112        to close any streams pushed after the reset.
113        """
114        c = h2.connection.H2Connection()
115        c.initiate_connection()
116        c.send_headers(stream_id=1, headers=self.example_request_headers)
117        c.reset_stream(stream_id=1)
118        c.clear_outbound_data_buffer()
119
120        if clear_streams:
121            # Call open_outbound_streams to force the connection to clean
122            # closed streams.
123            c.open_outbound_streams
124
125        f = frame_factory.build_push_promise_frame(
126            stream_id=1,
127            promised_stream_id=2,
128            headers=self.example_request_headers,
129        )
130        events = c.receive_data(f.serialize())
131        assert not events
132
133        f = frame_factory.build_rst_stream_frame(
134            stream_id=2,
135            error_code=h2.errors.ErrorCodes.REFUSED_STREAM,
136        )
137        assert c.data_to_send() == f.serialize()
138