1 2
import json
2 2
import logging
3 2
from unittest.mock import patch
4

5 2
from freezegun import freeze_time
6 2
import pytest
7 2
import requests_mock
8 2
import requests
9

10 2
from django.core.cache import caches
11

12 2
from directory_client_core.base import AbstractAPIClient
13 2
from directory_client_core import helpers
14

15

16 2
@pytest.fixture
17
def fallback_cache():
18 2
    return caches['fallback']
19

20

21 2
@pytest.fixture(autouse=True)
22
def clear_fallback_cache(fallback_cache):
23 2
    fallback_cache.clear()
24

25

26 2
@pytest.fixture
27
def cached_client(fallback_cache):
28

29 2
    class APIClient(AbstractAPIClient):
30 2
        version = 1
31

32 2
        @helpers.fallback(cache=fallback_cache)
33
        def get(self, *args, **kwargs):
34 2
            return super().get(*args, **kwargs)
35

36 2
        def retrieve(self, slug):
37 2
            return self.get(
38
                url='/some/path/{slug}/'.format(slug=slug),
39
                params={'x': 'y', 'a': 'b'},
40
            )
41

42 2
    return APIClient(
43
        base_url='http://example.com',
44
        api_key='debug',
45
        sender_id='test-sender',
46
        timeout=5,
47
    )
48

49

50 2
def test_good_response_cached(cached_client, fallback_cache):
51 2
    expected_data = bytes(json.dumps({'key': 'value'}), 'utf8')
52 2
    path = '/some/path/thing/'
53

54 2
    with requests_mock.mock() as mock:
55 2
        mock.get('http://example.com' + path, content=expected_data)
56 2
        cached_client.retrieve('thing')
57

58 2
    cache_key = path + '?a=b&x=y'
59 2
    assert fallback_cache.get(cache_key) == expected_data
60

61

62 2
def test_good_response_etag(cached_client, fallback_cache):
63 2
    expected_data = bytes(
64
        json.dumps({'key': 'value', 'etag': '123'}), 'utf8'
65
    )
66 2
    path = '/some/path/thing/'
67

68 2
    url = 'http://example.com' + path
69 2
    headers = {'ETag': '"123"'}
70

71
    # given the page has been cached
72 2
    with requests_mock.mock() as mock:
73 2
        mock.get(url, content=expected_data, headers=headers)
74 2
        cached_client.retrieve('thing')
75

76 2
    cache_key = path + '?a=b&x=y'
77 2
    assert fallback_cache.get(cache_key) == expected_data
78

79
    # when the same page is requested and the remote server returns 304
80 2
    with requests_mock.mock() as mock:
81 2
        mock.get(url, content=b'', headers=headers, status_code=304)
82 2
        response = cached_client.retrieve('thing')
83 2
        request = mock.request_history[0]
84

85
    # then the request exposed the etag cache headers
86 2
    assert request.headers['If-None-Match'] == '"123"'
87

88
    # and the cached content is returned
89 2
    assert isinstance(response, helpers.CacheResponse)
90

91

92 2
@freeze_time('2012-01-14')
93
def test_good_response_cache_timeout(cached_client, fallback_cache, settings):
94 2
    settings.DIRECTORY_CLIENT_CORE_CACHE_EXPIRE_SECONDS = 100
95 2
    expected_data = bytes(json.dumps({'key': 'value'}), 'utf8')
96 2
    path = '/some/path/thing/'
97

98 2
    with requests_mock.mock() as mock:
99 2
        mock.get('http://example.com' + path, content=expected_data)
100 2
        cached_client.retrieve('thing')
101

102 2
    cache_key = path + '?a=b&x=y'
103

104 2
    key = fallback_cache.make_key(cache_key)
105 2
    fallback_cache.validate_key(key)
106

107 2
    assert fallback_cache._expire_info.get(key) == 1326499300.0
108

109

110 2
def test_bad_resonse_cache_hit(cached_client, caplog):
111 2
    path = '/some/path/thing/'
112 2
    expected_data = bytes(json.dumps({'key': 'value'}), 'utf8')
113 2
    url = 'http://example.com' + path
114

115 2
    with requests_mock.mock() as mock:
116 2
        mock.get(url, content=expected_data)
117 2
        response_one = cached_client.retrieve('thing')
118

119 2
    with requests_mock.mock() as mock:
120 2
        mock.get(url, status_code=400)
121 2
        response_two = cached_client.retrieve('thing')
122

123 2
    assert response_one.status_code == 200
124 2
    assert response_one.content == expected_data
125 2
    assert isinstance(response_one, helpers.LiveResponse)
126

127 2
    assert response_two.status_code == 200
128 2
    assert response_two.content == expected_data
129 2
    assert isinstance(response_two, helpers.CacheResponse)
130

131 2
    log = caplog.records[-1]
132 2
    assert log.levelname == 'ERROR'
133 2
    assert log.msg == helpers.MESSAGE_CACHE_HIT
134 2
    assert log.status_code == 400
135 2
    assert log.url == path
136

137

138 2
def test_bad_response_cache_miss(cached_client, caplog):
139 2
    path = '/some/path/thing/'
140 2
    url = 'http://example.com' + path
141

142 2
    with requests_mock.mock() as mock:
143 2
        mock.get(url, status_code=400)
144 2
        response = cached_client.retrieve('thing')
145

146 2
    assert response.status_code == 400
147 2
    assert isinstance(response, helpers.FailureResponse)
148

149 2
    log = caplog.records[-1]
150 2
    assert log.levelname == 'ERROR'
