mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Adds unit testing for fancy new metadata class
This commit is contained in:
parent
eaa5913c8c
commit
82a6ff7772
@ -73,6 +73,22 @@ class InvenTreeAPITestCase(APITestCase):
|
|||||||
ruleset.save()
|
ruleset.save()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def getActions(self, url):
|
||||||
|
"""
|
||||||
|
Return a dict of the 'actions' available at a given endpoint.
|
||||||
|
Makes use of the HTTP 'OPTIONS' method to request this.
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = self.client.options(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
actions = response.data.get('actions', None)
|
||||||
|
|
||||||
|
if not actions:
|
||||||
|
actions = {}
|
||||||
|
|
||||||
|
return actions
|
||||||
|
|
||||||
def get(self, url, data={}, expected_code=200):
|
def get(self, url, data={}, expected_code=200):
|
||||||
"""
|
"""
|
||||||
Issue a GET request
|
Issue a GET request
|
||||||
|
@ -40,14 +40,14 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
|
|
||||||
table = f"{app_label}_{tbl_label}"
|
table = f"{app_label}_{tbl_label}"
|
||||||
|
|
||||||
actions = metadata['actions']
|
actions = metadata.get('actions', None)
|
||||||
|
|
||||||
|
if actions is not None:
|
||||||
|
|
||||||
check = users.models.RuleSet.check_table_permission
|
check = users.models.RuleSet.check_table_permission
|
||||||
|
|
||||||
# Map the request method to a permission type
|
# Map the request method to a permission type
|
||||||
rolemap = {
|
rolemap = {
|
||||||
'GET': 'view',
|
|
||||||
'OPTIONS': 'view',
|
|
||||||
'POST': 'add',
|
'POST': 'add',
|
||||||
'PUT': 'change',
|
'PUT': 'change',
|
||||||
'PATCH': 'change',
|
'PATCH': 'change',
|
||||||
@ -59,6 +59,14 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
if method in actions and not check(user, table, permission):
|
if method in actions and not check(user, table, permission):
|
||||||
del actions[method]
|
del actions[method]
|
||||||
|
|
||||||
|
# Add a 'DELETE' action if we are allowed to delete
|
||||||
|
if check(user, table, 'delete'):
|
||||||
|
actions['DELETE'] = True
|
||||||
|
|
||||||
|
# Add a 'VIEW' action if we are allowed to view
|
||||||
|
if check(user, table, 'view'):
|
||||||
|
actions['GET'] = True
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# We will assume that if the serializer class does *not* have a Meta
|
# We will assume that if the serializer class does *not* have a Meta
|
||||||
# then we don't need a permission
|
# then we don't need a permission
|
||||||
|
@ -157,3 +157,68 @@ class APITests(InvenTreeAPITestCase):
|
|||||||
# New role permissions should have been added now
|
# New role permissions should have been added now
|
||||||
self.assertIn('delete', roles['part'])
|
self.assertIn('delete', roles['part'])
|
||||||
self.assertIn('change', roles['build'])
|
self.assertIn('change', roles['build'])
|
||||||
|
|
||||||
|
def test_list_endpoint_actions(self):
|
||||||
|
"""
|
||||||
|
Tests for the OPTIONS method for API endpoints.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.basicAuth()
|
||||||
|
|
||||||
|
# Without any 'part' permissions, we should not see any available actions
|
||||||
|
url = reverse('api-part-list')
|
||||||
|
|
||||||
|
actions = self.getActions(url)
|
||||||
|
|
||||||
|
# No actions, as there are no permissions!
|
||||||
|
self.assertEqual(len(actions), 0)
|
||||||
|
|
||||||
|
# Assign a new role
|
||||||
|
self.assignRole('part.view')
|
||||||
|
actions = self.getActions(url)
|
||||||
|
|
||||||
|
# As we don't have "add" permission, there should be no available API actions
|
||||||
|
self.assertEqual(len(actions), 0)
|
||||||
|
|
||||||
|
# But let's make things interesting...
|
||||||
|
# Why don't we treat ourselves to some "add" permissions
|
||||||
|
self.assignRole('part.add')
|
||||||
|
|
||||||
|
actions = self.getActions(url)
|
||||||
|
|
||||||
|
self.assertIn('POST', actions)
|
||||||
|
self.assertEqual(len(actions), 1)
|
||||||
|
|
||||||
|
def test_detail_endpoint_actions(self):
|
||||||
|
"""
|
||||||
|
Tests for detail API endpoint actions
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.basicAuth()
|
||||||
|
|
||||||
|
url = reverse('api-part-detail', kwargs={'pk': 1})
|
||||||
|
|
||||||
|
actions = self.getActions(url)
|
||||||
|
|
||||||
|
# No actions, as we do not have any permissions!
|
||||||
|
self.assertEqual(len(actions), 0)
|
||||||
|
|
||||||
|
# Add a 'add' permission
|
||||||
|
# Note: 'add' permission automatically implies 'change' also
|
||||||
|
self.assignRole('part.add')
|
||||||
|
|
||||||
|
actions = self.getActions(url)
|
||||||
|
|
||||||
|
# 'add' permission does not apply here!
|
||||||
|
self.assertEqual(len(actions), 1)
|
||||||
|
self.assertIn('PUT', actions.keys())
|
||||||
|
|
||||||
|
# Add some other permissions
|
||||||
|
self.assignRole('part.change')
|
||||||
|
self.assignRole('part.delete')
|
||||||
|
|
||||||
|
actions = self.getActions(url)
|
||||||
|
|
||||||
|
self.assertEqual(len(actions), 2)
|
||||||
|
self.assertIn('PUT', actions.keys())
|
||||||
|
self.assertIn('DELETE', actions.keys())
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
# Generated by Django 3.2 on 2021-06-01 05:25
|
# Generated by Django 3.2 on 2021-06-01 05:25
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
def assign_bom_items(apps, schema_editor):
|
def assign_bom_items(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
Run through existing BuildItem objects,
|
Run through existing BuildItem objects,
|
||||||
@ -13,7 +18,7 @@ def assign_bom_items(apps, schema_editor):
|
|||||||
BomItem = apps.get_model('part', 'bomitem')
|
BomItem = apps.get_model('part', 'bomitem')
|
||||||
Part = apps.get_model('part', 'part')
|
Part = apps.get_model('part', 'part')
|
||||||
|
|
||||||
print("Assigning BomItems to existing BuildItem objects")
|
logger.info("Assigning BomItems to existing BuildItem objects")
|
||||||
|
|
||||||
count_valid = 0
|
count_valid = 0
|
||||||
count_total = 0
|
count_total = 0
|
||||||
@ -41,7 +46,7 @@ def assign_bom_items(apps, schema_editor):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if count_total > 0:
|
if count_total > 0:
|
||||||
print(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):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Generated by Django 3.0.7 on 2020-11-10 10:11
|
# Generated by Django 3.0.7 on 2020-11-10 10:11
|
||||||
|
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from moneyed import CURRENCIES
|
from moneyed import CURRENCIES
|
||||||
@ -7,6 +8,9 @@ from django.db import migrations, connection
|
|||||||
from company.models import SupplierPriceBreak
|
from company.models import SupplierPriceBreak
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
def migrate_currencies(apps, schema_editor):
|
def migrate_currencies(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
Migrate from the 'old' method of handling currencies,
|
Migrate from the 'old' method of handling currencies,
|
||||||
@ -19,7 +23,7 @@ def migrate_currencies(apps, schema_editor):
|
|||||||
for the SupplierPriceBreak model, to a new django-money compatible currency.
|
for the SupplierPriceBreak model, to a new django-money compatible currency.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print("Updating currency references for SupplierPriceBreak model...")
|
logger.info("Updating currency references for SupplierPriceBreak model...")
|
||||||
|
|
||||||
# A list of available currency codes
|
# A list of available currency codes
|
||||||
currency_codes = CURRENCIES.keys()
|
currency_codes = CURRENCIES.keys()
|
||||||
@ -39,7 +43,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:
|
||||||
print("Missing suffix:", suffix)
|
logger.warning(f"Missing suffix: '{suffix}'")
|
||||||
|
|
||||||
while suffix not in currency_codes:
|
while suffix not in currency_codes:
|
||||||
# Ask the user to input a valid currency
|
# Ask the user to input a valid currency
|
||||||
@ -72,7 +76,7 @@ def migrate_currencies(apps, schema_editor):
|
|||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
if count > 0:
|
if count > 0:
|
||||||
print(f"Updated {count} SupplierPriceBreak rows")
|
logger.info(f"Updated {count} SupplierPriceBreak rows")
|
||||||
|
|
||||||
def reverse_currencies(apps, schema_editor):
|
def reverse_currencies(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
|
@ -208,7 +208,7 @@ class RuleSet(models.Model):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# Print message instead of throwing an error
|
# Print message instead of throwing an error
|
||||||
print("Failed permission check for", table, permission)
|
logger.info(f"User '{user.name}' failed permission check for {table}.{permission}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
Loading…
Reference in New Issue
Block a user