File: base.py

package info (click to toggle)
python-pook 2.1.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 672 kB
  • sloc: python: 3,558; makefile: 13
file content (248 lines) | stat: -rw-r--r-- 8,555 bytes parent folder | download
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
import asyncio
from collections.abc import Sequence
import json
from typing import Mapping, Optional, Tuple

import pytest

import pook
from pook.exceptions import PookNoMatches


class StandardTests:
    is_async: bool = False
    loop: asyncio.AbstractEventLoop

    async def amake_request(
        self,
        method: str,
        url: str,
        content: Optional[bytes] = None,
        headers: Optional[Sequence[tuple[str, str]]] = None,
    ) -> Tuple[int, Optional[bytes], Mapping[str, str]]:
        raise NotImplementedError(
            "Sub-classes for async transports must implement `amake_request`"
        )

    def make_request(
        self,
        method: str,
        url: str,
        content: Optional[bytes] = None,
        headers: Optional[Sequence[tuple[str, str]]] = None,
    ) -> Tuple[int, Optional[bytes], Mapping[str, str]]:
        if self.is_async:
            return self.loop.run_until_complete(
                self.amake_request(method, url, content, headers)
            )

        raise NotImplementedError("Sub-classes must implement `make_request`")

    @pytest.fixture(autouse=True, scope="class")
    def _loop(self, request):
        if self.is_async:
            request.cls.loop = asyncio.new_event_loop()
            yield
            request.cls.loop.close()
        else:
            yield

    @pytest.fixture
    def url_404(self, httpbin):
        """404 httpbin URL.

        Useful in tests if pook is configured to reply 200, and the status is checked.
        If pook does not match the request (and if that was the intended behaviour)
        then the 404 status code makes that obvious!"""
        return f"{httpbin.url}/status/404"

    @pytest.fixture
    def url_500(self, httpbin):
        return f"{httpbin.url}/status/500"

    @pytest.fixture
    def url_401(self, httpbin):
        return httpbin + "/status/401"

    @pytest.mark.pook
    def test_activate_deactivate(self, url_404):
        """Deactivating pook allows requests to go through."""
        pook.get(url_404).reply(200).body("hello from pook")

        status, body, *_ = self.make_request("GET", url_404)

        assert status == 200
        assert body == b"hello from pook"

        pook.disable()

        status, body, *_ = self.make_request("GET", url_404)

        assert status == 404

    @pytest.mark.pook(allow_pending_mocks=True)
    def test_network_mode(self, url_404, url_500):
        """Enabling network mode allows requests to pass through even if no mock is matched."""
        pook.get(url_404).reply(200).body("hello from pook")
        pook.enable_network()

        # Avoid matching the mocks
        status, *_ = self.make_request("POST", url_500)

        assert status == 500

        mocked_status, mocked_body, *_ = self.make_request("GET", url_404)

        assert mocked_status == 200
        assert mocked_body == b"hello from pook"

    @pytest.mark.pook(allow_pending_mocks=True)
    def test_network_mode_hostname(self, url_401):
        example_com = "http://example.com"
        pook.get(example_com).header("x-pook", "1").reply(200).body("hello from pook")
        # httpbin runs on loopback
        pook.enable_network("127.0.0.1")

        httpbin_status, *_ = self.make_request("GET", url_401)

        # network is enabled for httpbin hostname so it goes through
        assert httpbin_status == 401

        with pytest.raises(PookNoMatches):
            # Make the request without query params to avoid matching the mock
            # which should raise a no match exception, as network mode is not enabled
            # for example.com hostname
            self.make_request("GET", example_com)

        # this matches the mock on the header, so gets 200 with the hello from pook body
        example_status, body, *_ = self.make_request(
            "GET", example_com, headers=[("x-pook", "1")]
        )

        assert example_status == 200
        assert body == b"hello from pook"

    @pytest.mark.pook(allow_pending_mocks=True)
    def test_multiple_network_filters(self, url_401):
        """When multiple network filters are added, only one is required to match for the
        request to be allowed through the network."""

        def has_x_header(request: pook.Request):
            return request.headers.get("x-pook") == "x"

        def has_y_header(request: pook.Request):
            return request.headers.get("x-pook") == "y"

        pook.enable_network()

        pook.use_network_filter(has_x_header, has_y_header)

        pook.get(url_401).header("x-pook", "z").reply(200).body("hello from pook")

        # Network filter matches, so request is allowed despite not matching a mock
        x_status, *_ = self.make_request("GET", url_401, headers=[("x-pook", "x")])
        assert x_status == 401

        # Network filter matches, so request is allowed despite not matching a mock
        y_status, *_ = self.make_request("GET", url_401, headers=[("x-pook", "y")])
        assert y_status == 401

        # Mock matches, so the response is mocked
        z_status, z_body, *_ = self.make_request(
            "GET", url_401, headers=[("x-pook", "z")]
        )
        assert z_status == 200
        assert z_body == b"hello from pook"

    @pytest.mark.pook
    def test_json_request(self, url_404):
        """JSON request bodies are correctly matched."""
        json_request = {"hello": "json-request"}
        pook.get(url_404).json(json_request).reply(200).body("hello from pook")

        status, body, *_ = self.make_request(
            "GET", url_404, json.dumps(json_request).encode()
        )

        assert status == 200
        assert body == b"hello from pook"

    @pytest.mark.pook
    def test_json_response(self, url_404):
        """JSON responses are correctly mocked."""
        json_response = {"hello": "json-request"}
        pook.get(url_404).reply(200).json(json_response)

        status, body, *_ = self.make_request("GET", url_404)

        assert status == 200
        assert body
        assert json.loads(body) == json_response

    @pytest.mark.pook
    def test_json_request_and_response(self, url_404):
        """JSON requests and responses do not interfere with each other."""
        json_request = {"id": "123abc"}
        json_response = {"title": "123abc title"}
        pook.get(url_404).json(json_request).reply(200).json(json_response)

        status, body, *_ = self.make_request(
            "GET", url_404, content=json.dumps(json_request).encode()
        )

        assert status == 200
        assert body
        assert json.loads(body) == json_response

    @pytest.mark.pook
    def test_header_sent(self, url_404):
        """Sent headers can be matched."""
        headers = [("x-hello", "from pook")]
        pook.get(url_404).header("x-hello", "from pook").reply(200).body(
            "hello from pook"
        )

        status, body, _ = self.make_request("GET", url_404, headers=headers)

        assert status == 200
        assert body == b"hello from pook"

    @pytest.mark.pook
    def test_mocked_response_headers(self, url_404):
        """Mocked response headers are appropriately returned."""
        pook.get(url_404).reply(200).header("x-hello", "from pook")

        status, _, headers = self.make_request("GET", url_404)

        assert status == 200
        assert headers["x-hello"] == "from pook"

    @pytest.mark.pook
    def test_mutli_value_headers(self, url_404):
        """Multi-value headers can be matched."""
        match_headers = [("x-hello", "from pook"), ("x-hello", "another time")]
        pook.get(url_404).header("x-hello", "from pook, another time").reply(200)

        status, *_ = self.make_request("GET", url_404, headers=match_headers)

        assert status == 200

    @pytest.mark.pook
    def test_mutli_value_response_headers(self, url_404):
        """Multi-value response headers can be mocked."""
        pook.get(url_404).reply(200).header("x-hello", "from pook").header(
            "x-hello", "another time"
        )

        status, _, headers = self.make_request("GET", url_404)

        assert status == 200
        assert headers["x-hello"] == "from pook, another time"

    @pytest.mark.pook(allow_pending_mocks=True)
    def test_unmatched_headers_none_sent(self, url_404):
        """Header matching will run, but not match, on requests that send no headers."""
        pook.get(url_404).header("x-hello", "from pook").reply(200)

        with pytest.raises(PookNoMatches):
            self.make_request("GET", url_404)