diff --git a/OpenLearn/app.py b/OpenLearn/app.py index 138bb81..74ffa87 100644 --- a/OpenLearn/app.py +++ b/OpenLearn/app.py @@ -11,12 +11,12 @@ from . import settings -def create_app(): +def create_app(config=settings.ConfigType.Auto): """Create application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/. """ app = Flask(__name__.split(".")[0], instance_relative_config=True) - settings.configure_app(app) + settings.configure_app(app, config) register_extensions(app) register_blueprints(app) @@ -39,6 +39,8 @@ def register_extensions(app): def load_user(user_id): return database.User.query.get(int(user_id)) + extensions.login_manager.login_view = "public.login" + def register_blueprints(app): """Register Flask blueprints.""" diff --git a/OpenLearn/settings.py b/OpenLearn/settings.py index 03bff99..0afd98a 100644 --- a/OpenLearn/settings.py +++ b/OpenLearn/settings.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +import enum import os +from typing import Optional + import OpenLearn config = { @@ -10,7 +13,25 @@ } +class ConfigType(enum.Enum): + Auto = 0 + Development = "development" + Testing = "testing" + Production = "production" + + @property + def config(self): + return { + ConfigType.Development: lambda: DevelopmentConfig, + ConfigType.Testing: lambda: TestingConfig, + ConfigType.Production: lambda: ProductionConfig, + ConfigType.Auto: lambda: ConfigType(os.getenv("FLASK_ENV", "development")).config + }[self]() + + class BaseConfig(object): + CONFIG_FILE = "config.cfg" + DEBUG = False TESTING = False VERSION = OpenLearn.__version__ @@ -25,15 +46,20 @@ class DevelopmentConfig(BaseConfig): class TestingConfig(BaseConfig): + CONFIG_FILE = "testing.cfg" + DEBUG = False TESTING = True + SQLALCHEMY_DATABASE_URI = "sqlite://" + WTF_CSRF_ENABLED = False + BCRYPT_LOG_ROUNDS = 4 + class ProductionConfig(BaseConfig): pass -def configure_app(app): - config_name = os.getenv("FLASK_ENV", "default") - app.config.from_object(config[config_name]) - app.config.from_pyfile('config.cfg', silent=False) +def configure_app(app, config_type: ConfigType): + app.config.from_object(config_type.config) + app.config.from_pyfile(app.config["CONFIG_FILE"], silent=False) diff --git a/OpenLearn/templates/public/index.html b/OpenLearn/templates/public/index.html index 12c1857..8578373 100644 --- a/OpenLearn/templates/public/index.html +++ b/OpenLearn/templates/public/index.html @@ -5,7 +5,7 @@ {% set noscript_can_close = true %} {% block nav_items %} - Sign In + Sign In Join a Room {% endblock %} diff --git a/OpenLearn/templates/public/register.html b/OpenLearn/templates/public/register.html index e6bf7d4..3ee286f 100644 --- a/OpenLearn/templates/public/register.html +++ b/OpenLearn/templates/public/register.html @@ -54,7 +54,7 @@ id="room-code-form"> {{ form.hidden_tag() }}
Register
-
Already have an account? Already have an account? Sign In Here
{{ form.username.label(class_="siimple-form-field-label") }} diff --git a/OpenLearn/templates/student/join.html b/OpenLearn/templates/student/join.html index 318adee..633b409 100644 --- a/OpenLearn/templates/student/join.html +++ b/OpenLearn/templates/student/join.html @@ -139,6 +139,7 @@ } }; xhttp.open("POST", "{{url_for("student.join_room")}}", true); + xhttp.setRequestHeader("X-CSRFToken", "{{ form.csrf_token.current_token }}"); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhttp.send(requestBody); }); diff --git a/OpenLearn/views/public/controller.py b/OpenLearn/views/public/controller.py index 6d43dbf..b35fb01 100644 --- a/OpenLearn/views/public/controller.py +++ b/OpenLearn/views/public/controller.py @@ -28,12 +28,12 @@ def index(): @blueprint.route("/login") -def sign_in(): +def login(): form = LoginForm() if form.validate_on_submit(): login_user(form.user) return redirect(url_for("teacher.dashboard")) - return render_template("sign-in.html", form=form) + return render_template("login.html", form=form) @blueprint.route("/register", methods=["GET", "POST"]) diff --git a/OpenLearn/views/public/forms.py b/OpenLearn/views/public/forms.py index b40343b..c256d15 100644 --- a/OpenLearn/views/public/forms.py +++ b/OpenLearn/views/public/forms.py @@ -35,7 +35,6 @@ def validate(self): return True - class RegisterForm(FlaskForm): username = StringField("Username", validators=[ InputRequired(), diff --git a/OpenLearn/views/teacher/controller.py b/OpenLearn/views/teacher/controller.py index 14f8d3b..967fca6 100644 --- a/OpenLearn/views/teacher/controller.py +++ b/OpenLearn/views/teacher/controller.py @@ -17,4 +17,4 @@ def dashboard(): @blueprint.route("/logout") def logout(): logout_user() - return redirect(url_for("public.sign_in")) + return redirect(url_for("public.login")) diff --git a/poetry.lock b/poetry.lock index 8adf5cb..fc27578 100644 --- a/poetry.lock +++ b/poetry.lock @@ -172,6 +172,14 @@ optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" version = "1.1.1" +[[package]] +category = "dev" +description = "nose extends unittest to make testing easier" +name = "nose" +optional = false +python-versions = "*" +version = "1.3.7" + [[package]] category = "main" description = "psycopg2 - Python-PostgreSQL Database Adapter" @@ -221,7 +229,7 @@ python-versions = "*" version = "2.2.1" [metadata] -content-hash = "acb4bafadee738a11b76cabd6d2fdbe14c619cacfb885d1ea0d3368d33efa1dc" +content-hash = "7e3b61c21f1474487bb9cde4642cfc24d8e35b7e7ed1992ab09ced5fdb3af835" python-versions = "^3.7" [metadata.hashes] @@ -241,6 +249,7 @@ flask-wtf = ["5d14d55cfd35f613d99ee7cba0fc3fbbe63ba02f544d349158c14ca15561cc36", itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"] jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] +nose = ["9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", "dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", "f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"] psycopg2 = ["128d0fa910ada0157bba1cb74a9c5f92bb8a1dca77cf91a31eb274d1f889e001", "227fd46cf9b7255f07687e5bde454d7d67ae39ca77e170097cdef8ebfc30c323", "2315e7f104681d498ccf6fd70b0dba5bce65d60ac92171492bfe228e21dcc242", "4b5417dcd2999db0f5a891d54717cfaee33acc64f4772c4bc574d4ff95ed9d80", "640113ddc943522aaf71294e3f2d24013b0edd659b7820621492c9ebd3a2fb0b", "897a6e838319b4bf648a574afb6cabcb17d0488f8c7195100d48d872419f4457", "8dceca81409898c870e011c71179454962dec152a1a6b86a347f4be74b16d864", "b1b8e41da09a0c3ef0b3d4bb72da0dde2abebe583c1e8462973233fd5ad0235f", "cb407fccc12fc29dc331f2b934913405fa49b9b75af4f3a72d0f50f57ad2ca23", "d3a27550a8185e53b244ad7e79e307594b92fede8617d80200a8cce1fba2c60f", "f0e6b697a975d9d3ccd04135316c947dd82d841067c7800ccf622a8717e98df1"] pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] diff --git a/pyproject.toml b/pyproject.toml index 47feb0c..c9735ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ flask-login = "^0.4.1" [tool.poetry.dev-dependencies] flask-debugtoolbar = "^0.10.1" flask-testing = "^0.7.1" +nose = "^1.3" [build-system] requires = ["poetry>=0.12"] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..3d11fa2 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +import configparser +import pathlib +import unittest + +import OpenLearn + + +class TestApp(unittest.TestCase): + def test_version_number(self): + """Ensure the version is the same everywhere""" + config_path = pathlib.Path(OpenLearn.__file__).parent.parent / "pyproject.toml" + parser = configparser.ConfigParser() + parser.read(config_path) + version_number_config_file = parser["tool.poetry"]["version"].strip('"') + self.assertEqual(version_number_config_file, OpenLearn.__version__) diff --git a/tests/test_utilities.py b/tests/test_utilities.py new file mode 100644 index 0000000..5035c4c --- /dev/null +++ b/tests/test_utilities.py @@ -0,0 +1,19 @@ +import os +import unittest +from unittest import mock + +from OpenLearn import settings + + +class TestSettingsUtilities(unittest.TestCase): + @mock.patch.dict(os.environ, {'FLASK_ENV': 'development'}) + def test_config_type_auto_resolution_development(self): + self.assertIs(settings.ConfigType.Auto.config, settings.DevelopmentConfig) + + @mock.patch.dict(os.environ, {'FLASK_ENV': 'production'}) + def test_config_type_auto_resolution_production(self): + self.assertIs(settings.ConfigType.Auto.config, settings.ProductionConfig) + + @mock.patch.dict(os.environ, {'FLASK_ENV': 'testing'}) + def test_config_type_auto_resolution_testing(self): + self.assertIs(settings.ConfigType.Auto.config, settings.TestingConfig) diff --git a/tests/test_views.py b/tests/test_views.py new file mode 100644 index 0000000..e5a463b --- /dev/null +++ b/tests/test_views.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +import unittest + +import flask_login +from flask import url_for +from flask_testing import TestCase + +from OpenLearn import settings, database +from OpenLearn.app import create_app +from OpenLearn.extensions import db + + +class BaseTestCase(TestCase): + def create_app(self): + app = create_app(config=settings.ConfigType.Testing) + app.testing = True + return app + + def setUp(self): + db.create_all() + self.user = database.User(username="testUser", password="password@123") + + def tearDown(self): + db.session.remove() + db.drop_all() + + +class TestPublicView(BaseTestCase): + def test_index_page(self): + response = self.client.get(url_for('public.index')) + self.assert200(response) + self.assertTrue(flask_login.current_user.is_anonymous) + self.assertTemplateUsed('index.html') + + def test_register_page(self): + response = self.client.get(url_for('public.register')) + self.assert200(response) + self.assertTrue(flask_login.current_user.is_anonymous) + self.assertTemplateUsed('register.html') + + def test_login_page(self): + response = self.client.get(url_for('public.login')) + self.assert200(response) + self.assertTrue(flask_login.current_user.is_anonymous) + self.assertTemplateUsed('login.html')