From f36f02b27f58ac0198bcecebdd081cc9038966fb Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 25 Apr 2019 17:30:44 +1000 Subject: [PATCH] Tests for stock app - Increase coverage of Stock/models.py to 84% --- .coveragerc | 3 +- InvenTree/part/test_category.py | 2 +- InvenTree/stock/models.py | 25 ++-- InvenTree/stock/test_stock_item.py | 7 -- InvenTree/stock/test_stock_location.py | 7 -- InvenTree/stock/test_stock_tracking.py | 7 -- InvenTree/stock/tests.py | 162 +++++++++++++++++++++++++ 7 files changed, 176 insertions(+), 37 deletions(-) delete mode 100644 InvenTree/stock/test_stock_item.py delete mode 100644 InvenTree/stock/test_stock_location.py delete mode 100644 InvenTree/stock/test_stock_tracking.py create mode 100644 InvenTree/stock/tests.py diff --git a/.coveragerc b/.coveragerc index ed8dd1d86e..0b7bc7b9dc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,4 +2,5 @@ source = ./InvenTree omit = # Do not run coverage on migration files - */migrations/* \ No newline at end of file + */migrations/* + InvenTree/manage.py \ No newline at end of file diff --git a/InvenTree/part/test_category.py b/InvenTree/part/test_category.py index 9fee29332c..bebe2c96ac 100644 --- a/InvenTree/part/test_category.py +++ b/InvenTree/part/test_category.py @@ -42,7 +42,7 @@ class CategoryTest(TestCase): childs = self.p1.getUniqueChildren() self.assertIn(self.p2.id, childs) - self.assertIn(self.p2.id, childs) + self.assertIn(self.p3.id, childs) def test_unique_parents(self): parents = self.p2.getUniqueParents() diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 312bff9bbe..7333cc4f7f 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -28,11 +28,6 @@ class StockLocation(InvenTreeTree): def get_absolute_url(self): return reverse('stock-location-detail', kwargs={'pk': self.id}) - @property - def stock_items(self): - return self.stockitem_set.all() - - @property def has_items(self): return self.stock_items.count() > 0 @@ -128,7 +123,7 @@ class StockItem(models.Model): # Where the part is stored. If the part has been used to build another stock item, the location may not make sense location = models.ForeignKey(StockLocation, on_delete=models.DO_NOTHING, - related_name='items', blank=True, null=True, + related_name='stock_items', blank=True, null=True, help_text='Where is this stock item located?') # If this StockItem belongs to another StockItem (e.g. as part of a sub-assembly) @@ -253,7 +248,7 @@ class StockItem(models.Model): count = int(count) if count < 0 or self.infinite: - return + return False self.quantity = count self.stocktake_date = datetime.now().date() @@ -265,6 +260,8 @@ class StockItem(models.Model): notes=notes, system=True) + return True + @transaction.atomic def add_stock(self, quantity, user, notes=''): """ Add items to stock @@ -276,7 +273,7 @@ class StockItem(models.Model): # Ignore amounts that do not make sense if quantity <= 0 or self.infinite: - return + return False self.quantity += quantity @@ -287,18 +284,20 @@ class StockItem(models.Model): notes=notes, system=True) + return True + @transaction.atomic def take_stock(self, quantity, user, notes=''): """ Remove items from stock """ if self.quantity == 0: - return + return False quantity = int(quantity) if quantity <= 0 or self.infinite: - return + return False self.quantity -= quantity @@ -312,6 +311,8 @@ class StockItem(models.Model): notes=notes, system=True) + return True + def __str__(self): s = '{n} x {part}'.format( n=self.quantity, @@ -322,10 +323,6 @@ class StockItem(models.Model): return s - @property - def is_trackable(self): - return self.part.trackable - class StockItemTracking(models.Model): """ Stock tracking entry diff --git a/InvenTree/stock/test_stock_item.py b/InvenTree/stock/test_stock_item.py deleted file mode 100644 index becd6e4e69..0000000000 --- a/InvenTree/stock/test_stock_item.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.test import TestCase - - -class StockItemTest(TestCase): - - def setUp(self): - pass diff --git a/InvenTree/stock/test_stock_location.py b/InvenTree/stock/test_stock_location.py deleted file mode 100644 index 0a89aeed33..0000000000 --- a/InvenTree/stock/test_stock_location.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.test import TestCase - - -class StockLocationTest(TestCase): - - def setUp(self): - pass diff --git a/InvenTree/stock/test_stock_tracking.py b/InvenTree/stock/test_stock_tracking.py deleted file mode 100644 index 15b79cbe66..0000000000 --- a/InvenTree/stock/test_stock_tracking.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.test import TestCase - - -class StockTrackingTest(TestCase): - - def setUp(self): - pass diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py new file mode 100644 index 0000000000..d5b1f8c3f1 --- /dev/null +++ b/InvenTree/stock/tests.py @@ -0,0 +1,162 @@ +from django.test import TestCase + +from .models import StockLocation, StockItem, StockItemTracking +from part.models import Part + + +class StockTest(TestCase): + """ + Tests to ensure that the stock location tree functions correcly + """ + + def setUp(self): + # Initialize some categories + self.loc1 = StockLocation.objects.create(name='L0', + description='Top level category', + parent=None) + + self.loc2 = StockLocation.objects.create(name='L1.1', + description='Second level 1/2', + parent=self.loc1) + + self.loc3 = StockLocation.objects.create(name='L1.2', + description='Second level 2/2', + parent=self.loc1) + + self.loc4 = StockLocation.objects.create(name='L2.1', + description='Third level 1/2', + parent=self.loc2) + + self.loc5 = StockLocation.objects.create(name='L2.2', + description='Third level 2/2', + parent=self.loc3) + + # Add some items to loc4 (all copies of a single part) + p = Part.objects.create(name='ACME Part', description='This is a part!') + + StockItem.objects.create(part=p, location=self.loc4, quantity=1000) + StockItem.objects.create(part=p, location=self.loc4, quantity=250) + StockItem.objects.create(part=p, location=self.loc4, quantity=12) + + def test_simple(self): + it = StockItem.objects.get(pk=2) + self.assertEqual(it.get_absolute_url(), '/stock/item/2/') + self.assertEqual(self.loc4.get_absolute_url(), '/stock/location/4/') + + def test_strings(self): + it = StockItem.objects.get(pk=2) + self.assertEqual(str(it), '250 x ACME Part @ L2.1') + + def test_parent(self): + self.assertEqual(StockLocation.objects.count(), 5) + self.assertEqual(self.loc1.parent, None) + self.assertEqual(self.loc2.parent, self.loc1) + self.assertEqual(self.loc5.parent, self.loc3) + + def test_children(self): + self.assertTrue(self.loc1.has_children) + self.assertFalse(self.loc5.has_children) + + childs = self.loc1.getUniqueChildren() + + self.assertIn(self.loc2.id, childs) + self.assertIn(self.loc4.id, childs) + + def test_paths(self): + self.assertEqual(self.loc5.pathstring, 'L0/L1.2/L2.2') + + def test_items(self): + # Location 5 should have no items + self.assertFalse(self.loc5.has_items()) + self.assertFalse(self.loc3.has_items()) + + # Location 4 should have three stock items + self.assertEqual(self.loc4.stock_items.count(), 3) + + def test_stock_count(self): + part = Part.objects.get(pk=1) + + # There should be 1262 items in stock + self.assertEqual(part.total_stock, 1262) + + def test_delete_location(self): + # Delete location - parts should move to parent location + self.loc4.delete() + + # There should still be 3 stock items + self.assertEqual(StockItem.objects.count(), 3) + + # Parent location should have moved up to loc2 + for it in StockItem.objects.all(): + self.assertEqual(it.location, self.loc2) + + def test_move(self): + # Move the first stock item to loc5 + it = StockItem.objects.get(pk=1) + self.assertNotEqual(it.location, self.loc5) + self.assertTrue(it.move(self.loc5, 'Moved to another place', None)) + self.assertEqual(it.location, self.loc5) + + # Check that a tracking item was added + track = StockItemTracking.objects.filter(item=it).latest('id') + + self.assertEqual(track.item, it) + self.assertIn('Moved to', track.title) + self.assertEqual(track.notes, 'Moved to another place') + + def test_self_move(self): + # Try to move an item to its current location (should fail) + it = StockItem.objects.get(pk=1) + + n = it.tracking_info.count() + self.assertFalse(it.move(it.location, 'Moved to same place', None)) + + # Ensure tracking info was not added + self.assertEqual(it.tracking_info.count(), n) + + def test_stocktake(self): + # Perform stocktake + it = StockItem.objects.get(pk=2) + self.assertEqual(it.quantity, 250) + it.stocktake(255, None, notes='Counted items!') + + self.assertEqual(it.quantity, 255) + + # Check that a tracking item was added + track = StockItemTracking.objects.filter(item=it).latest('id') + + self.assertIn('Stocktake', track.title) + self.assertIn('Counted items', track.notes) + + n = it.tracking_info.count() + self.assertFalse(it.stocktake(-1, None, 'test negative stocktake')) + + # Ensure tracking info was not added + self.assertEqual(it.tracking_info.count(), n) + + def test_add_stock(self): + it = StockItem.objects.get(pk=2) + n = it.quantity + it.add_stock(45, None, notes='Added some items') + + self.assertEqual(it.quantity, n + 45) + + # Check that a tracking item was added + track = StockItemTracking.objects.filter(item=it).latest('id') + + self.assertIn('Added', track.title) + self.assertIn('Added some items', track.notes) + + def test_take_stock(self): + it = StockItem.objects.get(pk=2) + n = it.quantity + it.take_stock(15, None, notes='Removed some items') + + self.assertEqual(it.quantity, n - 15) + + # Check that a tracking item was added + track = StockItemTracking.objects.filter(item=it).latest('id') + + self.assertIn('Removed', track.title) + self.assertIn('Removed some items', track.notes) + self.assertTrue(it.has_tracking_info)