The following test code does not pass even though manually submitting the form on my web interface actually does work.
import os
from flask.ext.testing import TestCase
from flask import url_for
from config import _basedir
from app import app, db
from app.users.models import User
class TestUser(TestCase):
def create_app(self):
"""
Required method. Always implement this so that app is returned with context.
"""
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(_basedir, 'test.db')
app.config['WTF_CSRF_ENABLED'] = False # This must be disabled for post to succeed during tests
self.client = app.test_client()
ctx = app.app_context()
ctx.push()
return app
def setUp(self):
db.create_all()
#pass
#app.teardown_appcontext
def tearDown(self):
db.session.remove()
db.drop_all()
#pass
def test_admin_home(self):
# url is the admin home page
url = url_for('admin.index')
resp = self.client.get(url)
self.assertTrue(resp.status_code == 200)
def test_admin_registration(self):
url = url_for('admin.register_view')
data = {'username': 'admin', 'email': 'admin#example.com', 'password': 'admin'}
resp = self.client.post(url, data)
self.assertTrue(resp.status_code == 200)
u = User.query.filter_by(username=u'admin').first()
self.assertTrue(u.username == 'admin') # <----- This fails. Why?
After the test client has post to the register_view url and returns a 200 OK response, I fail to retrieve the 'admin' user from the test database. Why is this so?
Here's the view code (this is a flask admin view)
from flask import request
from flask.ext.admin import expose, AdminIndexView, helpers
from app.auth.forms import LoginForm, RegistrationForm
from app.users.models import User
from app import db
class MyAdminIndexView(AdminIndexView):
#expose('/', methods=('GET', 'POST'))
def index(self):
# handle user login
form = LoginForm(request.form)
self._template_args['form'] = form
return super(MyAdminIndexView, self).index()
#expose('/register/', methods=('GET', 'POST'))
def register_view(self):
# handle user registration
form = RegistrationForm(request.form)
if helpers.validate_form_on_submit(form):
user = User()
form.populate_obj(user)
db.session.add(user)
db.session.commit()
self._template_args['form'] = form
return super(MyAdminIndexView, self).index()
Dumbest mistake ever.
The offending line in my test code is
resp = self.client.post(url, data)
It should be
resp = self.client.post(url, data=data)
I managed to track it down by painstakingly walking through the logic and inserting ipdb.set_trace() step by step until I found the bad POST request made by my client.
Related
I am trying to create a flask app that will allow users to login, thereafter, they will be redirected to a specific dashboard created and being served with Bokeh.
So, for example, we have an user1, at the beginning he will start in https:myurl/login, after successful login he will be redirected to https:myurl/user1 where his specific dashboard is.
So, my question is, how I can avoid user1 accessing dashboard from other users user2, user3, etc. It is actually possible to do that? I am relatively new to flask, so my apologies if the question sounds silly.
from multiprocessing import connection
from functools import wraps
from re import A
from flask import Flask, render_template, request, flash, redirect, url_for, session
import sqlite3
from sqlalchemy import DATE
# Setup
app = Flask(__name__)
app.secret_key = "my_key"
# Routes
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
connection = sqlite3.connect("user_login.db")
cursor = connection.cursor()
# Get what the user has typed in the HTML form
username = request.form["username"]
password = request.form["password"]
# SQL query
cursor.execute(
"SELECT * FROM users WHERE username=? AND password=?", (username, password)
)
data = cursor.fetchone()
if data:
session["username"] = data[1]
session["password"] = data[2]
return redirect(url_for("user({data[1]})"))
# return redirect(f"https://myurl/{session['username']}", code=302)
else:
flash("Username and Password Mismatch", "DANGER! Please try again")
# Render HTML template
return render_template("login.html")
# Check if user is logged in
# def is_logged_in(f):
# #wraps(f)
# def secure_function(*args, **kwargs):
# if "logged_in" in session:
# return f(*args, **kwargs)
# else:
# flash("Unauthorized, please login", "danger")
# return redirect(url_for("login"))
# return secure_function
#app.route("/<username>")
def user(username):
if username == session['username']:
return redirect(
f"https://myurl/{session['username']}", code=302
)
else:
return flash("Unauthorized")
# #app.route('/')
# def logged():
# return redirect(f"https://myurl/{session['username']}", code=302)
if __name__ == "__main__":
app.run(debug=True)
How about verifying if the
current_user.username == myurl/<username>
.username being the name of your user in your Models(if it is name then current_user.name, etc.)
Like
#app.route("/dashboard/<username>")
def dashboard(username):
if username == current_user.username:
#proceed
else:
return "Access Denied"
*** Edit ***
Your provided code for the return statement
redirect(url_for("user({data[1]})"))
Could be written as:
return redirect(url_for('user', username = data[1]))
I would like to test a View in Django using Sellenium that has a decorator, that requires being logged in:
#method_decorator(login_required, name='dispatch')
This is how mu code looks like:
class TestAddOrder(LiveServerTestCase):
def setUp(self):
self.selenium = webdriver.Firefox()
super(TestAddOrder, self).setUp()
def tearDown(self):
self.selenium.quit()
super(TestAddOrder, self).tearDown()
def test_add_order(self):
selenium = self.selenium
selenium.get('http://127.0.0.1:8000/orders/create/')
date = selenium.find_element_by_name('date').send_keys('01/31/2019')
hours = selenium.find_element_by_id('id_hour').send_keys('18')
submit = selenium.find_element_by_name('submit').send_keys(Keys.RETURN).send_keys(Keys.RETURN)
And the error:
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [name="date"]
How can I keep the session of logged in user when trying to automise my test with Sellenium?
In your setUp() method you should create a user, log in the user and set the session cookie so that it's sent with every subsequent request:
def setUp(self):
self.selenium = webdriver.Firefox()
super().setup()
user = User.objects.create_user(...)
self.client.force_login(user) # TestCase client login method
session_key = self.client.cookies[settings.SESSION_COOKIE_NAME].value
self.selenium.get('http://127.0.0.1/') # load any page
self.selenium.add_cookie({'name': settings.SESSION_COOKIE_NAME, 'value': session_key, 'path': '/'})
I've been stuck on this problem for a couple hours. I'm new to django and automated testing, and I've been playing around with Selenium and django's StaticLiveServerTestCase. I've been trying to test my login form (which works fine when I use runserver and test it myself, by the way.)
Everything is working great, except I can't seem to successfully login my test user. I've narrowed down the break point to django's User.authenticate method.
The User object is created successfully in my setUp and I can confirm that by accessing it's attributes in my test method. However, authenticate fails.
I've looked at the following for help but they didn't get me very far:
Django Unittests Client Login: fails in test suite, but not in Shell
Authentication failed in Django test
Any idea why authenticate fails? Do I need to add something to my settings?
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
class AccountTestCase(StaticLiveServerTestCase):
def setUp(self):
self.selenium = webdriver.Chrome()
super().setUp()
User.objects.create(username='test', email='test#test.com', password='Test1234', is_active=True)
def tearDownClass(self):
self.selenium.quit()
super().tearDown()
def test_register(self):
user = authenticate(username='test', password='Test1234')
if user is not None: # prints Backend login failed
print("Backend login successful")
else:
print("Backend login failed")
user = User.objects.get(username='test')
print(user)
print(user.username) # prints test
print(user.password) # prints Test1234
I found the issue. The User.authenticate() method hashes the password provided. However, I set the password directly when creating the user, which means it was stored as Test1234, so the hashed password provided during authentication did not match 'Test1234', hence the failure.
To properly store a hashed password, you need to use the set_password() method.
Updated setUp code:
def setUp(self):
self.selenium = webdriver.Chrome()
super().setUp()
user = User.objects.create(username='test', email='test#test.com', is_active=True)
user.set_password('Test1234')
user.save()
Using create_superuser resolved the issue. Below code resolves it.
from django.contrib.auth.models import User
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import Client
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
import time
from django.contrib.auth import authenticate
#from apps.digital.models import User
class MyTests(StaticLiveServerTestCase):
port = 0
host = 'my host'
def setUp(self):
super(MyTests, self).setUp()
self.selenium = WebDriver()
self.client = Client()
self.user = User.objects.create_superuser(username='test', password='Test1234', email='test#test.com', is_active=True)
self.user.save()
def tearDown(self):
self.selenium.quit()
super(MyTests, self).tearDown()
def test_login(self):
self.user = authenticate(username='test', password='Test1234')
if self.user is not None: # prints Backend login failed
self.user = User.objects.get(username='test')
print(self.user.username) # prints test
print(self.user.password) # prints Test1234
self.login = self.client.login(username='test', password='Test1234')
self.assertEqual(self.login, True)
print("Backend login successful")
self.selenium.get('%s%s' % (self.live_server_url, '/admin/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys(self.user.username)
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('Test1234')
self.selenium.find_element_by_xpath('//input[#value="Log in"]').click()
time.sleep(1)
else:
print("Backend login failed")
User Authentication is getting passed but when I try to login with the same credentials, the login fails.
from django.contrib.auth.models import User
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import Client
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
import time
from django.contrib.auth import authenticate
#from apps.digital.models import User
class MyTests(StaticLiveServerTestCase):
port = 0
host = '<my host>'
def setUp(self):
super(MyTests, self).setUp()
self.selenium = WebDriver()
self.client = Client()
self.user = User.objects.create(username='test', email='test#test.com', is_active=True)
self.user.set_password('Test1234')
self.user.save()
def tearDown(self):
self.selenium.quit()
super(MyTests, self).tearDown()
def test_login(self):
self.user = authenticate(username='test', password='Test1234')
if self.user is not None: # prints Backend login failed
self.user = User.objects.get(username='test')
print(self.user.username) # prints test
print(self.user.password) # prints Test1234
self.login = self.client.login(username='test', password='Test1234')
self.assertEqual(self.login, True)
print("Backend login successful")
self.selenium.get('%s%s' % (self.live_server_url, '/admin/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys(self.user.username)
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('Test1234')
self.selenium.find_element_by_xpath('//input[#value="Log in"]').click()
time.sleep(1)
else:
print("Backend login failed")
This is my DRF view:
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def check_user(request):
user = request.user
# use user object here
return JSONResponse({})
And this is my unit test for said view:
class CheckUserViewTest(TestCase):
def test_check_user(self):
user = User.objects.create_user('username', 'Pas$w0rd')
self.client.login(username='username', password='Pas$w0rd')
response = self.client.get(reverse('check_user'))
self.assertEqual(response.status_code, httplib.OK)
But I always get a 401 UNAUTHORIZED response from my view. I have logged in the user in my test. What am I doing wrong?
Since you are using Django REST Framework you have to also use DRF's test client called APIClient instead of Django's test client. This happens automagically if you inherit from DRF's APITestCase instead of Django's TestCase.
Complete example:
class CheckUserViewTest(APITestCase):
def test_check_user(self):
user = User.objects.create_user('username', 'Pas$w0rd')
self.assertTrue(self.client.login(username='username', password='Pas$w0rd'))
response = self.client.get(reverse('check_user'))
self.assertEqual(response.status_code, httplib.OK)
An alternative is to use force_authenticate:
class CheckUserViewTest(APITestCase):
def test_check_user(self):
user = User.objects.create_user('username', 'Pas$w0rd')
self.client.force_authenticate(user)
response = self.client.get(reverse('check_user'))
self.assertEqual(response.status_code, httplib.OK)
If your are using djangorestframework you must have to use APITestCase here.
An complete example is just below
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password
class TestLogin(APITestCase):
'''
This will handle login testcases
'''
def setUp(self):
self.url = reverse('check_user')
def test_login(self):
'''
This will test successfull login
'''
data = {
"full_name" : "full name",
'email' : "email#gmail.com",
'password' : "password"
}
User.objects.create(
full_name = data.get('full_name'),
email = data.get('email'),
password = make_password(data.get('password'))
)
response = self.client.get(self.url, data=data)
self.assertEqual(response.status_code,status.HTTP_200_OK)
I'm trying to figure out why my Flask test is not working correctly. I'm testing a view function '/register' that successfully redirects to a dashboard when I run the site on localhost.
My test for this behavior fails and instead feeds back a 200 response.
Traceback (most recent call last):
File "/Users/casey/python/storm/project/tests/test_views.py", line 30, in test_register_user
self.assertEqual(resp.status_code, 302)
AssertionError: 200 != 302
My test and view code are below:
# tests/helpers.py
from unittest import TestCase
from views import app
class PhotogTestCase(TestCase):
def setUp(self):
self.client = app.test_client()
self.client.testing = True
app.config['WTF_CSRF_ENABLED'] = False
def tearDown(self):
pass
# tests/test_views.py
from helpers import PhotogTestCase
class TestRegister(PhotogTestCase):
"""Test our registration view."""
def test_register_user(self):
# Ensure page loads with correct text
resp = self.client.get('/register')
assert 'Register for an Account' in resp.data
# Ensure that valid fields result in success.
resp = self.client.post('/register', {
'email': 'c#gmail.com',
'password': 'woot1LoveCookies!',
'password_again': 'woot1LoveCookies!'
}, follow_redirects=True)
self.assertEqual(resp.status_code, 302)
# views.py
import uuid
from forms import RegistrationForm
from flask import Flask, redirect, render_template, \
request, url_for, flash, current_app, abort
from flask.ext.stormpath import StormpathManager, login_required, \
groups_required, user, User
from stormpath.error import Error as StormpathError
from flask.ext.login import login_user
#app.route('/register', methods=['GET', 'POST'])
def register():
"""
Register a new user with Stormpath.
"""
form = RegistrationForm()
# If we received a POST request with valid information, we'll continue
# processing.
if form.validate_on_submit():
data = {}
# Attempt to create the user's account on Stormpath.
try:
# email and password
data['email'] = request.form['email']
data['password'] = request.form['password']
# given_name and surname are required fields
data['given_name'] = 'Anonymous'
data['surname'] = 'Anonymous'
# create a tenant ID
tenant_id = str(uuid.uuid4())
data['custom_data'] = {
'tenant_id': tenant_id,
'site_admin': True
}
# Create the user account on Stormpath. If this fails, an
# exception will be raised.
account = User.create(**data)
# create a new stormpath group
directory = stormpath_manager.application.default_account_store_mapping.account_store
tenant_group = directory.groups.create({
'name': tenant_id,
'description': data['email']
})
# assign new user to the newly created group
account.add_group(tenant_group)
account.add_group('site_admin')
# If we're able to successfully create the user's account,
# we'll log the user in (creating a secure session using
# Flask-Login), then redirect the user to the
# STORMPATH_REDIRECT_URL setting.
login_user(account, remember=True)
# redirect to dashboard
redirect_url = app.config['STORMPATH_REDIRECT_URL']
return redirect(redirect_url)
except StormpathError as err:
flash(err.message.get('message'))
return render_template(
'account/register.html',
form=form,
)
When making the request, you set follow_redirects=True, so naturally you see the final page rather than the intermediate redirect. Set follow_redirects=False instead.