How can I use JWT token in DRF tests? - unit-testing

I need to test my API. For example, I have images list page. I need to do tests for this page, but I cannot do this without authentication. I use JWT. I do not know how to do it.
Help me please.
tests.py
class ImagesListTestCase(APITestCase):
def test_images_list(self):
response = self.client.get('/api/', HTTP_AUTHORIZATION="JWT {}".format("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTkzNzUxMzMxLCJqdGkiOiI3NWE3NDNkMGU3MDQ0MGNiYjQ3NDExNjQ3MTI5NWVjNSIsInVzZXJfaWQiOjF9.7HkhZ1hRV8OtQJMMLEAVwUnJ0yDt8agFadAsJztFb6A"))
self.assertEqual(response.status_code, status.HTTP_200_OK)
I tried to do
response = self.client.get('/api/', Authorization="Bearer <token>")
Also
response = self.client.get('/api/', Authorization="JWT <token>")
response = self.client.get('/api/', HTTP_AUTHORIZATION="Bearer <token>")

django creates a temporary database for test; so it's better to get token from username and password like this:
class ImagesListTestCase(APITestCase):
def setUp(self) :
self.register_url = reverse("your:register:view") # for example : "users:register"
self.user_data = {
"username": "test_user",
"email": "test_user#gmail.com",
"password": "123456"
}
self.client.post(self.register_url,self.user_data) # user created
auth_url = reverse("your:login:view") #for example :"users:token_obtain_pair"
self.access_token = self.client.post(auth_url,{
"username" : self.user_data.get("username") ,
"password" : self.user_data.get("password")
}).data.get("access") # get access_token for authorization
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
def test_images_list(self):
response = self.client.get('/api/')
self.assertEqual(response.status_code, status.HTTP_200_OK)

You can create users that require JWT authentication like:
def setUp(self) -> None:
self.client = APIClient()
if os.environ.get('GITHUB_WORKFLOW'):
local_token_url = 'https://testing/app/token/jwt'
else:
local_token_url = 'http://127.0.0.1:8000/app/token/jwt'
response = requests.post(local_token_url, {'email': 'test_contact1#user.com', 'password': '123pass321'})
self.test_user1 = json.loads(response.text)
response = requests.post(local_token_url, {'email': 'test_contact2#user.com', 'password': '123pass321'})
self.test_user2 = json.loads(response.text)
self.contact_person = apm.AppUser.objects.create(email="test_contact1#user.com", first_name="John",
last_name="Doe",
company_name="test_company_1", phone_number="08077745673",
is_active=True)
you can then use them like below parsing data = {}
self.client.credentials(HTTP_AUTHORIZATION="Bearer {}".format(self.test_user1.get('access')))
response = self.client.post(reverse('create-list'), data=data, format='json')
print(response.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATE

Instead of invoking api to get the token, Another very simple solution can as follows
from rest_framework_simplejwt.tokens import AccessToken
def setUp(self):
self.factory = APIRequestFactory()
user = User.objects.create_user(
username='jacob', email='jacob#gmail.com', password='top_secret')
self.client = APIClient()
token = AccessToken.for_user(user=user)
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
and it will be used as
def test_get_cities(self):
response = self.client.get('/<actual path to url>/cities/')
assert response.status_code == 200
assert len(response.data) == 0
self.assertEqual(response.data, [])
Important thing to notice here is in setUp function token is generated using AcessToken class from JWT itself, so no actual API is required for testing other flows in your app.

Related

How to make Post request by list action for create function in viewsets in DRF

I want to send post request from testing. Its working for postman but it didnot work for my test case. How can I give the data by post request.
Views.py,
class PersonalInfoAPI(viewsets.ViewSet):
permission_classes = [IsOwnerPermission]
def get_object(self, pk):
obj = get_object_or_404(Employee.objects.all(), pk=pk)
self.check_object_permissions(self.request, obj)
return obj
def create(self, request):
personal_info = JSONParser().parse(request)
.....
return ...
test.py
url = reverse('employee1-list')
self.client = Client(HTTP_AUTHORIZATION='Token ' + token.key)
resp1 = self.client.post(url, {"data": {
"appraisal_master_id": 1,
"personal_info": {
"employee_id": 1,
"experience": "abcd",
"education": "Nonoooo"
}
}
}, format='json')
print(resp1)
self.assertEqual(resp1.status_code, 200)
I have got 400 error. Please, tell anyone how can I pass data im properway,..
I got solution. When I gave request which passes dictionary data. then, I coverted into json its working fine.
url = reverse('employee1-list')
self.client = Client(HTTP_AUTHORIZATION='Token ' + token.key)
resp1 = self.client.post(url, data=data, content_type="application/json")
print(resp1)
self.assertEqual(resp1.status_code, 201)

Unable to authenticate using factory-boy Django

I'm quite new to factory-boy and I'm trying to send a request to an API endpoint in my unit test, which requires a user to be authenticated. The endpoint expects a token in the header in the form of 'Bearer ' + token. I've looked at a few examples online and this is what I've come up with so far in my unit test:
test_user.py
class UserFactory(factory.Factory):
class Meta:
model = user
username = factory.LazyAttribute(lambda t: "myuser")
password = factory.PostGenerationMethodCall('set_password', 'my_super_secret')
is_staff = True
is_active = True
class UserViewSetTest(TestCase):
def setUp(self):
pwd = 'my_super_secret'
self.user = UserFactory(password=pwd)
self.client = Client()
self.assertTrue(self.client.login(username=self.user.username, password=pwd))
def test_user_list(self):
response = self.client.get(reverse('user', kwargs={'fromdate': '2017-01-01', 'todate': '2017-04-01'})), format='json')
self.assertEqual(response.status_code, 200)
The initial error is the fact that this assertion self.assertTrue(self.client.login(username=self.user.username, password=pwd)) is false so the test fails right away. Even if I remove that line, the API call returns a 401 because the authentication isn't successful.
How can I successfully authenticate a user in this API call with factory-boy so that I can send a token in the API request? Can I use a user model provided by the framework?
EDIT:
I've tried to create a token in order to pass it through the header as such:
def setUp(self):
self.user = UserFactory.create()
self.factory = APIRequestFactory()
self.token = Token.objects.create(user=self.user)
self.token.save()
def test_user_list(self):
self.client = APIClient()
self.client.credentials(HTTP-AUTHORIZATION='Bearer ' + self.token.key)
response = self.client.get(reverse('user', kwargs={'fromdate': '2017-01-01', 'todate': '2017-04-01'})), format='json')
self.assertEqual(response.status_code, 200)
However, I'm still getting an
AssertionError: 401 != 200
I've also seen that there is a force_authentication method but I'm not sure how to use it. Any help is appreciated.
You're using factory.Factory instead of factory.django.DjangoModelFactory.
factory.Factory doesn't automatically save to the db, so you can either switch to DjangoModelFactory, or run self.user.save() manually
You also don't need self.client = Client(), as self.client already exists
I have forced the authentication in the following way. I'm not sure this is the best practice as it seems to be bypassing the issue rather than resolving it, so if anyone else has other ideas, please let me know.
view = views.{VIEWSET_ENDPOINT}.as_view({'get': 'list'})
request = self.factory.get('{ENDPOINT}')
force_authenticate(request, user=self.user)
response = view(request)

