summaryrefslogtreecommitdiff
path: root/tests/unit/policy
diff options
context:
space:
mode:
authorJordan Cook <jordan.cook@pioneer.com>2022-04-22 20:10:57 -0500
committerJordan Cook <jordan.cook@pioneer.com>2022-05-04 16:17:22 -0500
commitbbd984375d22dedaf33c8d0cad718cc09d072d25 (patch)
tree37eb6cfad188feefcd18dff05de9e591c7dff1aa /tests/unit/policy
parent3aa84ee6724491858a81ce401487fc85d0ee9c9d (diff)
downloadrequests-cache-bbd984375d22dedaf33c8d0cad718cc09d072d25.tar.gz
Implement Cache-Control: stale-while-revalidate
Diffstat (limited to 'tests/unit/policy')
-rw-r--r--tests/unit/policy/test_actions.py116
1 files changed, 76 insertions, 40 deletions
diff --git a/tests/unit/policy/test_actions.py b/tests/unit/policy/test_actions.py
index 5c63f31..317ecc8 100644
--- a/tests/unit/policy/test_actions.py
+++ b/tests/unit/policy/test_actions.py
@@ -1,5 +1,5 @@
from datetime import datetime, timedelta
-from unittest.mock import MagicMock, patch
+from unittest.mock import patch
import pytest
from requests import PreparedRequest, Request
@@ -16,6 +16,8 @@ IGNORED_DIRECTIVES = [
'public',
's-maxage=<seconds>',
]
+BASIC_REQUEST = Request(method='GET', url='https://site.com/img.jpg', headers={})
+EXPIRED_RESPONSE = CachedResponse(expires=datetime.utcnow() - timedelta(1))
@pytest.mark.parametrize(
@@ -142,6 +144,19 @@ def test_init_from_settings_and_headers(
assert actions.skip_read == expected_skip_read
+def test_update_from_cached_response__new_request():
+ actions = CacheActions.from_request('key', BASIC_REQUEST)
+ actions.update_from_cached_response(None)
+ assert actions.send_request is True
+
+
+def test_update_from_cached_response__resend_request():
+ actions = CacheActions.from_request('key', BASIC_REQUEST)
+
+ actions.update_from_cached_response(EXPIRED_RESPONSE)
+ assert actions.resend_request is True
+
+
@pytest.mark.parametrize(
'response_headers, expected_validation_headers',
[
@@ -154,16 +169,15 @@ def test_init_from_settings_and_headers(
),
],
)
-def test_update_from_cached_response(response_headers, expected_validation_headers):
+def test_update_from_cached_response__revalidate(response_headers, expected_validation_headers):
"""Conditional request headers should be added if the cached response is expired"""
- actions = CacheActions.from_request(
- 'key', MagicMock(url='https://img.site.com/base/img.jpg', headers={})
- )
+ actions = CacheActions.from_request('key', BASIC_REQUEST)
cached_response = CachedResponse(
- headers=response_headers, expires=datetime.now() - timedelta(1)
+ headers=response_headers, expires=datetime.utcnow() - timedelta(1)
)
actions.update_from_cached_response(cached_response)
+ assert actions.send_request is bool(expected_validation_headers)
assert actions._validation_headers == expected_validation_headers
@@ -174,24 +188,24 @@ def test_update_from_cached_response(response_headers, expected_validation_heade
({}, {'Cache-Control': 'max-age=0,must-revalidate'}),
],
)
-def test_update_from_cached_response__revalidate_headers(request_headers, response_headers):
- """Conditional request headers should be added if requested by headers (even if the response
- is not expired)"""
+def test_update_from_cached_response__refresh(request_headers, response_headers):
+ """Conditional request headers should be added if requested by response headers, even if the
+ response is not expired
+ """
actions = CacheActions.from_request(
- 'key', MagicMock(url='https://img.site.com/base/img.jpg', headers=request_headers)
+ 'key', Request(url='https://img.site.com/base/img.jpg', headers=request_headers)
)
cached_response = CachedResponse(headers={'ETag': ETAG, **response_headers}, expires=None)
actions.update_from_cached_response(cached_response)
+ assert actions.send_request is True
assert actions._validation_headers == {'If-None-Match': ETAG}
-def test_update_from_cached_response__ignored():
+def test_update_from_cached_response__no_revalidation():
"""Conditional request headers should NOT be added if the cached response is not expired and
revalidation is otherwise not requested"""
- actions = CacheActions.from_request(
- 'key', MagicMock(url='https://img.site.com/base/img.jpg', headers={})
- )
+ actions = CacheActions.from_request('key', BASIC_REQUEST)
cached_response = CachedResponse(
headers={'ETag': ETAG, 'Last-Modified': LAST_MODIFIED}, expires=None
)
@@ -200,6 +214,27 @@ def test_update_from_cached_response__ignored():
assert actions._validation_headers == {}
+def test_update_from_cached_response__504():
+ settings = CacheSettings(only_if_cached=True)
+ actions = CacheActions.from_request('key', BASIC_REQUEST, settings=settings)
+ actions.update_from_cached_response(EXPIRED_RESPONSE)
+ assert actions.error_504 is True
+
+
+def test_update_from_cached_response__stale_if_error():
+ settings = CacheSettings(only_if_cached=True, stale_if_error=True)
+ actions = CacheActions.from_request('key', BASIC_REQUEST, settings=settings)
+ actions.update_from_cached_response(EXPIRED_RESPONSE)
+ assert actions.error_504 is False and actions.resend_request is False
+
+
+def test_update_from_cached_response__stale_while_revalidate():
+ settings = CacheSettings(only_if_cached=True, stale_while_revalidate=True)
+ actions = CacheActions.from_request('key', BASIC_REQUEST, settings=settings)
+ actions.update_from_cached_response(EXPIRED_RESPONSE)
+ assert actions.resend_async is True
+
+
@pytest.mark.parametrize('max_stale, usable', [(5, False), (15, True)])
def test_is_usable__max_stale(max_stale, usable):
"""For a response that expired 10 seconds ago, it may be either accepted or rejected based on
@@ -210,9 +245,7 @@ def test_is_usable__max_stale(max_stale, usable):
headers={'Cache-Control': f'max-stale={max_stale}'},
)
actions = CacheActions.from_request('key', request)
- cached_response = CachedResponse(
- expires=datetime.utcnow() - timedelta(seconds=10),
- )
+ cached_response = CachedResponse(expires=datetime.utcnow() - timedelta(seconds=10))
assert actions.is_usable(cached_response) is usable
@@ -226,9 +259,7 @@ def test_is_usable__min_fresh(min_fresh, usable):
headers={'Cache-Control': f'min-fresh={min_fresh}'},
)
actions = CacheActions.from_request('key', request)
- cached_response = CachedResponse(
- expires=datetime.utcnow() + timedelta(seconds=10),
- )
+ cached_response = CachedResponse(expires=datetime.utcnow() + timedelta(seconds=10))
assert actions.is_usable(cached_response) is usable
@@ -249,13 +280,31 @@ def test_is_usable__stale_if_error(stale_if_error, error, usable):
headers={'Cache-Control': f'stale-if-error={stale_if_error}'},
)
actions = CacheActions.from_request('key', request)
- cached_response = CachedResponse(
- expires=datetime.utcnow() - timedelta(seconds=10),
- )
+ cached_response = CachedResponse(expires=datetime.utcnow() - timedelta(seconds=10))
assert actions.is_usable(cached_response, error=error) is usable
@pytest.mark.parametrize(
+ 'stale_while_revalidate, usable',
+ [
+ (5, False),
+ (15, True),
+ ],
+)
+def test_is_usable__stale_while_revalidate(stale_while_revalidate, usable):
+ """For a response that expired 10 seconds ago, if an error occured while refreshing, it may be
+ either accepted or rejected based on stale-while-revalidate
+ """
+ request = Request(
+ url='https://img.site.com/base/img.jpg',
+ headers={'Cache-Control': f'stale-while-revalidate={stale_while_revalidate}'},
+ )
+ actions = CacheActions.from_request('key', request)
+ cached_response = CachedResponse(expires=datetime.utcnow() - timedelta(seconds=10))
+ assert actions.is_usable(cached_response=cached_response) is usable
+
+
+@pytest.mark.parametrize(
'headers, expected_expiration',
[
({}, None),
@@ -272,10 +321,7 @@ def test_is_usable__stale_if_error(stale_if_error, error, usable):
)
def test_update_from_response(headers, expected_expiration):
"""Test with Cache-Control response headers"""
- url = 'https://img.site.com/base/img.jpg'
- actions = CacheActions.from_request(
- 'key', MagicMock(url=url), CacheSettings(cache_control=True)
- )
+ actions = CacheActions.from_request('key', BASIC_REQUEST, CacheSettings(cache_control=True))
actions.update_from_response(get_mock_response(headers=headers))
assert actions.expire_after == expected_expiration
@@ -283,19 +329,13 @@ def test_update_from_response(headers, expected_expiration):
def test_update_from_response__no_store():
- url = 'https://img.site.com/base/img.jpg'
- actions = CacheActions.from_request(
- 'key', MagicMock(url=url), CacheSettings(cache_control=True)
- )
+ actions = CacheActions.from_request('key', BASIC_REQUEST, CacheSettings(cache_control=True))
actions.update_from_response(get_mock_response(headers={'Cache-Control': 'no-store'}))
assert actions.skip_write is True
def test_update_from_response__ignored():
- url = 'https://img.site.com/base/img.jpg'
- actions = CacheActions.from_request(
- 'key', MagicMock(url=url), CacheSettings(cache_control=False)
- )
+ actions = CacheActions.from_request('key', BASIC_REQUEST, CacheSettings(cache_control=False))
actions.update_from_response(get_mock_response(headers={'Cache-Control': 'max-age=5'}))
assert actions.expire_after is None
@@ -307,10 +347,7 @@ def test_update_from_response__revalidate(mock_datetime, cache_headers, validato
"""If expiration is 0 and there's a validator, the response should be cached, but with immediate
expiration
"""
- url = 'https://img.site.com/base/img.jpg'
- actions = CacheActions.from_request(
- 'key', MagicMock(url=url), CacheSettings(cache_control=True)
- )
+ actions = CacheActions.from_request('key', BASIC_REQUEST, CacheSettings(cache_control=True))
response = get_mock_response(headers={**cache_headers, **validator_headers})
actions.update_from_response(response)
@@ -324,7 +361,6 @@ def test_ignored_headers(directive):
request = Request(
method='GET', url='https://img.site.com/base/img.jpg', headers={'Cache-Control': directive}
).prepare()
-
settings = CacheSettings(expire_after=1, cache_control=True)
actions = CacheActions.from_request('key', request, settings)