The "override_settings" in TestCase is not work - django

I used "override_settings" in my test case, and I want to test DEFAULT_THROTTLE_RATES of REST_FRAMEWORK in the test case. When I tested my api, it didn't work.
my settings.py:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'anon': '60/min',
'user': '500/hour',
'custom': '200/day',
}
}
test api:
#throttle_classes([AnonRateThrottle, UserRateThrottle])
def test_api(request):
pass
TestCase:
#override_settings(REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'anon': '600000/min',
'user': '5000000/hour',
'custom': '200000/day',
}
})
def test_api(self):
from rest_framework.settings import api_settings
print(api_settings.DEFAULT_THROTTLE_RATES)
print(api_settings.user_settings)
from rest_framework.throttling import AnonRateThrottle, api_settings as throttling_setting
print(AnonRateThrottle().get_rate())
print(id(throttling_setting))
print(id(api_settings))
print(id(AnonRateThrottle().THROTTLE_RATES))
print(id(api_settings.DEFAULT_THROTTLE_RATES))
print(id(throttling_setting.DEFAULT_THROTTLE_RATES))
url = 'api'
for i in range(100000):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
I want the test case to work, but it asserts in the state of 429.
I print the id of "apisetting", "DEFAULT_THROTTLE_RATES" , which is the same, but the "THROTTLE_RATES" id in the "AnonRateThrottle" instance is not the same, the value is not the same. The value is "{'anon': '60/min', 'user' : '500/hour', 'custom': '200/day',}". Please help me, I am going crazy.

The throttle of drf is worked by cache. I clear the cache with every request to make it work.

Related

Unittesting FastAPI CRUD doesn't work as expected

I have a problem with unit tests in a project that use FastAPI.
I used TestClient, pytest, monkeypatch.
When I test a GET function I don't know why but instead to have the result of the mocked function the result come from the "real" service, getting the results from the DB.
The function to test:
from fastapi import APIRouter, Body
...
...
router = APIRouter()
#router.post('/', response_description="feature data added into the database")
async def add_feature_data(feature: FeatureSchema = Body(...)):
feature = jsonable_encoder(feature)
new_feature = await add_feature(feature)
return ResponseModel(new_feature, "feature added successfully.")
my file conftest.py
import pytest
from fastapi.testclient import TestClient
from app.main import klingon
#pytest.fixture(scope='module')
def test_app():
client = TestClient(klingon)
yield client
the test file:
import json
import pytest
from app.server.routes import feature_route
def test_get_features(test_app, monkeypatch):
test_data = [
{
'change': '1',
'urs': 'something',
'state': 'active',
'created_at': '2020-11-09T11:51:14Z',
'created_by': '9'
},
{
'change': '2',
'urs': 'something 2',
'state': 'active',
'created_at': '2020-10-16T11:51:14Z',
'created_by': '3'
}
]
async def mock_get_features():
return test_data
monkeypatch.setattr(feature_route, 'get_features', mock_get_features)
response = test_app.get('/feature/')
assert response.status_code == 200
assert response.json() == test_data
Thanks

Vue-apollo. Queries don't work return 400 Bad Request

