2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 20:16:44 +00:00

Merge pull request #2630 from matmair/coverage-fixes

Coverage fixes
This commit is contained in:
Oliver 2022-02-16 07:55:12 +11:00 committed by GitHub
commit 49fe528f4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 299 additions and 223 deletions

View File

@ -38,7 +38,7 @@ class InvenTreeConfig(AppConfig):
try: try:
from django_q.models import Schedule from django_q.models import Schedule
except (AppRegistryNotReady): except AppRegistryNotReady: # pragma: no cover
return return
# Remove any existing obsolete tasks # Remove any existing obsolete tasks
@ -48,7 +48,7 @@ class InvenTreeConfig(AppConfig):
try: try:
from django_q.models import Schedule from django_q.models import Schedule
except (AppRegistryNotReady): except AppRegistryNotReady: # pragma: no cover
return return
logger.info("Starting background tasks...") logger.info("Starting background tasks...")
@ -103,7 +103,7 @@ class InvenTreeConfig(AppConfig):
from InvenTree.tasks import update_exchange_rates from InvenTree.tasks import update_exchange_rates
from common.settings import currency_code_default from common.settings import currency_code_default
except AppRegistryNotReady: except AppRegistryNotReady: # pragma: no cover
pass pass
base_currency = currency_code_default() base_currency = currency_code_default()

View File

@ -1,5 +1,6 @@
""" """
Pull rendered copies of the templated Pull rendered copies of the templated
only used for testing the js files! - This file is omited from coverage
""" """
from django.test import TestCase from django.test import TestCase

View File

@ -23,7 +23,7 @@ def health_status(request):
if request.path.endswith('.js'): if request.path.endswith('.js'):
# Do not provide to script requests # Do not provide to script requests
return {} return {} # pragma: no cover
if hasattr(request, '_inventree_health_status'): if hasattr(request, '_inventree_health_status'):
# Do not duplicate efforts # Do not duplicate efforts

View File

