Browse Source

Merge pull request #521 from flaskbb/ci-improvements

CI and testing improvements
Peter Justin 6 years ago
parent
commit
5a37577b76

+ 0 - 27
.landscape.yml

@@ -1,27 +0,0 @@
-doc-warnings: false
-test-warnings: false
-strictness: veryhigh
-max-line-length: 80
-autodetect: yes
-requirements:
-    - requirements.txt
-ignore-paths:
-    - docs
-    - migrations
-    - wsgi.py
-    - setup.py
-    - flaskbb/configs
-    - flaskbb/_compat.py
-pep8:
-  full: true
-  disable:
-    - E711
-    - E712
-
-pylint:
-  disable:
-    - too-few-public-methods
-    - too-many-public-methods
-    - too-many-locals
-    - invalid-name
-    - unused-argument

+ 20 - 14
.travis.yml

@@ -1,42 +1,48 @@
+os: linux
+dist: xenial
 language: python
 python:
   - 2.7
-  - 3.4
   - 3.5
   - 3.6
-env:
+  - 3.7
 matrix:
   fast_finish: true
   include:
     - python: 2.7
       env: TOXENV=py27-unpinned
-    - python: 3.6
-      env: TOXENV=flake8
-    - python: 3.6
-      env: TOXENV=py36-unpinned
-    - python: 3.6
+    - python: 3.7
+      env: TOXENV=py37-unpinned
+    - python: 3.7
       env: TOXENV=black
-    # see the following issue on why 3.7 is weirdo
-    # https://github.com/travis-ci/travis-ci/issues/9815
     - python: 3.7
-      dist: xenial
-      sudo: true
+      env: TOXENV=flake8
   allow_failures:
     # until the entire code base is properly formatted
     # allow failures to occur here rather than doing a
     # big all at once reformat that could hide errors
     - env: TOXENV=black
+    - env: TOXENV=flake8
+
 install:
-- pip install -r requirements-travis.txt
+- pip install -r requirements-dev.txt
+- pip install tox-travis
+
 script:
 - flaskbb translations compile
 - tox
+
 after_success:
-- coverage combine tests
-- coveralls
+- coverage combine
+- codecov
+
+cache:
+- pip
+
 notifications:
   webhooks: https://www.travisbuddy.com/
   on_success: never
+
 deploy:
   provider: pypi
   user: sh4nks

+ 2 - 2
AUTHORS

@@ -3,12 +3,12 @@ contributors:
 
 # FlaskBB Team
 
-* sh4nks <sh4nks7@gmail.com>
+* Peter Justin <peter.justin@outlook.com>
+* Alec Reiter <https://github.com/justanr>
 * Looking for more people!
 
 
 # Contributors
 
 * CasperVg
-* Alec Reiter <https://github.com/justanr>
 * Feel free to join! :)

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (c) 2013-2018 by the FlaskBB Team  and contributors. See AUTHORS
+Copyright (c) 2013-2019 by the FlaskBB Team  and contributors. See AUTHORS
 for more details.
 
 Some rights reserved.

+ 3 - 4
README.md

