adapters: do not log errors on cold cache (#84933)
gitea/django-mellon/pipeline/head This commit looks good
Details
gitea/django-mellon/pipeline/head This commit looks good
Details
Only log errors if the cache is older than 24 hours.
This commit is contained in:
parent
200e009b1e
commit
af81da4954
|
@ -78,83 +78,100 @@ class DefaultAdapter:
|
||||||
yield idp
|
yield idp
|
||||||
|
|
||||||
def load_metadata_path(self, idp, i):
|
def load_metadata_path(self, idp, i):
|
||||||
|
now = time.time()
|
||||||
path = idp['METADATA_PATH']
|
path = idp['METADATA_PATH']
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
logger.warning('mellon: metadata path %s does not exist', path)
|
logger.error('mellon: metadata path %s does not exist', path)
|
||||||
return
|
return
|
||||||
last_update = idp.get('METADATA_PATH_LAST_UPDATE', 0)
|
|
||||||
try:
|
metadata_last_update = idp.get('METADATA_LAST_UPDATE', None)
|
||||||
mtime = os.stat(path).st_mtime
|
|
||||||
except OSError as e:
|
if not idp.get('METADATA'):
|
||||||
logger.warning('mellon: metadata path %s : stat() call failed, %s', path, e)
|
should_load = True
|
||||||
return
|
elif not metadata_last_update:
|
||||||
if last_update == 0 or mtime >= last_update:
|
should_load = True
|
||||||
idp['METADATA_PATH_LAST_UPDATE'] = time.time()
|
else:
|
||||||
try:
|
try:
|
||||||
with open(path) as fd:
|
mtime = os.stat(path).st_mtime
|
||||||
metadata = fd.read()
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logger.warning('mellon: metadata path %s : open()/read() call failed, %s', path, e)
|
logger.error('mellon: metadata path %s : stat() call failed, %s', path, e)
|
||||||
return
|
return
|
||||||
entity_id = self.load_entity_id(metadata, i)
|
should_load = metadata_last_update <= mtime
|
||||||
if not entity_id:
|
|
||||||
logger.error('mellon: invalid metadata file retrieved from %s', path)
|
if not should_load:
|
||||||
return
|
return
|
||||||
if 'ENTITY_ID' in idp and idp['ENTITY_ID'] != entity_id:
|
|
||||||
logger.error(
|
try:
|
||||||
'mellon: metadata path %s : entityID changed %r != %r', path, entity_id, idp['ENTITY_ID']
|
with open(path) as fd:
|
||||||
)
|
metadata = fd.read()
|
||||||
del idp['ENTITY_ID']
|
except OSError as e:
|
||||||
idp['METADATA'] = metadata
|
logger.error('mellon: metadata path %s : open()/read() call failed, %s', path, e)
|
||||||
|
return
|
||||||
|
entity_id = self.load_entity_id(metadata, i)
|
||||||
|
if not entity_id:
|
||||||
|
logger.error('mellon: invalid metadata file retrieved from %s', path)
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'ENTITY_ID' in idp and idp['ENTITY_ID'] != entity_id:
|
||||||
|
raise RuntimeError(
|
||||||
|
f'mellon: metadata path {path} : entityID changed {entity_id!r} != {idp["ENTITY_ID"]!r}'
|
||||||
|
)
|
||||||
|
|
||||||
|
idp['METADATA'] = metadata
|
||||||
|
idp['METADATA_LAST_UPDATE'] = now
|
||||||
|
|
||||||
def load_metadata_url(self, idp, i):
|
def load_metadata_url(self, idp, i):
|
||||||
|
now = time.time()
|
||||||
url = idp['METADATA_URL']
|
url = idp['METADATA_URL']
|
||||||
metadata_cache_time = utils.get_setting(idp, 'METADATA_CACHE_TIME')
|
metadata_cache_time = utils.get_setting(idp, 'METADATA_CACHE_TIME')
|
||||||
timeout = utils.get_setting(idp, 'METADATA_HTTP_TIMEOUT')
|
timeout = utils.get_setting(idp, 'METADATA_HTTP_TIMEOUT')
|
||||||
|
|
||||||
warning = logger.warning
|
metadata_last_update = idp.get('METADATA_LAST_UPDATE', None)
|
||||||
if 'METADATA' not in idp:
|
metadata_last_url_update = idp.get('METADATA_LAST_URL_CHECK', 0)
|
||||||
# if we have no metadata in cache, we must emit errors
|
|
||||||
warning = logger.error
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hostname = urlparse(url).hostname
|
hostname = urlparse(url).hostname
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
warning('invalid METADATA_URL %r: %s', url, e)
|
logger.error('invalid METADATA_URL %r: %s', url, e)
|
||||||
return
|
return
|
||||||
if not hostname:
|
if not hostname:
|
||||||
warning('no hostname in METADATA_URL %r: %s', url)
|
logger.error('no hostname in METADATA_URL %r: %s', url)
|
||||||
return
|
return
|
||||||
|
|
||||||
last_update = idp.get('METADATA_URL_LAST_UPDATE', 0)
|
|
||||||
now = time.time()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url_fingerprint = hashlib.md5(url.encode('ascii')).hexdigest()
|
url_fingerprint = hashlib.md5(url.encode('ascii')).hexdigest()
|
||||||
file_cache_key = '%s_%s.xml' % (hostname, url_fingerprint)
|
file_cache_key = '%s_%s.xml' % (hostname, url_fingerprint)
|
||||||
except (UnicodeError, TypeError, ValueError):
|
except (UnicodeError, TypeError, ValueError):
|
||||||
warning('unable to compute file_cache_key')
|
logger.error('unable to compute file_cache_key')
|
||||||
return
|
return
|
||||||
|
|
||||||
cache_directory = default_storage.path('mellon_metadata_cache')
|
cache_directory = default_storage.path('mellon_metadata_cache')
|
||||||
file_cache_path = os.path.join(cache_directory, file_cache_key)
|
file_cache_path = os.path.join(cache_directory, file_cache_key)
|
||||||
|
|
||||||
if metadata_cache_time:
|
if not os.path.exists(cache_directory):
|
||||||
# METADATA_CACHE_TIME == 0 disable the file cache
|
os.makedirs(cache_directory)
|
||||||
if not os.path.exists(cache_directory):
|
|
||||||
os.makedirs(cache_directory)
|
|
||||||
|
|
||||||
if os.path.exists(file_cache_path) and 'METADATA' not in idp:
|
if os.path.exists(file_cache_path) and 'METADATA' not in idp:
|
||||||
try:
|
try:
|
||||||
with open(file_cache_path) as fd:
|
with open(file_cache_path) as fd:
|
||||||
idp['METADATA'] = fd.read()
|
metadata = fd.read()
|
||||||
# use file cache mtime as last_update time, prevent too many loading from different workers
|
# use file cache mtime as last_update time, prevent too many loading from different workers
|
||||||
last_update = max(last_update, os.stat(file_cache_path).st_mtime)
|
except OSError:
|
||||||
except OSError:
|
logger.error('metadata url %s : error when loading the file cache %s', url, file_cache_path)
|
||||||
warning('metadata url %s : error when loading the file cache %s', url, file_cache_path)
|
else:
|
||||||
|
idp['METADATA'] = metadata
|
||||||
|
metadata_last_update = idp['METADATA_LAST_UPDATE'] = os.stat(file_cache_path).st_mtime
|
||||||
|
|
||||||
# fresh cache, skip loading
|
# fresh cache, skip loading
|
||||||
if last_update and 'METADATA' in idp and (now - last_update) < metadata_cache_time:
|
if (
|
||||||
|
metadata_last_update
|
||||||
|
and 'METADATA' in idp
|
||||||
|
and (
|
||||||
|
(now - metadata_last_update) < metadata_cache_time
|
||||||
|
# if HTTP GET is failing, try every 5 minutes
|
||||||
|
or (now - metadata_last_url_update) < 60 * 5
|
||||||
|
)
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
def __http_get():
|
def __http_get():
|
||||||
|
@ -164,12 +181,12 @@ class DefaultAdapter:
|
||||||
response = requests.get(url, verify=verify_ssl_certificate, timeout=timeout)
|
response = requests.get(url, verify=verify_ssl_certificate, timeout=timeout)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
warning('metadata url %s : HTTP request failed %s', url, e)
|
logger.warning('metadata url %s : HTTP request failed %s', url, e)
|
||||||
return
|
return
|
||||||
|
|
||||||
entity_id = self.load_entity_id(response.text, i)
|
entity_id = self.load_entity_id(response.text, i)
|
||||||
if not entity_id:
|
if not entity_id:
|
||||||
warning('invalid metadata file retrieved from %s', url)
|
logger.warning('invalid metadata file retrieved from %s', url)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'ENTITY_ID' in idp and idp['ENTITY_ID'] != entity_id:
|
if 'ENTITY_ID' in idp and idp['ENTITY_ID'] != entity_id:
|
||||||
|
@ -177,11 +194,13 @@ class DefaultAdapter:
|
||||||
logger.error(
|
logger.error(
|
||||||
'mellon: metadata url %s, entityID changed %r != %r', url, entity_id, idp['ENTITY_ID']
|
'mellon: metadata url %s, entityID changed %r != %r', url, entity_id, idp['ENTITY_ID']
|
||||||
)
|
)
|
||||||
del idp['ENTITY_ID']
|
return
|
||||||
|
|
||||||
|
if idp.get('METADATA') != response.text:
|
||||||
|
idp['METADATA'] = response.text
|
||||||
|
logger.info('mellon: metadata url %s updated', url)
|
||||||
|
idp['METADATA_LAST_UPDATE'] = now
|
||||||
|
|
||||||
idp['METADATA'] = response.text
|
|
||||||
idp['METADATA_URL_LAST_UPDATE'] = now
|
|
||||||
if metadata_cache_time:
|
|
||||||
try:
|
try:
|
||||||
with atomic_write(file_cache_path, mode='wb', overwrite=True) as fd:
|
with atomic_write(file_cache_path, mode='wb', overwrite=True) as fd:
|
||||||
fd.write(response.text.encode('utf-8'))
|
fd.write(response.text.encode('utf-8'))
|
||||||
|
@ -192,46 +211,38 @@ class DefaultAdapter:
|
||||||
file_cache_path,
|
file_cache_path,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
idp['METADATA_PATH'] = file_cache_path
|
|
||||||
# prevent reloading of the file cache immediately
|
|
||||||
idp['METADATA_PATH_LAST_UPDATE'] = time.time() + 1
|
|
||||||
logger.info('mellon: metadata url %s updated', url)
|
|
||||||
finally:
|
finally:
|
||||||
# release thread object
|
# release thread object
|
||||||
idp.pop('METADATA_URL_UPDATE_THREAD', None)
|
idp.pop('METADATA_URL_UPDATE_THREAD', None)
|
||||||
# emit an error if cache is too old
|
|
||||||
if metadata_cache_time:
|
|
||||||
stale_timeout = 24 * metadata_cache_time
|
|
||||||
if last_update and (now - idp['METADATA_URL_LAST_UPDATE']) > stale_timeout:
|
|
||||||
logger.error(
|
|
||||||
'mellon: metadata url %s: not updated since %.1f hours',
|
|
||||||
url,
|
|
||||||
stale_timeout / 3600.0,
|
|
||||||
)
|
|
||||||
|
|
||||||
# we have cache, update in background
|
if metadata_last_update:
|
||||||
if last_update and 'METADATA' in idp:
|
# we have cache, update in background
|
||||||
|
idp['METADATA_LAST_URL_CHECK'] = time.time()
|
||||||
t = threading.Thread(target=__http_get)
|
t = threading.Thread(target=__http_get)
|
||||||
t.start()
|
t.start()
|
||||||
# store thread in idp for tests
|
# store thread in idp for tests
|
||||||
idp['METADATA_URL_UPDATE_THREAD'] = t
|
idp['METADATA_URL_UPDATE_THREAD'] = t
|
||||||
# suspend updates for HTTP timeout + 5 seconds
|
|
||||||
idp['METADATA_URL_LAST_UPDATE'] = last_update + timeout + 5
|
|
||||||
else:
|
else:
|
||||||
# synchronous update
|
# synchronous update
|
||||||
__http_get()
|
__http_get()
|
||||||
|
|
||||||
def load_metadata(self, idp, i):
|
def load_metadata(self, idp, i):
|
||||||
# legacy support
|
now = time.time()
|
||||||
|
|
||||||
if 'METADATA' in idp and idp['METADATA'].startswith('/'):
|
if 'METADATA' in idp and idp['METADATA'].startswith('/'):
|
||||||
|
# support legacy configuration
|
||||||
idp['METADATA_PATH'] = idp['METADATA']
|
idp['METADATA_PATH'] = idp['METADATA']
|
||||||
del idp['METADATA']
|
del idp['METADATA']
|
||||||
|
|
||||||
if 'METADATA_PATH' in idp:
|
metadata_cache_time = utils.get_setting(idp, 'METADATA_CACHE_TIME')
|
||||||
self.load_metadata_path(idp, i)
|
metadata_last_update = idp.get('METADATA_LAST_UPDATE', None)
|
||||||
|
age = metadata_last_update and (now - metadata_last_update)
|
||||||
|
|
||||||
if 'METADATA_URL' in idp:
|
if age is None or age > metadata_cache_time:
|
||||||
self.load_metadata_url(idp, i)
|
if 'METADATA_PATH' in idp:
|
||||||
|
self.load_metadata_path(idp, i)
|
||||||
|
elif 'METADATA_URL' in idp:
|
||||||
|
self.load_metadata_url(idp, i)
|
||||||
|
|
||||||
if 'METADATA' in idp:
|
if 'METADATA' in idp:
|
||||||
if 'ENTITY_ID' not in idp:
|
if 'ENTITY_ID' not in idp:
|
||||||
|
@ -239,21 +250,28 @@ class DefaultAdapter:
|
||||||
if entity_id:
|
if entity_id:
|
||||||
idp['ENTITY_ID'] = entity_id
|
idp['ENTITY_ID'] = entity_id
|
||||||
|
|
||||||
|
if age and age > (24 * metadata_cache_time):
|
||||||
|
logger.error(
|
||||||
|
'mellon: metadata for idp %s: not updated since %.1f hours',
|
||||||
|
idp.get('ENTITY_ID', i),
|
||||||
|
age / 3600.0,
|
||||||
|
)
|
||||||
|
|
||||||
if 'ENTITY_ID' in idp:
|
if 'ENTITY_ID' in idp:
|
||||||
return idp['METADATA']
|
return idp['METADATA']
|
||||||
|
|
||||||
|
logger.error('mellon: could not load metadata for %d-th idp: %s', i, idp)
|
||||||
|
return None
|
||||||
|
|
||||||
def load_entity_id(self, metadata, i):
|
def load_entity_id(self, metadata, i):
|
||||||
try:
|
try:
|
||||||
doc = ET.fromstring(metadata)
|
doc = ET.fromstring(metadata)
|
||||||
except (TypeError, ET.ParseError):
|
except (TypeError, ET.ParseError):
|
||||||
logger.error('mellon: METADATA of %d-th idp is invalid', i)
|
|
||||||
return None
|
return None
|
||||||
if doc.tag != '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF:
|
if doc.tag != '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF:
|
||||||
logger.error('mellon: METADATA of %d-th idp has no EntityDescriptor root tag', i)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if 'entityID' not in doc.attrib:
|
if 'entityID' not in doc.attrib:
|
||||||
logger.error('mellon: METADATA of %d-th idp has no entityID attribute on its root tag', i)
|
|
||||||
return None
|
return None
|
||||||
return doc.attrib['entityID']
|
return doc.attrib['entityID']
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ from unittest import mock
|
||||||
|
|
||||||
import lasso
|
import lasso
|
||||||
import pytest
|
import pytest
|
||||||
|
import responses
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
|
||||||
|
@ -381,10 +382,13 @@ def test_load_metadata_simple(adapter, metadata):
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
|
|
||||||
|
|
||||||
def test_load_metadata_legacy(adapter, metadata_path, metadata):
|
def test_load_metadata_legacy(adapter, metadata_path, metadata, freezer):
|
||||||
|
now = time.time()
|
||||||
idp = {'METADATA': metadata_path}
|
idp = {'METADATA': metadata_path}
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
assert idp['METADATA'] == metadata
|
assert idp['METADATA'] == metadata
|
||||||
|
assert idp['METADATA_PATH'] == metadata_path
|
||||||
|
assert idp['METADATA_LAST_UPDATE'] == now
|
||||||
|
|
||||||
|
|
||||||
def test_load_metadata_path(adapter, metadata_path, metadata, freezer):
|
def test_load_metadata_path(adapter, metadata_path, metadata, freezer):
|
||||||
|
@ -392,71 +396,74 @@ def test_load_metadata_path(adapter, metadata_path, metadata, freezer):
|
||||||
idp = {'METADATA_PATH': str(metadata_path)}
|
idp = {'METADATA_PATH': str(metadata_path)}
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
assert idp['METADATA'] == metadata
|
assert idp['METADATA'] == metadata
|
||||||
assert idp['METADATA_PATH_LAST_UPDATE'] == now
|
assert idp['METADATA_LAST_UPDATE'] == now
|
||||||
|
|
||||||
|
|
||||||
def test_load_metadata_url(settings, adapter, metadata, httpserver, freezer, caplog):
|
METADATA_URL = 'https://example.com/metadata'
|
||||||
now = time.time()
|
|
||||||
httpserver.serve_content(content=metadata, headers={'Content-Type': 'application/xml'})
|
|
||||||
idp = {'METADATA_URL': httpserver.url}
|
@responses.activate
|
||||||
|
def test_load_metadata_url(settings, adapter, metadata, freezer, caplog):
|
||||||
|
idp = {'METADATA_URL': METADATA_URL}
|
||||||
|
|
||||||
|
def wait_for_update_thread():
|
||||||
|
# wait for update thread to finish
|
||||||
|
try:
|
||||||
|
idp['METADATA_URL_UPDATE_THREAD'].join()
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# test normal loading
|
||||||
|
responses.get(METADATA_URL, body=metadata, headers={'Content-Type': 'application/xml'})
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
|
assert 'METADATA_URL_UPDATE_THREAD' not in idp
|
||||||
assert idp['METADATA'] == metadata
|
assert idp['METADATA'] == metadata
|
||||||
assert idp['METADATA_URL_LAST_UPDATE'] == now
|
assert idp['METADATA_LAST_UPDATE'] == time.time()
|
||||||
assert 'METADATA_PATH' in idp
|
|
||||||
assert idp['METADATA_PATH'].startswith(settings.MEDIA_ROOT)
|
|
||||||
with open(idp['METADATA_PATH']) as fd:
|
|
||||||
assert fd.read() == metadata
|
|
||||||
assert idp['METADATA_PATH_LAST_UPDATE'] == now + 1
|
|
||||||
httpserver.serve_content(
|
|
||||||
content=metadata.replace('idp5', 'idp6'), headers={'Content-Type': 'application/xml'}
|
|
||||||
)
|
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
|
||||||
|
|
||||||
freezer.move_to(datetime.timedelta(seconds=3601))
|
# test ENTITY_ID change
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
freezer.move_to(datetime.timedelta(seconds=3601))
|
||||||
|
responses.replace(
|
||||||
|
responses.GET,
|
||||||
|
METADATA_URL,
|
||||||
|
body=metadata.replace('idp5', 'idp6'),
|
||||||
|
headers={'Content-Type': 'application/xml'},
|
||||||
|
)
|
||||||
|
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
# wait for update thread to finish
|
wait_for_update_thread()
|
||||||
try:
|
assert 'WARNING' not in caplog.text
|
||||||
idp['METADATA_URL_UPDATE_THREAD'].join()
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
new_meta = adapter.load_metadata(idp, 0)
|
|
||||||
assert new_meta != metadata
|
|
||||||
assert new_meta == metadata.replace('idp5', 'idp6')
|
|
||||||
assert any(
|
assert any(
|
||||||
'entityID changed' in record.message and record.levelname == 'ERROR' for record in caplog.records
|
'entityID changed' in record.message and record.levelname == 'ERROR' for record in caplog.records
|
||||||
)
|
)
|
||||||
|
|
||||||
# test load from file cache
|
# test load from file cache
|
||||||
del idp['METADATA']
|
|
||||||
del idp['METADATA_PATH']
|
|
||||||
del idp['METADATA_PATH_LAST_UPDATE']
|
|
||||||
httpserver.serve_content(content='', headers={'Content-Type': 'application/xml'})
|
|
||||||
assert adapter.load_metadata(idp, 0) == metadata.replace('idp5', 'idp6')
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_metadata_url_stale_timeout(settings, adapter, metadata, httpserver, freezer, caplog):
|
|
||||||
httpserver.serve_content(content=metadata, headers={'Content-Type': 'application/xml'})
|
|
||||||
idp = {'METADATA_URL': httpserver.url}
|
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
|
||||||
httpserver.serve_content(content='', headers={'Content-Type': 'application/xml'})
|
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
|
||||||
|
|
||||||
freezer.move_to(datetime.timedelta(seconds=24 * 3600 - 1))
|
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
|
||||||
|
|
||||||
# wait for update thread to finish
|
|
||||||
try:
|
|
||||||
idp['METADATA_URL_UPDATE_THREAD'].join()
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
assert caplog.records[-1].levelname == 'WARNING'
|
|
||||||
|
|
||||||
freezer.move_to(datetime.timedelta(seconds=3601))
|
freezer.move_to(datetime.timedelta(seconds=3601))
|
||||||
|
caplog.clear()
|
||||||
|
del idp['METADATA']
|
||||||
|
request = responses.replace(
|
||||||
|
responses.GET, METADATA_URL, body='', headers={'Content-Type': 'application/xml'}
|
||||||
|
)
|
||||||
assert adapter.load_metadata(idp, 0) == metadata
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
|
wait_for_update_thread()
|
||||||
|
assert len(caplog.records) == 1
|
||||||
|
assert caplog.records[0].levelname == 'WARNING'
|
||||||
|
assert 'invalid metadata' in caplog.records[0].message
|
||||||
|
assert request.call_count == 1
|
||||||
|
|
||||||
# wait for update thread to finish
|
# test http get is not redone before 5 minutes
|
||||||
try:
|
freezer.move_to(datetime.timedelta(seconds=4 * 60))
|
||||||
idp['METADATA_URL_UPDATE_THREAD'].join()
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
except KeyError:
|
wait_for_update_thread()
|
||||||
pass
|
assert request.call_count == 1
|
||||||
assert caplog.records[-1].levelname == 'ERROR'
|
freezer.move_to(datetime.timedelta(seconds=2 * 60))
|
||||||
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
|
wait_for_update_thread()
|
||||||
|
assert request.call_count == 2
|
||||||
|
|
||||||
|
# test error after 24hours
|
||||||
|
caplog.clear()
|
||||||
|
freezer.move_to(datetime.timedelta(seconds=3600 * 23))
|
||||||
|
assert adapter.load_metadata(idp, 0) == metadata
|
||||||
|
assert 'ERROR' in caplog.text
|
||||||
|
assert 'not updated since 25' in caplog.text
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -25,13 +25,13 @@ deps =
|
||||||
pytest-mock
|
pytest-mock
|
||||||
pytest-django
|
pytest-django
|
||||||
pytest-freezegun
|
pytest-freezegun
|
||||||
pytest-localserver
|
|
||||||
pytz
|
pytz
|
||||||
lxml
|
lxml
|
||||||
cssselect
|
cssselect
|
||||||
django-webtest>1.9.3
|
django-webtest>1.9.3
|
||||||
WebTest
|
WebTest
|
||||||
pyquery
|
pyquery
|
||||||
|
responses
|
||||||
allowlist_externals =
|
allowlist_externals =
|
||||||
./getlasso3.sh
|
./getlasso3.sh
|
||||||
commands =
|
commands =
|
||||||
|
|
Loading…
Reference in New Issue