Django - approach to code repetition in Unit tests - django

Short introduction.
I'm just learning to write tests for my application. The application is an online gradebook.
Below is the code from the homepage view test. Only the login form is there. If the user logs in for the first time, he is redirected to the view that forces to change the default password. If this is the next login, it is redirected to the right view, depending on the user rights.
Problem.
In this test of the view, I had to create 6 users and related models (Student, Parent, Teacher), so I wrote the methods to create these objects (create_user / create_person).
In many future view tests, I will also have to log in users and use models (Student, Parent, Teacher).
How should I repeat these steps? Two ways come to mind.
1. Convert methods to static and use them in the next test classes to create objects. Will the tests still be "unitary"?
2. Copy the method definitions to each next test class in which they will be needed. Will this not break the DRY principle?
Which approach is good?
class HomepageViewTestCase(TestCase):
password = 'testerspass'
def create_user(self):
username = 'tester'
numbering = 0
while True:
try:
User.objects.get(username=username)
numbering += 1
username += str(numbering)
except User.DoesNotExist:
return User.objects.create_user(
username=username, password=self.password,
first_name='Name', last_name='Surname')
def create_person(self, kind):
content_type = ContentType.objects.get_for_model(RightsSupport)
permission = Permission.objects.get(content_type=content_type,
codename=kind)
if kind in {'student', 'parent'}:
user = self.create_user()
user.user_permissions.add(permission)
try:
school_class = SchoolClass.objects.get(unique_code='2a2020')
except SchoolClass.DoesNotExist:
school_class = SchoolClass.objects.create(unique_code='2a2020',
name='2a', year=2020)
student = Student.objects.create(user=user,
school_class=school_class,
name=user.first_name,
surname=user.last_name,
birthday='2010-03-29')
if kind == 'student':
return student
if kind == 'parent':
user = self.create_user()
user.user_permissions.add(permission)
return Parent.objects.create(user=user, student=student,
name=user.first_name,
surname=user.last_name)
if kind == 'teacher':
user = self.create_user()
user.user_permissions.add(permission)
return Teacher.objects.create(user=user, name=user.first_name,
surname=user.last_name)
def setUp(self):
# Create users and student/parent/teacher models objects to test login
# by homepage
self.student_1 = self.create_person('student')
self.student_1_form_data = {'username': self.student_1.user.username,
'password': self.password}
self.student_2 = self.create_person('student')
self.student_2.first_login = False
self.student_2.save()
self.student_2_form_data = {'username': self.student_2.user.username,
'password': self.password}
self.parent_1 = self.create_person('parent')
self.parent_1_form_data = {'username': self.parent_1.user.username,
'password': self.password}
self.parent_2 = self.create_person('parent')
self.parent_2.first_login = False
self.parent_2.save()
self.parent_2_form_data = {'username': self.parent_2.user.username,
'password': self.password}
self.teacher_1 = self.create_person('teacher')
self.teacher_1_form_data = {'username': self.teacher_1.user.username,
'password': self.password}
self.teacher_2 = self.create_person('teacher')
self.teacher_2.first_login = False
self.teacher_2.save()
self.teacher_2_form_data = {'username': self.teacher_2.user.username,
'password': self.password}
def test_homepage_view(self):
response = Client().get(reverse('yourgrades:homepage'))
self.assertEqual(response.status_code, 200)
# Post with clean form or with wrong data -> not redirects
response = Client().post(reverse('yourgrades:homepage'))
self.assertEqual(response.status_code, 200)
response = Client().post(reverse('yourgrades:homepage'),
{'username': 'tester',
'password': 'wrongpass'})
self.assertEqual(response.status_code, 200)
# Student redirection tests
# if first_login field is True -> redirects to FirstLoginView
self.assertTrue(LoginForm(self.student_1_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.student_1_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.student_1_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/firstlogin')
# if first_login field is False, redirects to StudentParentView
self.assertTrue(LoginForm(self.student_2_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.student_2_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.student_2_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/studentparent')
self.assertTemplateUsed(response, 'yourgrades/studentparent.html')
# Parent redirection tests
# if first_login field is True, redirects to FirstLoginView
self.assertTrue(LoginForm(self.parent_1_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.parent_1_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.parent_1_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/firstlogin')
# if first_login field is False, redirects to StudentParentView
self.assertTrue(LoginForm(self.parent_2_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.parent_2_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.parent_2_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/studentparent')
self.assertTemplateUsed(response, 'yourgrades/studentparent.html')
# Teacher redirection tests
# if first_login field is True, redirects to FirstLoginView
self.assertTrue(LoginForm(self.teacher_1_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.teacher_1_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.teacher_1_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/firstlogin')
# if first_login field is False, redirects to TeacherPanelView
self.assertTrue(LoginForm(self.teacher_2_form_data).is_valid())
response = Client().post(reverse('yourgrades:homepage'),
self.teacher_2_form_data, )
self.assertEqual(response.status_code, 302)
response = Client().post(reverse('yourgrades:homepage'),
self.teacher_2_form_data, follow=True)
self.assertRedirects(response, '/yourgrades/teacher')
self.assertTemplateUsed(response, 'yourgrades/teacher.html')```

Related

How to handle a file form request and send by REST API

I'm trying to upload files, documents and images (once) using a Django form. The idea is to send it over an internal REST API that processes the file, stores it, and does other things.
The API is working but I can't successfully send the file over the API.
This is my code.
Here I process the form
def postNewFile(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = PostNewFile(request.POST, request.FILES)
# check whether it's valid:
if form.is_valid():
file_uploaded = request.FILES.get('file_uploaded')
file_name = file_uploaded.name
content_type = file_uploaded.content_type
formatType = form.cleaned_data['type']
category = form.cleaned_data['type']
description = form.cleaned_data['type']
response = postFile(file_uploaded, file_name, formatType, category, description, content_type)
if response == 0:
return HttpResponse('<H1>Enviado exitosamente</H1>')
else:
return HttpResponse('<H1>Error interno</H1>')
else:
print('No es valido')
return HttpResponse('<H1>Error en el formulario</H1>')
else:
return HttpResponse('<H1>Error en el formulario</H1>')
Here I make de API request
def postFile(doc, name, docType, category, description, content_type):
try:
url = URL_API+"file/"
payload={}
docs=[
('document',(name+'.'+docType, open(doc,'rb'),content_type))
]
headers = {
'Authorization': 'Bearer as',
'name': name,
'type': docType,
'category': category,
'description': description,
'content-type': content_type
}
response = requests.request("POST", url, headers=headers, data=payload, files=docs)
print(response.text)
return 0
except Exception as e:
print(e)
return -1

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

How can I use JWT token in DRF tests?

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.

Why I can't login to admin using client?

I try to test my custom action. But when I go to admin page using client I get error <HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/admin/login/?next=/admin/donation/donation/">
class ExportToExcelTestCase(TestCase):
def setUp(self) -> None:
self.user = UserFactory()
def test_export_to_excel(self) -> None:
data = {'action': 'export_to_excel'}
change_url = '/admin/donation/donation/'
self.user.is_staff = True
self.user.is_superuser = True
self.user.save()
self.client.login(username=self.user.username, password=self.user.password)
response = self.client.post(change_url, data)
print(response) #<HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/admin/login/?next=/admin/donation/donation/">
You can't log in with self.user.password - that is the hashed password for the user, not the password they would use to log in - hence your login is failing (you can verify this by checking the return value of login() - it will be False).
Since you're not actually testing authentication here, you should just use force_login instead:
def test_export_to_excel(self) -> None:
data = {'action': 'export_to_excel'}
change_url = '/admin/donation/donation/'
self.user.is_staff = True
self.user.is_superuser = True
self.user.save()
self.client.force_login(self.user)
response = self.client.post(change_url, data)

TypeError: 'NoneType' object is not subscriptable in Django unittests for message

Am trying to do a unit test for a view that returns a message when some input is duplicated in the database.it gives me the error TypeError: 'NoneType' object is not subscriptable
Here is the view
#login_required
def worker_create(request):
worker_create_form = WorkerCreateForm(request.POST)
if request.method == 'POST':
if worker_create_form.is_valid():
form = worker_create_form.save(commit=False)
phone = form.phone
check_phone = Worker.objects.filter(phone=phone)
if check_phone.count() != 0:
messages.error(request, 'رقم الهاتف مستخدم من قبل')
else:
form.save()
return redirect('worker_list')
else:
worker_create_form = WorkerCreateForm(request.POST)
context = {
'worker_create_form': worker_create_form,
}
return render(request, 'erp_system/worker/create.html', context)
and here are the tests I've created for it
class WorkerCreateTest(TestCase):
def setUp(self):
User.objects.create_user(username='test_user', email='test#gmail.com', password='test_password')
branch = Branch.objects.create(name='test branch')
Worker.objects.create(name="ay btngan ", phone='01207199086', branch=branch)
def test_get_request_unauthenticated(self):
response = self.client.get(reverse('worker_create'))
url = reverse('worker_create')
self.assertRedirects(response, '/login/?next=' + url)
def test_get_request_authenticated(self):
self.client.login(username='test_user', password='test_password')
response = self.client.get(reverse('worker_create'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed('erp_system/worker/create.html')
def test_post_request_empty_data(self):
self.client.login(username='test_user', password='test_password')
response = self.client.post(reverse('worker_create'), {})
self.assertFormError(response, 'worker_create_form', 'name', 'This field is required.')
self.assertFormError(response, 'worker_create_form', 'phone', 'This field is required.')
self.assertFormError(response, 'worker_create_form', 'branch', 'This field is required.')
def test_post_request_invalid_data(self):
self.client.login(username='test_user', password='test_password')
branch = Branch.objects.create(name='test again ')
name = 'just a name'
for i in range(300):
name += 'h'
response = self.client.post(reverse('worker_create'),
{'name': name, 'phone': '01207199086', 'branch': branch.id})
self.assertEqual(response.status_code, 200)
def test_post_request_duplicated_phone(self):
self.client.login(username='test_user', password='test_password')
branch = Branch.objects.create(name='test again ')
response = self.client.post(reverse('worker_create'),
{'name': 'test worker', 'phone': '01207199086', 'branch': branch.id})
print(response)
messages = list(response.context['messages'])
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'رقم الهاتف مستخدم من قبل')
def test_post_request_valid_data(self):
self.client.login(username='test_user', password='test_password')
branch = Branch.objects.create(name='test branch1234')
name = 'new valid name'
response = self.client.post(reverse('worker_create'),
{'name': name, 'branch': branch.id, 'phone': '0151951115'})
self.assertEqual(response.status_code, 302)
Important
I noticed when I added print(response) that it gives me HttpResponseRedirect not just HttpResponse what means there is no context given. Why it using redirect in here!?
In your test test_post_request_duplicated_phone, you make a POST request to the worker_create, and you expect to retrieve an error, because there exists already a record for the given phone.
The documentation on tests [Django-doc] however mentions that:
A TestCase, on the other hand, does not truncate tables after a test. Instead, it encloses the test code in a database transaction that is rolled back at the end of the test. This guarantees that the rollback at the end of the test restores the database to its initial state.
So that means that, unless you implement some "tricks" to prevent this, the side-effects that one test has (on the database) will be gone when you enter a second test. This makes sense, since reordering the tests should not result in a different outcome.
You can however create such Worker object in your test in advance, and thus make sure that the test will indeed error:
def test_post_request_duplicated_phone(self):
self.client.login(username='test_user', password='test_password')
branch = Branch.objects.create(name='test again ')
Worker.objects.create(name=" just a name ", phone='01207199086', branch=branch)
response = self.client.post(reverse('worker_create'),
{'name': 'test worker', 'phone': '01207199086', 'branch': branch.id})
print(response)
messages = list(response.context['messages'])
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'رقم الهاتف مستخدم من قبل')