(unittest) Testing Flask-Security: Cannot get past login page
I finally figured it out after a week of killing myself...
There were several problems:
There is some conflict between Flask-Security and Flask-SSLify. Although the automatic https redirection works fine on the actual web server, in the tests it prevent logging in completely. I figured this out by making fake test routes and trying to
POST
random data. NoPOST
in the test client worked. To fix this, I had to change my test configDEBUG
toTrue
. Note that this is not a great workaround, since stuff like CSS and JS won't be compiled and other app behavior might be different...Flask-Security does not work with factory_boy(or I was not using factory_boy correctly?). I was trying to create users and roles through factory_boy because I saw some sample app tutorial that used it. After I fixed the above problem, it kept telling me that the user did not exist. I ended up stealing the code from Flask-Security's own tests for creating fake users with different roles.
The problematic code:
session = db.create_scoped_session()class RoleFactory(SQLAlchemyModelFactory): FACTORY_FOR = Role FACTORY_SESSION = session id = Sequence(int) name = 'admin' description = 'Administrator'class EmployeeFactory(SQLAlchemyModelFactory): FACTORY_FOR = Employee FACTORY_SESSION = session id = Sequence(int) email = Sequence(lambda n: 'user{0}@app.com'.format(n)) password = LazyAttribute(lambda a: encrypt_password('password')) username = Sequence(lambda n: 'user{0}'.format(n)) #last_login_at = datetime.utcnow() #current_login_at = datetime.utcnow() last_login_ip = '127.0.0.1' current_login_ip = '127.0.0.1' login_count = 1 roles = LazyAttribute(lambda _: [RoleFactory()]) active = True
This was my test case:
class MyAppTestCase(FlaskTestCaseMixin, MyTestCase): def _create_app(self): raise NotImplementedError def _create_fixtures(self): #self.user = EmployeeFactory() populate_data(1) def setUp(self): super(MyAppTestCase, self).setUp() self.app = self._create_app() self.client = self.app.test_client() self.app_context = self.app.app_context() self.app_context.push() db.create_all() self._create_fixtures() self._create_csrf_token() def tearDown(self): super(MyAppTestCase, self).tearDown() db.drop_all() self.app_context.pop() def _post(self, route, data=None, content_type=None, follow_redirects=True, headers=None): content_type = content_type or 'application/x-www-form-urlencoded' return self.client.post(route, data=data, follow_redirects=follow_redirects, content_type=content_type, headers=headers) def _login(self, email=None, password=None): email = email or 'matt@lp.com' #self.user.email password = password or 'password' data = { 'email': email, 'password': password, 'remember': 'y' } return self._post('/login', data=data)
I had a problem with login/logout on tests with Flask-Security (that uses Flask-Login) app also. I solved this problem by mocking flask_login._get_user
function and setting app.login_manager._login_disabled
to True.
Context manager that mocks user:
class LoginLogoutMixin(object): @contextmanager def login(self, user): mock_get_user = patch('flask_login._get_user', Mock(return_value=user)) self.app.login_manager._login_disabled = True mock_get_user.start() yield mock_get_user.stop() self.app.login_manager._login_disabled = False
And an example of it's usage:
class ProtectedViewTestCase(LoginLogoutMixin, TestCase): def setUp(self): super(ProtectedViewTestCase, self).setUp() # create user here def test_protected_view(self): with self.login(self.user): rv = self.client.get(url_for('protected_view')) self.assert200(rv)