Commit e73a1a7048dfa721ce0343ca2a89d8c305d3b8d2
Exists in
master
and in
4 other branches
More tests and refactoring
Showing
13 changed files
with
93 additions
and
47 deletions
Show diff stats
CHANGELOG.md
... | ... | @@ -23,6 +23,9 @@ Should be noted only functionnal changes, |
23 | 23 | or major refactoring improvments. |
24 | 24 | |
25 | 25 | ## Unreleased |
26 | +## [0.3.pre-7] - 2021-05-03 - Better Tests Coverage | |
27 | +### Changed | |
28 | +Add tests and fix some code issues | |
26 | 29 | |
27 | 30 | ## [0.3.pre-6] - 2021-04-30 - Nicer Display |
28 | 31 | ### Changed | ... | ... |
VERSION.txt
app/auth/models.py
... | ... | @@ -30,7 +30,7 @@ _nameToRole = { |
30 | 30 | } |
31 | 31 | |
32 | 32 | |
33 | -def _checkRole(role): | |
33 | +def _check_role(role): | |
34 | 34 | if isinstance(role, int): |
35 | 35 | rv = role |
36 | 36 | elif str(role) == role: |
... | ... | @@ -65,14 +65,14 @@ class User(UserMixin, db.Model): |
65 | 65 | self.password = None |
66 | 66 | |
67 | 67 | def set_role(self, role): |
68 | - self.role = _checkRole(role) | |
68 | + self.role = _check_role(role) | |
69 | 69 | |
70 | 70 | def has_role(self, role): |
71 | - role = _checkRole(role) | |
71 | + role = _check_role(role) | |
72 | 72 | return self.role == role |
73 | 73 | |
74 | 74 | def has_role_or_higher(self, role): |
75 | - role = _checkRole(role) | |
75 | + role = _check_role(role) | |
76 | 76 | return self.role and (self.role >= role) |
77 | 77 | |
78 | 78 | def set_password(self, password): | ... | ... |
app/auth/routes.py
... | ... | @@ -7,6 +7,8 @@ from flask_login import login_user, logout_user |
7 | 7 | from app.auth.models import User |
8 | 8 | from app.auth import bp |
9 | 9 | |
10 | +main_index = 'main.index' | |
11 | + | |
10 | 12 | |
11 | 13 | # |
12 | 14 | # Decorator used to protect routes by role |
... | ... | @@ -30,10 +32,10 @@ def role_required(role): |
30 | 32 | try: |
31 | 33 | is_authorised = current_user.has_role_or_higher(role) |
32 | 34 | except ValueError: |
33 | - raise Exception("Unknowk role provided %s" % role) | |
35 | + raise ValueError("Unknown role provided %s" % role) | |
34 | 36 | if not is_authorised: |
35 | 37 | flash("Vous n'avez pas les autorisations pour accรฉder ร cette page", 'dark') |
36 | - return redirect(url_for('main.index')) | |
38 | + return redirect(url_for(main_index)) | |
37 | 39 | return f(*args, **kwargs) |
38 | 40 | |
39 | 41 | return decorated_function |
... | ... | @@ -50,12 +52,11 @@ def login(): |
50 | 52 | def login_post(): |
51 | 53 | user_login = request.form.get('login') |
52 | 54 | user_password = request.form.get('password') |
53 | - # user_remember = request.form.get('remember') | |
54 | 55 | user = User.query.filter_by(login=user_login).one_or_none() |
55 | 56 | if user and user.check_password(user_password): |
56 | 57 | login_user(user) |
57 | 58 | flash("Connection Rรฉussie !", 'success') |
58 | - return redirect(url_for('main.index')) | |
59 | + return redirect(url_for(main_index)) | |
59 | 60 | else: |
60 | 61 | flash("Mauvais login ou mot de passe.", 'warning') |
61 | 62 | return redirect(url_for('auth.login')) |
... | ... | @@ -65,4 +66,4 @@ def login_post(): |
65 | 66 | def logout(): |
66 | 67 | logout_user() |
67 | 68 | flash("Vous รชtes maintenant dรฉconnectรฉ", 'info') |
68 | - return redirect(url_for('main.index')) | |
69 | + return redirect(url_for(main_index)) | ... | ... |
app/commands/commands.py
... | ... | @@ -38,7 +38,7 @@ def create_db(): |
38 | 38 | sys.exit(-1) |
39 | 39 | |
40 | 40 | if sqlite_uri: |
41 | - current_app.logger.info("Created sqlite db: " + sqlite_uri) | |
41 | + print("Created sqlite db: " + sqlite_uri) | |
42 | 42 | |
43 | 43 | |
44 | 44 | @bp.cli.command("feed_from_irap") |
... | ... | @@ -433,7 +433,7 @@ def feed_periods(begin_year, end_year): |
433 | 433 | @click.option('--agent', '-a', 'agent', default=None, help="the agent id you want to charge") |
434 | 434 | def feed_random_charges(agent): |
435 | 435 | """ Randomly fill in the agents charges. """ |
436 | - for i in range(0, 100): | |
436 | + for _ in range(0, 100): | |
437 | 437 | if agent is None: |
438 | 438 | agent_id = random.choice([i for (i,) in db.session.query(Agent.id).all()]) |
439 | 439 | else: | ... | ... |
app/commands/lesia_db.py
... | ... | @@ -13,7 +13,8 @@ engine = create_engine(current_app.config['LESIA_AGENTS_DB_URI']) |
13 | 13 | # reflect the tables |
14 | 14 | try: |
15 | 15 | lesia_base.prepare(engine, reflect=True) |
16 | -except OperationalError: | |
16 | +except OperationalError as oe: | |
17 | + current_app.logger.error(oe) | |
17 | 18 | current_app.logger.error("Please, configure the mysql database (see db_config.py)") |
18 | 19 | sys.exit(-1) |
19 | 20 | ... | ... |
app/errors/handlers.py
... | ... | @@ -2,30 +2,32 @@ from flask import render_template |
2 | 2 | from app import db |
3 | 3 | from . import bp |
4 | 4 | |
5 | - | |
6 | 5 | # Inspired by: |
7 | 6 | # https://flask.palletsprojects.com/en/master/patterns/errorpages/ |
8 | 7 | |
8 | +error_page = 'error.html' | |
9 | + | |
10 | + | |
9 | 11 | @bp.app_errorhandler(403) |
10 | 12 | def forbidden_error(error): |
11 | 13 | error_title = "Page Interdite" |
12 | - return render_template('error.html', error_title=error_title, error_msg=error), 403 | |
14 | + return render_template(error_page, error_title=error_title, error_msg=error), 403 | |
13 | 15 | |
14 | 16 | |
15 | 17 | @bp.app_errorhandler(404) |
16 | 18 | def not_found_error(error): |
17 | 19 | error_title = "Page Introuvable." |
18 | - return render_template('error.html', error_title=error_title, error_msg=error), 404 | |
20 | + return render_template(error_page, error_title=error_title, error_msg=error), 404 | |
19 | 21 | |
20 | 22 | |
21 | 23 | @bp.app_errorhandler(405) |
22 | 24 | def method_error(error): |
23 | 25 | error_title = "Erreur de Mรฉthode." |
24 | - return render_template('error.html', error_title=error_title, error_msg=error), 405 | |
26 | + return render_template(error_page, error_title=error_title, error_msg=error), 405 | |
25 | 27 | |
26 | 28 | |
27 | 29 | @bp.app_errorhandler(500) |
28 | 30 | def internal_error(error): |
29 | 31 | db.session.rollback() |
30 | 32 | error_title = "Erreur Interne. Administrateur Prรฉvenu." |
31 | - return render_template('error.html', error_title=error_title, error_msg=error), 500 | |
33 | + return render_template(error_page, error_title=error_title, error_msg=error), 500 | ... | ... |
resources/db_config.py
... | ... | @@ -49,7 +49,7 @@ MYSQL_LESIA = { |
49 | 49 | 'host': '127.0.0.1', |
50 | 50 | 'port': '3306', |
51 | 51 | } |
52 | -mysql_lesia_uri = 'mysql+pymysql://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s' % MYSQL | |
52 | +mysql_lesia_uri = 'mysql+pymysql://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s' % MYSQL_LESIA | |
53 | 53 | |
54 | 54 | # To set you databases uri, |
55 | 55 | # uncomment the needed lines to fit your specific needs; | ... | ... |
resources/lesia-btp.sqlite
No preview for this file type
tests/backend_tests.py
... | ... | @@ -6,20 +6,14 @@ from shutil import copyfile |
6 | 6 | from pdc_config import TestConfig |
7 | 7 | from app import create_app, db_mgr, db |
8 | 8 | from app.auth.models import User |
9 | +from tests.common_db_feed import resources_to_instancedb | |
9 | 10 | |
10 | 11 | |
11 | 12 | class BaseTestCase(unittest.TestCase): |
12 | 13 | def setUp(self): |
13 | 14 | # initialise app |
14 | 15 | self.app = create_app(TestConfig) |
15 | - | |
16 | - # copy resource demo db to test file | |
17 | - appdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) | |
18 | - sqlite_file_name = os.path.abspath(os.path.join(appdir, 'resources', 'lesia-btp.sqlite')) | |
19 | - if not os.path.isdir(self.app.instance_path): | |
20 | - os.mkdir(self.app.instance_path) | |
21 | - self.db_path = os.path.join(self.app.instance_path, 'test.db') | |
22 | - copyfile(sqlite_file_name, self.db_path) | |
16 | + self.db_path = resources_to_instancedb(self.app) | |
23 | 17 | |
24 | 18 | # force db path to newly create file |
25 | 19 | self.app.config.update( |
... | ... | @@ -59,4 +53,3 @@ class DbMgrTestCase(BaseTestCase): |
59 | 53 | stacked_charges = db_mgr.charges_by_project_stacked(60) |
60 | 54 | # Waiting for 17 periods + headers line |
61 | 55 | self.assertEqual(18, len(stacked_charges)) |
62 | - | ... | ... |
tests/cli_tests.py
1 | 1 | import unittest |
2 | 2 | |
3 | 3 | from app import create_app, db, User |
4 | -from app.commands.commands import feed_from_irap, show_roles, user_add, user_show_all | |
4 | +from app.commands.commands import feed_from_irap, show_roles, user_add, user_show_all, create_db | |
5 | 5 | from pdc_config import TestConfig |
6 | 6 | |
7 | 7 | from app.commands import bp as cli_bp |
... | ... | @@ -10,7 +10,8 @@ from app.commands import bp as cli_bp |
10 | 10 | class CliBaseTestCase(unittest.TestCase): |
11 | 11 | |
12 | 12 | def setUp(self): |
13 | - # Get rid of a strange 'ValueError: I/O operation' when app.logger outputs | |
13 | + # Get rid of a strange 'ValueError: I/O operation' when pytest catches app.logger outputs | |
14 | + # Error can also be bypassed with 'pytest -s' option | |
14 | 15 | TestConfig.PDC_LOGS_LEVEL = 'ERROR' |
15 | 16 | # Force sqlite in memory db |
16 | 17 | TestConfig.SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' |
... | ... | @@ -24,16 +25,20 @@ class CliBaseTestCase(unittest.TestCase): |
24 | 25 | def tearDown(self): |
25 | 26 | pass |
26 | 27 | |
27 | - def test_hello3(self): | |
28 | + def test_create_db(self): | |
29 | + runner = self.app.test_cli_runner() | |
30 | + result = runner.invoke(create_db) | |
31 | + self.assertTrue('Created sqlite' in result.output) | |
32 | + | |
33 | + def test_show_all(self): | |
28 | 34 | runner = self.app.test_cli_runner() |
29 | 35 | result = runner.invoke(user_show_all) |
30 | - print("\n") | |
31 | - print(result.output) | |
36 | + self.assertTrue('id' in result.output) | |
32 | 37 | |
33 | - def test_hello2(self): | |
38 | + def test_show_roles(self): | |
34 | 39 | runner = self.app.test_cli_runner() |
35 | 40 | result = runner.invoke(show_roles) |
36 | - print(result.output) | |
41 | + self.assertTrue('PUBLIC' in result.output) | |
37 | 42 | |
38 | 43 | def test_add_user(self): |
39 | 44 | runner = self.app.test_cli_runner() |
... | ... | @@ -41,9 +46,7 @@ class CliBaseTestCase(unittest.TestCase): |
41 | 46 | all_users = User.query.all() |
42 | 47 | self.assertEqual(0, len(all_users)) |
43 | 48 | # invoke the command directly |
44 | - arguments = ['geo@ici.fr', 'Joseph Hitier', 'joh', 'passwd', 'PUBLIC'] | |
45 | - result = runner.invoke(user_add, arguments) | |
46 | - print(result.output) | |
49 | + arguments = ['geo@ici.fr', 'Gรฉo Trouvetou', 'gt', 'passwd', 'PUBLIC'] | |
50 | + runner.invoke(user_add, arguments) | |
47 | 51 | all_users = User.query.all() |
48 | - print(len(all_users)) | |
49 | - # self.assertEqual(1, len(all_users)) | |
52 | + self.assertEqual(1, len(all_users)) | ... | ... |
tests/common_db_feed.py
1 | +import os | |
2 | +from shutil import copyfile | |
3 | + | |
1 | 4 | from app.models import Category, db, Label, Project, ProjectLabel |
2 | 5 | |
6 | + | |
7 | +def resources_to_instancedb(app): | |
8 | + # copy resource demo db to test file | |
9 | + appdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) | |
10 | + sqlite_file_name = os.path.abspath(os.path.join(appdir, 'resources', 'lesia-btp.sqlite')) | |
11 | + if not os.path.isdir(app.instance_path): | |
12 | + os.mkdir(app.instance_path) | |
13 | + db_path = os.path.join(app.instance_path, 'test.db') | |
14 | + copyfile(sqlite_file_name, db_path) | |
15 | + return db_path | |
16 | + | |
17 | + | |
3 | 18 | categorized_labels = {'pole': ['Spatial', 'Sol'], 'domaine': ['soleil-terre', 'atmosphere', 'r&t', 'gรฉologie']} |
4 | 19 | projects_categories = {'ChemCam': {'pole': 'Spatial', 'domaine': 'gรฉologie'}, |
5 | 20 | 'Pilot': {'pole': 'Spatial', 'domaine': 'atmosphere'}, |
... | ... | @@ -30,7 +45,7 @@ def feed_projects(): |
30 | 45 | n_l = db.session.query(Label).filter(Label.name == l_name).one() |
31 | 46 | n_pc = ProjectLabel(project=n_p, category=n_c, label=n_l) |
32 | 47 | db.session.add(n_pc) |
33 | - | |
48 | + | |
34 | 49 | db.session.commit() |
35 | 50 | |
36 | 51 | ... | ... |
tests/frontend_tests.py
1 | 1 | import os |
2 | 2 | import urllib.request |
3 | 3 | |
4 | +from flask import url_for | |
4 | 5 | from flask_testing import LiveServerTestCase |
5 | 6 | from selenium import webdriver |
6 | 7 | |
7 | 8 | from app import create_app |
8 | 9 | from pdc_config import TestConfig |
10 | +from tests.common_db_feed import resources_to_instancedb | |
9 | 11 | |
10 | 12 | |
11 | 13 | class BaseFrontTestCase(LiveServerTestCase): |
... | ... | @@ -18,18 +20,18 @@ class BaseFrontTestCase(LiveServerTestCase): |
18 | 20 | os.environ['WERKZEUG_RUN_MAIN'] = 'true' |
19 | 21 | |
20 | 22 | # pass in test configurations |
21 | - app = create_app(TestConfig) | |
22 | - self.db_path = os.path.join(app.instance_path, 'test-app.db') | |
23 | - app.config.update( | |
23 | + self.app = create_app(TestConfig) | |
24 | + self.db_path = resources_to_instancedb(self.app) | |
25 | + self.app.config.update( | |
24 | 26 | # Specify the test database as the default in_memory wont work for web tests |
25 | 27 | SQLALCHEMY_DATABASE_URI='sqlite:///' + self.db_path, |
26 | 28 | # Change the port that the liveserver listens on as we dont want to conflict with running:5000 |
27 | 29 | LIVESERVER_PORT=8943 |
28 | 30 | ) |
29 | 31 | |
30 | - self.app_context = app.app_context() | |
32 | + self.app_context = self.app.app_context() | |
31 | 33 | self.app_context.push() |
32 | - return app | |
34 | + return self.app | |
33 | 35 | |
34 | 36 | def setUp(self): |
35 | 37 | self.driver = self.create_chrome_driver() |
... | ... | @@ -86,4 +88,30 @@ class AccessTestCase(BaseFrontTestCase): |
86 | 88 | |
87 | 89 | def test_server_is_up_and_running(self): |
88 | 90 | response = urllib.request.urlopen(self.get_server_url()) |
89 | - self.assertEqual(response.code, 200) | |
90 | 91 | \ No newline at end of file |
92 | + self.assertEqual(response.code, 200) | |
93 | + | |
94 | + def test_agents_page(self): | |
95 | + target_url = self.get_server_url() + url_for('main.agents') | |
96 | + self.driver.get(target_url) | |
97 | + self.assertEqual(self.driver.current_url, 'http://localhost:8943/agents') | |
98 | + | |
99 | + def test_agent_page(self): | |
100 | + agent_id = 60 | |
101 | + target_url = self.get_server_url() + url_for('main.agent', agent_id=agent_id) | |
102 | + self.driver.get(target_url) | |
103 | + self.assertEqual(self.driver.current_url, f'http://localhost:8943/agent/{agent_id}') | |
104 | + td_elmts = self.driver.find_elements_by_xpath("//table[@id='charge_table']/tbody/tr/td") | |
105 | + self.assertEqual(260, len(td_elmts)) | |
106 | + | |
107 | + def test_projects_page(self): | |
108 | + target_url = self.get_server_url() + url_for('main.projects') | |
109 | + self.driver.get(target_url) | |
110 | + self.assertEqual(self.driver.current_url, 'http://localhost:8943/projects') | |
111 | + | |
112 | + def test_project_page(self): | |
113 | + project_id = 8 | |
114 | + target_url = self.get_server_url() + url_for('main.project', project_id=project_id) | |
115 | + self.driver.get(target_url) | |
116 | + self.assertEqual(self.driver.current_url, f'http://localhost:8943/project/{project_id}') | |
117 | + td_elmts = self.driver.find_elements_by_xpath("//table[@id='charge_table']/tbody/tr/td") | |
118 | + self.assertEqual(1085, len(td_elmts)) | ... | ... |