From 80771beee91dc7c215f27c9384397d497a38531b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 15 Aug 2019 21:16:12 +1000 Subject: [PATCH 1/7] FIxtures for build testing --- InvenTree/build/fixtures/build.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 InvenTree/build/fixtures/build.yaml 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 From 62f007529ea68352c1362e2c020ea06cde7c46c5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 15 Aug 2019 21:19:59 +1000 Subject: [PATCH 2/7] Bug fix in build views - Untested code path meant variable was not being set --- InvenTree/build/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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) From cd05ed91aa3da0646bbf0362af08fe2e1e011a5a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 15 Aug 2019 21:35:16 +1000 Subject: [PATCH 3/7] More tests for Build API and views --- InvenTree/build/tests.py | 210 +++++++++++++++++++++++++++++++++++---- 1 file changed, 192 insertions(+), 18 deletions(-) diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index 36e8223a9b..6816b93fcd 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -2,6 +2,13 @@ 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 @@ -11,23 +18,20 @@ 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 +40,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 +66,183 @@ 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_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 \ No newline at end of file From bed74f273c4d12a27102dd7cef05393dbb25c73b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 15 Aug 2019 21:47:26 +1000 Subject: [PATCH 4/7] Fix .coveragerc --- .coveragerc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.coveragerc b/.coveragerc index 948d835f41..b6b5f5a09d 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/secret.py + InvenTree/InvenTree/middleware.py + InvenTree/InvenTree/utils.py + InvenTree/InvenTree/wsgi.py InvenTree/users/apps.py \ No newline at end of file From 087492faf8a2b942e712a5ccfeac8e2abb5f9fa6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 15 Aug 2019 21:49:40 +1000 Subject: [PATCH 5/7] More build tests --- .coveragerc | 2 +- InvenTree/build/tests.py | 48 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/.coveragerc b/.coveragerc index b6b5f5a09d..409c378cac 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,7 +4,7 @@ omit = # Do not run coverage on migration files */migrations/* InvenTree/manage.py - InvenTree/secret.py + InvenTree/setup.py InvenTree/InvenTree/middleware.py InvenTree/InvenTree/utils.py InvenTree/InvenTree/wsgi.py diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index 6816b93fcd..0092cab091 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -221,6 +221,32 @@ class TestBuildViews(TestCase): # 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 """ @@ -231,7 +257,6 @@ class TestBuildViews(TestCase): self.assertEqual(response.status_code, 200) data = json.loads(response.content) - self.assertFalse(data['form_valid']) b = Build.objects.get(pk=1) @@ -245,4 +270,23 @@ class TestBuildViews(TestCase): self.assertTrue(data['form_valid']) b = Build.objects.get(pk=1) - self.assertEqual(b.status, 30) # Build status is now CANCELLED \ No newline at end of file + 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']) From 9f5325d61f72046abffcc0bf3dbcdacc1c1c0e3c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 15 Aug 2019 21:50:42 +1000 Subject: [PATCH 6/7] PEP fixes --- InvenTree/build/tests.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index 0092cab091..06eafa41ab 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -11,7 +11,6 @@ from rest_framework import status import json from .models import Build -from part.models import Part from InvenTree.status_codes import BuildStatus @@ -78,7 +77,6 @@ class BuildTestSimple(TestCase): self.assertEqual(build.status, BuildStatus.CANCELLED) - class TestBuildAPI(APITestCase): """ Series of tests for the Build DRF API @@ -199,11 +197,11 @@ class TestBuildViews(TestCase): url = reverse('build-item-create') # Try without a part specified - response = self.client.get(url, {'build': 1,}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + 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') + response = self.client.get(url, {'build': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) # Try with a valid part specified From 41bfdc14325a0e37285f8e06ddc33f91656f0c0a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 15 Aug 2019 21:57:34 +1000 Subject: [PATCH 7/7] Enforce usage of sqlite3 for running tests - Simplifies tests by creating a database in memory - Does not affect the user setup at all --- InvenTree/InvenTree/settings.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) 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',