mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	Adds unit testing for fancy new metadata class
This commit is contained in:
		| @@ -73,6 +73,22 @@ class InvenTreeAPITestCase(APITestCase): | ||||
|                 ruleset.save() | ||||
|                 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): | ||||
|         """ | ||||
|         Issue a GET request | ||||
|   | ||||
| @@ -40,24 +40,32 @@ class InvenTreeMetadata(SimpleMetadata): | ||||
|  | ||||
|             table = f"{app_label}_{tbl_label}" | ||||
|  | ||||
|             actions = metadata['actions'] | ||||
|             actions = metadata.get('actions', None) | ||||
|  | ||||
|             check = users.models.RuleSet.check_table_permission | ||||
|             if actions is not None: | ||||
|  | ||||
|             # Map the request method to a permission type | ||||
|             rolemap = { | ||||
|                 'GET': 'view', | ||||
|                 'OPTIONS': 'view', | ||||
|                 'POST': 'add', | ||||
|                 'PUT': 'change', | ||||
|                 'PATCH': 'change', | ||||
|                 'DELETE': 'delete', | ||||
|             } | ||||
|                 check = users.models.RuleSet.check_table_permission | ||||
|  | ||||
|             # Remove any HTTP methods that the user does not have permission for | ||||
|             for method, permission in rolemap.items(): | ||||
|                 if method in actions and not check(user, table, permission): | ||||
|                     del actions[method] | ||||
|                 # Map the request method to a permission type | ||||
|                 rolemap = { | ||||
|                     'POST': 'add', | ||||
|                     'PUT': 'change', | ||||
|                     'PATCH': 'change', | ||||
|                     'DELETE': 'delete', | ||||
|                 } | ||||
|  | ||||
|                 # Remove any HTTP methods that the user does not have permission for | ||||
|                 for method, permission in rolemap.items(): | ||||
|                     if method in actions and not check(user, table, permission): | ||||
|                         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: | ||||
|             # We will assume that if the serializer class does *not* have a Meta | ||||
|   | ||||
| @@ -157,3 +157,68 @@ class APITests(InvenTreeAPITestCase): | ||||
|         # New role permissions should have been added now | ||||
|         self.assertIn('delete', roles['part']) | ||||
|         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 | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| logger = logging.getLogger('inventree') | ||||
|  | ||||
|  | ||||
| def assign_bom_items(apps, schema_editor): | ||||
|     """ | ||||
|     Run through existing BuildItem objects, | ||||
| @@ -13,7 +18,7 @@ def assign_bom_items(apps, schema_editor): | ||||
|     BomItem = apps.get_model('part', 'bomitem') | ||||
|     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_total = 0 | ||||
| @@ -41,7 +46,7 @@ def assign_bom_items(apps, schema_editor): | ||||
|             pass | ||||
|  | ||||
|     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): | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| # Generated by Django 3.0.7 on 2020-11-10 10:11 | ||||
|  | ||||
| import logging | ||||
| import sys | ||||
|  | ||||
| from moneyed import CURRENCIES | ||||
| @@ -7,6 +8,9 @@ from django.db import migrations, connection | ||||
| from company.models import SupplierPriceBreak | ||||
|  | ||||
|  | ||||
| logger = logging.getLogger('inventree') | ||||
|  | ||||
|  | ||||
| def migrate_currencies(apps, schema_editor): | ||||
|     """ | ||||
|     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. | ||||
|     """ | ||||
|  | ||||
|     print("Updating currency references for SupplierPriceBreak model...") | ||||
|     logger.info("Updating currency references for SupplierPriceBreak model...") | ||||
|  | ||||
|     # A list of available currency codes | ||||
|     currency_codes = CURRENCIES.keys() | ||||
| @@ -39,7 +43,7 @@ def migrate_currencies(apps, schema_editor): | ||||
|         suffix = suffix.strip().upper() | ||||
|  | ||||
|         if suffix not in currency_codes: | ||||
|             print("Missing suffix:", suffix) | ||||
|             logger.warning(f"Missing suffix: '{suffix}'") | ||||
|  | ||||
|             while suffix not in currency_codes: | ||||
|                 # Ask the user to input a valid currency | ||||
| @@ -72,7 +76,7 @@ def migrate_currencies(apps, schema_editor): | ||||
|         count += 1 | ||||
|  | ||||
|     if count > 0: | ||||
|         print(f"Updated {count} SupplierPriceBreak rows") | ||||
|         logger.info(f"Updated {count} SupplierPriceBreak rows") | ||||
|  | ||||
| def reverse_currencies(apps, schema_editor): | ||||
|     """ | ||||
|   | ||||
| @@ -208,7 +208,7 @@ class RuleSet(models.Model): | ||||
|                     return True | ||||
|  | ||||
|         # 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 | ||||
|  | ||||
|     @staticmethod | ||||
|   | ||||
		Reference in New Issue
	
	Block a user