@@ -1,8 +1,7 @@
 # FlaskBB
 
 [![Build Status](https://travis-ci.org/flaskbb/flaskbb.svg?branch=master)](https://travis-ci.org/flaskbb/flaskbb)
-[![Coverage Status](https://coveralls.io/repos/sh4nks/flaskbb/badge.png)](https://coveralls.io/r/sh4nks/flaskbb)
-[![Code Health](https://landscape.io/github/sh4nks/flaskbb/master/landscape.svg?style=flat)](https://landscape.io/github/sh4nks/flaskbb/master)
+[![codecov](https://codecov.io/gh/flaskbb/flaskbb/branch/master/graph/badge.svg)](https://codecov.io/gh/flaskbb/flaskbb)
 [![License](https://img.shields.io/badge/license-BSD-blue.svg)](https://flaskbb.org)
 [![flaskbb@freenode](https://img.shields.io/badge/irc.freenode.net-%23flaskbb-blue.svg)](https://webchat.freenode.net/?channels=flaskbb)
 
@@ -47,11 +46,11 @@ This is how you set up an development instance of FlaskBB:
 
 ## License
 
-FlaskBB is licensed under the [BSD License](https://github.com/sh4nks/flaskbb/blob/master/LICENSE).
+FlaskBB is licensed under the [BSD License](https://github.com/flaskbb/flaskbb/blob/master/LICENSE).
 
 
 # Links
 
 * [Project Website](https://flaskbb.org)
 * [Documentation](https://flaskbb.readthedocs.io)
-* [Source Code](https://github.com/sh4nks/flaskbb)
+* [Source Code](https://github.com/flaskbb/flaskbb)

+ 1 - 1
docs/installation.rst

@@ -160,7 +160,7 @@ as explained in `Configuration <#configuration>`_::
 
     flaskbb makeconfig -d
 
-or
+or::
 
     flaskbb makeconfig --development
 

+ 1 - 1
flaskbb/app.py

@@ -199,7 +199,7 @@ def configure_app(app, config):
     if all('WarningsPanel' not in p for p in debug_panels):
         debug_panels.append('flask_debugtoolbar_warnings.WarningsPanel')
 
-    app.pluggy = FlaskBBPluginManager("flaskbb", implprefix="flaskbb_")
+    app.pluggy = FlaskBBPluginManager("flaskbb")
 
 
 def configure_celery_app(app, celery):

+ 2 - 3
flaskbb/plugins/manager.py

@@ -16,7 +16,6 @@ from pkg_resources import (DistributionNotFound, VersionConflict,
 
 from flaskbb.utils.helpers import parse_pkg_metadata
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -25,9 +24,9 @@ class FlaskBBPluginManager(pluggy.PluginManager):
     specific stuff.
     """
 
-    def __init__(self, project_name, implprefix=None):
+    def __init__(self, project_name):
         super(FlaskBBPluginManager, self).__init__(
-            project_name=project_name, implprefix=implprefix
+            project_name=project_name
         )
         self._plugin_metadata = {}
         self._disabled_plugins = {}

+ 0 - 1
requirements-cov.txt

@@ -1 +0,0 @@
-coverage>=4.5.1

+ 8 - 4
requirements-dev.txt

@@ -1,11 +1,15 @@
 -rrequirements.txt
+Sphinx
+alabaster
+bumpversion>=0.5.3
 cov-core
 coverage
+flake8
+flake8-bugbear ; python_version>'3.5'
+freezegun
 py
 pytest
 pytest-cov
-Sphinx
-alabaster
-flake8
+pytest-mock
+responses
 tox>=3.0.0
-bumpversion>=0.5.3

+ 0 - 2
requirements-lint.txt

@@ -1,2 +0,0 @@
-flake8>=3.5.0
-flake8-bugbear>=18.2.0

+ 0 - 7
requirements-test.txt

@@ -1,7 +0,0 @@
--rrequirements-cov.txt
-flake8==3.6.0
-freezegun==0.3.11
-mock==2.0.0 ; python_version<'3.3'
-pytest==4.1.1
-pytest-mock==1.10.0
-responses==0.10.5

+ 0 - 4
requirements-travis.txt

@@ -1,4 +0,0 @@
--r requirements-cov.txt
--rrequirements.txt
-tox-travis>=0.10
-coveralls>=1.3.0

+ 2 - 2
requirements.txt

@@ -25,8 +25,8 @@ Flask-SQLAlchemy==2.3.2
 Flask-Themes2==0.1.4
 flask-whooshee==0.6.0
 Flask-WTF==0.14.2
-flaskbb-plugin-conversations==1.0.3
-flaskbb-plugin-portal==1.1.1
+flaskbb-plugin-conversations==1.0.4
+flaskbb-plugin-portal==1.1.2
 idna==2.8
 itsdangerous==1.1.0
 Jinja2==2.10

+ 81 - 70
tests/unit/user/test_controllers.py

@@ -5,26 +5,14 @@ from flask import get_flashed_messages, url_for
 from flask_login import current_user, login_user
 from werkzeug.datastructures import MultiDict
 
-from flaskbb.core.exceptions import PersistenceError, StopValidation
 from flaskbb.core.changesets import ChangeSetHandler
-from flaskbb.core.user.update import (
-    EmailUpdate,
-    PasswordUpdate,
-    SettingsUpdate,
-    UserDetailsChange,
-)
-from flaskbb.user.forms import (
-    ChangeEmailForm,
-    ChangePasswordForm,
-    ChangeUserDetailsForm,
-    GeneralSettingsForm,
-)
-from flaskbb.user.views import (
-    ChangeEmail,
-    ChangePassword,
-    ChangeUserDetails,
-    UserSettings,
-)
+from flaskbb.core.exceptions import PersistenceError, StopValidation
+from flaskbb.core.user.update import (EmailUpdate, PasswordUpdate,
+                                      SettingsUpdate, UserDetailsChange)
+from flaskbb.user.forms import (ChangeEmailForm, ChangePasswordForm,
+                                ChangeUserDetailsForm, GeneralSettingsForm)
+from flaskbb.user.views import (ChangeEmail, ChangePassword, ChangeUserDetails,
+                                UserSettings)
 
 
 @pytest.fixture(scope="function", autouse=True)
@@ -33,16 +21,18 @@ def setup_request(user, default_settings, post_request_context):
 
 
 class TestUserSettingsView(object):
-    def test_renders_get_okay(self, mock):
+    def test_renders_get_okay(self, mocker):
         form = self.produce_form({})
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler = UserSettings(form=form, settings_update_handler=handler)
 
         handler.get()
 
-    def test_update_user_settings_successfully(self, user, mock):
-        form = self.produce_form(data={"language": "python", "theme": "solarized"})
-        handler = mock.Mock(spec=ChangeSetHandler)
+    def test_update_user_settings_successfully(self, user, mocker):
+        form = self.produce_form(
+            data={"language": "python", "theme": "solarized"}
+        )
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = UserSettings(form=form, settings_update_handler=handler)
 
         result = view.post()
@@ -56,9 +46,11 @@ class TestUserSettingsView(object):
             user, SettingsUpdate(language="python", theme="solarized")
         )
 
-    def test_update_user_settings_fail_with_not_valid(self, mock):
-        form = self.produce_form(data={"language": "ruby", "theme": "solarized"})
-        handler = mock.Mock(spec=ChangeSetHandler)
+    def test_update_user_settings_fail_with_not_valid(self, mocker):
+        form = self.produce_form(
+            data={"language": "ruby", "theme": "solarized"}
+        )
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = UserSettings(form=form, settings_update_handler=handler)
 
         view.post()
@@ -68,9 +60,11 @@ class TestUserSettingsView(object):
         handler.apply_changeset.assert_not_called()
         assert form.errors
 
-    def test_update_user_settings_fail_with_stopvalidation_error(self, mock):
-        form = self.produce_form(data={"language": "python", "theme": "molokai"})
-        handler = mock.Mock(spec=ChangeSetHandler)
+    def test_update_user_settings_fail_with_stopvalidation_error(self, mocker):
+        form = self.produce_form(
+            data={"language": "python", "theme": "molokai"}
+        )
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler.apply_changeset.side_effect = StopValidation(
             [("theme", "Solarized is better")]
         )
@@ -82,9 +76,11 @@ class TestUserSettingsView(object):
         assert not (len(flashed))
         assert form.errors["theme"] == ["Solarized is better"]
 
-    def test_update_user_settings_fails_with_persistence_error(self, mock):
-        form = self.produce_form(data={"language": "python", "theme": "molokai"})
-        handler = mock.Mock(spec=ChangeSetHandler)
+    def test_update_user_settings_fails_with_persistence_error(self, mocker):
+        form = self.produce_form(
+            data={"language": "python", "theme": "molokai"}
+        )
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler.apply_changeset.side_effect = PersistenceError("Nope")
         view = UserSettings(form=form, settings_update_handler=handler)
 
@@ -97,27 +93,35 @@ class TestUserSettingsView(object):
         assert result.headers["Location"] == url_for("user.settings")
 
     def produce_form(self, data):
-        form = GeneralSettingsForm(formdata=MultiDict(data), meta={"csrf": False})
-        form.language.choices = [("python", "python"), ("ecmascript", "ecmascript")]
-        form.theme.choices = [("molokai", "molokai"), ("solarized", "solarized")]
+        form = GeneralSettingsForm(
+            formdata=MultiDict(data), meta={"csrf": False}
+        )
+        form.language.choices = [
+            ("python", "python"),
+            ("ecmascript", "ecmascript"),
+        ]
+        form.theme.choices = [
+            ("molokai", "molokai"),
+            ("solarized", "solarized"),
+        ]
         return form
 
 
 class TestChangePasswordView(object):
-    def test_renders_get_okay(self, mock):
+    def test_renders_get_okay(self, mocker):
         form = self.produce_form()
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangePassword(form=form, password_update_handler=handler)
 
         view.get()
 
-    def test_updates_user_password_okay(self, user, mock):
+    def test_updates_user_password_okay(self, user, mocker):
         form = self.produce_form(
             old_password="password",
             new_password="newpassword",
             confirm_new_password="newpassword",
         )
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangePassword(form=form, password_update_handler=handler)
 
         result = view.post()
@@ -128,16 +132,17 @@ class TestChangePasswordView(object):
         assert result.status_code == 302
         assert result.headers["Location"] == url_for("user.change_password")
         handler.apply_changeset.assert_called_once_with(
-            user, PasswordUpdate(old_password="password", new_password="newpassword")
+            user,
+            PasswordUpdate(old_password="password", new_password="newpassword"),
         )
 
-    def test_updates_user_password_fails_with_invalid_inpit(self, mock, user):
+    def test_updates_user_password_fails_with_invalid_inpit(self, mocker, user):
         form = self.produce_form(
             old_password="password",
             new_password="newpassword",
             confirm_new_password="whoops",
         )
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangePassword(form=form, password_update_handler=handler)
 
         view.post()
@@ -145,13 +150,13 @@ class TestChangePasswordView(object):
         handler.apply_changeset.assert_not_called()
         assert "new_password" in form.errors
 
-    def test_update_user_password_fails_with_stopvalidation_error(self, mock):
+    def test_update_user_password_fails_with_stopvalidation_error(self, mocker):
         form = self.produce_form(
             old_password="password",
             new_password="newpassword",
             confirm_new_password="newpassword",
         )
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler.apply_changeset.side_effect = StopValidation(
             [("new_password", "That's not a very strong password")]
         )
@@ -159,15 +164,17 @@ class TestChangePasswordView(object):
 
         view.post()
 
-        assert form.errors["new_password"] == ["That's not a very strong password"]
+        assert form.errors["new_password"] == [
+            "That's not a very strong password"
+        ]
 
-    def test_update_user_password_fails_with_persistence_error(self, mock):
+    def test_update_user_password_fails_with_persistence_error(self, mocker):
         form = self.produce_form(
             old_password="password",
             new_password="newpassword",
             confirm_new_password="newpassword",
         )
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler.apply_changeset.side_effect = PersistenceError("no")
         view = ChangePassword(form=form, password_update_handler=handler)
 
@@ -185,20 +192,20 @@ class TestChangePasswordView(object):
 
 
 class TestChangeEmailView(object):
-    def test_renders_get_okay(self, mock):
+    def test_renders_get_okay(self, mocker):
         form = self.produce_form()
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangeEmail(form=form, update_email_handler=handler)
 
         view.get()
 
-    def test_update_user_email_successfully(self, user, mock):
+    def test_update_user_email_successfully(self, user, mocker):
         form = self.produce_form(
             old_email=user.email,
             new_email="new@email.mail",
             confirm_new_email="new@email.mail",
         )
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangeEmail(form=form, update_email_handler=handler)
 
         result = view.post()
@@ -211,9 +218,9 @@ class TestChangeEmailView(object):
         assert result.status_code == 302
         assert result.headers["Location"] == url_for("user.change_email")
 
-    def test_update_user_email_fails_with_invalid_input(self, user, mock):
+    def test_update_user_email_fails_with_invalid_input(self, user, mocker):
         form = self.produce_form(old_email=user.email, new_email="new@e.mail")
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangeEmail(form=form, update_email_handler=handler)
 
         view.post()
@@ -221,11 +228,13 @@ class TestChangeEmailView(object):
         assert form.errors
         handler.apply_changeset.assert_not_called()
 
-    def test_update_user_email_fails_with_stopvalidation(self, user, mock):
+    def test_update_user_email_fails_with_stopvalidation(self, user, mocker):
         form = self.produce_form(
-            old_email=user.email, new_email="new@e.mail", confirm_new_email="new@e.mail"
+            old_email=user.email,
+            new_email="new@e.mail",
+            confirm_new_email="new@e.mail",
         )
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler.apply_changeset.side_effect = StopValidation(
             [("new_email", "bad email")]
         )
@@ -235,11 +244,13 @@ class TestChangeEmailView(object):
 
         assert form.errors == {"new_email": ["bad email"]}
 
-    def test_update_email_fails_with_persistence_error(self, user, mock):
+    def test_update_email_fails_with_persistence_error(self, user, mocker):
         form = self.produce_form(
-            old_email=user.email, new_email="new@e.mail", confirm_new_email="new@e.mail"
+            old_email=user.email,
+            new_email="new@e.mail",
+            confirm_new_email="new@e.mail",
         )
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler.apply_changeset.side_effect = PersistenceError("nope")
         view = ChangeEmail(form=form, update_email_handler=handler)
 
@@ -257,14 +268,14 @@ class TestChangeEmailView(object):
 
 
 class TestChangeUserDetailsView(object):
-    def test_renders_get_okay(self, mock):
+    def test_renders_get_okay(self, mocker):
         form = self.produce_form()
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangeUserDetails(form=form, details_update_handler=handler)
 
         view.get()
 
-    def test_update_user_details_successfully_updates(self, user, mock):
+    def test_update_user_details_successfully_updates(self, user, mocker):
         form = self.produce_form(
             birthday="25 04 2000",
             gender="awesome",
@@ -274,7 +285,7 @@ class TestChangeUserDetailsView(object):
             signature="use a cursive font",
             notes="got 'em",
         )
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangeUserDetails(form=form, details_update_handler=handler)
 
         result = view.post()
@@ -296,18 +307,18 @@ class TestChangeUserDetailsView(object):
             ),
         )
 
-    def test_update_user_fails_with_invalid_input(self, mock):
+    def test_update_user_fails_with_invalid_input(self, mocker):
         form = self.produce_form(birthday="99 99 999999")
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         view = ChangeUserDetails(form=form, details_update_handler=handler)
 
         view.post()
 
         assert form.errors == {"birthday": ["Not a valid date value"]}
 
-    def test_update_user_fails_with_stopvalidation(self, mock):
+    def test_update_user_fails_with_stopvalidation(self, mocker):
         form = self.produce_form(birthday="25 04 2000")
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler.apply_changeset.side_effect = StopValidation(
             [("birthday", "I just want you to know that's a great birthday")]
         )
@@ -319,9 +330,9 @@ class TestChangeUserDetailsView(object):
             "birthday": ["I just want you to know that's a great birthday"]
         }
 
-    def test_update_user_fails_with_persistence_error(self, mock):
+    def test_update_user_fails_with_persistence_error(self, mocker):
         form = self.produce_form(birthday="25 04 2000")
-        handler = mock.Mock(spec=ChangeSetHandler)
+        handler = mocker.Mock(spec=ChangeSetHandler)
         handler.apply_changeset.side_effect = PersistenceError("no")
         view = ChangeUserDetails(form=form, details_update_handler=handler)
 

+ 15 - 10
tests/unit/user/test_update_details_handler.py

@@ -3,8 +3,9 @@ from uuid import uuid4
 import pytest
 from pluggy import HookimplMarker
 
-from flaskbb.core.exceptions import PersistenceError, StopValidation, ValidationError
-from flaskbb.core.changesets import ChangeSetValidator, ChangeSetPostProcessor
+from flaskbb.core.changesets import ChangeSetPostProcessor, ChangeSetValidator
+from flaskbb.core.exceptions import (PersistenceError, StopValidation,
+                                     ValidationError)
 from flaskbb.core.user.update import UserDetailsChange
 from flaskbb.user.models import User
 from flaskbb.user.services.update import DefaultDetailsUpdateHandler
@@ -12,15 +13,15 @@ from flaskbb.user.services.update import DefaultDetailsUpdateHandler
 
 class TestDefaultDetailsUpdateHandler(object):
     def test_raises_stop_validation_if_errors_occur(
-        self, mock, user, database, plugin_manager
+        self, mocker, user, database, plugin_manager
     ):
-        validator = mock.Mock(spec=ChangeSetValidator)
+        validator = mocker.Mock(spec=ChangeSetValidator)
         validator.validate.side_effect = ValidationError(
             "location", "Dont be from there"
         )
 
         details = UserDetailsChange()
-        hook_impl = mock.Mock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.Mock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultDetailsUpdateHandler(
             validators=[validator], db=database, plugin_manager=plugin_manager
@@ -32,12 +33,14 @@ class TestDefaultDetailsUpdateHandler(object):
         assert excinfo.value.reasons == [("location", "Dont be from there")]
         hook_impl.post_process_changeset.assert_not_called()
 
-    def test_raises_persistence_error_if_save_fails(self, mock, user, plugin_manager):
+    def test_raises_persistence_error_if_save_fails(
+        self, mocker, user, plugin_manager
+    ):
         details = UserDetailsChange()
-        db = mock.Mock()
+        db = mocker.Mock()
         db.session.commit.side_effect = Exception("no")
 
-        hook_impl = mock.Mock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.Mock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultDetailsUpdateHandler(
             validators=[], db=db, plugin_manager=plugin_manager
@@ -49,10 +52,12 @@ class TestDefaultDetailsUpdateHandler(object):
         assert "Could not update details" in str(excinfo.value)
         hook_impl.post_process_changeset.assert_not_called()
 
-    def test_actually_updates_users_details(self, user, database, plugin_manager, mock):
+    def test_actually_updates_users_details(
+        self, user, database, plugin_manager, mocker
+    ):
         location = str(uuid4())
         details = UserDetailsChange(location=location)
-        hook_impl = mock.Mock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.Mock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultDetailsUpdateHandler(
             db=database, plugin_manager=plugin_manager

+ 14 - 9
tests/unit/user/test_update_email_handler.py

@@ -3,8 +3,9 @@ from uuid import uuid4
 import pytest
 from pluggy import HookimplMarker
 
-from flaskbb.core.exceptions import PersistenceError, StopValidation, ValidationError
 from flaskbb.core.changesets import ChangeSetPostProcessor, ChangeSetValidator
+from flaskbb.core.exceptions import (PersistenceError, StopValidation,
+                                     ValidationError)
 from flaskbb.core.user.update import EmailUpdate
 from flaskbb.user.models import User
 from flaskbb.user.services.update import DefaultEmailUpdateHandler
@@ -16,13 +17,13 @@ def random_email():
 
 class TestDefaultEmailUpdateHandler(object):
     def test_raises_stop_validation_if_errors_occur(
-        self, mock, user, database, plugin_manager
+        self, mocker, user, database, plugin_manager
     ):
-        validator = mock.Mock(spec=ChangeSetValidator)
+        validator = mocker.Mock(spec=ChangeSetValidator)
         validator.validate.side_effect = ValidationError(
             "new_email", "That's not even valid"
         )
-        hook_impl = mock.Mock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.Mock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         email_change = EmailUpdate(user.email, random_email())
         handler = DefaultEmailUpdateHandler(
@@ -35,11 +36,13 @@ class TestDefaultEmailUpdateHandler(object):
         assert excinfo.value.reasons == [("new_email", "That's not even valid")]
         hook_impl.post_process_changeset.assert_not_called()
 
-    def test_raises_persistence_error_if_save_fails(self, mock, user, plugin_manager):
+    def test_raises_persistence_error_if_save_fails(
+        self, mocker, user, plugin_manager
+    ):
         email_change = EmailUpdate(user.email, random_email())
-        db = mock.Mock()
+        db = mocker.Mock()
         db.session.commit.side_effect = Exception("no")
-        hook_impl = mock.Mock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.Mock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultEmailUpdateHandler(
             db=db, validators=[], plugin_manager=plugin_manager
@@ -51,10 +54,12 @@ class TestDefaultEmailUpdateHandler(object):
         assert "Could not update email" in str(excinfo.value)
         hook_impl.post_process_changeset.assert_not_called()
 
-    def test_actually_updates_email(self, user, database, mock, plugin_manager):
+    def test_actually_updates_email(
+        self, user, database, mocker, plugin_manager
+    ):
         new_email = random_email()
         email_change = EmailUpdate("test", new_email)
-        hook_impl = mock.Mock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.Mock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultEmailUpdateHandler(
             db=database, validators=[], plugin_manager=plugin_manager

+ 19 - 14
tests/unit/user/test_update_password_handler.py

@@ -3,25 +3,24 @@ from uuid import uuid4
 import pytest
 from pluggy import HookimplMarker
 
-from flaskbb.core.changesets import ChangeSetValidator, ChangeSetPostProcessor
-from flaskbb.core.exceptions import PersistenceError, StopValidation, ValidationError
-from flaskbb.core.user.update import (
-    PasswordUpdate,
-)
+from flaskbb.core.changesets import ChangeSetPostProcessor, ChangeSetValidator
+from flaskbb.core.exceptions import (PersistenceError, StopValidation,
+                                     ValidationError)
+from flaskbb.core.user.update import PasswordUpdate
 from flaskbb.user.models import User
 from flaskbb.user.services.update import DefaultPasswordUpdateHandler
 
 
 class TestDefaultPasswordUpdateHandler(object):
     def test_raises_stop_validation_if_errors_occur(
-        self, mock, user, database, plugin_manager
+        self, mocker, user, database, plugin_manager
     ):
-        validator = mock.Mock(spec=ChangeSetValidator)
+        validator = mocker.Mock(spec=ChangeSetValidator)
         validator.validate.side_effect = ValidationError(
             "new_password", "Don't use that password"
         )
         password_change = PasswordUpdate(str(uuid4()), str(uuid4()))
-        hook_impl = mock.MagicMock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.MagicMock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultPasswordUpdateHandler(
             db=database, plugin_manager=plugin_manager, validators=[validator]
@@ -29,14 +28,18 @@ class TestDefaultPasswordUpdateHandler(object):
 
         with pytest.raises(StopValidation) as excinfo:
             handler.apply_changeset(user, password_change)
-        assert excinfo.value.reasons == [("new_password", "Don't use that password")]
+        assert excinfo.value.reasons == [
+            ("new_password", "Don't use that password")
+        ]
         hook_impl.post_process_changeset.assert_not_called()
 
-    def test_raises_persistence_error_if_save_fails(self, mock, user, plugin_manager):
+    def test_raises_persistence_error_if_save_fails(
+        self, mocker, user, plugin_manager
+    ):
         password_change = PasswordUpdate(str(uuid4()), str(uuid4()))
-        db = mock.Mock()
+        db = mocker.Mock()
         db.session.commit.side_effect = Exception("no")
-        hook_impl = mock.MagicMock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.MagicMock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultPasswordUpdateHandler(
             db=db, plugin_manager=plugin_manager, validators=[]
@@ -48,10 +51,12 @@ class TestDefaultPasswordUpdateHandler(object):
         assert "Could not update password" in str(excinfo.value)
         hook_impl.post_process_changeset.assert_not_called()
 
-    def test_actually_updates_password(self, user, database, plugin_manager, mock):
+    def test_actually_updates_password(
+        self, user, database, plugin_manager, mocker
+    ):
         new_password = str(uuid4())
         password_change = PasswordUpdate("test", new_password)
-        hook_impl = mock.MagicMock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.MagicMock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultPasswordUpdateHandler(
             db=database, plugin_manager=plugin_manager, validators=[]

+ 13 - 6
tests/unit/user/test_update_settings.py

@@ -9,13 +9,17 @@ from flaskbb.user.services.update import DefaultSettingsUpdateHandler
 
 
 class TestDefaultSettingsUpdateHandler(object):
-    def test_raises_persistence_error_if_save_fails(self, mock, user, plugin_manager):
+    def test_raises_persistence_error_if_save_fails(
+        self, mocker, user, plugin_manager
+    ):
         settings_update = SettingsUpdate(language="python", theme="molokai")
-        db = mock.Mock()
+        db = mocker.Mock()
         db.session.commit.side_effect = Exception("no")
-        hook_impl = mock.Mock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.Mock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
-        handler = DefaultSettingsUpdateHandler(db=db, plugin_manager=plugin_manager)
+        handler = DefaultSettingsUpdateHandler(
+            db=db, plugin_manager=plugin_manager
+        )
 
         with pytest.raises(PersistenceError) as excinfo:
             handler.apply_changeset(user, settings_update)
@@ -23,9 +27,11 @@ class TestDefaultSettingsUpdateHandler(object):
         assert "Could not update settings" in str(excinfo.value)
         hook_impl.post_process_changeset.assert_not_called()
 
-    def test_actually_updates_password(self, user, database, mock, plugin_manager):
+    def test_actually_updates_password(
+        self, user, database, mocker, plugin_manager
+    ):
         settings_update = SettingsUpdate(language="python", theme="molokai")
-        hook_impl = mock.Mock(spec=ChangeSetPostProcessor)
+        hook_impl = mocker.Mock(spec=ChangeSetPostProcessor)
         plugin_manager.register(self.impl(hook_impl))
         handler = DefaultSettingsUpdateHandler(
             db=database, plugin_manager=plugin_manager
@@ -48,4 +54,5 @@ class TestDefaultSettingsUpdateHandler(object):
                 post_processor.post_process_changeset(
                     user=user, settings_update=settings_update
                 )
+
         return Impl()

+ 17 - 6
tests/unit/user/test_update_validator.py

@@ -4,7 +4,8 @@ import pytest
 from requests.exceptions import RequestException
 
 from flaskbb.core.exceptions import StopValidation, ValidationError
-from flaskbb.core.user.update import EmailUpdate, PasswordUpdate, UserDetailsChange
+from flaskbb.core.user.update import (EmailUpdate, PasswordUpdate,
+                                      UserDetailsChange)
 from flaskbb.user.models import User
 from flaskbb.user.services import validators
 
@@ -57,12 +58,16 @@ class TestCantShareEmailValidator(object):
 
 class TestOldEmailMustMatchValidator(object):
     def test_raises_if_old_email_doesnt_match(self, Fred):
-        change = EmailUpdate("not@the.same.one.bit", "probably@real.email.provider")
+        change = EmailUpdate(
+            "not@the.same.one.bit", "probably@real.email.provider"
+        )
 
         with pytest.raises(StopValidation) as excinfo:
             validators.OldEmailMustMatch().validate(Fred, change)
 
-        assert [("old_email", "Old email does not match")] == excinfo.value.reasons
+        assert [
+            ("old_email", "Old email does not match")
+        ] == excinfo.value.reasons
 
     def test_doesnt_raise_if_old_email_matches(self, Fred):
         change = EmailUpdate(Fred.email, "probably@real.email.provider")
@@ -77,7 +82,9 @@ class TestOldPasswordMustMatchValidator(object):
         with pytest.raises(StopValidation) as excinfo:
             validators.OldPasswordMustMatch().validate(Fred, change)
 
-        assert [("old_password", "Old password is wrong")] == excinfo.value.reasons
+        assert [
+            ("old_password", "Old password is wrong")
+        ] == excinfo.value.reasons
 
     def test_doesnt_raise_if_old_passwords_match(self, Fred):
         change = PasswordUpdate("fred", str(uuid4()))
@@ -100,7 +107,9 @@ class TestValidateAvatarURL(object):
         assert excinfo.value.attribute == "avatar"
         assert excinfo.value.reason == "Could not retrieve avatar"
 
-    def test_raises_if_image_doesnt_pass_checks(self, image_too_tall, Fred, responses):
+    def test_raises_if_image_doesnt_pass_checks(
+        self, image_too_tall, Fred, responses
+    ):
         change = UserDetailsChange(avatar=image_too_tall.url)
         responses.add(image_too_tall)
 
@@ -109,7 +118,9 @@ class TestValidateAvatarURL(object):
 
         assert "too high" in excinfo.value.reason
 
-    def tests_passes_if_image_is_just_right(self, image_just_right, Fred, responses):
+    def tests_passes_if_image_is_just_right(
+        self, image_just_right, Fred, responses
+    ):
         change = UserDetailsChange(avatar=image_just_right.url)
         responses.add(image_just_right)
         validators.ValidateAvatarURL().validate(Fred, change)

+ 26 - 24
tox.ini

@@ -1,53 +1,55 @@
 [tox]
-envlist = flake8,py{27,34,35,36,37},cov-report,cov-store
+skip_missing_interpreters=true
+envlist =
+    py{35,36,37},
+    flake8,
+    coverage_report
 
 [testenv]
 use_develop = true
 deps =
-    -r{toxinidir}/requirements-test.txt
+    -r{toxinidir}/requirements-dev.txt
     # setup.py has unpinned dependencies by default
-    !unpinned: -r{toxinidir}/requirements.txt
+    !unpinned: -r{toxinidir}/requirements-dev.txt
 setenv =
     COVERAGE_FILE = tests/.coverage.{envname}
     PYTHONDONTWRITEBYTECODE=1
 commands =
     coverage run -m pytest {toxinidir}/tests {toxinidir}/flaskbb {posargs}
 
-[testenv:flake8]
-skip_install = true
-deps = -r{toxinidir}/requirements-lint.txt
-basepython=python3.6
-commands =
-    flake8 --version
-    flake8 --config={toxinidir}/tox.ini {toxinidir}/flaskbb {toxinidir}/tests
-
-[testenv:cov-report]
+[testenv:coverage_report]
 skip_install = true
-setenv =
-    COVERAGE_FILE = tests/.coverage
-deps =
-    -r{toxinidir}/requirements-cov.txt
 commands =
     coverage combine tests
     coverage report
 
-[testenv:cov-store]
+[testenv:codecov]
 skip_install = true
-setenv =
-    COVERAGE_FILE = tests/.coverage
-deps =
-    -r{toxinidir}/requirements-cov.txt
+deps = codecov
 commands =
-    coverage html
-
+    coverage combine tests
+    coverage report
+    codecov
 
 [testenv:black]
 skip_install = true
 deps = black
-basepython=python3.6
+basepython=python3.7
 commands = black --check tests/ flaskbb/
 
+[testenv:flake8]
+skip_install = true
+basepython=python3.7
+commands =
+    flake8 --version
+    flake8 --config={toxinidir}/tox.ini {toxinidir}/flaskbb {toxinidir}/tests
+
 [flake8]
+# B = bugbear
+# E = pycodestyle errors
+# F = flake8 pyflakes
+# W = pycodestyle warnings
+# B9 = bugbear opinions
 ignore = E203, E712, E711, W503
 select = C,E,F,W,B,B9
 max-complexity = 10