Commit e73a1a7048dfa721ce0343ca2a89d8c305d3b8d2

Authored by hitier
2 parents 3ba117bb 7c7dae6d

More tests and refactoring

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
1   -0.3.pre-6
  1 +0.3.pre-7
... ...
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))
... ...