Commit e73a1a7048dfa721ce0343ca2a89d8c305d3b8d2

Authored by hitier
2 parents 3ba117bb 7c7dae6d

More tests and refactoring

@@ -23,6 +23,9 @@ Should be noted only functionnal changes, @@ -23,6 +23,9 @@ Should be noted only functionnal changes,
23 or major refactoring improvments. 23 or major refactoring improvments.
24 24
25 ## Unreleased 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 ## [0.3.pre-6] - 2021-04-30 - Nicer Display 30 ## [0.3.pre-6] - 2021-04-30 - Nicer Display
28 ### Changed 31 ### Changed
1 -0.3.pre-6 1 +0.3.pre-7
app/auth/models.py
@@ -30,7 +30,7 @@ _nameToRole = { @@ -30,7 +30,7 @@ _nameToRole = {
30 } 30 }
31 31
32 32
33 -def _checkRole(role): 33 +def _check_role(role):
34 if isinstance(role, int): 34 if isinstance(role, int):
35 rv = role 35 rv = role
36 elif str(role) == role: 36 elif str(role) == role:
@@ -65,14 +65,14 @@ class User(UserMixin, db.Model): @@ -65,14 +65,14 @@ class User(UserMixin, db.Model):
65 self.password = None 65 self.password = None
66 66
67 def set_role(self, role): 67 def set_role(self, role):
68 - self.role = _checkRole(role) 68 + self.role = _check_role(role)
69 69
70 def has_role(self, role): 70 def has_role(self, role):
71 - role = _checkRole(role) 71 + role = _check_role(role)
72 return self.role == role 72 return self.role == role
73 73
74 def has_role_or_higher(self, role): 74 def has_role_or_higher(self, role):
75 - role = _checkRole(role) 75 + role = _check_role(role)
76 return self.role and (self.role >= role) 76 return self.role and (self.role >= role)
77 77
78 def set_password(self, password): 78 def set_password(self, password):
app/auth/routes.py
@@ -7,6 +7,8 @@ from flask_login import login_user, logout_user @@ -7,6 +7,8 @@ from flask_login import login_user, logout_user
7 from app.auth.models import User 7 from app.auth.models import User
8 from app.auth import bp 8 from app.auth import bp
9 9
  10 +main_index = 'main.index'
  11 +
10 12
11 # 13 #
12 # Decorator used to protect routes by role 14 # Decorator used to protect routes by role
@@ -30,10 +32,10 @@ def role_required(role): @@ -30,10 +32,10 @@ def role_required(role):
30 try: 32 try:
31 is_authorised = current_user.has_role_or_higher(role) 33 is_authorised = current_user.has_role_or_higher(role)
32 except ValueError: 34 except ValueError:
33 - raise Exception("Unknowk role provided %s" % role) 35 + raise ValueError("Unknown role provided %s" % role)
34 if not is_authorised: 36 if not is_authorised:
35 flash("Vous n'avez pas les autorisations pour accéder à cette page", 'dark') 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 return f(*args, **kwargs) 39 return f(*args, **kwargs)
38 40
39 return decorated_function 41 return decorated_function
@@ -50,12 +52,11 @@ def login(): @@ -50,12 +52,11 @@ def login():
50 def login_post(): 52 def login_post():
51 user_login = request.form.get('login') 53 user_login = request.form.get('login')
52 user_password = request.form.get('password') 54 user_password = request.form.get('password')
53 - # user_remember = request.form.get('remember')  
54 user = User.query.filter_by(login=user_login).one_or_none() 55 user = User.query.filter_by(login=user_login).one_or_none()
55 if user and user.check_password(user_password): 56 if user and user.check_password(user_password):
56 login_user(user) 57 login_user(user)
57 flash("Connection Réussie !", 'success') 58 flash("Connection Réussie !", 'success')
58 - return redirect(url_for('main.index')) 59 + return redirect(url_for(main_index))
59 else: 60 else:
60 flash("Mauvais login ou mot de passe.", 'warning') 61 flash("Mauvais login ou mot de passe.", 'warning')
61 return redirect(url_for('auth.login')) 62 return redirect(url_for('auth.login'))
@@ -65,4 +66,4 @@ def login_post(): @@ -65,4 +66,4 @@ def login_post():
65 def logout(): 66 def logout():
66 logout_user() 67 logout_user()
67 flash("Vous êtes maintenant déconnecté", 'info') 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,7 +38,7 @@ def create_db():
38 sys.exit(-1) 38 sys.exit(-1)
39 39
40 if sqlite_uri: 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 @bp.cli.command("feed_from_irap") 44 @bp.cli.command("feed_from_irap")
@@ -433,7 +433,7 @@ def feed_periods(begin_year, end_year): @@ -433,7 +433,7 @@ def feed_periods(begin_year, end_year):
433 @click.option('--agent', '-a', 'agent', default=None, help="the agent id you want to charge") 433 @click.option('--agent', '-a', 'agent', default=None, help="the agent id you want to charge")
434 def feed_random_charges(agent): 434 def feed_random_charges(agent):
435 """ Randomly fill in the agents charges. """ 435 """ Randomly fill in the agents charges. """
436 - for i in range(0, 100): 436 + for _ in range(0, 100):
437 if agent is None: 437 if agent is None:
438 agent_id = random.choice([i for (i,) in db.session.query(Agent.id).all()]) 438 agent_id = random.choice([i for (i,) in db.session.query(Agent.id).all()])
439 else: 439 else:
app/commands/lesia_db.py
@@ -13,7 +13,8 @@ engine = create_engine(current_app.config['LESIA_AGENTS_DB_URI']) @@ -13,7 +13,8 @@ engine = create_engine(current_app.config['LESIA_AGENTS_DB_URI'])
13 # reflect the tables 13 # reflect the tables
14 try: 14 try:
15 lesia_base.prepare(engine, reflect=True) 15 lesia_base.prepare(engine, reflect=True)
16 -except OperationalError: 16 +except OperationalError as oe:
  17 + current_app.logger.error(oe)
