async def test_etag_set_etag_in_response(self, app, schemas, paginate): blp = Blueprint('test', __name__) etag_schema = schemas.DocEtagSchema item = {'item_id': 1, 'db_field': 0} if paginate: extra_data = (('X-Pagination', 'Dummy pagination header'), ) else: extra_data = tuple() etag = blp._generate_etag(item, extra_data=extra_data) etag_with_schema = blp._generate_etag(item, etag_schema, extra_data=extra_data) async with app.test_request_context('/'): resp = Response([]) if extra_data: resp.headers['X-Pagination'] = 'Dummy pagination header' get_appcontext()['result_dump'] = item blp._set_etag_in_response(resp, None) assert resp.get_etag() == (etag, False) async with app.test_request_context('/'): resp = Response([]) if extra_data: resp.headers['X-Pagination'] = 'Dummy pagination header' get_appcontext()['result_raw'] = item blp._set_etag_in_response(resp, etag_schema) assert resp.get_etag() == (etag_with_schema, False)
async def test_etag_check_etag(self, app, schemas, etag_disabled): app.config['ETAG_DISABLED'] = etag_disabled blp = Blueprint('test', __name__) etag_schema = schemas.DocEtagSchema old_item = {'item_id': 1, 'db_field': 0} new_item = {'item_id': 1, 'db_field': 1} old_etag = blp._generate_etag(old_item) old_etag_with_schema = blp._generate_etag(old_item, etag_schema) async with app.test_request_context('/', headers={'If-Match': old_etag}): blp.check_etag(old_item) if not etag_disabled: with pytest.raises(PreconditionFailed): blp.check_etag(new_item) else: blp.check_etag(new_item) async with app.test_request_context( '/', headers={'If-Match': old_etag_with_schema}): blp.check_etag(old_item, etag_schema) if not etag_disabled: with pytest.raises(PreconditionFailed): blp.check_etag(new_item, etag_schema) else: blp.check_etag(new_item)
async def test_etag_set_etag(self, app, schemas, etag_disabled): app.config['ETAG_DISABLED'] = etag_disabled blp = Blueprint('test', __name__) etag_schema = schemas.DocEtagSchema item = {'item_id': 1, 'db_field': 0} etag = blp._generate_etag(item) etag_with_schema = blp._generate_etag(item, etag_schema) async with app.test_request_context('/'): blp.set_etag(item) if not etag_disabled: assert _get_etag_ctx()['etag'] == etag del _get_etag_ctx()['etag'] else: assert 'etag' not in _get_etag_ctx() async with app.test_request_context('/', headers={'If-None-Match': etag}): if not etag_disabled: with pytest.raises(NotModified): blp.set_etag(item) else: blp.set_etag(item) assert 'etag' not in _get_etag_ctx() async with app.test_request_context( '/', headers={'If-None-Match': etag_with_schema}): if not etag_disabled: with pytest.raises(NotModified): blp.set_etag(item, etag_schema) else: blp.set_etag(item, etag_schema) assert 'etag' not in _get_etag_ctx() async with app.test_request_context('/', headers={'If-None-Match': 'dummy'}): if not etag_disabled: blp.set_etag(item) assert _get_etag_ctx()['etag'] == etag del _get_etag_ctx()['etag'] blp.set_etag(item, etag_schema) assert _get_etag_ctx()['etag'] == etag_with_schema del _get_etag_ctx()['etag'] else: blp.set_etag(item) assert 'etag' not in _get_etag_ctx() blp.set_etag(item, etag_schema) assert 'etag' not in _get_etag_ctx()
def test_etag_is_deterministic(self): """Check etag computation is deterministic _generate_etag should return the same value everytime the same dictionary is passed. This is not obvious since dictionaries are unordered by design. We check this by feeding it different OrderedDict instances that are equivalent to the same dictionary. """ blp = Blueprint('test', __name__) data = OrderedDict([('a', 1), ('b', 2), ('c', OrderedDict([('a', 1), ('b', 2)]))]) etag = blp._generate_etag(data) data_copies = [ OrderedDict([ ('b', 2), ('a', 1), ('c', OrderedDict([('a', 1), ('b', 2)])), ]), OrderedDict([ ('a', 1), ('b', 2), ('c', OrderedDict([('b', 2), ('a', 1)])), ]), OrderedDict([ ('a', 1), ('c', OrderedDict([('a', 1), ('b', 2)])), ('b', 2), ]), OrderedDict([ ('c', OrderedDict([('a', 1), ('b', 2)])), ('b', 2), ('a', 1), ]), ] data_copies_etag = [blp._generate_etag(d) for d in data_copies] assert all(e == etag for e in data_copies_etag)
def test_etag_generate_etag(self, schemas, extra_data): blp = Blueprint('test', __name__) etag_schema = schemas.DocEtagSchema item = {'item_id': 1, 'db_field': 0} item_schema_dump = etag_schema().dump(item) if MARSHMALLOW_VERSION_MAJOR < 3: item_schema_dump = item_schema_dump[0] if extra_data is None or extra_data == {}: data = item data_dump = item_schema_dump else: data = (item, extra_data) data_dump = (item_schema_dump, extra_data) etag = blp._generate_etag(item, extra_data=extra_data) assert etag == hashlib.sha1( bytes(json.dumps(data, sort_keys=True), 'utf-8')).hexdigest() etag = blp._generate_etag(item, etag_schema, extra_data=extra_data) assert etag == hashlib.sha1( bytes(json.dumps(data_dump, sort_keys=True), 'utf-8')).hexdigest() etag = blp._generate_etag(item, etag_schema(), extra_data=extra_data) assert etag == hashlib.sha1( bytes(json.dumps(data_dump, sort_keys=True), 'utf-8')).hexdigest()
async def test_etag_response_object(self, app): api = Api(app) blp = Blueprint('test', __name__, url_prefix='/test') client = app.test_client() @blp.route('/') @blp.etag @blp.response() async def func_response_etag(): # When the view function returns a Response object, # the ETag must be specified manually blp.set_etag('test') return jsonify({}) api.register_blueprint(blp) response = await client.get('/test/') assert await response.json == {} assert response.get_etag() == (blp._generate_etag('test'), False)
async def test_etag_verify_check_etag_warning(self, app, method): blp = Blueprint('test', __name__) old_item = {'item_id': 1, 'db_field': 0} old_etag = blp._generate_etag(old_item) with mock.patch.object(app.logger, 'warning') as mock_warning: async with app.test_request_context('/', method=method, headers={'If-Match': old_etag}): blp._verify_check_etag() if method in ['PUT', 'PATCH', 'DELETE']: assert mock_warning.called mock_warning.reset_mock() else: assert not mock_warning.called blp.check_etag(old_item) blp._verify_check_etag() assert not mock_warning.called