I am building a frontend with Vue cli and backend with Django with Graphene. The mutation works fine but not the queries.
When I run the same query from GraphiQL, works fine.
Frontend
#vue/cli 4.1.2
vue-apollo 3.0.2
Backend
python 3.8
django 3.0.2
graphene-django 2.8.0
django-graphql-jwt 0.3.0
queries.js
import gql from 'graphql-tag'
export const ME_QUERY = gql`
query me {
me {
username
is_active
}
}
`
Home.vue
<script>
import { ME_QUERY } from '#/graphql/queries'
export default {
name: 'home',
async mounted () {
await this.$apollo
.query({
query: ME_QUERY
})
.then((result) => {
console.log(result)
})
.catch(({ graphQLErrors }) => {
graphQLErrors.map(({ message, locations, path }) => console.log(`Error Message: ${message}, Location: ${locations}, Path: ${path}`))
})
}
}
</script>
schema.py
from django.contrib.auth import authenticate, login, get_user_model
import graphene
from graphene_django import DjangoObjectType
import graphql_jwt
from graphql_jwt.decorators import jwt_cookie
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
class ObtainJSONWebToken(graphql_jwt.JSONWebTokenMutation):
user = graphene.Field(UserType)
#classmethod
def resolve(cls, root, info, **kwargs):
return cls(user=info.context.user)
class Query(graphene.ObjectType):
me = graphene.Field(UserType)
def resolve_me(root, info):
user = info.context.user
if user.is_anonymous:
raise Exception('Authentication failure!!')
return user
class Mutation(graphene.ObjectType):
# token_auth = graphql_jwt.ObtainJSONWebToken.Field()
verify_token = graphql_jwt.Verify.Field()
refresh_token = graphql_jwt.Refresh.Field()
revoke_token = graphql_jwt.Revoke.Field()
log_in = ObtainJSONWebToken.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
Thanks for advance
after to install the extension Graphql in Firefox and found my mistake.
{
"errors": [
{
"message": "Cannot query field \"is_active\" on type \"UserType\". Did you mean \"isActive\"?",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}
Changing to isActive the query worked fine.

How can I rewrite one value in the settings for the test

How can I rewrite 'user': '5/minute' on 'user': '2/day' in the settings for the test
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'user': '5/minute'
}
}
How can i do this with #override_settings or mb with context manage?
I need to apply this only in one test, I can not rewrite the whole dictionary
def test_mytest(self):
value = settings.REST_FRAMEWORK
value['DEFAULT_THROTTLE_RATES'] = {'user':'2/day'}
data1 = {}
data2 = {}
data3 = {}
with self.settings(REST_FRAMEWORK=value):
resp1 = self.client.post(self.url, data1, format='json')
resp2 = self.client.post(self.url, data1, format='json')
resp3 = self.client.post(self.url, data1, format='json')
assert resp3.status_code == 429, resp3.data
But no have 429 error, although the value has changed
from unittest import mock
#mock.patch('rest_framework.throttling.SimpleRateThrottle.get_rate')
def test_api(self, get_rate):
get_rate.return_value = '1/minute'
from rest_framework.settings import api_settings
print(api_settings.DEFAULT_THROTTLE_RATES)
print(api_settings.user_settings)
url = 'api'
response = self.client.get(url)
self.assertEqual(response.status_code, HTTP_200_OK)
response = self.client.get(url)
self.assertEqual(response.status_code, HTTP_429_TOO_MANY_REQUESTS)
Solution 1: With the #override_default() wrapper
You want to apply a wrapper function to the viewset that you wish to override with this value:
from rest_framework.throttling import UserRateThrottle
class UserViewSet(viewsets.ViewSet):
#api_view(['PUT'])
#throttle_classes([UserRateThrottle])
#override_settings(REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['user'] = "2/day")
def update(self, request, pk=None):
...
Add the wrapper before any view or viewset you wish to apply this overridden customs value.
You also want this in your settings.py:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '13500/day'
}
}
Solution 2: Custom throttling class
However, if you want different throttling rates for when you're in a test environment, perhaps try the following in settings.py:
TEST = True ## <-- Set to False when not in testing!!
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.CustomUserRateThrottle'
)
}
And designate your own custom throttling class:
from django.conf import settings
class CustomUserRateThrottle(throttling.UserRateThrottle):
if settings.TEST:
THROTTLE_RATES = 'DEFAULT_THROTTLE_RATES': {
'user': '13500/day',
}
else:
THROTTLE_RATES = 'DEFAULT_THROTTLE_RATES': {
'user': '2/day',
}
return settings.TEST # <-- Custom throttling classes must always return either True or False, so this makes sense.
Solution 3: directly in your "testing shell":
Include your custom wrapper as we defined above but this time in your test_something() method in test.py:
from django.conf import settings
from django.test import TestCase
class TestCase(TestCase):
def test_something(self):
with self.settings(REST_FRAMEWORK = ['DEFAULT_THROTTLE_RATES']['user'] = '13500/day'):
# START TESTING HERE WITH TEST SETTINGS
Depending on your use case you can follow one of the following ways :
Suggestion 1 : If you want to override settings in few specific test cases the you can use #override_settings decorator. But this make no sense if you want that for all test cases, if that's the case then you can follow suggestion 2.
Suggestion 2 : This is more generic and efficient solution.
You can have several settings file for various running environment (inside settings python module) something like this:
base.py (containing all of your basic settings)
local.py (import * from base here and override/add settings specific to local/dev environment)
test.py (import * from base here and override/add settings specific to test environment)
you can also have a settings specific for production environment in prod.py or something.
With this setup can run tests like python manage.py test --settings=yourproject.settings.test

Django rest framework: unit testing database issue