@ -82,7 +82,7 @@ logging.basicConfig(
) )
if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
log_level = 'WARNING' log_level = 'WARNING' # pragma: no cover
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
@ -119,20 +119,20 @@ d) Create "secret_key.txt" if it does not exist
if os.getenv("INVENTREE_SECRET_KEY"): if os.getenv("INVENTREE_SECRET_KEY"):
# Secret key passed in directly # Secret key passed in directly
SECRET_KEY = os.getenv("INVENTREE_SECRET_KEY").strip() SECRET_KEY = os.getenv("INVENTREE_SECRET_KEY").strip() # pragma: no cover
logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") # pragma: no cover
else: else:
# Secret key passed in by file location # Secret key passed in by file location
key_file = os.getenv("INVENTREE_SECRET_KEY_FILE") key_file = os.getenv("INVENTREE_SECRET_KEY_FILE")
if key_file: if key_file:
key_file = os.path.abspath(key_file) key_file = os.path.abspath(key_file) # pragma: no cover
else: else:
# default secret key location # default secret key location
key_file = os.path.join(BASE_DIR, "secret_key.txt") key_file = os.path.join(BASE_DIR, "secret_key.txt")
key_file = os.path.abspath(key_file) 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}'") logger.info(f"Generating random key file at '{key_file}'")
# Create a random key file # Create a random key file
with open(key_file, 'w') as f: with open(key_file, 'w') as f:
@ -144,7 +144,7 @@ else:
try: try:
SECRET_KEY = open(key_file, "r").read().strip() SECRET_KEY = open(key_file, "r").read().strip()
except Exception: except Exception: # pragma: no cover
logger.exception(f"Couldn't load keyfile {key_file}") logger.exception(f"Couldn't load keyfile {key_file}")
sys.exit(-1) 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") print("ERROR: INVENTREE_STATIC_ROOT directory not defined")
sys.exit(1) 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") print("ERROR: INVENTREE_MEDIA_ROOT directory is not defined")
sys.exit(1) sys.exit(1)
@ -187,7 +187,7 @@ if cors_opt:
CORS_ORIGIN_ALLOW_ALL = cors_opt.get('allow_all', False) CORS_ORIGIN_ALLOW_ALL = cors_opt.get('allow_all', False)
if not CORS_ORIGIN_ALLOW_ALL: 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 # Web URL endpoint for served static files
STATIC_URL = '/static/' STATIC_URL = '/static/'
@ -215,7 +215,7 @@ if DEBUG:
logger.info("InvenTree running with DEBUG enabled") logger.info("InvenTree running with DEBUG enabled")
if DEMO_MODE: 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"MEDIA_ROOT: '{MEDIA_ROOT}'")
logger.debug(f"STATIC_ROOT: '{STATIC_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 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") logger.info("Running with DEBUG_TOOLBAR enabled")
INSTALLED_APPS.append('debug_toolbar') INSTALLED_APPS.append('debug_toolbar')
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware') MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
@ -396,7 +396,7 @@ for key in db_keys:
reqiured_keys = ['ENGINE', 'NAME'] reqiured_keys = ['ENGINE', 'NAME']
for key in reqiured_keys: 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}' error_msg = f'Missing required database configuration value {key}'
logger.error(error_msg) logger.error(error_msg)
@ -415,7 +415,7 @@ db_engine = db_config['ENGINE'].lower()
# Correct common misspelling # Correct common misspelling
if db_engine == 'sqlite': if db_engine == 'sqlite':
db_engine = 'sqlite3' db_engine = 'sqlite3' # pragma: no cover
if db_engine in ['sqlite3', 'postgresql', 'mysql']: if db_engine in ['sqlite3', 'postgresql', 'mysql']:
# Prepend the required python module string # 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", {})) db_options = db_config.get("OPTIONS", db_config.get("options", {}))
# Specific options for postgres backend # Specific options for postgres backend
if "postgres" in db_engine: if "postgres" in db_engine: # pragma: no cover
from psycopg2.extensions import ( from psycopg2.extensions import (
ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_READ_COMMITTED,
ISOLATION_LEVEL_SERIALIZABLE, ISOLATION_LEVEL_SERIALIZABLE,
@ -505,7 +505,7 @@ if "postgres" in db_engine:
) )
# Specific options for MySql / MariaDB backend # 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 # TODO TCP time outs and keepalives
# MariaDB's default isolation level is Repeatable Read which is # 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") "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, # 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 # so don't wait too long for the cache as nothing in the cache should be
# irreplacable. # irreplacable.
@ -591,7 +591,7 @@ else:
try: try:
# 4 background workers seems like a sensible default # 4 background workers seems like a sensible default
background_workers = int(os.environ.get('INVENTREE_BACKGROUND_WORKERS', 4)) background_workers = int(os.environ.get('INVENTREE_BACKGROUND_WORKERS', 4))
except ValueError: except ValueError: # pragma: no cover
background_workers = 4 background_workers = 4
# django-q configuration # django-q configuration
@ -606,7 +606,7 @@ Q_CLUSTER = {
'sync': False, 'sync': False,
} }
if _cache_host: if _cache_host: # pragma: no cover
# If using external redis cache, make the cache the broker for Django Q # If using external redis cache, make the cache the broker for Django Q
# as well # as well
Q_CLUSTER["django_redis"] = "worker" Q_CLUSTER["django_redis"] = "worker"
@ -641,7 +641,7 @@ AUTH_PASSWORD_VALIDATORS = [
EXTRA_URL_SCHEMES = CONFIG.get('extra_url_schemes', []) 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") logger.warning("extra_url_schemes not correctly formatted")
EXTRA_URL_SCHEMES = [] EXTRA_URL_SCHEMES = []
@ -675,7 +675,7 @@ LANGUAGES = [
] ]
# Testing interface translations # Testing interface translations
if get_setting('TEST_TRANSLATIONS', False): if get_setting('TEST_TRANSLATIONS', False): # pragma: no cover
# Set default language # Set default language
LANGUAGE_CODE = 'xx' LANGUAGE_CODE = 'xx'
@ -703,7 +703,7 @@ CURRENCIES = CONFIG.get(
# Check that each provided currency is supported # Check that each provided currency is supported
for currency in CURRENCIES: 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") print(f"Currency code '{currency}' is not supported")
sys.exit(1) sys.exit(1)
@ -777,7 +777,7 @@ USE_L10N = True
# Do not use native timezone support in "test" mode # Do not use native timezone support in "test" mode
# It generates a *lot* of cruft in the logs # It generates a *lot* of cruft in the logs
if not TESTING: if not TESTING:
USE_TZ = True USE_TZ = True # pragma: no cover
DATE_INPUT_FORMATS = [ DATE_INPUT_FORMATS = [
"%Y-%m-%d", "%Y-%m-%d",
@ -805,7 +805,7 @@ SITE_ID = 1
# Load the allauth social backends # Load the allauth social backends
SOCIAL_BACKENDS = CONFIG.get('social_backends', []) SOCIAL_BACKENDS = CONFIG.get('social_backends', [])
for app in SOCIAL_BACKENDS: for app in SOCIAL_BACKENDS:
INSTALLED_APPS.append(app) INSTALLED_APPS.append(app) # pragma: no cover
SOCIALACCOUNT_PROVIDERS = CONFIG.get('social_providers', []) SOCIALACCOUNT_PROVIDERS = CONFIG.get('social_providers', [])
@ -879,7 +879,7 @@ PLUGIN_DIRS = ['plugin.builtin', 'barcodes.plugins', ]
if not TESTING: if not TESTING:
# load local deploy directory in prod # load local deploy directory in prod
PLUGIN_DIRS.append('plugins') PLUGIN_DIRS.append('plugins') # pragma: no cover
if DEBUG or TESTING: if DEBUG or TESTING:
# load samples in debug mode # load samples in debug mode

View File

@ -60,21 +60,21 @@ def is_email_configured():
configured = False configured = False
# Display warning unless in test mode # Display warning unless in test mode
if not settings.TESTING: if not settings.TESTING: # pragma: no cover
logger.debug("EMAIL_HOST is not configured") logger.debug("EMAIL_HOST is not configured")
if not settings.EMAIL_HOST_USER: if not settings.EMAIL_HOST_USER:
configured = False configured = False
# Display warning unless in test mode # 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") logger.debug("EMAIL_HOST_USER is not configured")
if not settings.EMAIL_HOST_PASSWORD: if not settings.EMAIL_HOST_PASSWORD:
configured = False configured = False
# Display warning unless in test mode # 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") logger.debug("EMAIL_HOST_PASSWORD is not configured")
return configured return configured
@ -89,15 +89,15 @@ def check_system_health(**kwargs):
result = True result = True
if not is_worker_running(**kwargs): if not is_worker_running(**kwargs): # pragma: no cover
result = False result = False
logger.warning(_("Background worker check failed")) logger.warning(_("Background worker check failed"))
if not is_email_configured(): if not is_email_configured(): # pragma: no cover
result = False result = False
logger.warning(_("Email backend not configured")) logger.warning(_("Email backend not configured"))
if not result: if not result: # pragma: no cover
logger.warning(_("InvenTree system health checks failed")) logger.warning(_("InvenTree system health checks failed"))
return result return result

View File

@ -28,7 +28,7 @@ def schedule_task(taskname, **kwargs):
try: try:
from django_q.models import Schedule from django_q.models import Schedule
except (AppRegistryNotReady): except AppRegistryNotReady: # pragma: no cover
logger.info("Could not start background tasks - App registry not ready") logger.info("Could not start background tasks - App registry not ready")
return return
@ -47,7 +47,7 @@ def schedule_task(taskname, **kwargs):
func=taskname, func=taskname,
**kwargs **kwargs
) )
except (OperationalError, ProgrammingError): except (OperationalError, ProgrammingError): # pragma: no cover
# Required if the DB is not ready yet # Required if the DB is not ready yet
pass pass
@ -108,10 +108,10 @@ def offload_task(taskname, *args, force_sync=False, **kwargs):
# Workers are not running: run it as synchronous task # Workers are not running: run it as synchronous task
_func(*args, **kwargs) _func(*args, **kwargs)
except (AppRegistryNotReady): except AppRegistryNotReady: # pragma: no cover
logger.warning(f"Could not offload task '{taskname}' - app registry not ready") logger.warning(f"Could not offload task '{taskname}' - app registry not ready")
return return
except (OperationalError, ProgrammingError): except (OperationalError, ProgrammingError): # pragma: no cover
logger.warning(f"Could not offload task '{taskname}' - database not ready") logger.warning(f"Could not offload task '{taskname}' - database not ready")
@ -127,7 +127,7 @@ def heartbeat():
try: try:
from django_q.models import Success from django_q.models import Success
logger.info("Could not perform heartbeat task - App registry not ready") logger.info("Could not perform heartbeat task - App registry not ready")
except AppRegistryNotReady: except AppRegistryNotReady: # pragma: no cover
return return
threshold = timezone.now() - timedelta(minutes=30) threshold = timezone.now() - timedelta(minutes=30)
@ -150,7 +150,7 @@ def delete_successful_tasks():
try: try:
from django_q.models import Success 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") logger.info("Could not perform 'delete_successful_tasks' - App registry not ready")
return return
@ -184,7 +184,7 @@ def delete_old_error_logs():
logger.info(f"Deleting {errors.count()} old error logs") logger.info(f"Deleting {errors.count()} old error logs")
errors.delete() errors.delete()
except AppRegistryNotReady: except AppRegistryNotReady: # pragma: no cover
# Apps not yet loaded # Apps not yet loaded
logger.info("Could not perform 'delete_old_error_logs' - App registry not ready") logger.info("Could not perform 'delete_old_error_logs' - App registry not ready")
return return
@ -197,7 +197,7 @@ def check_for_updates():
try: try:
import common.models import common.models
except AppRegistryNotReady: except AppRegistryNotReady: # pragma: no cover
# Apps not yet loaded! # Apps not yet loaded!
logger.info("Could not perform 'check_for_updates' - App registry not ready") logger.info("Could not perform 'check_for_updates' - App registry not ready")
return return
@ -244,7 +244,7 @@ def update_exchange_rates():
from InvenTree.exchange import InvenTreeExchange from InvenTree.exchange import InvenTreeExchange
from djmoney.contrib.exchange.models import ExchangeBackend, Rate from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from common.settings import currency_code_default, currency_codes from common.settings import currency_code_default, currency_codes
except AppRegistryNotReady: except AppRegistryNotReady: # pragma: no cover
# Apps not yet loaded! # Apps not yet loaded!
logger.info("Could not perform 'update_exchange_rates' - App registry not ready") logger.info("Could not perform 'update_exchange_rates' - App registry not ready")
return return

View File

@ -92,7 +92,7 @@ class URLTest(TestCase):
result[0].strip(), result[0].strip(),
result[1].strip() result[1].strip()
]) ])
elif len(result) == 1: elif len(result) == 1: # pragma: no cover
urls.append([ urls.append([
result[0].strip(), result[0].strip(),
'' ''

View File

@ -12,6 +12,8 @@ from djmoney.contrib.exchange.exceptions import MissingRate
from .validators import validate_overage, validate_part_name from .validators import validate_overage, validate_part_name
from . import helpers from . import helpers
from . import version from . import version
from . import status
from . import ready
from decimal import Decimal from decimal import Decimal
@ -389,3 +391,19 @@ class CurrencyTests(TestCase):
# Convert to a symbol which is not covered # Convert to a symbol which is not covered
with self.assertRaises(MissingRate): with self.assertRaises(MissingRate):
convert_money(Money(100, 'GBP'), 'ZWL') convert_money(Money(100, 'GBP'), 'ZWL')
class TestStatus(TestCase):
"""
Unit tests for status functions
"""
def test_check_system_healt(self):
"""test that the system health check is false in testing -> background worker not running"""
self.assertEqual(status.check_system_health(), False)
def test_TestMode(self):
self.assertTrue(ready.isInTestMode())
def test_Importing(self):
self.assertEqual(ready.isImportingData(), False)

View File

@ -204,7 +204,7 @@ if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Debug toolbar access (only allowed in DEBUG mode) # Debug toolbar access (only allowed in DEBUG mode)
if 'debug_toolbar' in settings.INSTALLED_APPS: if 'debug_toolbar' in settings.INSTALLED_APPS: # pragma: no cover
import debug_toolbar import debug_toolbar
urlpatterns = [ urlpatterns = [
path('__debug/', include(debug_toolbar.urls)), path('__debug/', include(debug_toolbar.urls)),

View File

@ -11,7 +11,7 @@ def update_tree(apps, schema_editor):
Build.objects.rebuild() Build.objects.rebuild()
def nupdate_tree(apps, schema_editor): def nupdate_tree(apps, schema_editor): # pragma: no cover
pass pass

View File

@ -23,7 +23,7 @@ def add_default_reference(apps, schema_editor):
print(f"\nUpdated build reference for {count} existing BuildOrder objects") print(f"\nUpdated build reference for {count} existing BuildOrder objects")
def reverse_default_reference(apps, schema_editor): def reverse_default_reference(apps, schema_editor): # pragma: no cover
""" """
Do nothing! But we need to have a function here so the whole process is reversible. Do nothing! But we need to have a function here so the whole process is reversible.
""" """

View File

@ -23,7 +23,7 @@ def assign_bom_items(apps, schema_editor):
count_valid = 0 count_valid = 0
count_total = 0 count_total = 0
for build_item in BuildItem.objects.all(): for build_item in BuildItem.objects.all(): # pragma: no cover
# Try to find a BomItem which matches the BuildItem # Try to find a BomItem which matches the BuildItem
# Note: Before this migration, variant stock assignment was not allowed, # Note: Before this migration, variant stock assignment was not allowed,
@ -45,11 +45,11 @@ def assign_bom_items(apps, schema_editor):
except BomItem.DoesNotExist: except BomItem.DoesNotExist:
pass pass
if count_total > 0: if count_total > 0: # pragma: no cover
logger.info(f"Assigned BomItem for {count_valid}/{count_total} entries") logger.info(f"Assigned BomItem for {count_valid}/{count_total} entries")
def unassign_bom_items(apps, schema_editor): def unassign_bom_items(apps, schema_editor): # pragma: no cover
""" """
Reverse migration does not do anything. Reverse migration does not do anything.
Function here to preserve ability to reverse migration Function here to preserve ability to reverse migration

View File

@ -21,13 +21,13 @@ def build_refs(apps, schema_editor):
if result and len(result.groups()) == 1: if result and len(result.groups()) == 1:
try: try:
ref = int(result.groups()[0]) ref = int(result.groups()[0])
except: except: # pragma: no cover
ref = 0 ref = 0
build.reference_int = ref build.reference_int = ref
build.save() build.save()
def unbuild_refs(apps, schema_editor): def unbuild_refs(apps, schema_editor): # pragma: no cover
""" """
Provided only for reverse migration compatibility Provided only for reverse migration compatibility
""" """

View File

@ -83,9 +83,6 @@ class BuildTest(TestCase):
ref = get_next_build_number() ref = get_next_build_number()
if ref is None:
ref = "0001"
# Create a "Build" object to make 10x objects # Create a "Build" object to make 10x objects
self.build = Build.objects.create( self.build = Build.objects.create(
reference=ref, reference=ref,

View File

@ -19,7 +19,7 @@ def delete_old_notifications():
try: try:
from common.models import NotificationEntry from common.models import NotificationEntry
except AppRegistryNotReady: except AppRegistryNotReady: # pragma: no cover
logger.info("Could not perform 'delete_old_notifications' - App registry not ready") logger.info("Could not perform 'delete_old_notifications' - App registry not ready")
return return

View File

@ -59,15 +59,15 @@ class SettingsTest(TestCase):
name = setting.get('name', None) name = setting.get('name', None)
if name is None: if name is None:
raise ValueError(f'Missing GLOBAL_SETTING name for {key}') raise ValueError(f'Missing GLOBAL_SETTING name for {key}') # pragma: no cover
description = setting.get('description', None) description = setting.get('description', None)
if description is None: if description is None:
raise ValueError(f'Missing GLOBAL_SETTING description for {key}') raise ValueError(f'Missing GLOBAL_SETTING description for {key}') # pragma: no cover
if not key == key.upper(): if not key == key.upper():
raise ValueError(f"SETTINGS key '{key}' is not uppercase") raise ValueError(f"SETTINGS key '{key}' is not uppercase") # pragma: no cover
def test_defaults(self): def test_defaults(self):
""" """
@ -87,10 +87,10 @@ class SettingsTest(TestCase):
if setting.is_bool(): if setting.is_bool():
if setting.default_value in ['', None]: if setting.default_value in ['', None]:
raise ValueError(f'Default value for boolean setting {key} not provided') raise ValueError(f'Default value for boolean setting {key} not provided') # pragma: no cover
if setting.default_value not in [True, False]: if setting.default_value not in [True, False]:
raise ValueError(f'Non-boolean default value specified for {key}') raise ValueError(f'Non-boolean default value specified for {key}') # pragma: no cover
class WebhookMessageTests(TestCase): class WebhookMessageTests(TestCase):

View File

@ -14,11 +14,11 @@ So a simplified version of the migration is implemented.
TESTING = 'test' in sys.argv TESTING = 'test' in sys.argv
def clear(): def clear():
if not TESTING: if not TESTING: # pragma: no cover
os.system('cls' if os.name == 'nt' else 'clear') os.system('cls' if os.name == 'nt' else 'clear')
def reverse_association(apps, schema_editor): def reverse_association(apps, schema_editor): # pragma: no cover
""" """
This is the 'reverse' operation of the manufacturer reversal. This is the 'reverse' operation of the manufacturer reversal.
This operation is easier: This operation is easier:
@ -108,7 +108,7 @@ def associate_manufacturers(apps, schema_editor):
if len(row) > 0: if len(row) > 0:
return row[0] return row[0]
return '' return '' # pragma: no cover
cursor = connection.cursor() cursor = connection.cursor()
@ -139,7 +139,7 @@ def associate_manufacturers(apps, schema_editor):
""" Attempt to link Part to an existing Company """ """ Attempt to link Part to an existing Company """
# Matches a company name directly # Matches a company name directly
if name in companies.keys(): if name in companies.keys(): # pragma: no cover
print(" - Part[{pk}]: '{n}' maps to existing manufacturer".format(pk=part_id, n=name)) print(" - Part[{pk}]: '{n}' maps to existing manufacturer".format(pk=part_id, n=name))
manufacturer_id = companies[name] manufacturer_id = companies[name]
@ -150,7 +150,7 @@ def associate_manufacturers(apps, schema_editor):
return True return True
# Have we already mapped this # Have we already mapped this
if name in links.keys(): if name in links.keys(): # pragma: no cover
print(" - Part[{pk}]: Mapped '{n}' - manufacturer <{c}>".format(pk=part_id, n=name, c=links[name])) print(" - Part[{pk}]: Mapped '{n}' - manufacturer <{c}>".format(pk=part_id, n=name, c=links[name]))
manufacturer_id = links[name] manufacturer_id = links[name]
@ -196,10 +196,10 @@ def associate_manufacturers(apps, schema_editor):
# Case-insensitive matching # Case-insensitive matching
ratio = fuzz.partial_ratio(name.lower(), text.lower()) ratio = fuzz.partial_ratio(name.lower(), text.lower())
if ratio > threshold: if ratio > threshold: # pragma: no cover
matches.append({'name': name, 'match': ratio}) matches.append({'name': name, 'match': ratio})
if len(matches) > 0: if len(matches) > 0: # pragma: no cover
return [match['name'] for match in sorted(matches, key=lambda item: item['match'], reverse=True)] return [match['name'] for match in sorted(matches, key=lambda item: item['match'], reverse=True)]
else: else:
return [] return []
@ -212,12 +212,12 @@ def associate_manufacturers(apps, schema_editor):
name = get_manufacturer_name(part_id) name = get_manufacturer_name(part_id)
# Skip empty names # Skip empty names
if not name or len(name) == 0: if not name or len(name) == 0: # pragma: no cover
print(" - Part[{pk}]: No manufacturer_name provided, skipping".format(pk=part_id)) print(" - Part[{pk}]: No manufacturer_name provided, skipping".format(pk=part_id))
return return
# Can be linked to an existing manufacturer # Can be linked to an existing manufacturer
if link_part(part_id, name): if link_part(part_id, name): # pragma: no cover
return return
# Find a list of potential matches # Find a list of potential matches
@ -226,12 +226,12 @@ def associate_manufacturers(apps, schema_editor):
clear() clear()
# Present a list of options # Present a list of options
if not TESTING: if not TESTING: # pragma: no cover
print("----------------------------------") print("----------------------------------")
print("Checking part [{pk}] ({idx} of {total})".format(pk=part_id, idx=idx+1, total=total)) print("Checking part [{pk}] ({idx} of {total})".format(pk=part_id, idx=idx+1, total=total))
if not TESTING: if not TESTING: # pragma: no cover
print("Manufacturer name: '{n}'".format(n=name)) print("Manufacturer name: '{n}'".format(n=name))
print("----------------------------------") print("----------------------------------")
print("Select an option from the list below:") print("Select an option from the list below:")
@ -249,7 +249,7 @@ def associate_manufacturers(apps, schema_editor):
if TESTING: if TESTING:
# When running unit tests, simply select the name of the part # When running unit tests, simply select the name of the part
response = '0' response = '0'
else: else: # pragma: no cover
response = str(input("> ")).strip() response = str(input("> ")).strip()
# Attempt to parse user response as an integer # Attempt to parse user response as an integer
@ -263,7 +263,7 @@ def associate_manufacturers(apps, schema_editor):
return return
# Options 1) - n) select an existing manufacturer # Options 1) - n) select an existing manufacturer
else: else: # pragma: no cover
n = n - 1 n = n - 1
if n < len(matches): if n < len(matches):
@ -287,7 +287,7 @@ def associate_manufacturers(apps, schema_editor):
else: else:
print("Please select a valid option") print("Please select a valid option")
except ValueError: except ValueError: # pragma: no cover
# User has typed in a custom name! # User has typed in a custom name!
if not response or len(response) == 0: if not response or len(response) == 0:
@ -312,7 +312,7 @@ def associate_manufacturers(apps, schema_editor):
print("") print("")
clear() clear()
if not TESTING: if not TESTING: # pragma: no cover
print("---------------------------------------") print("---------------------------------------")
print("The SupplierPart model needs to be migrated,") print("The SupplierPart model needs to be migrated,")
print("as the new 'manufacturer' field maps to a 'Company' reference.") print("as the new 'manufacturer' field maps to a 'Company' reference.")
@ -339,7 +339,7 @@ def associate_manufacturers(apps, schema_editor):
for index, row in enumerate(results): for index, row in enumerate(results):
pk, MPN, SKU, manufacturer_id, manufacturer_name = row pk, MPN, SKU, manufacturer_id, manufacturer_name = row
if manufacturer_id is not None: if manufacturer_id is not None: # pragma: no cover
print(f" - SupplierPart <{pk}> already has a manufacturer associated (skipping)") print(f" - SupplierPart <{pk}> already has a manufacturer associated (skipping)")
continue continue

View File

@ -1,7 +1,7 @@
from django.db import migrations, models from django.db import migrations, models
def reverse_empty_email(apps, schema_editor): def reverse_empty_email(apps, schema_editor): # pragma: no cover
Company = apps.get_model('company', 'Company') Company = apps.get_model('company', 'Company')
for company in Company.objects.all(): for company in Company.objects.all():
if company.email == None: if company.email == None:

View File

@ -42,7 +42,7 @@ def migrate_currencies(apps, schema_editor):
suffix = suffix.strip().upper() suffix = suffix.strip().upper()
if suffix not in currency_codes: if suffix not in currency_codes: # pragma: no cover
logger.warning(f"Missing suffix: '{suffix}'") logger.warning(f"Missing suffix: '{suffix}'")
while suffix not in currency_codes: while suffix not in currency_codes:
@ -78,7 +78,7 @@ def migrate_currencies(apps, schema_editor):
if count > 0: if count > 0:
logger.info(f"Updated {count} SupplierPriceBreak rows") logger.info(f"Updated {count} SupplierPriceBreak rows")
def reverse_currencies(apps, schema_editor): def reverse_currencies(apps, schema_editor): # pragma: no cover
""" """
Reverse the "update" process. Reverse the "update" process.

View File

@ -15,12 +15,12 @@ def supplierpart_make_manufacturer_parts(apps, schema_editor):
for supplier_part in supplier_parts: for supplier_part in supplier_parts:
print(f'{supplier_part.supplier.name[:15].ljust(15)} | {supplier_part.SKU[:15].ljust(15)}\t', end='') print(f'{supplier_part.supplier.name[:15].ljust(15)} | {supplier_part.SKU[:15].ljust(15)}\t', end='')
if supplier_part.manufacturer_part: if supplier_part.manufacturer_part: # pragma: no cover
print(f'[ERROR: MANUFACTURER PART ALREADY EXISTS]') print(f'[ERROR: MANUFACTURER PART ALREADY EXISTS]')
continue continue
part = supplier_part.part part = supplier_part.part
if not part: if not part: # pragma: no cover
print(f'[ERROR: SUPPLIER PART IS NOT CONNECTED TO PART]') print(f'[ERROR: SUPPLIER PART IS NOT CONNECTED TO PART]')
continue continue
@ -67,7 +67,7 @@ def supplierpart_make_manufacturer_parts(apps, schema_editor):
print(f'{"-"*10}\nDone\n') print(f'{"-"*10}\nDone\n')
def supplierpart_populate_manufacturer_info(apps, schema_editor): def supplierpart_populate_manufacturer_info(apps, schema_editor): # pragma: no cover
Part = apps.get_model('part', 'Part') Part = apps.get_model('part', 'Part')
ManufacturerPart = apps.get_model('company', 'ManufacturerPart') ManufacturerPart = apps.get_model('company', 'ManufacturerPart')
SupplierPart = apps.get_model('company', 'SupplierPart') SupplierPart = apps.get_model('company', 'SupplierPart')

View File

@ -3,8 +3,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import json
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -49,28 +47,6 @@ class CompanyViewTestBase(TestCase):
self.client.login(username='username', password='password') self.client.login(username='username', password='password')
def post(self, url, data, valid=None):
"""
POST against this form and return the response (as a JSON object)
"""
response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
json_data = json.loads(response.content)
# If a particular status code is required
if valid is not None:
if valid:
self.assertEqual(json_data['form_valid'], True)
else:
self.assertEqual(json_data['form_valid'], False)
form_errors = json.loads(json_data['form_errors'])
return json_data, form_errors
class CompanyViewTest(CompanyViewTestBase): class CompanyViewTest(CompanyViewTestBase):
""" """

View File

@ -5,6 +5,7 @@ import hashlib
from django.apps import AppConfig from django.apps import AppConfig
from django.conf import settings from django.conf import settings
from django.core.exceptions import AppRegistryNotReady
from InvenTree.ready import canAppAccessDatabase from InvenTree.ready import canAppAccessDatabase
@ -35,9 +36,15 @@ class LabelConfig(AppConfig):
""" """
if canAppAccessDatabase(): if canAppAccessDatabase():
self.create_stock_item_labels() self.create_labels() # pragma: no cover
self.create_stock_location_labels()
self.create_part_labels() def create_labels(self):
"""
Create all default templates
"""
self.create_stock_item_labels()
self.create_stock_location_labels()
self.create_part_labels()
def create_stock_item_labels(self): def create_stock_item_labels(self):
""" """
@ -47,7 +54,7 @@ class LabelConfig(AppConfig):
try: try:
from .models import StockItemLabel from .models import StockItemLabel
except: except AppRegistryNotReady: # pragma: no cover
# Database might not by ready yet # Database might not by ready yet
return return
@ -98,7 +105,7 @@ class LabelConfig(AppConfig):
# File already exists - let's see if it is the "same", # File already exists - let's see if it is the "same",
# or if we need to overwrite it with a newer copy! # or if we need to overwrite it with a newer copy!
if not hashFile(dst_file) == hashFile(src_file): if not hashFile(dst_file) == hashFile(src_file): # pragma: no cover
logger.info(f"Hash differs for '{filename}'") logger.info(f"Hash differs for '{filename}'")
to_copy = True to_copy = True
@ -112,7 +119,7 @@ class LabelConfig(AppConfig):
# Check if a label matching the template already exists # Check if a label matching the template already exists
if StockItemLabel.objects.filter(label=filename).exists(): if StockItemLabel.objects.filter(label=filename).exists():
continue continue # pragma: no cover
logger.info(f"Creating entry for StockItemLabel '{label['name']}'") logger.info(f"Creating entry for StockItemLabel '{label['name']}'")
@ -134,7 +141,7 @@ class LabelConfig(AppConfig):
try: try:
from .models import StockLocationLabel from .models import StockLocationLabel
except: except AppRegistryNotReady: # pragma: no cover
# Database might not yet be ready # Database might not yet be ready
return return
@ -192,7 +199,7 @@ class LabelConfig(AppConfig):
# File already exists - let's see if it is the "same", # File already exists - let's see if it is the "same",
# or if we need to overwrite it with a newer copy! # or if we need to overwrite it with a newer copy!
if not hashFile(dst_file) == hashFile(src_file): if not hashFile(dst_file) == hashFile(src_file): # pragma: no cover
logger.info(f"Hash differs for '{filename}'") logger.info(f"Hash differs for '{filename}'")
to_copy = True to_copy = True
@ -206,7 +213,7 @@ class LabelConfig(AppConfig):
# Check if a label matching the template already exists # Check if a label matching the template already exists
if StockLocationLabel.objects.filter(label=filename).exists(): if StockLocationLabel.objects.filter(label=filename).exists():
continue continue # pragma: no cover
logger.info(f"Creating entry for StockLocationLabel '{label['name']}'") logger.info(f"Creating entry for StockLocationLabel '{label['name']}'")
@ -228,7 +235,7 @@ class LabelConfig(AppConfig):
try: try:
from .models import PartLabel from .models import PartLabel
except: except AppRegistryNotReady: # pragma: no cover
# Database might not yet be ready # Database might not yet be ready
return return
@ -277,7 +284,7 @@ class LabelConfig(AppConfig):
if os.path.exists(dst_file): if os.path.exists(dst_file):
# File already exists - let's see if it is the "same" # File already exists - let's see if it is the "same"
if not hashFile(dst_file) == hashFile(src_file): if not hashFile(dst_file) == hashFile(src_file): # pragma: no cover
logger.info(f"Hash differs for '{filename}'") logger.info(f"Hash differs for '{filename}'")
to_copy = True to_copy = True
@ -291,7 +298,7 @@ class LabelConfig(AppConfig):
# Check if a label matching the template already exists # Check if a label matching the template already exists
if PartLabel.objects.filter(label=filename).exists(): if PartLabel.objects.filter(label=filename).exists():
continue continue # pragma: no cover
logger.info(f"Creating entry for PartLabel '{label['name']}'") logger.info(f"Creating entry for PartLabel '{label['name']}'")

View File

@ -30,7 +30,7 @@ import part.models
try: try:
from django_weasyprint import WeasyTemplateResponseMixin from django_weasyprint import WeasyTemplateResponseMixin
except OSError as err: except OSError as err: # pragma: no cover
print("OSError: {e}".format(e=err)) print("OSError: {e}".format(e=err))
print("You may require some further system packages to be installed.") print("You may require some further system packages to be installed.")
sys.exit(1) sys.exit(1)

View File

@ -66,3 +66,39 @@ class TestReportTests(InvenTreeAPITestCase):
'items': [10, 11, 12], 'items': [10, 11, 12],
} }
) )
class TestLabels(InvenTreeAPITestCase):
"""
Tests for the label APIs
"""
fixtures = [
'category',
'part',
'location',
'stock',
]
roles = [
'stock.view',
'stock_location.view',
]
def do_list(self, filters={}):
response = self.client.get(self.list_url, filters, format='json')
self.assertEqual(response.status_code, 200)
return response.data
def test_lists(self):
self.list_url = reverse('api-stockitem-label-list')
self.do_list()
self.list_url = reverse('api-stocklocation-label-list')
self.do_list()
self.list_url = reverse('api-part-label-list')
self.do_list()

View File

@ -7,6 +7,7 @@ import os
from django.test import TestCase from django.test import TestCase
from django.conf import settings from django.conf import settings
from django.apps import apps
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from InvenTree.helpers import validateFilterString from InvenTree.helpers import validateFilterString
@ -17,8 +18,11 @@ from stock.models import StockItem
class LabelTest(TestCase): class LabelTest(TestCase):
# TODO - Implement this test properly. Looks like apps.py is not run first def setUp(self) -> None:
def _test_default_labels(self): # ensure the labels were created
apps.get_app_config('label').create_labels()
def test_default_labels(self):
""" """
Test that the default label templates are copied across Test that the default label templates are copied across
""" """
@ -31,8 +35,7 @@ class LabelTest(TestCase):
self.assertTrue(labels.count() > 0) self.assertTrue(labels.count() > 0)
# TODO - Implement this test properly. Looks like apps.py is not run first def test_default_files(self):
def _test_default_files(self):
""" """
Test that label files exist in the MEDIA directory Test that label files exist in the MEDIA directory
""" """

View File

@ -6,12 +6,12 @@ if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "InvenTree.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "InvenTree.settings")
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError: except ImportError: # pragma: no cover
# The above import may fail for some other reason. Ensure that the # The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other # issue is really that Django is missing to avoid masking other
# exceptions on Python 2. # exceptions on Python 2.
try: try:
import django # NOQA import django # noqa: F401
except ImportError: except ImportError:
raise ImportError( raise ImportError(
"Couldn't import Django. Are you sure it's installed and " "Couldn't import Django. Are you sure it's installed and "

View File

@ -20,7 +20,7 @@ def build_refs(apps, schema_editor):
if result and len(result.groups()) == 1: if result and len(result.groups()) == 1:
try: try:
ref = int(result.groups()[0]) ref = int(result.groups()[0])
except: except: # pragma: no cover
ref = 0 ref = 0
order.reference_int = ref order.reference_int = ref
@ -37,14 +37,14 @@ def build_refs(apps, schema_editor):
if result and len(result.groups()) == 1: if result and len(result.groups()) == 1:
try: try:
ref = int(result.groups()[0]) ref = int(result.groups()[0])
except: except: # pragma: no cover
ref = 0 ref = 0
order.reference_int = ref order.reference_int = ref
order.save() order.save()
def unbuild_refs(apps, schema_editor): def unbuild_refs(apps, schema_editor): # pragma: no cover
""" """
Provided only for reverse migration compatibility Provided only for reverse migration compatibility
""" """

View File

@ -33,7 +33,7 @@ def add_shipment(apps, schema_editor):
line__order=order line__order=order
) )
if allocations.count() == 0 and order.status != SalesOrderStatus.PENDING: if allocations.count() == 0 and order.status != SalesOrderStatus.PENDING: # pragma: no cover
continue continue
# Create a new Shipment instance against this order # Create a new Shipment instance against this order
@ -41,13 +41,13 @@ def add_shipment(apps, schema_editor):
order=order, order=order,
) )
if order.status == SalesOrderStatus.SHIPPED: if order.status == SalesOrderStatus.SHIPPED: # pragma: no cover
shipment.shipment_date = order.shipment_date shipment.shipment_date = order.shipment_date
shipment.save() shipment.save()
# Iterate through each allocation associated with this order # Iterate through each allocation associated with this order
for allocation in allocations: for allocation in allocations: # pragma: no cover
allocation.shipment = shipment allocation.shipment = shipment
allocation.save() allocation.save()
@ -57,7 +57,7 @@ def add_shipment(apps, schema_editor):
print(f"\nCreated SalesOrderShipment for {n} SalesOrder instances") print(f"\nCreated SalesOrderShipment for {n} SalesOrder instances")
def reverse_add_shipment(apps, schema_editor): def reverse_add_shipment(apps, schema_editor): # pragma: no cover
""" """
Reverse the migration, delete and SalesOrderShipment instances Reverse the migration, delete and SalesOrderShipment instances
""" """

View File

@ -22,7 +22,7 @@ def calculate_shipped_quantity(apps, schema_editor):
StockItem = apps.get_model('stock', 'stockitem') StockItem = apps.get_model('stock', 'stockitem')
SalesOrderLineItem = apps.get_model('order', 'salesorderlineitem') SalesOrderLineItem = apps.get_model('order', 'salesorderlineitem')
for item in SalesOrderLineItem.objects.all(): for item in SalesOrderLineItem.objects.all(): # pragma: no cover
if item.order.status == SalesOrderStatus.SHIPPED: if item.order.status == SalesOrderStatus.SHIPPED:
item.shipped = item.quantity item.shipped = item.quantity
@ -40,7 +40,7 @@ def calculate_shipped_quantity(apps, schema_editor):
item.save() item.save()
def reverse_calculate_shipped_quantity(apps, schema_editor): def reverse_calculate_shipped_quantity(apps, schema_editor): # pragma: no cover
""" """
Provided only for reverse migration compatibility. Provided only for reverse migration compatibility.
This function does nothing. This function does nothing.

View File

@ -40,6 +40,6 @@ class PartConfig(AppConfig):
item.part.trackable = True item.part.trackable = True
item.part.clean() item.part.clean()
item.part.save() item.part.save()
except (OperationalError, ProgrammingError): except (OperationalError, ProgrammingError): # pragma: no cover
# Exception if the database has not been migrated yet # Exception if the database has not been migrated yet
pass pass

View File

@ -11,7 +11,7 @@ import InvenTree.validators
import part.models import part.models
def attach_file(instance, filename): def attach_file(instance, filename): # pragma: no cover
""" """
Generate a filename for the uploaded attachment. Generate a filename for the uploaded attachment.

View File

@ -10,7 +10,7 @@ def update_tree(apps, schema_editor):
Part.objects.rebuild() Part.objects.rebuild()
def nupdate_tree(apps, schema_editor): def nupdate_tree(apps, schema_editor): # pragma: no cover
pass pass

View File

@ -33,7 +33,7 @@ def migrate_currencies(apps, schema_editor):
remap = {} remap = {}
for index, row in enumerate(results): for index, row in enumerate(results): # pragma: no cover
pk, suffix, description = row pk, suffix, description = row
suffix = suffix.strip().upper() suffix = suffix.strip().upper()
@ -57,7 +57,7 @@ def migrate_currencies(apps, schema_editor):
count = 0 count = 0
for index, row in enumerate(results): for index, row in enumerate(results): # pragma: no cover
pk, cost, currency_id, price, price_currency = row pk, cost, currency_id, price, price_currency = row
# Copy the 'cost' field across to the 'price' field # Copy the 'cost' field across to the 'price' field
@ -71,10 +71,10 @@ def migrate_currencies(apps, schema_editor):
count += 1 count += 1
if count > 0: if count > 0: # pragma: no cover
print(f"Updated {count} SupplierPriceBreak rows") print(f"Updated {count} SupplierPriceBreak rows")
def reverse_currencies(apps, schema_editor): def reverse_currencies(apps, schema_editor): # pragma: no cover
""" """
Reverse the "update" process. Reverse the "update" process.

View File

@ -855,7 +855,7 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
return part return part
# We should never get here! # We should never get here!
self.assertTrue(False) self.assertTrue(False) # pragma: no cover
def test_stock_quantity(self): def test_stock_quantity(self):
""" """

View File

@ -55,7 +55,7 @@ class BomItemTest(TestCase):
with self.assertRaises(django_exceptions.ValidationError): with self.assertRaises(django_exceptions.ValidationError):
# A validation error should be raised here # A validation error should be raised here
item = BomItem.objects.create(part=self.bob, sub_part=self.bob, quantity=7) item = BomItem.objects.create(part=self.bob, sub_part=self.bob, quantity=7)
item.clean() item.clean() # pragma: no cover
def test_integer_quantity(self): def test_integer_quantity(self):
""" """

View File

@ -127,7 +127,7 @@ class CategoryTest(TestCase):
with self.assertRaises(ValidationError) as err: with self.assertRaises(ValidationError) as err:
cat.full_clean() cat.full_clean()
cat.save() cat.save() # pragma: no cover
self.assertIn('Illegal character in name', str(err.exception.error_dict.get('name'))) self.assertIn('Illegal character in name', str(err.exception.error_dict.get('name')))
@ -160,10 +160,6 @@ class CategoryTest(TestCase):
self.assertEqual(str(self.fasteners.default_location), 'Office/Drawer_1 - In my desk') self.assertEqual(str(self.fasteners.default_location), 'Office/Drawer_1 - In my desk')
# Test that parts in this location return the same default location, too
for p in self.fasteners.children.all():
self.assert_equal(p.get_default_location().pathstring, 'Office/Drawer_1')
# Any part under electronics should default to 'Home' # Any part under electronics should default to 'Home'
r1 = Part.objects.get(name='R_2K2_0805') r1 = Part.objects.get(name='R_2K2_0805')
self.assertIsNone(r1.default_location) self.assertIsNone(r1.default_location)

View File

@ -44,7 +44,7 @@ class TestParams(TestCase):
with self.assertRaises(django_exceptions.ValidationError): with self.assertRaises(django_exceptions.ValidationError):
t3 = PartParameterTemplate(name='aBcde', units='dd') t3 = PartParameterTemplate(name='aBcde', units='dd')
t3.full_clean() t3.full_clean()
t3.save() t3.save() # pragma: no cover
class TestCategoryTemplates(TransactionTestCase): class TestCategoryTemplates(TransactionTestCase):

View File

@ -110,7 +110,7 @@ class PartTest(TestCase):
try: try:
part.save() part.save()
self.assertTrue(False) self.assertTrue(False) # pragma: no cover
except: except:
pass pass

View File

@ -21,7 +21,7 @@ class PluginAppConfig(AppConfig):
def ready(self): def ready(self):
if settings.PLUGINS_ENABLED: if settings.PLUGINS_ENABLED:
if isImportingData(): if isImportingData(): # pragma: no cover
logger.info('Skipping plugin loading for data import') logger.info('Skipping plugin loading for data import')
else: else:
logger.info('Loading InvenTree plugins') logger.info('Loading InvenTree plugins')

View File

@ -51,7 +51,7 @@ class SettingsMixin:
try: try:
plugin, _ = PluginConfig.objects.get_or_create(key=self.plugin_slug(), name=self.plugin_name()) plugin, _ = PluginConfig.objects.get_or_create(key=self.plugin_slug(), name=self.plugin_name())
except (OperationalError, ProgrammingError): except (OperationalError, ProgrammingError): # pragma: no cover
plugin = None plugin = None
if not plugin: if not plugin:

View File

@ -23,7 +23,7 @@ class IntegrationPluginError(Exception):
self.message = message self.message = message
def __str__(self): def __str__(self):
return self.message return self.message # pragma: no cover
class MixinImplementationError(ValueError): class MixinImplementationError(ValueError):
@ -55,7 +55,7 @@ def log_error(error, reference: str = 'general'):
registry.errors[reference].append(error) registry.errors[reference].append(error)
def handle_error(error, do_raise: bool = True, do_log: bool = True, do_return: bool = False, log_name: str = ''): def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: str = ''):
""" """
Handles an error and casts it as an IntegrationPluginError Handles an error and casts it as an IntegrationPluginError
""" """
@ -69,7 +69,7 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, do_return: b
path_parts = [*path_obj.parts] path_parts = [*path_obj.parts]
path_parts[-1] = path_parts[-1].replace(path_obj.suffix, '') # remove suffix path_parts[-1] = path_parts[-1].replace(path_obj.suffix, '') # remove suffix
# remove path preixes # remove path prefixes
if path_parts[0] == 'plugin': if path_parts[0] == 'plugin':
path_parts.remove('plugin') path_parts.remove('plugin')
path_parts.pop(0) path_parts.pop(0)
@ -84,13 +84,8 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, do_return: b
log_kwargs['reference'] = log_name log_kwargs['reference'] = log_name
log_error({package_name: str(error)}, **log_kwargs) log_error({package_name: str(error)}, **log_kwargs)
new_error = IntegrationPluginError(package_name, str(error))
if do_raise: if do_raise:
raise IntegrationPluginError(package_name, str(error)) raise IntegrationPluginError(package_name, str(error))
if do_return:
return new_error
# endregion # endregion
@ -101,14 +96,16 @@ def get_git_log(path):
""" """
path = path.replace(os.path.dirname(settings.BASE_DIR), '')[1:] path = path.replace(os.path.dirname(settings.BASE_DIR), '')[1:]
command = ['git', 'log', '-n', '1', "--pretty=format:'%H%n%aN%n%aE%n%aI%n%f%n%G?%n%GK'", '--follow', '--', path] command = ['git', 'log', '-n', '1', "--pretty=format:'%H%n%aN%n%aE%n%aI%n%f%n%G?%n%GK'", '--follow', '--', path]
output = None
try: try:
output = str(subprocess.check_output(command, cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')[1:-1] output = str(subprocess.check_output(command, cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')[1:-1]
if output: if output:
output = output.split('\n') output = output.split('\n')
else: except subprocess.CalledProcessError: # pragma: no cover
output = 7 * [''] pass
except subprocess.CalledProcessError:
output = 7 * [''] if not output:
output = 7 * [''] # pragma: no cover
return {'hash': output[0], 'author': output[1], 'mail': output[2], 'date': output[3], 'message': output[4], 'verified': output[5], 'key': output[6]} return {'hash': output[0], 'author': output[1], 'mail': output[2], 'date': output[3], 'message': output[4], 'verified': output[5], 'key': output[6]}
@ -153,7 +150,7 @@ def get_modules(pkg):
if not k.startswith('_') and (pkg_names is None or k in pkg_names): if not k.startswith('_') and (pkg_names is None or k in pkg_names):
context[k] = v context[k] = v
context[name] = module context[name] = module
except AppRegistryNotReady: except AppRegistryNotReady: # pragma: no cover
pass pass
except Exception as error: except Exception as error:
# this 'protects' against malformed plugin modules by more or less silently failing # this 'protects' against malformed plugin modules by more or less silently failing

View File

@ -135,7 +135,7 @@ class IntegrationPluginBase(MixinBase, plugin_base.InvenTreePluginBase):
if not author: if not author:
author = self.package.get('author') author = self.package.get('author')
if not author: if not author:
author = _('No author found') author = _('No author found') # pragma: no cover
return author return author
@property @property
@ -149,7 +149,7 @@ class IntegrationPluginBase(MixinBase, plugin_base.InvenTreePluginBase):
else: else:
pub_date = datetime.fromisoformat(str(pub_date)) pub_date = datetime.fromisoformat(str(pub_date))
if not pub_date: if not pub_date:
pub_date = _('No date found') pub_date = _('No date found') # pragma: no cover
return pub_date return pub_date
@property @property
@ -226,7 +226,7 @@ class IntegrationPluginBase(MixinBase, plugin_base.InvenTreePluginBase):
""" """
Get package metadata for plugin Get package metadata for plugin
""" """
return {} return {} # pragma: no cover # TODO add usage for package metadata
def define_package(self): def define_package(self):
""" """
@ -241,11 +241,11 @@ class IntegrationPluginBase(MixinBase, plugin_base.InvenTreePluginBase):
# process sign state # process sign state
sign_state = getattr(GitStatus, str(package.get('verified')), GitStatus.N) sign_state = getattr(GitStatus, str(package.get('verified')), GitStatus.N)
if sign_state.status == 0: if sign_state.status == 0:
self.sign_color = 'success' self.sign_color = 'success' # pragma: no cover
elif sign_state.status == 1: elif sign_state.status == 1:
self.sign_color = 'warning' self.sign_color = 'warning'
else: else:
self.sign_color = 'danger' self.sign_color = 'danger' # pragma: no cover
# set variables # set variables
self.package = package self.package = package

View File

@ -102,7 +102,7 @@ class PluginsRegistry:
self._init_plugins(blocked_plugin) self._init_plugins(blocked_plugin)
self._activate_plugins() self._activate_plugins()
registered_successful = True registered_successful = True
except (OperationalError, ProgrammingError): except (OperationalError, ProgrammingError): # pragma: no cover
# Exception if the database has not been migrated yet # Exception if the database has not been migrated yet
logger.info('Database not accessible while loading plugins') logger.info('Database not accessible while loading plugins')
break break

View File

@ -117,7 +117,7 @@ class PluginConfigInstallSerializer(serializers.Serializer):
ret['result'] = str(result, 'utf-8') ret['result'] = str(result, 'utf-8')
ret['success'] = True ret['success'] = True
success = True success = True
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as error: # pragma: no cover
ret['result'] = str(error.output, 'utf-8') ret['result'] = str(error.output, 'utf-8')
ret['error'] = True ret['error'] = True

View File

@ -52,7 +52,7 @@ def navigation_enabled(*args, **kwargs):
""" """
if djangosettings.PLUGIN_TESTING: if djangosettings.PLUGIN_TESTING:
return True return True
return InvenTreeSetting.get_setting('ENABLE_PLUGINS_NAVIGATION') return InvenTreeSetting.get_setting('ENABLE_PLUGINS_NAVIGATION') # pragma: no cover
@register.simple_tag() @register.simple_tag()

View File

@ -22,6 +22,7 @@ class PluginDetailAPITest(InvenTreeAPITestCase):
self.MSG_NO_PKG = 'Either packagename of URL must be provided' self.MSG_NO_PKG = 'Either packagename of URL must be provided'
self.PKG_NAME = 'minimal' self.PKG_NAME = 'minimal'
self.PKG_URL = 'git+https://github.com/geoffrey-a-reed/minimal'
super().setUp() super().setUp()
def test_plugin_install(self): def test_plugin_install(self):
@ -35,7 +36,13 @@ class PluginDetailAPITest(InvenTreeAPITestCase):
'confirm': True, 'confirm': True,
'packagename': self.PKG_NAME 'packagename': self.PKG_NAME
}, expected_code=201).data }, expected_code=201).data
self.assertEqual(data['success'], True)
# valid - github url
data = self.post(url, {
'confirm': True,
'url': self.PKG_URL
}, expected_code=201).data
self.assertEqual(data['success'], True) self.assertEqual(data['success'], True)
# invalid tries # invalid tries

