mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Add more test coverage for backend (#8048)
* add test for asset redirect * fully cover spa_bundler * test token api * check without beeing authed * not possible to be reached - no cover * remove unneeded except * fully test group apis * move ignore * add tests for admin site * add full admin testing * use output as ref * ignore admin edge-case * test display name settings * refactor admin test * add more admin testing * fix tests assertation * fix assertations * add test for importer admin * remove old test for function that will not be re-added for now see https://github.com/inventree/InvenTree/pull/8018#discussion_r1734011050 * Add stock detail with wrong pk * add a few stock tests
This commit is contained in:
		| @@ -10,10 +10,11 @@ from pathlib import Path | ||||
|  | ||||
| from django.contrib.auth import get_user_model | ||||
| from django.contrib.auth.models import Group, Permission | ||||
| from django.db import connections | ||||
| from django.db import connections, models | ||||
| from django.http.response import StreamingHttpResponse | ||||
| from django.test import TestCase | ||||
| from django.test.utils import CaptureQueriesContext | ||||
| from django.urls import reverse | ||||
|  | ||||
| from djmoney.contrib.exchange.models import ExchangeBackend, Rate | ||||
| from rest_framework.test import APITestCase | ||||
| @@ -486,3 +487,30 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase): | ||||
|     def assertDictContainsSubset(self, a, b): | ||||
|         """Assert that dictionary 'a' is a subset of dictionary 'b'.""" | ||||
|         self.assertEqual(b, b | a) | ||||
|  | ||||
|  | ||||
| class AdminTestCase(InvenTreeAPITestCase): | ||||
|     """Tests for the admin interface integration.""" | ||||
|  | ||||
|     superuser = True | ||||
|  | ||||
|     def helper(self, model: type[models.Model], model_kwargs=None): | ||||
|         """Test the admin URL.""" | ||||
|         if model_kwargs is None: | ||||
|             model_kwargs = {} | ||||
|  | ||||
|         # Add object | ||||
|         obj = model.objects.create(**model_kwargs) | ||||
|         app_app, app_mdl = model._meta.app_label, model._meta.model_name | ||||
|  | ||||
|         # 'Test listing | ||||
|         response = self.get(reverse(f'admin:{app_app}_{app_mdl}_changelist')) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         # Test change view | ||||
|         response = self.get( | ||||
|             reverse(f'admin:{app_app}_{app_mdl}_change', kwargs={'object_id': obj.pk}) | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|         return obj | ||||
|   | ||||
| @@ -4,11 +4,27 @@ import os | ||||
|  | ||||
| from django.core.files.base import ContentFile | ||||
|  | ||||
| from importer.models import DataImportSession | ||||
| from InvenTree.unit_test import InvenTreeTestCase | ||||
| from importer.models import DataImportRow, DataImportSession | ||||
| from InvenTree.unit_test import AdminTestCase, InvenTreeTestCase | ||||
|  | ||||
|  | ||||
| class ImporterTest(InvenTreeTestCase): | ||||
| class ImporterMixin: | ||||
|     """Helpers for import tests.""" | ||||
|  | ||||
|     def helper_file(self): | ||||
|         """Return test data.""" | ||||
|         fn = os.path.join(os.path.dirname(__file__), 'test_data', 'companies.csv') | ||||
|  | ||||
|         with open(fn, encoding='utf-8') as input_file: | ||||
|             data = input_file.read() | ||||
|         return data | ||||
|  | ||||
|     def helper_content(self): | ||||
|         """Return content file.""" | ||||
|         return ContentFile(self.helper_file(), 'companies.csv') | ||||
|  | ||||
|  | ||||
| class ImporterTest(ImporterMixin, InvenTreeTestCase): | ||||
|     """Basic tests for file imports.""" | ||||
|  | ||||
|     def test_import_session(self): | ||||
| @@ -17,13 +33,8 @@ class ImporterTest(InvenTreeTestCase): | ||||
|  | ||||
|         n = Company.objects.count() | ||||
|  | ||||
|         fn = os.path.join(os.path.dirname(__file__), 'test_data', 'companies.csv') | ||||
|  | ||||
|         with open(fn, encoding='utf-8') as input_file: | ||||
|             data = input_file.read() | ||||
|  | ||||
|         session = DataImportSession.objects.create( | ||||
|             data_file=ContentFile(data, 'companies.csv'), model_type='company' | ||||
|             data_file=self.helper_content(), model_type='company' | ||||
|         ) | ||||
|  | ||||
|         session.extract_columns() | ||||
| @@ -61,3 +72,15 @@ class ImporterTest(InvenTreeTestCase): | ||||
|  | ||||
|     def test_field_defaults(self): | ||||
|         """Test default field values.""" | ||||
|  | ||||
|  | ||||
| class AdminTest(ImporterMixin, AdminTestCase): | ||||
|     """Tests for the admin interface integration.""" | ||||
|  | ||||
|     def test_admin(self): | ||||
|         """Test the admin URL.""" | ||||
|         session = self.helper( | ||||
|             model=DataImportSession, | ||||
|             model_kwargs={'data_file': self.helper_content(), 'model_type': 'company'}, | ||||
|         ) | ||||
|         self.helper(model=DataImportRow, model_kwargs={'session_id': session.id}) | ||||
|   | ||||
| @@ -9,7 +9,7 @@ from django.urls import reverse | ||||
|  | ||||
| from rest_framework import serializers | ||||
|  | ||||
| from InvenTree.unit_test import InvenTreeAPITestCase | ||||
| from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase | ||||
| from machine.machine_type import BaseDriver, BaseMachineType, MachineStatus | ||||
| from machine.machine_types.label_printer import LabelPrinterBaseDriver | ||||
| from machine.models import MachineConfig | ||||
| @@ -309,3 +309,11 @@ class TestLabelPrinterMachineType(TestMachineRegistryMixin, InvenTreeAPITestCase | ||||
|             }, | ||||
|             expected_code=400, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class AdminTest(AdminTestCase): | ||||
|     """Tests for the admin interface integration.""" | ||||
|  | ||||
|     def test_admin(self): | ||||
|         """Test the admin URL.""" | ||||
|         self.helper(model=MachineConfig) | ||||
|   | ||||
| @@ -16,7 +16,7 @@ from PIL import Image | ||||
| import report.models as report_models | ||||
| from build.models import Build | ||||
| from common.models import Attachment, InvenTreeSetting | ||||
| from InvenTree.unit_test import InvenTreeAPITestCase | ||||
| from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase | ||||
| from order.models import ReturnOrder, SalesOrder | ||||
| from plugin.registry import registry | ||||
| from report.models import LabelTemplate, ReportTemplate | ||||
| @@ -580,24 +580,6 @@ class TestReportTest(PrintTestMixins, ReportTest): | ||||
|             Attachment.objects.filter(model_id=item.pk, model_type='stockitem').exists() | ||||
|         ) | ||||
|  | ||||
|         return | ||||
|         # TODO @matmair - Re-add this test after https://github.com/inventree/InvenTree/pull/7074/files#r1600694356 is resolved | ||||
|         # Change the setting, now the test report should be attached automatically | ||||
|         InvenTreeSetting.set_setting('REPORT_ATTACH_TEST_REPORT', True, None) | ||||
|  | ||||
|         response = self.post( | ||||
|             url, {'template': template.pk, 'items': [item.pk]}, expected_code=201 | ||||
|         ) | ||||
|  | ||||
|         # There should be a link to the generated PDF | ||||
|         self.assertEqual(response.data['output'].startswith('/media/report/'), True) | ||||
|  | ||||
|         # Check that a report has been uploaded | ||||
|         attachment = Attachment.objects.filter( | ||||
|             model_id=item.pk, model_type='stockitem' | ||||
|         ).first() | ||||
|         self.assertIsNotNone(attachment) | ||||
|  | ||||
|     def test_mdl_build(self): | ||||
|         """Test the Build model.""" | ||||
|         self.run_print_test(Build, 'build', label=False) | ||||
| @@ -609,3 +591,11 @@ class TestReportTest(PrintTestMixins, ReportTest): | ||||
|     def test_mdl_salesorder(self): | ||||
|         """Test the SalesOrder model.""" | ||||
|         self.run_print_test(SalesOrder, 'salesorder', label=False) | ||||
|  | ||||
|  | ||||
| class AdminTest(AdminTestCase): | ||||
|     """Tests for the admin interface integration.""" | ||||
|  | ||||
|     def test_admin(self): | ||||
|         """Test the admin URL.""" | ||||
|         self.helper(model=ReportTemplate) | ||||
|   | ||||
| @@ -1527,6 +1527,11 @@ class StocktakeTest(StockAPITestCase): | ||||
|  | ||||
|     def test_action(self): | ||||
|         """Test each stocktake action endpoint, for validation.""" | ||||
|         target = { | ||||
|             'api-stock-count': '10.00000', | ||||
|             'api-stock-add': '10.00000', | ||||
|             'api-stock-remove': '10.00000', | ||||
|         } | ||||
|         for endpoint in ['api-stock-count', 'api-stock-add', 'api-stock-remove']: | ||||
|             url = reverse(endpoint) | ||||
|  | ||||
| @@ -1585,6 +1590,12 @@ class StocktakeTest(StockAPITestCase): | ||||
|                 status_code=status.HTTP_400_BAD_REQUEST, | ||||
|             ) | ||||
|  | ||||
|             # Valid POST | ||||
|             data = {'items': [{'pk': 1234, 'quantity': 10}]} | ||||
|             response = self.post(url, data, expected_code=201) | ||||
|             self.assertEqual(response.data['items'][0]['pk'], 1234) | ||||
|             self.assertEqual(response.data['items'][0]['quantity'], target[endpoint]) | ||||
|  | ||||
|     def test_transfer(self): | ||||
|         """Test stock transfers.""" | ||||
|         stock_item = StockItem.objects.get(pk=1234) | ||||
|   | ||||
| @@ -79,6 +79,11 @@ class StockDetailTest(StockViewTestCase): | ||||
|         for act in actions: | ||||
|             self.assertIn(act, html) | ||||
|  | ||||
|         # Check with a wrong pk | ||||
|         response = self.client.get(reverse('stock-item-detail', kwargs={'pk': 99})) | ||||
|         self.assertEqual(response.status_code, 302) | ||||
|         self.assertEqual(response.url, reverse('stock-index')) | ||||
|  | ||||
|  | ||||
| class StockOwnershipTest(StockViewTestCase): | ||||
|     """Tests for stock ownership views.""" | ||||
|   | ||||
| @@ -10,7 +10,7 @@ from django.test import override_settings | ||||
| from build.models import Build | ||||
| from common.models import InvenTreeSetting | ||||
| from company.models import Company | ||||
| from InvenTree.unit_test import InvenTreeTestCase | ||||
| from InvenTree.unit_test import AdminTestCase, InvenTreeTestCase | ||||
| from order.models import SalesOrder | ||||
| from part.models import Part, PartTestTemplate | ||||
| from stock.status_codes import StockHistoryCode | ||||
| @@ -1347,3 +1347,11 @@ class StockLocationTest(InvenTreeTestCase): | ||||
|         loc.location_type = None | ||||
|         loc.save() | ||||
|         self.assertEqual(loc.icon, '') | ||||
|  | ||||
|  | ||||
| class AdminTest(AdminTestCase): | ||||
|     """Tests for the admin interface integration.""" | ||||
|  | ||||
|     def test_admin(self): | ||||
|         """Test the admin URL.""" | ||||
|         self.helper(model=StockLocationType) | ||||
|   | ||||
| @@ -175,13 +175,10 @@ class GroupMixin: | ||||
|     def get_serializer(self, *args, **kwargs): | ||||
|         """Return serializer instance for this endpoint.""" | ||||
|         # Do we wish to include extra detail? | ||||
|         try: | ||||
|             params = self.request.query_params | ||||
|             kwargs['permission_detail'] = InvenTree.helpers.str2bool( | ||||
|                 params.get('permission_detail', None) | ||||
|             ) | ||||
|         except AttributeError: | ||||
|             pass | ||||
|         params = self.request.query_params | ||||
|         kwargs['permission_detail'] = InvenTree.helpers.str2bool( | ||||
|             params.get('permission_detail', None) | ||||
|         ) | ||||
|         kwargs['context'] = self.get_serializer_context() | ||||
|         return self.serializer_class(*args, **kwargs) | ||||
|  | ||||
| @@ -346,7 +343,7 @@ class GetAuthToken(APIView): | ||||
|             return Response(data) | ||||
|  | ||||
|         else: | ||||
|             raise exceptions.NotAuthenticated() | ||||
|             raise exceptions.NotAuthenticated()  # pragma: no cover | ||||
|  | ||||
|  | ||||
| class TokenListView(DestroyAPIView, ListAPI): | ||||
|   | ||||
| @@ -164,7 +164,7 @@ class ApiToken(AuthToken, InvenTree.models.MetadataMixin): | ||||
|         """ | ||||
|         # If the token has not yet been saved, return the raw key | ||||
|         if self.pk is None: | ||||
|             return self.key | ||||
|             return self.key  # pragma: no cover | ||||
|  | ||||
|         M = len(self.key) - 20 | ||||
|  | ||||
|   | ||||
| @@ -64,12 +64,21 @@ class UserAPITests(InvenTreeAPITestCase): | ||||
|         self.assertEqual(len(response.data), Group.objects.count()) | ||||
|  | ||||
|         # Check detail URL | ||||
|         pk = response.data[0]['pk'] | ||||
|         response = self.get( | ||||
|             reverse('api-group-detail', kwargs={'pk': response.data[0]['pk']}), | ||||
|             reverse('api-group-detail', kwargs={'pk': pk}), expected_code=200 | ||||
|         ) | ||||
|         self.assertIn('name', response.data) | ||||
|         self.assertNotIn('permissions', response.data) | ||||
|  | ||||
|         # Check more detailed URL | ||||
|         response = self.get( | ||||
|             reverse('api-group-detail', kwargs={'pk': pk}), | ||||
|             data={'permission_detail': True}, | ||||
|             expected_code=200, | ||||
|         ) | ||||
|  | ||||
|         self.assertIn('name', response.data) | ||||
|         self.assertIn('permissions', response.data) | ||||
|  | ||||
|     def test_logout(self): | ||||
|         """Test api logout endpoint.""" | ||||
| @@ -208,3 +217,34 @@ class UserTokenTests(InvenTreeAPITestCase): | ||||
|         ) | ||||
|         self.assertIn('key', response.data) | ||||
|         self.assertTrue(response.data['key'].startswith('inv-')) | ||||
|  | ||||
|     def test_token_api(self): | ||||
|         """Test the token API.""" | ||||
|         url = reverse('api-token-list') | ||||
|         response = self.get(url, expected_code=200) | ||||
|         self.assertEqual(response.data, []) | ||||
|  | ||||
|         # Get token | ||||
|         response = self.get(reverse('api-token'), expected_code=200) | ||||
|         self.assertIn('token', response.data) | ||||
|  | ||||
|         # Now there should be one token | ||||
|         response = self.get(url, expected_code=200) | ||||
|         self.assertEqual(len(response.data), 1) | ||||
|         self.assertEqual(response.data[0]['active'], True) | ||||
|         self.assertEqual(response.data[0]['revoked'], False) | ||||
|         self.assertEqual(response.data[0]['in_use'], False) | ||||
|         expected_day = str( | ||||
|             datetime.datetime.now().date() + datetime.timedelta(days=365) | ||||
|         ) | ||||
|         self.assertEqual(response.data[0]['expiry'], expected_day) | ||||
|  | ||||
|         # Destroy token | ||||
|         self.delete( | ||||
|             reverse('api-token-detail', kwargs={'pk': response.data[0]['id']}), | ||||
|             expected_code=204, | ||||
|         ) | ||||
|  | ||||
|         # Get token without auth (should fail) | ||||
|         self.client.logout() | ||||
|         self.get(reverse('api-token'), expected_code=401) | ||||
|   | ||||
| @@ -5,7 +5,8 @@ from django.contrib.auth.models import Group | ||||
| from django.test import TestCase, tag | ||||
| from django.urls import reverse | ||||
|  | ||||
| from InvenTree.unit_test import InvenTreeAPITestCase, InvenTreeTestCase | ||||
| from common.settings import set_global_setting | ||||
| from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase, InvenTreeTestCase | ||||
| from users.models import ApiToken, Owner, RuleSet | ||||
|  | ||||
|  | ||||
| @@ -270,6 +271,29 @@ class OwnerModelTest(InvenTreeTestCase): | ||||
|         ) | ||||
|         self.assertEqual(response['username'], self.username) | ||||
|  | ||||
|     def test_display_name(self): | ||||
|         """Test the display name for the owner.""" | ||||
|         owner = Owner.get_owner(self.user) | ||||
|         self.assertEqual(owner.name(), 'testuser') | ||||
|         self.assertEqual(str(owner), 'testuser (user)') | ||||
|  | ||||
|         # Change setting | ||||
|         set_global_setting('DISPLAY_FULL_NAMES', True) | ||||
|         self.user.first_name = 'first' | ||||
|         self.user.last_name = 'last' | ||||
|         self.user.save() | ||||
|         owner = Owner.get_owner(self.user) | ||||
|  | ||||
|         # Now first / last should be used | ||||
|         self.assertEqual(owner.name(), 'first last') | ||||
|         self.assertEqual(str(owner), 'first last (user)') | ||||
|  | ||||
|         # Reset | ||||
|         set_global_setting('DISPLAY_FULL_NAMES', False) | ||||
|         self.user.first_name = '' | ||||
|         self.user.last_name = '' | ||||
|         self.user.save() | ||||
|  | ||||
|  | ||||
| class MFALoginTest(InvenTreeAPITestCase): | ||||
|     """Some simplistic tests to ensure that MFA is working.""" | ||||
| @@ -313,3 +337,15 @@ class MFALoginTest(InvenTreeAPITestCase): | ||||
|         # Wrong login should not work | ||||
|         auth_data['password'] = 'wrong' | ||||
|         self.post(login_url, auth_data, expected_code=401) | ||||
|  | ||||
|  | ||||
| class AdminTest(AdminTestCase): | ||||
|     """Tests for the admin interface integration.""" | ||||
|  | ||||
|     def test_admin(self): | ||||
|         """Test the admin URL.""" | ||||
|         my_token = self.helper( | ||||
|             model=ApiToken, model_kwargs={'user': self.user, 'name': 'test-token'} | ||||
|         ) | ||||
|         # Additionally test str fnc | ||||
|         self.assertEqual(str(my_token), my_token.token) | ||||
|   | ||||
| @@ -37,12 +37,22 @@ class TemplateTagTest(InvenTreeTestCase): | ||||
|  | ||||
|         manifest_file = Path(__file__).parent.joinpath('static/web/.vite/manifest.json') | ||||
|         # Try with removed manifest file | ||||
|         manifest_file.rename(manifest_file.with_suffix('.json.bak'))  # Rename | ||||
|         new_name = manifest_file.rename( | ||||
|             manifest_file.with_suffix('.json.bak') | ||||
|         )  # Rename | ||||
|         resp = spa_helper.spa_bundle() | ||||
|         self.assertIsNone(resp) | ||||
|         manifest_file.with_suffix('.json.bak').rename( | ||||
|             manifest_file.with_suffix('.json') | ||||
|         )  # Name back | ||||
|  | ||||
|         # Try with differing name | ||||
|         resp = spa_helper.spa_bundle(new_name) | ||||
|         self.assertIsNotNone(resp) | ||||
|  | ||||
|         # Broken manifest file | ||||
|         manifest_file.write_text('broken') | ||||
|         resp = spa_helper.spa_bundle(manifest_file) | ||||
|         self.assertIsNone(resp) | ||||
|  | ||||
|         new_name.rename(manifest_file.with_suffix('.json'))  # Name back | ||||
|  | ||||
|     def test_spa_settings(self): | ||||
|         """Test the 'spa_settings' template tag.""" | ||||
| @@ -78,6 +88,11 @@ class TemplateTagTest(InvenTreeTestCase): | ||||
|             self.assertNotIn('show_server_selector', rsp) | ||||
|             self.assertEqual(rsp['server_list'], ['aa', 'bb']) | ||||
|  | ||||
|     def test_redirects(self): | ||||
|         """Test the redirect helper.""" | ||||
|         response = self.client.get('/assets/testpath') | ||||
|         self.assertEqual(response.url, '/static/web/assets/testpath') | ||||
|  | ||||
|  | ||||
| class TestWebHelpers(InvenTreeAPITestCase): | ||||
|     """Tests for the web helpers.""" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user