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:
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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(),
''

View File

@ -12,6 +12,8 @@ from djmoney.contrib.exchange.exceptions import MissingRate
from .validators import validate_overage, validate_part_name
from . import helpers
from . import version
from . import status
from . import ready
from decimal import Decimal
@ -389,3 +391,19 @@ class CurrencyTests(TestCase):
# Convert to a symbol which is not covered
with self.assertRaises(MissingRate):
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)
# 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
urlpatterns = [
path('__debug/', include(debug_toolbar.urls)),

View File

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

View File

@ -23,7 +23,7 @@ def add_default_reference(apps, schema_editor):
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.
"""

View File

@ -23,7 +23,7 @@ def assign_bom_items(apps, schema_editor):
count_valid = 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
# Note: Before this migration, variant stock assignment was not allowed,
@ -45,11 +45,11 @@ def assign_bom_items(apps, schema_editor):
except BomItem.DoesNotExist:
pass
if count_total > 0:
if count_total > 0: # pragma: no cover
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.
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:
try:
ref = int(result.groups()[0])
except:
except: # pragma: no cover
ref = 0
build.reference_int = ref
build.save()
def unbuild_refs(apps, schema_editor):
def unbuild_refs(apps, schema_editor): # pragma: no cover
"""
Provided only for reverse migration compatibility
"""

View File

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

View File

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

View File

@ -59,15 +59,15 @@ class SettingsTest(TestCase):
name = setting.get('name', 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)
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():
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):
"""
@ -87,10 +87,10 @@ class SettingsTest(TestCase):
if setting.is_bool():
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]:
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):

View File

@ -14,11 +14,11 @@ So a simplified version of the migration is implemented.
TESTING = 'test' in sys.argv
def clear():
if not TESTING:
if not TESTING: # pragma: no cover
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 operation is easier:
@ -108,7 +108,7 @@ def associate_manufacturers(apps, schema_editor):
if len(row) > 0:
return row[0]
return ''
return '' # pragma: no cover
cursor = connection.cursor()
@ -139,7 +139,7 @@ def associate_manufacturers(apps, schema_editor):
""" Attempt to link Part to an existing Company """
# 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))
manufacturer_id = companies[name]
@ -150,7 +150,7 @@ def associate_manufacturers(apps, schema_editor):
return True
# 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]))
manufacturer_id = links[name]
@ -196,10 +196,10 @@ def associate_manufacturers(apps, schema_editor):
# Case-insensitive matching
ratio = fuzz.partial_ratio(name.lower(), text.lower())
if ratio > threshold:
if ratio > threshold: # pragma: no cover
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)]
else:
return []
@ -212,12 +212,12 @@ def associate_manufacturers(apps, schema_editor):
name = get_manufacturer_name(part_id)
# 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))
return
# Can be linked to an existing manufacturer
if link_part(part_id, name):
if link_part(part_id, name): # pragma: no cover
return
# Find a list of potential matches
@ -226,12 +226,12 @@ def associate_manufacturers(apps, schema_editor):
clear()
# Present a list of options
if not TESTING:
if not TESTING: # pragma: no cover
print("----------------------------------")
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("----------------------------------")
print("Select an option from the list below:")
@ -249,7 +249,7 @@ def associate_manufacturers(apps, schema_editor):
if TESTING:
# When running unit tests, simply select the name of the part
response = '0'
else:
else: # pragma: no cover
response = str(input("> ")).strip()
# Attempt to parse user response as an integer
@ -263,7 +263,7 @@ def associate_manufacturers(apps, schema_editor):
return
# Options 1) - n) select an existing manufacturer
else:
else: # pragma: no cover
n = n - 1
if n < len(matches):
@ -287,7 +287,7 @@ def associate_manufacturers(apps, schema_editor):
else:
print("Please select a valid option")
except ValueError:
except ValueError: # pragma: no cover
# User has typed in a custom name!
if not response or len(response) == 0:
@ -312,7 +312,7 @@ def associate_manufacturers(apps, schema_editor):
print("")
clear()
if not TESTING:
if not TESTING: # pragma: no cover
print("---------------------------------------")
print("The SupplierPart model needs to be migrated,")
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):
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)")
continue