View File

@ -90,7 +90,7 @@ class ReportConfig(AppConfig):
try: try:
from .models import TestReport from .models import TestReport
except: except: # pragma: no cover
# Database is not ready yet # Database is not ready yet
return return
@ -113,7 +113,7 @@ class ReportConfig(AppConfig):
try: try:
from .models import BuildReport from .models import BuildReport
except: except: # pragma: no cover
# Database is not ready yet # Database is not ready yet
return return

View File

@ -34,7 +34,7 @@ from django.utils.translation import gettext_lazy as _
try: try:
from django_weasyprint import WeasyTemplateResponseMixin from django_weasyprint import WeasyTemplateResponseMixin
except OSError as err: except OSError as err: # pragma: no cover
print("OSError: {e}".format(e=err)) print("OSError: {e}".format(e=err))
print("You may require some further system packages to be installed.") print("You may require some further system packages to be installed.")
sys.exit(1) sys.exit(1)

View File

@ -60,13 +60,13 @@ class ReportTest(InvenTreeAPITestCase):
template_dir template_dir
) )
if not os.path.exists(dst_dir): if not os.path.exists(dst_dir): # pragma: no cover
os.makedirs(dst_dir, exist_ok=True) os.makedirs(dst_dir, exist_ok=True)
src_file = os.path.join(src_dir, filename) src_file = os.path.join(src_dir, filename)
dst_file = os.path.join(dst_dir, filename) dst_file = os.path.join(dst_dir, filename)
if not os.path.exists(dst_file): if not os.path.exists(dst_file): # pragma: no cover
shutil.copyfile(src_file, dst_file) shutil.copyfile(src_file, dst_file)
# Convert to an "internal" filename # Convert to an "internal" filename