Unit testing of OAuth on Flask

I'm new to coding and I feel really stuck. I decided to unit test my micro Flask app. I use "Google sign in" to authenticate my users, but I'm not able to get through the Google authentication and assert what is really on the page. I have no idea if I need to mock, play with the session or application context.
Here is the code for authentication process:
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
user = dict(session).get('profile', None)
if user is not None:
email = session['profile']['email']
else:
email = None
if user:
return f(*args, **kwargs)
return render_template('login.html', email=email)
return decorated_function
#app.route('/login/')
def login():
google = oauth.create_client('google') # create the google oauth client
redirect_uri = url_for('authorize', _external=True)
return google.authorize_redirect(redirect_uri)
#app.route('/authorize')
def authorize():
google = oauth.create_client('google') # create the google oauth client
token = google.authorize_access_token() # Access token from google (needed to get user info)
resp = google.get('userinfo') # userinfo specificed in the scrope
user_info = resp.json()
# Loading and cleaning all user emails
auth_users = db.session.query(users.email).all()
auth_users = [str(i).strip("(),'") for i in auth_users]
# Checking if user is in the list
if user_info['email'] in auth_users:
flash("Boli ste úspešne prihlásený.", category="success")
else:
flash("Nemáte práva na prístup.", category="primary")
return redirect('/')
session['profile'] = user_info
# make the session pernament so it keeps existing after broweser gets closed
session.permanent = True
return redirect('/')
The test structure look like this:
class Uvo_page(unittest.TestCase):
def setUp(self):
self.client = app.test_client(self)
self.user_info = {'email': 'xxx'}
self.auth_users = ['xxx', 'yyy']
# UVO page shows results
def test_uvo_page_show_results(self):
response = self.client.get('/show/1',
content_type='html/text',
follow_redirects=True)
test_string = bytes('Sledované stránky', 'utf-8')
self.assertTrue(test_string in response.data)
I thought that by defining the "user_info" it will be used for authentication evaluation. Instead of it it does not continue to "/authorize" and authentication, but stays on the login page.