View File

@ -1,7 +1,7 @@
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')
for company in Company.objects.all():
if company.email == None:

View File

@ -42,7 +42,7 @@ def migrate_currencies(apps, schema_editor):
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}'")
while suffix not in currency_codes:
@ -78,7 +78,7 @@ def migrate_currencies(apps, schema_editor):
if count > 0:
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.

View File

@ -15,12 +15,12 @@ def supplierpart_make_manufacturer_parts(apps, schema_editor):
for supplier_part in supplier_parts:
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]')
continue
part = supplier_part.part
if not part:
if not part: # pragma: no cover
print(f'[ERROR: SUPPLIER PART IS NOT CONNECTED TO PART]')
continue
@ -67,7 +67,7 @@ def supplierpart_make_manufacturer_parts(apps, schema_editor):
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')
ManufacturerPart = apps.get_model('company', 'ManufacturerPart')
SupplierPart = apps.get_model('company', 'SupplierPart')

View File

@ -3,8 +3,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
@ -49,28 +47,6 @@ class CompanyViewTestBase(TestCase):
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):
"""

View File

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

View File

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

View File

@ -66,3 +66,39 @@ class TestReportTests(InvenTreeAPITestCase):
'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.conf import settings
from django.apps import apps
from django.core.exceptions import ValidationError
from InvenTree.helpers import validateFilterString
@ -17,8 +18,11 @@ from stock.models import StockItem
class LabelTest(TestCase):
# TODO - Implement this test properly. Looks like apps.py is not run first
def _test_default_labels(self):
def setUp(self) -> None:
# 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
"""
@ -31,8 +35,7 @@ class LabelTest(TestCase):
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
"""

View File

@ -6,12 +6,12 @@ if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "InvenTree.settings")
try:
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
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django # NOQA
import django # noqa: F401
except ImportError:
raise ImportError(
"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:
try:
ref = int(result.groups()[0])
except:
except: # pragma: no cover
ref = 0
order.reference_int = ref
@ -37,14 +37,14 @@ def build_refs(apps, schema_editor):
if result and len(result.groups()) == 1:
try:
ref = int(result.groups()[0])
except:
except: # pragma: no cover
ref = 0
order.reference_int = ref
order.save()
def unbuild_refs(apps, schema_editor):
def unbuild_refs(apps, schema_editor): # pragma: no cover
"""
Provided only for reverse migration compatibility
"""

View File

@ -33,7 +33,7 @@ def add_shipment(apps, schema_editor):
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
# Create a new Shipment instance against this order
@ -41,13 +41,13 @@ def add_shipment(apps, schema_editor):
order=order,
)
if order.status == SalesOrderStatus.SHIPPED:
if order.status == SalesOrderStatus.SHIPPED: # pragma: no cover
shipment.shipment_date = order.shipment_date
shipment.save()
# Iterate through each allocation associated with this order
for allocation in allocations:
for allocation in allocations: # pragma: no cover
allocation.shipment = shipment
allocation.save()
@ -57,7 +57,7 @@ def add_shipment(apps, schema_editor):
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
"""

View File

@ -22,7 +22,7 @@ def calculate_shipped_quantity(apps, schema_editor):
StockItem = apps.get_model('stock', 'stockitem')
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:
item.shipped = item.quantity
@ -40,7 +40,7 @@ def calculate_shipped_quantity(apps, schema_editor):
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.
This function does nothing.

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ def migrate_currencies(apps, schema_editor):
remap = {}
for index, row in enumerate(results):
for index, row in enumerate(results): # pragma: no cover
pk, suffix, description = row
suffix = suffix.strip().upper()
@ -57,7 +57,7 @@ def migrate_currencies(apps, schema_editor):
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
# Copy the 'cost' field across to the 'price' field
@ -71,10 +71,10 @@ def migrate_currencies(apps, schema_editor):
count += 1
if count > 0:
if count > 0: # pragma: no cover
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.