View File

@ -21,7 +21,7 @@ def update_history(apps, schema_editor):
locations = StockLocation.objects.all() locations = StockLocation.objects.all()
for location in locations: for location in locations: # pragma: no cover
# Pre-calculate pathstring # Pre-calculate pathstring
# Note we cannot use the 'pathstring' function here as we don't have access to model functions! # Note we cannot use the 'pathstring' function here as we don't have access to model functions!
@ -35,7 +35,7 @@ def update_history(apps, schema_editor):
location._path = '/'.join(path) location._path = '/'.join(path)
for item in StockItem.objects.all(): for item in StockItem.objects.all(): # pragma: no cover
history = StockItemTracking.objects.filter(item=item).order_by('date') history = StockItemTracking.objects.filter(item=item).order_by('date')
@ -200,13 +200,13 @@ def update_history(apps, schema_editor):
if update_count > 0: if update_count > 0:
print(f"\n==========================\nUpdated {update_count} StockItemHistory entries") print(f"\n==========================\nUpdated {update_count} StockItemHistory entries") # pragma: no cover
def reverse_update(apps, schema_editor): def reverse_update(apps, schema_editor):
""" """
""" """
pass pass # pragma: no cover
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -26,12 +26,12 @@ def extract_purchase_price(apps, schema_editor):
# Find all the StockItem objects without a purchase_price which point to a PurchaseOrder # Find all the StockItem objects without a purchase_price which point to a PurchaseOrder
items = StockItem.objects.filter(purchase_price=None).exclude(purchase_order=None) items = StockItem.objects.filter(purchase_price=None).exclude(purchase_order=None)
if items.count() > 0: if items.count() > 0: # pragma: no cover
print(f"Found {items.count()} stock items with missing purchase price information") print(f"Found {items.count()} stock items with missing purchase price information")
update_count = 0 update_count = 0
for item in items: for item in items: # pragma: no cover
part_id = item.part part_id = item.part
@ -57,10 +57,10 @@ def extract_purchase_price(apps, schema_editor):
break break
if update_count > 0: if update_count > 0: # pragma: no cover
print(f"Updated pricing for {update_count} stock items") print(f"Updated pricing for {update_count} stock items")
def reverse_operation(apps, schema_editor): def reverse_operation(apps, schema_editor): # pragma: no cover
""" """
DO NOTHING! DO NOTHING!
""" """

