diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py index b32abf4a8e..76b918459c 100644 --- a/InvenTree/InvenTree/apps.py +++ b/InvenTree/InvenTree/apps.py @@ -38,7 +38,7 @@ class InvenTreeConfig(AppConfig): try: from django_q.models import Schedule - except (AppRegistryNotReady): + except AppRegistryNotReady: # pragma: no cover return # Remove any existing obsolete tasks @@ -48,7 +48,7 @@ class InvenTreeConfig(AppConfig): try: from django_q.models import Schedule - except (AppRegistryNotReady): + except AppRegistryNotReady: # pragma: no cover return logger.info("Starting background tasks...") @@ -103,7 +103,7 @@ class InvenTreeConfig(AppConfig): from InvenTree.tasks import update_exchange_rates from common.settings import currency_code_default - except AppRegistryNotReady: + except AppRegistryNotReady: # pragma: no cover pass base_currency = currency_code_default() diff --git a/InvenTree/InvenTree/ci_render_js.py b/InvenTree/InvenTree/ci_render_js.py index fed1dfb221..e747f1a3c0 100644 --- a/InvenTree/InvenTree/ci_render_js.py +++ b/InvenTree/InvenTree/ci_render_js.py @@ -1,5 +1,6 @@ """ Pull rendered copies of the templated +only used for testing the js files! - This file is omited from coverage """ from django.test import TestCase diff --git a/InvenTree/InvenTree/context.py b/InvenTree/InvenTree/context.py index bd68a0182f..94665f9e07 100644 --- a/InvenTree/InvenTree/context.py +++ b/InvenTree/InvenTree/context.py @@ -23,7 +23,7 @@ def health_status(request): if request.path.endswith('.js'): # Do not provide to script requests - return {} + return {} # pragma: no cover if hasattr(request, '_inventree_health_status'): # Do not duplicate efforts diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 171426bce5..4fb3dc61bb 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -82,7 +82,7 @@ logging.basicConfig( ) if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: - log_level = 'WARNING' + log_level = 'WARNING' # pragma: no cover LOGGING = { 'version': 1, @@ -119,20 +119,20 @@ d) Create "secret_key.txt" if it does not exist if os.getenv("INVENTREE_SECRET_KEY"): # Secret key passed in directly - SECRET_KEY = os.getenv("INVENTREE_SECRET_KEY").strip() - logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") + SECRET_KEY = os.getenv("INVENTREE_SECRET_KEY").strip() # pragma: no cover + logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") # pragma: no cover else: # Secret key passed in by file location key_file = os.getenv("INVENTREE_SECRET_KEY_FILE") if key_file: - key_file = os.path.abspath(key_file) + key_file = os.path.abspath(key_file) # pragma: no cover else: # default secret key location key_file = os.path.join(BASE_DIR, "secret_key.txt") key_file = os.path.abspath(key_file) - if not os.path.exists(key_file): + if not os.path.exists(key_file): # pragma: no cover logger.info(f"Generating random key file at '{key_file}'") # Create a random key file with open(key_file, 'w') as f: @@ -144,7 +144,7 @@ else: try: SECRET_KEY = open(key_file, "r").read().strip() - except Exception: + except Exception: # pragma: no cover logger.exception(f"Couldn't load keyfile {key_file}") sys.exit(-1) @@ -156,7 +156,7 @@ STATIC_ROOT = os.path.abspath( ) ) -if STATIC_ROOT is None: +if STATIC_ROOT is None: # pragma: no cover print("ERROR: INVENTREE_STATIC_ROOT directory not defined") sys.exit(1) @@ -168,7 +168,7 @@ MEDIA_ROOT = os.path.abspath( ) ) -if MEDIA_ROOT is None: +if MEDIA_ROOT is None: # pragma: no cover print("ERROR: INVENTREE_MEDIA_ROOT directory is not defined") sys.exit(1) @@ -187,7 +187,7 @@ if cors_opt: CORS_ORIGIN_ALLOW_ALL = cors_opt.get('allow_all', False) if not CORS_ORIGIN_ALLOW_ALL: - CORS_ORIGIN_WHITELIST = cors_opt.get('whitelist', []) + CORS_ORIGIN_WHITELIST = cors_opt.get('whitelist', []) # pragma: no cover # Web URL endpoint for served static files STATIC_URL = '/static/' @@ -215,7 +215,7 @@ if DEBUG: logger.info("InvenTree running with DEBUG enabled") if DEMO_MODE: - logger.warning("InvenTree running in DEMO mode") + logger.warning("InvenTree running in DEMO mode") # pragma: no cover logger.debug(f"MEDIA_ROOT: '{MEDIA_ROOT}'") logger.debug(f"STATIC_ROOT: '{STATIC_ROOT}'") @@ -304,7 +304,7 @@ AUTHENTICATION_BACKENDS = CONFIG.get('authentication_backends', [ ]) # If the debug toolbar is enabled, add the modules -if DEBUG and CONFIG.get('debug_toolbar', False): +if DEBUG and CONFIG.get('debug_toolbar', False): # pragma: no cover logger.info("Running with DEBUG_TOOLBAR enabled") INSTALLED_APPS.append('debug_toolbar') MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware') @@ -396,7 +396,7 @@ for key in db_keys: reqiured_keys = ['ENGINE', 'NAME'] for key in reqiured_keys: - if key not in db_config: + if key not in db_config: # pragma: no cover error_msg = f'Missing required database configuration value {key}' logger.error(error_msg) @@ -415,7 +415,7 @@ db_engine = db_config['ENGINE'].lower() # Correct common misspelling if db_engine == 'sqlite': - db_engine = 'sqlite3' + db_engine = 'sqlite3' # pragma: no cover if db_engine in ['sqlite3', 'postgresql', 'mysql']: # Prepend the required python module string @@ -443,7 +443,7 @@ Ref: https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-OPTIONS db_options = db_config.get("OPTIONS", db_config.get("options", {})) # Specific options for postgres backend -if "postgres" in db_engine: +if "postgres" in db_engine: # pragma: no cover from psycopg2.extensions import ( ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE, @@ -505,7 +505,7 @@ if "postgres" in db_engine: ) # Specific options for MySql / MariaDB backend -if "mysql" in db_engine: +if "mysql" in db_engine: # pragma: no cover # TODO TCP time outs and keepalives # MariaDB's default isolation level is Repeatable Read which is @@ -546,7 +546,7 @@ _cache_port = _cache_config.get( "port", os.getenv("INVENTREE_CACHE_PORT", "6379") ) -if _cache_host: +if _cache_host: # pragma: no cover # We are going to rely upon a possibly non-localhost for our cache, # so don't wait too long for the cache as nothing in the cache should be # irreplacable. @@ -591,7 +591,7 @@ else: try: # 4 background workers seems like a sensible default background_workers = int(os.environ.get('INVENTREE_BACKGROUND_WORKERS', 4)) -except ValueError: +except ValueError: # pragma: no cover background_workers = 4 # django-q configuration @@ -606,7 +606,7 @@ Q_CLUSTER = { 'sync': False, } -if _cache_host: +if _cache_host: # pragma: no cover # If using external redis cache, make the cache the broker for Django Q # as well Q_CLUSTER["django_redis"] = "worker" @@ -641,7 +641,7 @@ AUTH_PASSWORD_VALIDATORS = [ EXTRA_URL_SCHEMES = CONFIG.get('extra_url_schemes', []) -if not type(EXTRA_URL_SCHEMES) in [list]: +if not type(EXTRA_URL_SCHEMES) in [list]: # pragma: no cover logger.warning("extra_url_schemes not correctly formatted") EXTRA_URL_SCHEMES = [] @@ -675,7 +675,7 @@ LANGUAGES = [ ] # Testing interface translations -if get_setting('TEST_TRANSLATIONS', False): +if get_setting('TEST_TRANSLATIONS', False): # pragma: no cover # Set default language LANGUAGE_CODE = 'xx' @@ -703,7 +703,7 @@ CURRENCIES = CONFIG.get( # Check that each provided currency is supported for currency in CURRENCIES: - if currency not in moneyed.CURRENCIES: + if currency not in moneyed.CURRENCIES: # pragma: no cover print(f"Currency code '{currency}' is not supported") sys.exit(1) @@ -777,7 +777,7 @@ USE_L10N = True # Do not use native timezone support in "test" mode # It generates a *lot* of cruft in the logs if not TESTING: - USE_TZ = True + USE_TZ = True # pragma: no cover DATE_INPUT_FORMATS = [ "%Y-%m-%d", @@ -805,7 +805,7 @@ SITE_ID = 1 # Load the allauth social backends SOCIAL_BACKENDS = CONFIG.get('social_backends', []) for app in SOCIAL_BACKENDS: - INSTALLED_APPS.append(app) + INSTALLED_APPS.append(app) # pragma: no cover SOCIALACCOUNT_PROVIDERS = CONFIG.get('social_providers', []) @@ -879,7 +879,7 @@ PLUGIN_DIRS = ['plugin.builtin', 'barcodes.plugins', ] if not TESTING: # load local deploy directory in prod - PLUGIN_DIRS.append('plugins') + PLUGIN_DIRS.append('plugins') # pragma: no cover if DEBUG or TESTING: # load samples in debug mode diff --git a/InvenTree/InvenTree/status.py b/InvenTree/InvenTree/status.py index 512c68e93b..cc9df701c6 100644 --- a/InvenTree/InvenTree/status.py +++ b/InvenTree/InvenTree/status.py @@ -60,21 +60,21 @@ def is_email_configured(): configured = False # Display warning unless in test mode - if not settings.TESTING: + if not settings.TESTING: # pragma: no cover logger.debug("EMAIL_HOST is not configured") if not settings.EMAIL_HOST_USER: configured = False # Display warning unless in test mode - if not settings.TESTING: + if not settings.TESTING: # pragma: no cover logger.debug("EMAIL_HOST_USER is not configured") if not settings.EMAIL_HOST_PASSWORD: configured = False # Display warning unless in test mode - if not settings.TESTING: + if not settings.TESTING: # pragma: no cover logger.debug("EMAIL_HOST_PASSWORD is not configured") return configured @@ -89,15 +89,15 @@ def check_system_health(**kwargs): result = True - if not is_worker_running(**kwargs): + if not is_worker_running(**kwargs): # pragma: no cover result = False logger.warning(_("Background worker check failed")) - if not is_email_configured(): + if not is_email_configured(): # pragma: no cover result = False logger.warning(_("Email backend not configured")) - if not result: + if not result: # pragma: no cover logger.warning(_("InvenTree system health checks failed")) return result diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index a76f766120..9d4039d4b1 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -28,7 +28,7 @@ def schedule_task(taskname, **kwargs): try: from django_q.models import Schedule - except (AppRegistryNotReady): + except AppRegistryNotReady: # pragma: no cover logger.info("Could not start background tasks - App registry not ready") return @@ -47,7 +47,7 @@ def schedule_task(taskname, **kwargs): func=taskname, **kwargs ) - except (OperationalError, ProgrammingError): + except (OperationalError, ProgrammingError): # pragma: no cover # Required if the DB is not ready yet pass @@ -108,10 +108,10 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): # Workers are not running: run it as synchronous task _func(*args, **kwargs) - except (AppRegistryNotReady): + except AppRegistryNotReady: # pragma: no cover logger.warning(f"Could not offload task '{taskname}' - app registry not ready") return - except (OperationalError, ProgrammingError): + except (OperationalError, ProgrammingError): # pragma: no cover logger.warning(f"Could not offload task '{taskname}' - database not ready") @@ -127,7 +127,7 @@ def heartbeat(): try: from django_q.models import Success logger.info("Could not perform heartbeat task - App registry not ready") - except AppRegistryNotReady: + except AppRegistryNotReady: # pragma: no cover return threshold = timezone.now() - timedelta(minutes=30) @@ -150,7 +150,7 @@ def delete_successful_tasks(): try: from django_q.models import Success - except AppRegistryNotReady: + except AppRegistryNotReady: # pragma: no cover logger.info("Could not perform 'delete_successful_tasks' - App registry not ready") return @@ -184,7 +184,7 @@ def delete_old_error_logs(): logger.info(f"Deleting {errors.count()} old error logs") errors.delete() - except AppRegistryNotReady: + except AppRegistryNotReady: # pragma: no cover # Apps not yet loaded logger.info("Could not perform 'delete_old_error_logs' - App registry not ready") return @@ -197,7 +197,7 @@ def check_for_updates(): try: import common.models - except AppRegistryNotReady: + except AppRegistryNotReady: # pragma: no cover # Apps not yet loaded! logger.info("Could not perform 'check_for_updates' - App registry not ready") return @@ -244,7 +244,7 @@ def update_exchange_rates(): from InvenTree.exchange import InvenTreeExchange from djmoney.contrib.exchange.models import ExchangeBackend, Rate from common.settings import currency_code_default, currency_codes - except AppRegistryNotReady: + except AppRegistryNotReady: # pragma: no cover # Apps not yet loaded! logger.info("Could not perform 'update_exchange_rates' - App registry not ready") return diff --git a/InvenTree/InvenTree/test_urls.py b/InvenTree/InvenTree/test_urls.py index 457358ce2b..8b55d4e042 100644 --- a/InvenTree/InvenTree/test_urls.py +++ b/InvenTree/InvenTree/test_urls.py @@ -92,7 +92,7 @@ class URLTest(TestCase): result[0].strip(), result[1].strip() ]) - elif len(result) == 1: + elif len(result) == 1: # pragma: no cover urls.append([ result[0].strip(), '' diff --git a/InvenTree/InvenTree/test_views.py b/InvenTree/InvenTree/test_views.py index 4f7dddfde4..f6e6de56ff 100644 --- a/InvenTree/InvenTree/test_views.py +++ b/InvenTree/InvenTree/test_views.py @@ -1,11 +1,14 @@ -""" Unit tests for the main web views """ +""" +Unit tests for the main web views +""" + +import re +import os from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model -import os - class ViewTests(TestCase): """ Tests for various top-level views """ @@ -16,9 +19,13 @@ class ViewTests(TestCase): def setUp(self): # Create a user - get_user_model().objects.create_user(self.username, 'user@email.com', self.password) + self.user = get_user_model().objects.create_user(self.username, 'user@email.com', self.password) + self.user.set_password(self.password) + self.user.save() - self.client.login(username=self.username, password=self.password) + result = self.client.login(username=self.username, password=self.password) + + self.assertEqual(result, True) def test_api_doc(self): """ Test that the api-doc view works """ @@ -27,3 +34,51 @@ class ViewTests(TestCase): response = self.client.get(api_url) self.assertEqual(response.status_code, 200) + + def test_index_redirect(self): + """ + top-level URL should redirect to "index" page + """ + + response = self.client.get("/") + + self.assertEqual(response.status_code, 302) + + def get_index_page(self): + """ + Retrieve the index page (used for subsequent unit tests) + """ + + response = self.client.get("/index/") + + self.assertEqual(response.status_code, 200) + + return str(response.content.decode()) + + def test_panels(self): + """ + Test that the required 'panels' are present + """ + + content = self.get_index_page() + + self.assertIn("
", content) + + # TODO: In future, run the javascript and ensure that the panels get created! + + def test_js_load(self): + """ + Test that the required javascript files are loaded correctly + """ + + # Change this number as more javascript files are added to the index page + N_SCRIPT_FILES = 35 + + content = self.get_index_page() + + # Extract all required javascript files from the index page content + script_files = re.findall("