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"