View File

@ -12,7 +12,7 @@ def update_serials(apps, schema_editor):
StockItem = apps.get_model('stock', 'stockitem') StockItem = apps.get_model('stock', 'stockitem')
for item in StockItem.objects.all(): for item in StockItem.objects.all(): # pragma: no cover
if item.serial is None: if item.serial is None:
# Skip items without existing serial numbers # Skip items without existing serial numbers
@ -33,7 +33,7 @@ def update_serials(apps, schema_editor):
item.save() item.save()
def nupdate_serials(apps, schema_editor): def nupdate_serials(apps, schema_editor): # pragma: no cover
""" """
Provided only for reverse migration compatibility Provided only for reverse migration compatibility
""" """

View File

@ -29,7 +29,7 @@ def delete_scheduled(apps, schema_editor):
Task.objects.filter(func='stock.tasks.delete_old_stock_items').delete() Task.objects.filter(func='stock.tasks.delete_old_stock_items').delete()
def reverse(apps, schema_editor): def reverse(apps, schema_editor): # pragma: no cover
pass pass

View File

@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

View File

@ -269,9 +269,6 @@ class StockItemTest(StockAPITestCase):
list_url = reverse('api-stock-list') list_url = reverse('api-stock-list')
def detail_url(self, pk):
return reverse('api-stock-detail', kwargs={'pk': pk})
def setUp(self): def setUp(self):
super().setUp() super().setUp()
# Create some stock locations # Create some stock locations