Flask_restx #api.route return statement results in a TypeError

I have a problem I am not able to solve. I want to make use of http cookies in flask. This is the code from documentation:
#app.route('/token/auth', methods=['POST'])
def login():
username = request.json.get('username', None)
password = request.json.get('password', None)
if username != 'test' or password != 'test':
return jsonify({'login': False}), 401
# Create the tokens we will be sending back to the user
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
# Set the JWT cookies in the response
resp = jsonify({'login': True})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp, 200
I use flask_restx which automatically turns the response into JSON, so that jsonify in the first example is not needed. However, still still need to jsonify it, because i can not use set_access_cookie on a dictionary. This results at the end in a nested response like this jsonify(jsonify(x))
#api.route("/login")
class UserLogin(Resource):
def post(self):
"""Allows a new user to login with his email and password"""
email = request.get_json()["email"]
password = request.get_json()["password"]
user = User.query.filter_by(email=email).one_or_none()
if user is None:
return {"message": "user does not exist"}, 404
user = user.format()
if bcrypt.check_password_hash(pw_hash=user["password"], password=password):
if user["active"]:
resp = jsonify({"login": True})
access_token = create_access_token(identity=user)
refresh_token = create_refresh_token(user)
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp, 200
# return (
# {"access_token": access_token, "refresh_token": refresh_token},
# 200,
# )
else:
return {"message": "User not activated"}, 400
else:
return {"message": "Wrong credentials"}, 401
This is the error: TypeError: Object of type Response is not JSON serializable
Any ideas how can I overcome this?
Was able to solve it like this:
data = dict(login=True)
resp = make_response(jsonify(**data), 200)
access_token = create_access_token(identity=user)
refresh_token = create_refresh_token(user)
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp

Add existing API into django rest framework

Besides the API I created by using the django rest framework, now I want to add the existing commerial API into the backend, how to do this?
The commerial API include several part(API), for example, /prepare, /upload, /merge,/get_result, etc. They all use the "POST" indeed.
Do I need to call the commerial API directly on the frontend? Or I need to integrate the commerial API in to one backend API? Any suggestions are appreciated.Thanks.
For example:
```
class TestView(APIView):
"""
Add all the commerial API here in order
/prepare(POST)
/upload(POST)
/merge(POST)
/get_result(POST)
return Result
"""
```
Depending on your needs, I suggest making the external API calls on backend.
As a good practice, you should seperate your external API calls from your views. As it can be messy as the project gets bigger.
Check the sample code of how I manage external API calls, by seperating them to a different file, for example api_client.py
My default api_client.py looks something like this.
(You need to install "requests" pip package by pip install requests)
import requests
from django.conf import settings
class MyApiClient(object):
def __init__(self):
self.base_url = settings.EXTERNAL_API_1.get('url')
self.auth_url = "{0}login/".format(self.base_url)
self.username = settings.EXTERNAL_API_1.get('username')
self.password = settings.EXTERNAL_API_1.get('password')
self.session = None
self.access_token = None
self.token_type = None
self.token_expires_in = None
def _request(self, url, data=None, method="POST", as_json=True):
if self.session is None:
self.session = requests.Session()
if not self.access_token:
self.authenticate()
r = requests.Request(method, url=url, data=data, headers={
"Accept": "application/json",
"Content-Type": "application/json",
'Authorization': 'Token {0}'.format(
self.access_token)
})
prepared_req = r.prepare()
res = self.session.send(prepared_req, timeout=60)
if as_json:
json_result = res.json()
return json_result
return res
def _get(self, url):
return self._request(url=url, method="GET")
def _post(self, url, data):
return self._request(url=url, data=data, method="POST")
def authenticate(self):
res = requests.post(self.auth_url, json={'username': self.username,
'password': self.password},
headers={"Content-Type": "application/json"})
if res.status_code != 200:
res.raise_for_status()
json_result = res.json()
self.access_token = json_result.get('response', None).get('token',None)
def prepare(self):
something = 'bla bla'
request_url = "{0}prepare".format(self.base_url)
result = self._post(url=request_url, data=something)
return result
And in your views.py
from api_client import MyApiClient
class TestView(APIView):
api_client = MyApiClient()
def post(request, *args, **kwargs):
res1 = api_client.prepare()
res2 = api_client.your_other_method()
res3 = api_client.your_last_method()
return Response(res3)
Edited! Hope this helps now :)