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() |                 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,24 +40,32 @@ class InvenTreeMetadata(SimpleMetadata): | |||||||
|  |  | ||||||
|             table = f"{app_label}_{tbl_label}" |             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 |                 check = users.models.RuleSet.check_table_permission | ||||||
|             rolemap = { |  | ||||||
|                 'GET': 'view', |  | ||||||
|                 'OPTIONS': 'view', |  | ||||||
|                 'POST': 'add', |  | ||||||
|                 'PUT': 'change', |  | ||||||
|                 'PATCH': 'change', |  | ||||||
|                 'DELETE': 'delete', |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             # Remove any HTTP methods that the user does not have permission for |                 # Map the request method to a permission type | ||||||
|             for method, permission in rolemap.items(): |                 rolemap = { | ||||||
|                 if method in actions and not check(user, table, permission): |                     'POST': 'add', | ||||||
|                     del actions[method] |                     '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: |         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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user