View File

@ -5,7 +5,7 @@ from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from common.models import InvenTreeSetting # from common.models import InvenTreeSetting
class StockViewTestCase(TestCase): class StockViewTestCase(TestCase):
@ -64,6 +64,9 @@ class StockOwnershipTest(StockViewTestCase):
def setUp(self): def setUp(self):
""" Add another user for ownership tests """ """ Add another user for ownership tests """
"""
TODO: Refactor this following test to use the new API form
super().setUp() super().setUp()
# Promote existing user with staff, admin and superuser statuses # Promote existing user with staff, admin and superuser statuses
@ -100,8 +103,6 @@ class StockOwnershipTest(StockViewTestCase):
InvenTreeSetting.set_setting('STOCK_OWNERSHIP_CONTROL', True, self.user) InvenTreeSetting.set_setting('STOCK_OWNERSHIP_CONTROL', True, self.user)
self.assertEqual(True, InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')) self.assertEqual(True, InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL'))
"""
TODO: Refactor this following test to use the new API form
def test_owner_control(self): def test_owner_control(self):
# Test stock location and item ownership # Test stock location and item ownership
from .models import StockLocation from .models import StockLocation

View File

@ -162,11 +162,6 @@ class GetAuthToken(APIView):
'token': token.key, 'token': token.key,
}) })
else:
return Response({
'error': 'User not authenticated',
})
def logout(self, request): def logout(self, request):
try: try:
request.user.auth_token.delete() request.user.auth_token.delete()