View File

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

View File

@ -55,7 +55,7 @@ class BomItemTest(TestCase):
with self.assertRaises(django_exceptions.ValidationError):
# A validation error should be raised here
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):
"""

View File

@ -127,7 +127,7 @@ class CategoryTest(TestCase):
with self.assertRaises(ValidationError) as err:
cat.full_clean()
cat.save()
cat.save() # pragma: no cover
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')
# 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'
r1 = Part.objects.get(name='R_2K2_0805')
self.assertIsNone(r1.default_location)

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@ class SettingsMixin:
try:
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
if not plugin:

View File

@ -23,7 +23,7 @@ class IntegrationPluginError(Exception):
self.message = message
def __str__(self):
return self.message
return self.message # pragma: no cover
class MixinImplementationError(ValueError):
@ -55,7 +55,7 @@ def log_error(error, reference: str = 'general'):
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
"""
@ -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[-1] = path_parts[-1].replace(path_obj.suffix, '') # remove suffix
# remove path preixes
# remove path prefixes
if path_parts[0] == 'plugin':
path_parts.remove('plugin')
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_error({package_name: str(error)}, **log_kwargs)
new_error = IntegrationPluginError(package_name, str(error))
if do_raise:
raise IntegrationPluginError(package_name, str(error))
if do_return:
return new_error
# endregion
@ -101,14 +96,16 @@ def get_git_log(path):
"""
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]
output = None
try:
output = str(subprocess.check_output(command, cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')[1:-1]
if output:
output = output.split('\n')
else:
output = 7 * ['']
except subprocess.CalledProcessError:
output = 7 * ['']
except subprocess.CalledProcessError: # pragma: no cover
pass
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]}
@ -153,7 +150,7 @@ def get_modules(pkg):
if not k.startswith('_') and (pkg_names is None or k in pkg_names):
context[k] = v
context[name] = module
except AppRegistryNotReady:
except AppRegistryNotReady: # pragma: no cover
pass
except Exception as error:
# 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:
author = self.package.get('author')
if not author:
author = _('No author found')
author = _('No author found') # pragma: no cover
return author
@property
@ -149,7 +149,7 @@ class IntegrationPluginBase(MixinBase, plugin_base.InvenTreePluginBase):
else:
pub_date = datetime.fromisoformat(str(pub_date))
if not pub_date:
pub_date = _('No date found')
pub_date = _('No date found') # pragma: no cover
return pub_date
@property
@ -226,7 +226,7 @@ class IntegrationPluginBase(MixinBase, plugin_base.InvenTreePluginBase):
"""
Get package metadata for plugin
"""
return {}
return {} # pragma: no cover # TODO add usage for package metadata
def define_package(self):
"""
@ -241,11 +241,11 @@ class IntegrationPluginBase(MixinBase, plugin_base.InvenTreePluginBase):
# process sign state
sign_state = getattr(GitStatus, str(package.get('verified')), GitStatus.N)
if sign_state.status == 0:
self.sign_color = 'success'
self.sign_color = 'success' # pragma: no cover
elif sign_state.status == 1:
self.sign_color = 'warning'
else:
self.sign_color = 'danger'
self.sign_color = 'danger' # pragma: no cover
# set variables
self.package = package

View File

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

View File

@ -117,7 +117,7 @@ class PluginConfigInstallSerializer(serializers.Serializer):
ret['result'] = str(result, 'utf-8')
ret['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['error'] = True

View File

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

View File

@ -22,6 +22,7 @@ class PluginDetailAPITest(InvenTreeAPITestCase):
self.MSG_NO_PKG = 'Either packagename of URL must be provided'
self.PKG_NAME = 'minimal'
self.PKG_URL = 'git+https://github.com/geoffrey-a-reed/minimal'
super().setUp()
def test_plugin_install(self):
@ -35,7 +36,13 @@ class PluginDetailAPITest(InvenTreeAPITestCase):
'confirm': True,
'packagename': self.PKG_NAME
}, 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)
# invalid tries

View File

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

View File

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

View File