151 2
    assert log.msg == helpers.MESSAGE_CACHE_MISS
152 2
    assert log.status_code == 400
153 2
    assert log.url == path
154

155

156 2
def test_bad_response_404(cached_client, caplog):
157 2
    path = '/some/path/thing/'
158 2
    url = 'http://example.com' + path
159

160 2
    with requests_mock.mock() as mock:
161 2
        mock.get(url, status_code=404)
162 2
        response = cached_client.retrieve('thing')
163

164 2
    assert response.status_code == 404
165 2
    assert isinstance(response, helpers.LiveResponse)
166

167 2
    log = caplog.records[-1]
168 2
    assert log.levelname == 'ERROR'
169 2
    assert log.msg == helpers.MESSAGE_NOT_FOUND
170 2
    assert log.status_code == 404
171 2
    assert log.url == path
172

173

174 2
def test_connection_error_cache_hit(cached_client, caplog):
175 2
    path = '/some/path/thing/'
176 2
    expected_data = bytes(json.dumps({'key': 'value'}), 'utf8')
177 2
    url = 'http://example.com' + path
178

179 2
    with requests_mock.mock() as mock:
180 2
        mock.get(url, content=expected_data)
181 2
        response_one = cached_client.retrieve('thing')
182

183 2
    with patch('directory_client_core.base.AbstractAPIClient.get') as mock_get:
184 2
        mock_get.side_effect = requests.exceptions.ConnectionError()
185 2
        response_two = cached_client.retrieve('thing')
186

187 2
    assert response_one.status_code == 200
188 2
    assert response_one.content == expected_data
189 2
    assert isinstance(response_one, helpers.LiveResponse)
190

191 2
    assert response_two.status_code == 200
192 2
    assert response_two.content == expected_data
193 2
    assert isinstance(response_two, helpers.CacheResponse)
194

195 2
    log = caplog.records[-1]
196 2
    assert log.levelname == 'ERROR'
197 2
    assert log.msg == helpers.MESSAGE_CACHE_HIT
198 2
    assert log.url == path
199

200

201 2
def test_connection_error_cache_miss(cached_client, caplog):
202 2
    with patch('directory_client_core.base.AbstractAPIClient.get') as mock_get:
203 2
        mock_get.side_effect = requests.exceptions.ConnectionError()
204

205 2
        with pytest.raises(requests.exceptions.ConnectionError):
206 2
            cached_client.retrieve('thing')
207

208 2
    assert len(caplog.records) == 0
209

210

211 2
def test_cache_querystrings(cached_client, fallback_cache):
212 2
    expected_data = bytes(json.dumps({'key': 'value'}), 'utf8')
213 2
    path = '/some/path/thing/'
214

215 2
    with requests_mock.mock() as mock:
216 2
        mock.get('http://example.com' + path, content=expected_data)
217 2
        cached_client.retrieve('thing',)
218

219 2
    cache_key = path + '?a=b&x=y'
220 2
    assert fallback_cache.get(cache_key) == expected_data
221

222

223 2
def test_logging_noise_filtering(cached_client, caplog):
224

225 2
    path = '/some/path/thing/'
226 2
    expected_data = bytes(json.dumps({'key': 'value'}), 'utf8')
227 2
    url = 'http://example.com' + path
228

229 2
    with requests_mock.mock() as mock:
230 2
        mock.get(url, content=expected_data)
231 2
        cached_client.retrieve('thing')
232

233 2
    with patch('directory_client_core.base.AbstractAPIClient.get') as mock_get:
234 2
        mock_get.side_effect = requests.exceptions.ConnectionError()
235 2
        cached_client.retrieve('thing')
236

237 2
    errors = [item for item in caplog.records if item.levelname == 'ERROR']
238 2
    assert len(errors) == 1
239

240 2
    with patch('directory_client_core.base.AbstractAPIClient.get') as mock_get:
241 2
        mock_get.side_effect = requests.exceptions.ConnectionError()
242 2
        cached_client.retrieve('thing')
243

244
    # second log entry was filtered out
245 2
    errors = [item for item in caplog.records if item.levelname == 'ERROR']
246 2
    assert len(errors) == 1
247

248

249 2
def test_throttling_log_filter_timeout(settings, fallback_cache):
250 2
    settings.DIRECTORY_CLIENT_CORE_CACHE_LOG_THROTTLING_SECONDS = 40
251 2
    log_filter = helpers.ThrottlingFilter(cache=fallback_cache)
252

253 2
    assert log_filter.timeout_in_seconds == 40
254

255

256 2
def test_throttling_log_filter_timeout_default_day(settings, fallback_cache):
257 2
    settings.DIRECTORY_CLIENT_CORE_CACHE_LOG_THROTTLING_SECONDS = None
258 2
    log_filter = helpers.ThrottlingFilter(cache=fallback_cache)
259

260 2
    assert log_filter.timeout_in_seconds == 60*60*24  # 24 hours
261

262

263 2
def test_throttling_filter(fallback_cache):
264 2
    log_filter = helpers.ThrottlingFilter(cache=fallback_cache)
265 2
    logger = logging.getLogger()
266 2
    record = logger.makeRecord(
267
        name='',
268
        level='ERROR',
269
        fn='',
270
        lno='',
271
        msg='something bad happened',
272
        args=[],
273
        exc_info='',
274
        extra={'url': 'https://www.google.com'}
275
    )
276

277 2
    assert log_filter.filter(record) is True
278 2
    assert log_filter.filter(record) is False

Read our documentation on viewing source code .

Loading