View File

@ -32,7 +32,7 @@ class UsersConfig(AppConfig):
# First, delete any rule_set objects which have become outdated! # First, delete any rule_set objects which have become outdated!
for rule in RuleSet.objects.all(): for rule in RuleSet.objects.all():
if rule.name not in RuleSet.RULESET_NAMES: if rule.name not in RuleSet.RULESET_NAMES: # pragma: no cover # can not change ORM without the app beeing loaded
print("need to delete:", rule.name) print("need to delete:", rule.name)
rule.delete() rule.delete()

View File

@ -267,7 +267,7 @@ class RuleSet(models.Model):
def __str__(self, debug=False): def __str__(self, debug=False):
""" Ruleset string representation """ """ Ruleset string representation """
if debug: if debug: # pragma: no cover
# Makes debugging easier # Makes debugging easier
return f'{str(self.group).ljust(15)}: {self.name.title().ljust(15)} | ' \ return f'{str(self.group).ljust(15)}: {self.name.title().ljust(15)} | ' \
f'v: {str(self.can_view).ljust(5)} | a: {str(self.can_add).ljust(5)} | ' \ f'v: {str(self.can_view).ljust(5)} | a: {str(self.can_add).ljust(5)} | ' \
@ -340,7 +340,7 @@ def update_group_roles(group, debug=False):
""" """
if not canAppAccessDatabase(allow_test=True): if not canAppAccessDatabase(allow_test=True):
return return # pragma: no cover
# List of permissions already associated with this group # List of permissions already associated with this group
group_permissions = set() group_permissions = set()
@ -432,7 +432,7 @@ def update_group_roles(group, debug=False):
try: try:
content_type = ContentType.objects.get(app_label=app, model=model) content_type = ContentType.objects.get(app_label=app, model=model)
permission = Permission.objects.get(content_type=content_type, codename=perm) permission = Permission.objects.get(content_type=content_type, codename=perm)
except ContentType.DoesNotExist: except ContentType.DoesNotExist: # pragma: no cover
logger.warning(f"Error: Could not find permission matching '{permission_string}'") logger.warning(f"Error: Could not find permission matching '{permission_string}'")
permission = None permission = None
@ -450,7 +450,7 @@ def update_group_roles(group, debug=False):
if permission: if permission:
group.permissions.add(permission) group.permissions.add(permission)
if debug: if debug: # pragma: no cover
print(f"Adding permission {perm} to group {group.name}") print(f"Adding permission {perm} to group {group.name}")
# Remove any extra permissions from the group # Remove any extra permissions from the group
@ -465,7 +465,7 @@ def update_group_roles(group, debug=False):
if permission: if permission:
group.permissions.remove(permission) group.permissions.remove(permission)
if debug: if debug: # pragma: no cover
print(f"Removing permission {perm} from group {group.name}") print(f"Removing permission {perm} from group {group.name}")
# Enable all action permissions for certain children models # Enable all action permissions for certain children models
@ -617,7 +617,7 @@ class Owner(models.Model):
# Create new owner # Create new owner
try: try:
return cls.objects.create(owner=obj) return cls.objects.create(owner=obj)
except IntegrityError: except IntegrityError: # pragma: no cover
return None return None
return existing_owner return existing_owner

