python - Flask test_client() 没有 request.authorization 和 pytest
python - Flask test_client() doesn't have request.authorization with pytest
我在使用 pytest 测试我的烧瓶应用程序时遇到问题。
应用程序需要基本身份验证,这是 flask 中 request.authorization
的参数。
但是对于 pytest,flask.test_client() 没有 request.authorization
。
这里是夹具的代码:
@pytest.yield_fixture(scope='session')
def app()
app = create_app()
# some setup code
ctx = app.app_context()
ctx.push()
yield app
ctx.pop()
# some teadown code
@pytest.fixture
def test_client(app)
return app.test_client()
测试代码如下:
def test_index(test_client):
res = test_client.get("/", headers={"Authorization": "Basic {user}".format(user=b64encode(b"test_user"))})
assert res.status_code == 200
当我运行这个测试时,我得到了这个错误:
E assert 401 == 200
E + where 401 = <Response streamed [401 UNAUTHORIZED]>.status_code
不仅认证失败,而且request.authorization也没有任何值(None)。
为什么会这样?有什么解决办法吗?
谢谢。
HTTP 基本身份验证的凭据必须包含用冒号分隔的用户名和密码。如果您仍在使用 python 2,请尝试以下操作:
def test_index(test_client):
credentials = b64encode(b"test_user:test_password")
res = test_client.get("/", headers={"Authorization": "Basic {}".format(credentials)})
assert res.status_code == 200
Python 3 对数据完整性稍微严格一些,因此在将字节发送到服务器之前,您必须确保字节已正确解码:
def test_index(test_client):
credentials = b64encode(b"test_user:test_password").decode('utf-8')
res = test_client.get("/", headers={"Authorization": f"Basic {credentials}"})
assert res.status_code == 200
我找到了这个解决方案。也许它可以帮助某人:
from requests.auth import _basic_auth_str
headers = {
'Authorization': _basic_auth_str(username, password),
}
你只需要使用库 'requests'
from requests.auth import _basic_auth_str
headers = {
'Authorization': _basic_auth_str(username, password)
}
这适用于 python 3.6 和 2.7,而以下仅适用于 2.7:
res = test_client.get("/", headers={"Authorization": "Basic {user}".format(user=b64encode(b"test_user:test_password"))})
如果您使用的是 python 的新版本(在我的例子中是 3.7),您应该解码 base64 字符串。它 returns 字节并且在 stringify 之后它看起来像
b'basestring' 不正确。
>>> base64.b64encode(b"user:password")
b'dXNlcjpwYXNzd29yZA=='
>>> base64.b64encode(b"user:password").decode()
'dXNlcjpwYXNzd29yZA=='
所以,现在我的测试看起来像
class TestServer(unittest.TestCase):
def setUp(self) -> None:
self.client = app.test_client()
user_credentials = base64.b64encode(b"user:password").decode()
self.headers = {"Authorization": "Basic {}".format(user_credentials)}
以下是我为 API 编写需要使用自定义令牌进行身份验证的单元测试的方式。
###### In your conftest.py file have the below methods
from connexion import FlaskApp
logging.basicConfig(level=logging.DEBUG)
API_FOLDER = pathlib.Path(__file__).parent / '..'
@pytest.fixture(scope="session")
def insecure_client(): # This is used for route tests that DO NOT require authorization.
cxn_app = FlaskApp(__name__,
port=5001,
specification_dir=API_FOLDER,
debug=True,
options={"debug": True, "swagger_ui": False})
cxn_app.add_api('your_api.yaml', resolver=RestyPlusResolver('api.routes'))
cxn_app._spec_file = 'your_api.yaml'
# connection stores the Flask app at app
cxn_app.app.config['SOME_KEY'] = config.CONFIG['SOME_KEY']
flask_jwt.JWT(cxn_app.app, None, None)
flask_cors.CORS(cxn_app.app)
cxn_app.app.app_context()
return cxn_app.app.test_client()
@pytest.fixture(scope="session")
def secure_client(): # This is used for route tests that REQUIRE authorization.
cxn_app = FlaskApp(__name__,
port=5001,
specification_dir=API_FOLDER,
debug=True,
options={"debug": True, "swagger_ui": False})
cxn_app.add_api('open_api.yaml', resolver=RestyPlusResolver('api.routes'))
cxn_app._spec_file = 'openapi.yaml'
# connection stores the Flask app at app
cxn_app.app.config['SOME_KEY'] = config.CONFIG['SOME_KEY']
flask_jwt.JWT(cxn_app.app, None, None)
flask_cors.CORS(cxn_app.app)
cxn_app.app.app_context()
client = cxn_app.app.test_client()
json_dict = {'user': 'your_username', 'password': 'your_pwd'}
# call the auth to get a token which can be used for API calls that require authentication.
# see below on how this is used in pytest of a route.
response = client.post('/auth', data=json.dumps(json_dict), content_type='application/json')
data = json_of_response(response)
setattr(client, '__token', data['token'])
return client
def post_json(client, url, json_dict):
"""Send dictionary json_dict as a json to the specified url """
return client.post(url, data=json.dumps(json_dict), content_type='application/json')
def json_of_response(response):
"""Decode json from response"""
return json.loads(response.data.decode('utf8'))
### Example Pytest of API that requires authentication.
def test_my_post(mocker, secure_client):
json_dict = {'id': 'TEST_01', 'phone': 'PHONE_02'}
mocker.patch('yourapi.services.User.create_user', return_value=("Success", 201))
response = secure_client.post('/user', data=json.dumps(json_dict), content_type='application/json', headers={'X-Auth':secure_client.__token})
data = json_of_response(response)
assert response.status_code == 201
assert data == "Success"
我在使用 pytest 测试我的烧瓶应用程序时遇到问题。
应用程序需要基本身份验证,这是 flask 中 request.authorization
的参数。
但是对于 pytest,flask.test_client() 没有 request.authorization
。
这里是夹具的代码:
@pytest.yield_fixture(scope='session')
def app()
app = create_app()
# some setup code
ctx = app.app_context()
ctx.push()
yield app
ctx.pop()
# some teadown code
@pytest.fixture
def test_client(app)
return app.test_client()
测试代码如下:
def test_index(test_client):
res = test_client.get("/", headers={"Authorization": "Basic {user}".format(user=b64encode(b"test_user"))})
assert res.status_code == 200
当我运行这个测试时,我得到了这个错误:
E assert 401 == 200
E + where 401 = <Response streamed [401 UNAUTHORIZED]>.status_code
不仅认证失败,而且request.authorization也没有任何值(None)。
为什么会这样?有什么解决办法吗?
谢谢。
HTTP 基本身份验证的凭据必须包含用冒号分隔的用户名和密码。如果您仍在使用 python 2,请尝试以下操作:
def test_index(test_client):
credentials = b64encode(b"test_user:test_password")
res = test_client.get("/", headers={"Authorization": "Basic {}".format(credentials)})
assert res.status_code == 200
Python 3 对数据完整性稍微严格一些,因此在将字节发送到服务器之前,您必须确保字节已正确解码:
def test_index(test_client):
credentials = b64encode(b"test_user:test_password").decode('utf-8')
res = test_client.get("/", headers={"Authorization": f"Basic {credentials}"})
assert res.status_code == 200
我找到了这个解决方案。也许它可以帮助某人:
from requests.auth import _basic_auth_str
headers = {
'Authorization': _basic_auth_str(username, password),
}
你只需要使用库 'requests'
from requests.auth import _basic_auth_str
headers = {
'Authorization': _basic_auth_str(username, password)
}
这适用于 python 3.6 和 2.7,而以下仅适用于 2.7:
res = test_client.get("/", headers={"Authorization": "Basic {user}".format(user=b64encode(b"test_user:test_password"))})
如果您使用的是 python 的新版本(在我的例子中是 3.7),您应该解码 base64 字符串。它 returns 字节并且在 stringify 之后它看起来像 b'basestring' 不正确。
>>> base64.b64encode(b"user:password")
b'dXNlcjpwYXNzd29yZA=='
>>> base64.b64encode(b"user:password").decode()
'dXNlcjpwYXNzd29yZA=='
所以,现在我的测试看起来像
class TestServer(unittest.TestCase):
def setUp(self) -> None:
self.client = app.test_client()
user_credentials = base64.b64encode(b"user:password").decode()
self.headers = {"Authorization": "Basic {}".format(user_credentials)}
以下是我为 API 编写需要使用自定义令牌进行身份验证的单元测试的方式。
###### In your conftest.py file have the below methods
from connexion import FlaskApp
logging.basicConfig(level=logging.DEBUG)
API_FOLDER = pathlib.Path(__file__).parent / '..'
@pytest.fixture(scope="session")
def insecure_client(): # This is used for route tests that DO NOT require authorization.
cxn_app = FlaskApp(__name__,
port=5001,
specification_dir=API_FOLDER,
debug=True,
options={"debug": True, "swagger_ui": False})
cxn_app.add_api('your_api.yaml', resolver=RestyPlusResolver('api.routes'))
cxn_app._spec_file = 'your_api.yaml'
# connection stores the Flask app at app
cxn_app.app.config['SOME_KEY'] = config.CONFIG['SOME_KEY']
flask_jwt.JWT(cxn_app.app, None, None)
flask_cors.CORS(cxn_app.app)
cxn_app.app.app_context()
return cxn_app.app.test_client()
@pytest.fixture(scope="session")
def secure_client(): # This is used for route tests that REQUIRE authorization.
cxn_app = FlaskApp(__name__,
port=5001,
specification_dir=API_FOLDER,
debug=True,
options={"debug": True, "swagger_ui": False})
cxn_app.add_api('open_api.yaml', resolver=RestyPlusResolver('api.routes'))
cxn_app._spec_file = 'openapi.yaml'
# connection stores the Flask app at app
cxn_app.app.config['SOME_KEY'] = config.CONFIG['SOME_KEY']
flask_jwt.JWT(cxn_app.app, None, None)
flask_cors.CORS(cxn_app.app)
cxn_app.app.app_context()
client = cxn_app.app.test_client()
json_dict = {'user': 'your_username', 'password': 'your_pwd'}
# call the auth to get a token which can be used for API calls that require authentication.
# see below on how this is used in pytest of a route.
response = client.post('/auth', data=json.dumps(json_dict), content_type='application/json')
data = json_of_response(response)
setattr(client, '__token', data['token'])
return client
def post_json(client, url, json_dict):
"""Send dictionary json_dict as a json to the specified url """
return client.post(url, data=json.dumps(json_dict), content_type='application/json')
def json_of_response(response):
"""Decode json from response"""
return json.loads(response.data.decode('utf8'))
### Example Pytest of API that requires authentication.
def test_my_post(mocker, secure_client):
json_dict = {'id': 'TEST_01', 'phone': 'PHONE_02'}
mocker.patch('yourapi.services.User.create_user', return_value=("Success", 201))
response = secure_client.post('/user', data=json.dumps(json_dict), content_type='application/json', headers={'X-Auth':secure_client.__token})
data = json_of_response(response)
assert response.status_code == 201
assert data == "Success"