@ -60,13 +60,13 @@ class ReportTest(InvenTreeAPITestCase):
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)
src_file = os.path.join(src_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)
# Convert to an "internal" filename

View File

@ -21,7 +21,7 @@ def update_history(apps, schema_editor):
locations = StockLocation.objects.all()
for location in locations:
for location in locations: # pragma: no cover
# Pre-calculate pathstring
# 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)
for item in StockItem.objects.all():
for item in StockItem.objects.all(): # pragma: no cover
history = StockItemTracking.objects.filter(item=item).order_by('date')
@ -200,13 +200,13 @@ def update_history(apps, schema_editor):
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):
"""
"""
pass
pass # pragma: no cover
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
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")
update_count = 0
for item in items:
for item in items: # pragma: no cover
part_id = item.part
@ -57,10 +57,10 @@ def extract_purchase_price(apps, schema_editor):
break
if update_count > 0:
if update_count > 0: # pragma: no cover
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!
"""

View File

@ -12,7 +12,7 @@ def update_serials(apps, schema_editor):
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:
# Skip items without existing serial numbers
@ -33,7 +33,7 @@ def update_serials(apps, schema_editor):
item.save()
def nupdate_serials(apps, schema_editor):
def nupdate_serials(apps, schema_editor): # pragma: no cover
"""
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()
def reverse(apps, schema_editor):
def reverse(apps, schema_editor): # pragma: no cover
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')
def detail_url(self, pk):
return reverse('api-stock-detail', kwargs={'pk': pk})
def setUp(self):
super().setUp()
# 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.models import Group
from common.models import InvenTreeSetting
# from common.models import InvenTreeSetting
class StockViewTestCase(TestCase):
@ -64,6 +64,9 @@ class StockOwnershipTest(StockViewTestCase):
def setUp(self):
""" Add another user for ownership tests """
"""
TODO: Refactor this following test to use the new API form
super().setUp()
# 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)
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):
# Test stock location and item ownership
from .models import StockLocation

View File

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

View File

@ -32,7 +32,7 @@ class UsersConfig(AppConfig):
# First, delete any rule_set objects which have become outdated!
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)
rule.delete()

View File

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

View File

@ -3,9 +3,12 @@ from __future__ import unicode_literals
from django.test import TestCase
from django.apps import apps
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from rest_framework.authtoken.models import Token
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]
if len(missing) > 0:
if len(missing) > 0: # pragma: no cover
print("The following rulesets do not have models assigned:")
for m in missing:
print("-", m)
@ -30,7 +33,7 @@ class RuleSetModelTest(TestCase):
# 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]
if len(extra) > 0:
if len(extra) > 0: # pragma: no cover
print("The following rulesets have been improperly added to RULESET_MODELS:")
for e in extra:
print("-", e)
@ -38,7 +41,7 @@ class RuleSetModelTest(TestCase):
# Check that each ruleset has models assigned
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:")
for e in empty:
print("-", e)
@ -77,10 +80,10 @@ class RuleSetModelTest(TestCase):
missing_models = set()
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)
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:")
for m in missing_models:
print("-", m)
@ -95,11 +98,11 @@ class RuleSetModelTest(TestCase):
for model in RuleSet.RULESET_IGNORE:
defined_models.add(model)
for model in defined_models:
for model in defined_models: # pragma: no cover
if model not in available_tables:
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:")
for m in extra_models:
print("-", m)
@ -169,16 +172,16 @@ class OwnerModelTest(TestCase):
""" Add users and groups """
# Create a new user
self.user = get_user_model().objects.create_user(
username='john',
email='john@email.com',
password='custom123',
)
self.user = get_user_model().objects.create_user('username', 'user@email.com', 'password')
# Put the user into a new group
self.group = Group.objects.create(name='new_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):
# Check that owner was created for user
@ -203,3 +206,43 @@ class OwnerModelTest(TestCase):
self.group.delete()
group_as_owner = Owner.get_owner(self.group)
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]
source = ./InvenTree
[coverage:report]
omit=
InvenTree/wsgi.py
InvenTree/ci_render_js.py