View File

@ -3,9 +3,12 @@ from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from django.apps import apps from django.apps import apps
from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from rest_framework.authtoken.models import Token
from users.models import RuleSet, Owner from users.models import RuleSet, Owner
@ -22,7 +25,7 @@ class RuleSetModelTest(TestCase):
missing = [name for name in RuleSet.RULESET_NAMES if name not in keys] missing = [name for name in RuleSet.RULESET_NAMES if name not in keys]
if len(missing) > 0: if len(missing) > 0: # pragma: no cover
print("The following rulesets do not have models assigned:") print("The following rulesets do not have models assigned:")
for m in missing: for m in missing:
print("-", m) print("-", m)
@ -30,7 +33,7 @@ class RuleSetModelTest(TestCase):
# Check if models have been defined for a ruleset which is incorrect # Check if models have been defined for a ruleset which is incorrect
extra = [name for name in keys if name not in RuleSet.RULESET_NAMES] extra = [name for name in keys if name not in RuleSet.RULESET_NAMES]
if len(extra) > 0: if len(extra) > 0: # pragma: no cover
print("The following rulesets have been improperly added to RULESET_MODELS:") print("The following rulesets have been improperly added to RULESET_MODELS:")
for e in extra: for e in extra:
print("-", e) print("-", e)
@ -38,7 +41,7 @@ class RuleSetModelTest(TestCase):
# Check that each ruleset has models assigned # Check that each ruleset has models assigned
empty = [key for key in keys if len(RuleSet.RULESET_MODELS[key]) == 0] empty = [key for key in keys if len(RuleSet.RULESET_MODELS[key]) == 0]
if len(empty) > 0: if len(empty) > 0: # pragma: no cover
print("The following rulesets have empty entries in RULESET_MODELS:") print("The following rulesets have empty entries in RULESET_MODELS:")
for e in empty: for e in empty:
print("-", e) print("-", e)
@ -77,10 +80,10 @@ class RuleSetModelTest(TestCase):
missing_models = set() missing_models = set()
for model in available_tables: for model in available_tables:
if model not in assigned_models and model not in RuleSet.RULESET_IGNORE: if model not in assigned_models and model not in RuleSet.RULESET_IGNORE: # pragma: no cover
missing_models.add(model) missing_models.add(model)
if len(missing_models) > 0: if len(missing_models) > 0: # pragma: no cover
print("The following database models are not covered by the defined RuleSet permissions:") print("The following database models are not covered by the defined RuleSet permissions:")
for m in missing_models: for m in missing_models:
print("-", m) print("-", m)
@ -95,11 +98,11 @@ class RuleSetModelTest(TestCase):
for model in RuleSet.RULESET_IGNORE: for model in RuleSet.RULESET_IGNORE:
defined_models.add(model) defined_models.add(model)
for model in defined_models: for model in defined_models: # pragma: no cover
if model not in available_tables: if model not in available_tables:
extra_models.add(model) extra_models.add(model)
if len(extra_models) > 0: if len(extra_models) > 0: # pragma: no cover
print("The following RuleSet permissions do not match a database model:") print("The following RuleSet permissions do not match a database model:")
for m in extra_models: for m in extra_models:
print("-", m) print("-", m)
@ -169,16 +172,16 @@ class OwnerModelTest(TestCase):
""" Add users and groups """ """ Add users and groups """
# Create a new user # Create a new user
self.user = get_user_model().objects.create_user( self.user = get_user_model().objects.create_user('username', 'user@email.com', 'password')
username='john',
email='john@email.com',
password='custom123',
)
# Put the user into a new group # Put the user into a new group
self.group = Group.objects.create(name='new_group') self.group = Group.objects.create(name='new_group')
self.user.groups.add(self.group) self.user.groups.add(self.group)
def do_request(self, endpoint, filters, status_code=200):
response = self.client.get(endpoint, filters, format='json')
self.assertEqual(response.status_code, status_code)
return response.data
def test_owner(self): def test_owner(self):
# Check that owner was created for user # Check that owner was created for user
@ -203,3 +206,43 @@ class OwnerModelTest(TestCase):
self.group.delete() self.group.delete()
group_as_owner = Owner.get_owner(self.group) group_as_owner = Owner.get_owner(self.group)
self.assertEqual(group_as_owner, None) self.assertEqual(group_as_owner, None)
def test_api(self):
"""
Test user APIs
"""
# not authed
self.do_request(reverse('api-owner-list'), {}, 401)
self.do_request(reverse('api-owner-detail', kwargs={'pk': self.user.id}), {}, 401)
self.client.login(username='username', password='password')
# user list
self.do_request(reverse('api-owner-list'), {})
# user list with search
self.do_request(reverse('api-owner-list'), {'search': 'user'})
# user detail
# TODO fix this test
# self.do_request(reverse('api-owner-detail', kwargs={'pk': self.user.id}), {})
def test_token(self):
"""
Test token mechanisms
"""
token = Token.objects.filter(user=self.user)
# not authed
self.do_request(reverse('api-token'), {}, 401)
self.client.login(username='username', password='password')
# token get
response = self.do_request(reverse('api-token'), {})
self.assertEqual(response['token'], token.first().key)
# token delete
response = self.client.delete(reverse('api-token'), {}, format='json')
self.assertEqual(response.status_code, 202)
self.assertEqual(len(token), 0)
# token second delete
response = self.client.delete(reverse('api-token'), {}, format='json')
self.assertEqual(response.status_code, 400)

View File

@ -20,3 +20,7 @@ max-complexity = 20
[coverage:run] [coverage:run]
source = ./InvenTree source = ./InvenTree
[coverage:report]
omit=
InvenTree/wsgi.py
InvenTree/ci_render_js.py