Jony cruse
Jony cruse

Reputation: 845

python - Flask test_client() doesn't have request.authorization with pytest

I have problem when testing my flask app with pytest.
App is required basic auth which is parameters of request.authorization in flask.
But with pytest, flask.test_client() doesn't have request.authorization.

Here's a code of fixture:

@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()

Here's a code of test:

def test_index(test_client):
    res = test_client.get("/", headers={"Authorization": "Basic {user}".format(user=b64encode(b"test_user"))})
    assert res.status_code == 200

When I run this test, I got this error:

E       assert 401 == 200
E        +  where 401 = <Response streamed [401 UNAUTHORIZED]>.status_code

Not only auth failure, but also request.authorization doesn't have any value(None).
Why this happen? Is there any solution?

Thanks.

Upvotes: 14

Views: 13175

Answers (5)

Anuwork
Anuwork

Reputation: 1

Here is how I have wrote unit tests for API's that require authentication with custom token.

###### 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"

Upvotes: 0

Miguel Grinberg
Miguel Grinberg

Reputation: 67507

The credentials for HTTP Basic authentication must have a username and a password separated by a colon. If you're still using python 2, try this:

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 is a little stricter about data sanity, so you have to make sure that the bytes are properly decoded before sending them to the server:

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

Upvotes: 26

If you are using new version of python (in my case 3.7) you should decode base64 string. It returns bytes and after stringify it looks like b'basestring' which is not correct.

>>> base64.b64encode(b"user:password")
b'dXNlcjpwYXNzd29yZA=='

>>> base64.b64encode(b"user:password").decode()
'dXNlcjpwYXNzd29yZA=='

So, now my tests look like

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)}

Upvotes: 1

V Desai
V Desai

Reputation: 41

from requests.auth import _basic_auth_str
headers = {
   'Authorization': _basic_auth_str(username, password)
}

This works for me on both python 3.6 and 2.7 whereas the following only works for me on 2.7:

res = test_client.get("/", headers={"Authorization": "Basic {user}".format(user=b64encode(b"test_user:test_password"))})

Upvotes: 4

Kevin Vincent
Kevin Vincent

Reputation: 617

I found this solution. Maybe it can help someone:

from requests.auth import _basic_auth_str
headers = {
   'Authorization': _basic_auth_str(username, password),
}

You just have to use the library 'requests'

Upvotes: 15

Related Questions