diff --git a/.coveragerc b/.coveragerc index 948d835f41..409c378cac 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,8 +4,8 @@ omit = # Do not run coverage on migration files */migrations/* InvenTree/manage.py - InvenTree/keygen.py - Inventree/InvenTree/middleware.py - Inventree/InvenTree/utils.py - Inventree/InvenTree/wsgi.py + InvenTree/setup.py + InvenTree/InvenTree/middleware.py + InvenTree/InvenTree/utils.py + InvenTree/InvenTree/wsgi.py InvenTree/users/apps.py \ No newline at end of file diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 66f22be9e4..553768beeb 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -159,16 +159,28 @@ WSGI_APPLICATION = 'InvenTree.wsgi.application' DATABASES = {} -# Database backend selection -if 'database' in CONFIG: - DATABASES['default'] = CONFIG['database'] -else: - eprint("Warning: Database backend not specified - using default (sqlite)") +""" +When running unit tests, enforce usage of sqlite3 database, +so that the tests can be run in RAM without any setup requirements +""" +if 'test' in sys.argv: + eprint('Running tests - Using sqlite3 memory database') DATABASES['default'] = { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'inventree_db.sqlite3'), + 'NAME': 'test_db.sqlite3' } +# Database backend selection +else: + if 'database' in CONFIG: + DATABASES['default'] = CONFIG['database'] + else: + eprint("Warning: Database backend not specified - using default (sqlite)") + DATABASES['default'] = { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'inventree_db.sqlite3'), + } + CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', diff --git a/InvenTree/build/fixtures/build.yaml b/InvenTree/build/fixtures/build.yaml new file mode 100644 index 0000000000..d0fc30755a --- /dev/null +++ b/InvenTree/build/fixtures/build.yaml @@ -0,0 +1,21 @@ +# Construct build objects + +- model: build.build + fields: + part: 25 + batch: 'B1' + title: 'Building 7 parts' + quantity: 7 + notes: 'Some simple notes' + status: 10 # PENDING + creation_date: '2019-03-16' + +- model: build.build + fields: + part: 50 + title: 'Making things' + batch: 'B2' + status: 40 # COMPLETE + quantity: 21 + notes: 'Some more simple notes' + creation_date: '2019-03-16' \ No newline at end of file diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index 36e8223a9b..06eafa41ab 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -2,32 +2,35 @@ from __future__ import unicode_literals from django.test import TestCase +from django.urls import reverse +from django.contrib.auth import get_user_model + +from rest_framework.test import APITestCase +from rest_framework import status + +import json from .models import Build -from part.models import Part from InvenTree.status_codes import BuildStatus class BuildTestSimple(TestCase): + fixtures = [ + 'category', + 'part', + 'location', + 'build', + ] + def setUp(self): - part = Part.objects.create(name='Test part', - description='Simple description') + # Create a user for auth + User = get_user_model() + User.objects.create_user('testuser', 'test@testing.com', 'password') - Build.objects.create(part=part, - batch='B1', - status=BuildStatus.PENDING, - title='Building 7 parts', - quantity=7, - notes='Some simple notes') - - Build.objects.create(part=part, - batch='B2', - status=BuildStatus.COMPLETE, - title='Building 21 parts', - quantity=21, - notes='Some simple notes') + self.user = User.objects.get(username='testuser') + self.client.login(username='testuser', password='password') def test_build_objects(self): # Ensure the Build objects were correctly created @@ -36,7 +39,7 @@ class BuildTestSimple(TestCase): self.assertEqual(b.batch, 'B2') self.assertEqual(b.quantity, 21) - self.assertEqual(str(b), 'Build 21 x Test part - Simple description') + self.assertEqual(str(b), 'Build 21 x Orphan - A part without a category') def test_url(self): b1 = Build.objects.get(pk=1) @@ -62,13 +65,226 @@ class BuildTestSimple(TestCase): # TODO - Generate BOM for test part pass - def cancel_build(self): + def test_cancel_build(self): """ Test build cancellation function """ build = Build.objects.get(id=1) self.assertEqual(build.status, BuildStatus.PENDING) - build.cancelBuild() + build.cancelBuild(self.user) self.assertEqual(build.status, BuildStatus.CANCELLED) + + +class TestBuildAPI(APITestCase): + """ + Series of tests for the Build DRF API + - Tests for Build API + - Tests for BuildItem API + """ + + fixtures = [ + 'category', + 'part', + 'location', + 'build', + ] + + def setUp(self): + # Create a user for auth + User = get_user_model() + User.objects.create_user('testuser', 'test@testing.com', 'password') + + self.client.login(username='testuser', password='password') + + def test_get_build_list(self): + """ Test that we can retrieve list of build objects """ + url = reverse('api-build-list') + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_build_item_list(self): + """ Test that we can retrieve list of BuildItem objects """ + url = reverse('api-build-item-list') + + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Test again, filtering by park ID + response = self.client.get(url, {'part': '1'}, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class TestBuildViews(TestCase): + """ Tests for Build app views """ + + fixtures = [ + 'category', + 'part', + 'location', + 'build', + ] + + def setUp(self): + super().setUp() + + # Create a user + User = get_user_model() + User.objects.create_user('username', 'user@email.com', 'password') + + self.client.login(username='username', password='password') + + def test_build_index(self): + """ test build index view """ + + response = self.client.get(reverse('build-index')) + self.assertEqual(response.status_code, 200) + + content = str(response.content) + + # Content should contain build titles + for build in Build.objects.all(): + self.assertIn(build.title, content) + + def test_build_detail(self): + """ Test the detail view for a Build object """ + + pk = 1 + + response = self.client.get(reverse('build-detail', args=(pk,))) + self.assertEqual(response.status_code, 200) + + build = Build.objects.get(pk=pk) + + content = str(response.content) + + self.assertIn(build.title, content) + + def test_build_create(self): + """ Test the build creation view (ajax form) """ + + url = reverse('build-create') + + # Create build without specifying part + response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + # Create build with valid part + response = self.client.get(url, {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + # Create build with invalid part + response = self.client.get(url, {'part': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + def test_build_allocate(self): + """ Test the part allocation view for a Build """ + + url = reverse('build-allocate', args=(1,)) + + # Get the page normally + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + # Get the page in editing mode + response = self.client.get(url, {'edit': 1}) + self.assertEqual(response.status_code, 200) + + def test_build_item_create(self): + """ Test the BuildItem creation view (ajax form) """ + + url = reverse('build-item-create') + + # Try without a part specified + response = self.client.get(url, {'build': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + # Try with an invalid build ID + response = self.client.get(url, {'build': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + # Try with a valid part specified + response = self.client.get(url, {'build': 1, 'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + # Try with an invalid part specified + response = self.client.get(url, {'build': 1, 'part': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + def test_build_item_edit(self): + """ Test the BuildItem edit view (ajax form) """ + + # TODO + # url = reverse('build-item-edit') + pass + + def test_build_complete(self): + """ Test the build completion form """ + + url = reverse('build-complete', args=(1,)) + + # Test without confirmation + response = self.client.post(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + data = json.loads(response.content) + self.assertFalse(data['form_valid']) + + # Test with confirmation, valid location + response = self.client.post(url, {'confirm': 1, 'location': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + data = json.loads(response.content) + self.assertTrue(data['form_valid']) + + # Test with confirmation, invalid location + response = self.client.post(url, {'confirm': 1, 'location': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + data = json.loads(response.content) + self.assertFalse(data['form_valid']) + + def test_build_cancel(self): + """ Test the build cancellation form """ + + url = reverse('build-cancel', args=(1,)) + + # Test without confirmation + response = self.client.post(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + data = json.loads(response.content) + self.assertFalse(data['form_valid']) + + b = Build.objects.get(pk=1) + self.assertEqual(b.status, 10) # Build status is still PENDING + + # Test with confirmation + response = self.client.post(url, {'confirm_cancel': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + data = json.loads(response.content) + self.assertTrue(data['form_valid']) + + b = Build.objects.get(pk=1) + self.assertEqual(b.status, 30) # Build status is now CANCELLED + + def test_build_unallocate(self): + """ Test the build unallocation view (ajax form) """ + + url = reverse('build-unallocate', args=(1,)) + + # Test without confirmation + response = self.client.post(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + data = json.loads(response.content) + self.assertFalse(data['form_valid']) + + # Test with confirmation + response = self.client.post(url, {'confirm': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + + data = json.loads(response.content) + self.assertTrue(data['form_valid']) diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index eb267064b9..7a1dceaf23 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -518,7 +518,9 @@ class BuildItemCreate(AjaxCreateView): part = Part.objects.get(pk=part_id) except Part.DoesNotExist: part = None - + else: + part = None + if build_id: try: build = Build.objects.get(pk=build_id)