def test_etag_check_etag(self, app, schemas, method, 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) with app.test_request_context( "/", method=method, 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) with app.test_request_context( "/", method=method, 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)
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) 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) 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)
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()
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) 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) 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)
def test_etag_set_etag(self, app, schemas, method, 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) with app.test_request_context("/", method=method): 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() with app.test_request_context( "/", method=method, headers={"If-None-Match": etag}, ): if not etag_disabled: if method in ["GET", "HEAD"]: with pytest.raises(NotModified): blp.set_etag(item) else: blp.set_etag(item) assert "etag" not in _get_etag_ctx() with app.test_request_context( "/", method=method, headers={"If-None-Match": etag_with_schema}, ): if not etag_disabled: if method in ["GET", "HEAD"]: with pytest.raises(NotModified): blp.set_etag(item, etag_schema) else: blp.set_etag(item, etag_schema) assert "etag" not in _get_etag_ctx() with app.test_request_context( "/", method=method, 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_set_etag(self, app, schemas, method, 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) with app.test_request_context('/', method=method): 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() with app.test_request_context( '/', method=method, headers={'If-None-Match': etag}, ): if not etag_disabled: if method in ['GET', 'HEAD']: with pytest.raises(NotModified): blp.set_etag(item) else: blp.set_etag(item) assert 'etag' not in _get_etag_ctx() with app.test_request_context( '/', method=method, headers={'If-None-Match': etag_with_schema}, ): if not etag_disabled: if method in ['GET', 'HEAD']: with pytest.raises(NotModified): blp.set_etag(item, etag_schema) else: blp.set_etag(item, etag_schema) assert 'etag' not in _get_etag_ctx() with app.test_request_context( '/', method=method, 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_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 pytest.warns(None) as record: with app.test_request_context( '/', method=method, headers={'If-Match': old_etag}, ): blp._verify_check_etag() if method in ['PUT', 'PATCH', 'DELETE']: assert len(record) == 1 assert record[0].category == UserWarning assert str(record[0].message) == ( 'ETag not checked in endpoint {} on {} request.' .format(f_request.endpoint, method) ) else: assert not record blp.check_etag(old_item) record.clear() blp._verify_check_etag() assert not record
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 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())
def test_etag_response_object(self, app): api = Api(app) blp = Blueprint("test", __name__, url_prefix="/test") client = app.test_client() @blp.route("/<code>") @blp.etag @blp.response(200) @blp.alt_response(201, success=True) def func_response_etag(code): # When the view function returns a Response object, # the ETag must be specified manually # This is always the case when using alt_response blp.set_etag("test") return jsonify({}), code api.register_blueprint(blp) response = client.get("/test/200") assert response.json == {} assert response.get_etag() == (blp._generate_etag("test"), False) response = client.get("/test/201") assert response.json == {} assert response.get_etag() == (blp._generate_etag("test"), False)
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) 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) 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)
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: 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
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() 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 = client.get('/test/') assert response.json == {} assert response.get_etag() == (blp._generate_etag('test'), False)
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 pytest.warns(None) as record: with app.test_request_context( "/", method=method, headers={"If-Match": old_etag}, ): blp._verify_check_etag() if method in ["PUT", "PATCH", "DELETE"]: assert len(record) == 1 assert record[0].category == UserWarning assert str(record[0].message) == ( "ETag not checked in endpoint {} on {} request.". format(f_request.endpoint, method)) else: assert not record blp.check_etag(old_item) record.clear() blp._verify_check_etag() assert not record