I am doing unit testing of the rest Apis. I am using django rest framework.
Apis are saving data into and getting data from the database. Both of the operations are not working or if it is working i am not able to see that in the databases. Apis are also using django-fsm, because of which i need same data from the db for the other tests. Tests depends on previous tests due to django-fsm. There is always state changing with the api. But now i am not able to see any data in database during test runs. Don't know where it is saving the data or in which database.
Below is my test settings:-
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': join(PROJECT_ROOT, 'run', 'db_for_testing.sqlite3'),
'TEST': {
'NAME': 'test_db_for_testing',
},
},
}
below is my api:-
class DummyView(CreateAPIView):
def post(self, request, *args, **kwargs):
data = request.data.copy()
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
order = self.model(book=serializer.data.get('book'))
order.save()
data = {
'status_code': 200,
'message': 'successfully.'
}
return Response(data, status=status.HTTP_200_OK)
As my tests depends on the previous test saving the data to db, so the other tests also fails. I am using APITestCase of rest_framework.
Help guys.
thanks in advance.
If I'm understanding your question correctly, Django "clear" database after each test (either rolling back or truncating.) So you need to write your tests accordingly.
See: https://docs.djangoproject.com/en/1.10/topics/testing/tools/#transactiontestcase
TL;DR - Solution: Use SimpleTestCase - See example below
Explanation
The thing is that the recommended test classes provided by Django for tests involving database queries, TransactionTestCase and the subclass TestCase, wraps every test in a transaction to speed up the process of resetting the database after each test. Source: Django TransactionTestCase docs
It is possible to avoid this behaviour by using SimpleTestCase which is the parent class of TransactionTestCase. You then have to specify explicitly that you want to allow database queries by setting allow_database_queries to True-
Also note that you are then responsible for any cleaning that needs to be done after the test. You can do that by overriding the tearDownClass class method. Similarly there's a setUpClass class method for any initialization prior to running the test. Remember to call the super methods. See full details in the docs
Example
from django.test import SimpleTestCase
class MyTestCase(SimpleTestCase):
allow_database_queries = True
#classmethod
def setUpClass(cls):
# Do your pre test initialization here.
super(MyTestCase, cls).setUpClass()
#classmethod
def tearDownClass(cls):
# Do your post test clean uphere.
super(MyTestCase, cls).tearDownClass()
def test_add_data_through_api(self):
# Add 'dat data
...
def test_work_with_data_from_previous_test(self):
# Work 'dat data
...
Use the --keepdb option when calling manage.py test:
https://docs.djangoproject.com/en/2.1/ref/django-admin/#cmdoption-test-keepdb
It's available since django 1.8.
Hope this helps.
Here my three tests are dependent with previous one. If I run them as separate test the previous test data deleted and later one failed for lost of that data.
So I make them in two different functions both are not test function. Finally call the dependent functions from one test function.
class test_profile(APITestCase):
def setUp(self):
super_user = default_service.create_super_user()
self.application = default_service.create_oath2_application(super_user.id)
self.content_type = "application/x-www-form-urlencoded"
self.username = "user#domain.com"
self.password = "pass123"
def create_profile(self):
url = reverse('EmailSignUp')
body = {
"email": self.username,
"password": self.password,
"fullname": "Mamun Hasan"
}
response = self.client.post(url, body, CONTENT_TYPE=self.content_type)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))['data']
# print("Profile", data)
self.assertEqual(data['username'], self.username)
self.assertEqual(data['fullname'], data['fullname'])
def get_access_token(self):
url = reverse('oauth2_provider:token')
body = {
"username": self.username,
"password": self.password,
"grant_type": self.application.authorization_grant_type,
"client_id": self.application.client_id,
"client_secret": self.application.client_secret,
}
response = self.client.post(url, body, CONTENT_TYPE=self.content_type)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))
# print("AccessToken", data)
self.assertEqual(data['token_type'], 'Bearer')
return data
def get_profile(self, oath2_token):
url = reverse('GetProfile')
authorization = oath2_token["token_type"] + ' ' + oath2_token["access_token"]
response = self.client.get(url, CONTENT_TYPE=self.content_type, HTTP_AUTHORIZATION=authorization)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))['data']
# print("Profile", data)
self.assertEqual(data['username'], self.username)
def test_dependent(self):
self.create_profile()
oath2_token = self.get_access_token()
self.get_profile(oath2_token)
I did not find any solution to commit the previous API data. If anyone knows please comment. So I have done it this way. I don't know this is the best solution but it works and tested.

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.
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
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'
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"))})
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)}
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"