17 current_app.logger.error("Please, configure the mysql database (see db_config.py)") 18 current_app.logger.error("Please, configure the mysql database (see db_config.py)")
18 sys.exit(-1) 19 sys.exit(-1)
19 20
app/errors/handlers.py
@@ -2,30 +2,32 @@ from flask import render_template @@ -2,30 +2,32 @@ from flask import render_template
2 from app import db 2 from app import db
3 from . import bp 3 from . import bp
4 4
5 -  
6 # Inspired by: 5 # Inspired by:
7 # https://flask.palletsprojects.com/en/master/patterns/errorpages/ 6 # https://flask.palletsprojects.com/en/master/patterns/errorpages/
8 7
  8 +error_page = 'error.html'
  9 +
  10 +
9 @bp.app_errorhandler(403) 11 @bp.app_errorhandler(403)
10 def forbidden_error(error): 12 def forbidden_error(error):
11 error_title = "Page Interdite" 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 @bp.app_errorhandler(404) 17 @bp.app_errorhandler(404)
16 def not_found_error(error): 18 def not_found_error(error):
17 error_title = "Page Introuvable." 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 @bp.app_errorhandler(405) 23 @bp.app_errorhandler(405)
22 def method_error(error): 24 def method_error(error):
23 error_title = "Erreur de Méthode." 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 @bp.app_errorhandler(500) 29 @bp.app_errorhandler(500)
28 def internal_error(error): 30 def internal_error(error):
29 db.session.rollback() 31 db.session.rollback()
30 error_title = "Erreur Interne. Administrateur Prévenu." 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,7 +49,7 @@ MYSQL_LESIA = {
49 'host': '127.0.0.1', 49 'host': '127.0.0.1',
50 'port': '3306', 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 # To set you databases uri, 54 # To set you databases uri,
55 # uncomment the needed lines to fit your specific needs; 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,20 +6,14 @@ from shutil import copyfile
6 from pdc_config import TestConfig 6 from pdc_config import TestConfig
7 from app import create_app, db_mgr, db 7 from app import create_app, db_mgr, db
8 from app.auth.models import User 8 from app.auth.models import User
  9 +from tests.common_db_feed import resources_to_instancedb
9 10
10 11
11 class BaseTestCase(unittest.TestCase): 12 class BaseTestCase(unittest.TestCase):
12 def setUp(self): 13 def setUp(self):
13 # initialise app 14 # initialise app
14 self.app = create_app(TestConfig) 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 # force db path to newly create file 18 # force db path to newly create file
25 self.app.config.update( 19 self.app.config.update(
@@ -59,4 +53,3 @@ class DbMgrTestCase(BaseTestCase): @@ -59,4 +53,3 @@ class DbMgrTestCase(BaseTestCase):
59 stacked_charges = db_mgr.charges_by_project_stacked(60) 53 stacked_charges = db_mgr.charges_by_project_stacked(60)
60 # Waiting for 17 periods + headers line 54 # Waiting for 17 periods + headers line
61 self.assertEqual(18, len(stacked_charges)) 55 self.assertEqual(18, len(stacked_charges))
62 -  
tests/cli_tests.py
1 import unittest 1 import unittest
2 2
3 from app import create_app, db, User 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 from pdc_config import TestConfig 5 from pdc_config import TestConfig
6 6
7 from app.commands import bp as cli_bp 7 from app.commands import bp as cli_bp
@@ -10,7 +10,8 @@ from app.commands import bp as cli_bp @@ -10,7 +10,8 @@ from app.commands import bp as cli_bp
10 class CliBaseTestCase(unittest.TestCase): 10 class CliBaseTestCase(unittest.TestCase):
11 11
12 def setUp(self): 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 TestConfig.PDC_LOGS_LEVEL = 'ERROR' 15 TestConfig.PDC_LOGS_LEVEL = 'ERROR'
15 # Force sqlite in memory db 16 # Force sqlite in memory db
16 TestConfig.SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' 17 TestConfig.SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
@@ -24,16 +25,20 @@ class CliBaseTestCase(unittest.TestCase): @@ -24,16 +25,20 @@ class CliBaseTestCase(unittest.TestCase):
24 def tearDown(self): 25 def tearDown(self):
25 pass 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 runner = self.app.test_cli_runner() 34 runner = self.app.test_cli_runner()
29 result = runner.invoke(user_show_all) 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 runner = self.app.test_cli_runner() 39 runner = self.app.test_cli_runner()
35 result = runner.invoke(show_roles) 40 result = runner.invoke(show_roles)
36 - print(result.output) 41 + self.assertTrue('PUBLIC' in result.output)
37 42
38 def test_add_user(self): 43 def test_add_user(self):
39 runner = self.app.test_cli_runner() 44 runner = self.app.test_cli_runner()
@@ -41,9 +46,7 @@ class CliBaseTestCase(unittest.TestCase): @@ -41,9 +46,7 @@ class CliBaseTestCase(unittest.TestCase):
41 all_users = User.query.all() 46 all_users = User.query.all()
42 self.assertEqual(0, len(all_users)) 47 self.assertEqual(0, len(all_users))
43 # invoke the command directly 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 all_users = User.query.all() 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 from app.models import Category, db, Label, Project, ProjectLabel 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 categorized_labels = {'pole': ['Spatial', 'Sol'], 'domaine': ['soleil-terre', 'atmosphere', 'r&t', 'géologie']} 18 categorized_labels = {'pole': ['Spatial', 'Sol'], 'domaine': ['soleil-terre', 'atmosphere', 'r&t', 'géologie']}
4 projects_categories = {'ChemCam': {'pole': 'Spatial', 'domaine': 'géologie'}, 19 projects_categories = {'ChemCam': {'pole': 'Spatial', 'domaine': 'géologie'},
5 'Pilot': {'pole': 'Spatial', 'domaine': 'atmosphere'}, 20 'Pilot': {'pole': 'Spatial', 'domaine': 'atmosphere'},
@@ -30,7 +45,7 @@ def feed_projects(): @@ -30,7 +45,7 @@ def feed_projects():
30 n_l = db.session.query(Label).filter(Label.name == l_name).one() 45 n_l = db.session.query(Label).filter(Label.name == l_name).one()
31 n_pc = ProjectLabel(project=n_p, category=n_c, label=n_l) 46 n_pc = ProjectLabel(project=n_p, category=n_c, label=n_l)
32 db.session.add(n_pc) 47 db.session.add(n_pc)
33 - 48 +
34 db.session.commit() 49 db.session.commit()
35 50
36 51
tests/frontend_tests.py
1 import os 1 import os
2 import urllib.request 2 import urllib.request
3 3
  4 +from flask import url_for
4 from flask_testing import LiveServerTestCase 5 from flask_testing import LiveServerTestCase
5 from selenium import webdriver 6 from selenium import webdriver
6 7
7 from app import create_app 8 from app import create_app
8 from pdc_config import TestConfig 9 from pdc_config import TestConfig
  10 +from tests.common_db_feed import resources_to_instancedb
9 11
10 12
11 class BaseFrontTestCase(LiveServerTestCase): 13 class BaseFrontTestCase(LiveServerTestCase):
@@ -18,18 +20,18 @@ class BaseFrontTestCase(LiveServerTestCase): @@ -18,18 +20,18 @@ class BaseFrontTestCase(LiveServerTestCase):
18 os.environ['WERKZEUG_RUN_MAIN'] = 'true' 20 os.environ['WERKZEUG_RUN_MAIN'] = 'true'
19 21
20 # pass in test configurations 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 # Specify the test database as the default in_memory wont work for web tests 26 # Specify the test database as the default in_memory wont work for web tests
25 SQLALCHEMY_DATABASE_URI='sqlite:///' + self.db_path, 27 SQLALCHEMY_DATABASE_URI='sqlite:///' + self.db_path,
26 # Change the port that the liveserver listens on as we dont want to conflict with running:5000 28 # Change the port that the liveserver listens on as we dont want to conflict with running:5000
27 LIVESERVER_PORT=8943 29 LIVESERVER_PORT=8943
28 ) 30 )
29 31
30 - self.app_context = app.app_context() 32 + self.app_context = self.app.app_context()
31 self.app_context.push() 33 self.app_context.push()
32 - return app 34 + return self.app
33 35
34 def setUp(self): 36 def setUp(self):
35 self.driver = self.create_chrome_driver() 37 self.driver = self.create_chrome_driver()
@@ -86,4 +88,30 @@ class AccessTestCase(BaseFrontTestCase): @@ -86,4 +88,30 @@ class AccessTestCase(BaseFrontTestCase):
86 88
87 def test_server_is_up_and_running(self): 89 def test_server_is_up_and_running(self):
88 response = urllib.request.urlopen(self.get_server_url()) 90 response = urllib.request.urlopen(self.get_server_url())
89 - self.assertEqual(response.code, 200)  
90 \ No newline at end of file 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))