diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index e409c3dc4b..b787668a9d 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -205,10 +205,12 @@ 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') + eprint('InvenTree: Running tests - Using sqlite3 memory database') DATABASES['default'] = { + # Ensure sqlite3 backend is being used 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'test_db.sqlite3' + # Doesn't matter what the database is called, it is executed in RAM + 'NAME': 'ram_test_db.sqlite3', } # Database backend selection diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 6d90f56402..abafcb58df 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -371,11 +371,12 @@ class Build(MPTTModel): parts = [] for item in self.part.bom_items.all().prefetch_related('sub_part'): - part = {'part': item.sub_part, - 'per_build': item.quantity, - 'quantity': item.quantity * self.quantity, - 'allocated': self.getAllocatedQuantity(item.sub_part) - } + part = { + 'part': item.sub_part, + 'per_build': item.quantity, + 'quantity': item.quantity * self.quantity, + 'allocated': self.getAllocatedQuantity(item.sub_part) + } parts.append(part) diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py new file mode 100644 index 0000000000..125bc684da --- /dev/null +++ b/InvenTree/build/test_build.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- + +from django.test import TestCase + +from django.core.exceptions import ValidationError +from django.db import transaction +from django.db.utils import IntegrityError + +from build.models import Build, BuildItem +from stock.models import StockItem +from part.models import Part, BomItem +from InvenTree import status_codes as status + + +class BuildTest(TestCase): + """ + Run some tests to ensure that the Build model is working properly. + """ + + def setUp(self): + """ + Initialize data to use for these tests. + """ + + # Create a base "Part" + self.assembly = Part.objects.create( + name="An assembled part", + description="Why does it matter what my description is?", + assembly=True + ) + + self.sub_part_1 = Part.objects.create( + name="Widget A", + description="A widget", + component=True + ) + + self.sub_part_2 = Part.objects.create( + name="Widget B", + description="A widget", + component=True + ) + + # Create BOM item links for the parts + BomItem.objects.create( + part=self.assembly, + sub_part=self.sub_part_1, + quantity=10 + ) + + BomItem.objects.create( + part=self.assembly, + sub_part=self.sub_part_2, + quantity=25 + ) + + # Create a "Build" object to make 10x objects + self.build = Build.objects.create( + title="This is a build", + part=self.assembly, + quantity=10 + ) + + # Create some stock items to assign to the build + self.stock_1_1 = StockItem.objects.create(part=self.sub_part_1, quantity=1000) + self.stock_1_2 = StockItem.objects.create(part=self.sub_part_1, quantity=100) + + self.stock_2_1 = StockItem.objects.create(part=self.sub_part_2, quantity=5000) + + def test_init(self): + # Perform some basic tests before we start the ball rolling + + self.assertEqual(StockItem.objects.count(), 3) + self.assertEqual(self.build.status, status.BuildStatus.PENDING) + self.assertFalse(self.build.isFullyAllocated()) + + self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_1)) + self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2)) + + self.assertEqual(self.build.getRequiredQuantity(self.sub_part_1), 100) + self.assertEqual(self.build.getRequiredQuantity(self.sub_part_2), 250) + + self.assertTrue(self.build.can_build) + self.assertFalse(self.build.is_complete) + + def test_duplicate_bom_line(self): + # Try to add a duplicate BOM item - it should fail! + + with self.assertRaises(IntegrityError): + BomItem.objects.create( + part=self.assembly, + sub_part=self.sub_part_1, + quantity=99 + ) + + def allocate_stock(self, q11, q12, q21): + # Assign stock to this build + + BuildItem.objects.create( + build=self.build, + stock_item=self.stock_1_1, + quantity=q11 + ) + + BuildItem.objects.create( + build=self.build, + stock_item=self.stock_1_2, + quantity=q12 + ) + + BuildItem.objects.create( + build=self.build, + stock_item=self.stock_2_1, + quantity=q21 + ) + + with transaction.atomic(): + with self.assertRaises(IntegrityError): + BuildItem.objects.create( + build=self.build, + stock_item=self.stock_2_1, + quantity=99 + ) + + self.assertEqual(BuildItem.objects.count(), 3) + + def test_partial_allocation(self): + + self.allocate_stock(50, 50, 200) + + self.assertFalse(self.build.isFullyAllocated()) + self.assertTrue(self.build.isPartFullyAllocated(self.sub_part_1)) + self.assertFalse(self.build.isPartFullyAllocated(self.sub_part_2)) + + + def test_cancel(self): + + self.allocate_stock(50, 50, 200) + self.build.cancelBuild(None) + + self.assertEqual(BuildItem.objects.count(), 0) + + def test_complete(self): + + self.allocate_stock(50, 50, 250) + + self.assertTrue(self.build.isFullyAllocated()) + + self.build.completeBuild(None, None, None) + + self.assertEqual(self.build.status, status.BuildStatus.COMPLETE) + + # the original BuildItem objects should have been deleted! + self.assertEqual(BuildItem.objects.count(), 0) + + # Four new stock items should have been created! + # - One for the build output + # - Three for the split items assigned to the build + self.assertEqual(StockItem.objects.count(), 7) + + # Stock should have been subtracted from the original items + self.assertEqual(StockItem.objects.get(pk=1).quantity, 950) + self.assertEqual(StockItem.objects.get(pk=2).quantity, 50) + self.assertEqual(StockItem.objects.get(pk=3).quantity, 4750) + + # New stock items created and assigned to the build + self.assertEqual(StockItem.objects.get(pk=4).quantity, 50) + self.assertEqual(StockItem.objects.get(pk=4).build_order, self.build) + self.assertEqual(StockItem.objects.get(pk=4).status, status.StockStatus.ASSIGNED_TO_BUILD) + + self.assertEqual(StockItem.objects.get(pk=5).quantity, 50) + self.assertEqual(StockItem.objects.get(pk=5).build_order, self.build) + self.assertEqual(StockItem.objects.get(pk=5).status, status.StockStatus.ASSIGNED_TO_BUILD) + + self.assertEqual(StockItem.objects.get(pk=6).quantity, 250) + self.assertEqual(StockItem.objects.get(pk=6).build_order, self.build) + self.assertEqual(StockItem.objects.get(pk=6).status, status.StockStatus.ASSIGNED_TO_BUILD) + + # And a new stock item created for the build output + self.assertEqual(StockItem.objects.get(pk=7).quantity, 10) + self.assertEqual(StockItem.objects.get(pk=7).build, self.build) \ No newline at end of file diff --git a/InvenTree/order/test_sales_order.py b/InvenTree/order/test_sales_order.py index d0d34a517c..6cc48c3b6f 100644 --- a/InvenTree/order/test_sales_order.py +++ b/InvenTree/order/test_sales_order.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from django.test import TestCase from django.core.exceptions import ValidationError