Additional unit tests
This commit is contained in:
parent
98dc285ebf
commit
268b638a04
|
|
@ -1887,6 +1887,77 @@ class BuildLineTests(BuildAPITest):
|
|||
self.assertSetEqual(false_ids, expected_false)
|
||||
self.assertSetEqual(true_ids | false_ids, {line.pk for line in lines})
|
||||
|
||||
def test_filter_consumable_via_part(self):
|
||||
"""Filter BuildLine objects by 'consumable' status, accounting for the underlying part.
|
||||
|
||||
A BuildLine should be treated as 'consumable' if either the BOM line
|
||||
itself is marked as consumable, or the underlying part is marked as consumable.
|
||||
"""
|
||||
assembly = Part.objects.create(
|
||||
name='Consumable Filter Assembly',
|
||||
description='Assembly for consumable filter tests',
|
||||
assembly=True,
|
||||
)
|
||||
|
||||
plain = Part.objects.create(
|
||||
name='Consumable Filter Plain Component',
|
||||
description='A regular component',
|
||||
component=True,
|
||||
)
|
||||
|
||||
consumable_part = Part.objects.create(
|
||||
name='Consumable Filter Consumable Part',
|
||||
description='A part marked as consumable',
|
||||
component=True,
|
||||
consumable=True,
|
||||
)
|
||||
|
||||
consumable_line_part = Part.objects.create(
|
||||
name='Consumable Filter Consumable BOM Line',
|
||||
description='A part which is consumable only via its BOM line',
|
||||
component=True,
|
||||
)
|
||||
|
||||
bom_item_plain = BomItem.objects.create(
|
||||
part=assembly, sub_part=plain, quantity=1
|
||||
)
|
||||
bom_item_part_consumable = BomItem.objects.create(
|
||||
part=assembly, sub_part=consumable_part, quantity=1
|
||||
)
|
||||
bom_item_line_consumable = BomItem.objects.create(
|
||||
part=assembly, sub_part=consumable_line_part, quantity=1, consumable=True
|
||||
)
|
||||
|
||||
build = Build.objects.create(
|
||||
part=assembly,
|
||||
reference='BO-9997',
|
||||
quantity=1,
|
||||
title='Consumable Filter Build',
|
||||
)
|
||||
|
||||
url = reverse('api-build-line-list')
|
||||
|
||||
response = self.get(url, data={'build': build.pk, 'consumable': True})
|
||||
returned_bom_items = {item['bom_item'] for item in response.data}
|
||||
|
||||
self.assertIn(bom_item_part_consumable.pk, returned_bom_items)
|
||||
self.assertIn(bom_item_line_consumable.pk, returned_bom_items)
|
||||
self.assertNotIn(bom_item_plain.pk, returned_bom_items)
|
||||
|
||||
response = self.get(url, data={'build': build.pk, 'consumable': False})
|
||||
returned_bom_items = {item['bom_item'] for item in response.data}
|
||||
|
||||
self.assertIn(bom_item_plain.pk, returned_bom_items)
|
||||
self.assertNotIn(bom_item_part_consumable.pk, returned_bom_items)
|
||||
self.assertNotIn(bom_item_line_consumable.pk, returned_bom_items)
|
||||
|
||||
# Check that the serialized 'consumable' field reflects the combined status
|
||||
response = self.get(
|
||||
url, data={'build': build.pk, 'bom_item': bom_item_part_consumable.pk}
|
||||
)
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertTrue(response.data[0]['consumable'])
|
||||
|
||||
def test_output_options(self):
|
||||
"""Test output options for the BuildLine endpoint."""
|
||||
self.run_output_test(
|
||||
|
|
|
|||
|
|
@ -998,6 +998,38 @@ class AutoAllocationTests(BuildTestBase):
|
|||
|
||||
self.assertEqual(self.build.allocated_stock.count(), N - 8)
|
||||
|
||||
def test_consumable_via_part(self):
|
||||
"""A BOM line should be treated as consumable if the underlying part is consumable.
|
||||
|
||||
Even though 'bom_item_1' itself is not marked as consumable, marking
|
||||
'sub_part_1' as consumable should have the same effect as marking the
|
||||
BOM line itself as consumable.
|
||||
"""
|
||||
self.sub_part_1.consumable = True
|
||||
self.sub_part_1.save()
|
||||
|
||||
self.assertFalse(self.bom_item_1.consumable)
|
||||
self.assertTrue(self.bom_item_1.is_consumable)
|
||||
|
||||
# The BuildLine should be treated as fully allocated, without any stock allocated
|
||||
self.assertEqual(self.line_1.allocated_quantity(), 0)
|
||||
self.assertTrue(self.line_1.is_fully_allocated())
|
||||
|
||||
# The build should not consider this line when checking for unallocated lines
|
||||
unallocated_lines = self.build.unallocated_lines(tracked=False)
|
||||
self.assertNotIn(self.line_1, unallocated_lines)
|
||||
|
||||
# Auto-allocation should skip this line, even though stock exists
|
||||
self.build.auto_allocate_stock(
|
||||
interchangeable=True, substitutes=True, optional_items=True
|
||||
)
|
||||
|
||||
self.assertEqual(self.line_1.allocated_quantity(), 0)
|
||||
self.assertEqual(BuildItem.objects.filter(build_line=self.line_1).count(), 0)
|
||||
|
||||
# The other (non-consumable) line should still be allocated as normal
|
||||
self.assertEqual(self.line_2.allocated_quantity(), 30)
|
||||
|
||||
|
||||
class ExternalBuildTest(InvenTreeAPITestCase):
|
||||
"""Unit tests for external build order functionality."""
|
||||
|
|
|
|||
|
|
@ -277,6 +277,52 @@ class BomItemTest(TestCase):
|
|||
|
||||
self.assertEqual(assembly.can_build, 20)
|
||||
|
||||
# Mark 'c4' as consumable at the *part* level (not the BOM line itself)
|
||||
c4.consumable = True
|
||||
c4.save()
|
||||
|
||||
bom_item_c4 = BomItem.objects.create(part=assembly, sub_part=c4, quantity=200)
|
||||
|
||||
# The raw BomItem field is unset, but the part is marked as consumable
|
||||
self.assertFalse(bom_item_c4.consumable)
|
||||
self.assertTrue(bom_item_c4.is_consumable)
|
||||
|
||||
# A BomItem which is consumable via its part does not alter the can_build calculation
|
||||
self.assertEqual(assembly.can_build, 20)
|
||||
|
||||
def test_consumable_filter(self):
|
||||
"""Tests for the BomItem.consumable_filter() helper method."""
|
||||
assembly = Part.objects.create(
|
||||
name='Another assembly', description='Made with parts', assembly=True
|
||||
)
|
||||
|
||||
c1 = Part.objects.create(name='D1', description='Not consumable')
|
||||
c2 = Part.objects.create(name='D2', description='Consumable BOM line')
|
||||
c3 = Part.objects.create(
|
||||
name='D3', description='Consumable part', consumable=True
|
||||
)
|
||||
|
||||
bom_item_1 = BomItem.objects.create(part=assembly, sub_part=c1, quantity=1)
|
||||
bom_item_2 = BomItem.objects.create(
|
||||
part=assembly, sub_part=c2, quantity=1, consumable=True
|
||||
)
|
||||
bom_item_3 = BomItem.objects.create(part=assembly, sub_part=c3, quantity=1)
|
||||
|
||||
consumable_items = set(
|
||||
BomItem.objects.filter(BomItem.consumable_filter(consumable=True))
|
||||
)
|
||||
non_consumable_items = set(
|
||||
BomItem.objects.filter(BomItem.consumable_filter(consumable=False))
|
||||
)
|
||||
|
||||
self.assertIn(bom_item_2, consumable_items)
|
||||
self.assertIn(bom_item_3, consumable_items)
|
||||
self.assertNotIn(bom_item_1, consumable_items)
|
||||
|
||||
self.assertIn(bom_item_1, non_consumable_items)
|
||||
self.assertNotIn(bom_item_2, non_consumable_items)
|
||||
self.assertNotIn(bom_item_3, non_consumable_items)
|
||||
|
||||
def test_metadata(self):
|
||||
"""Unit tests for the metadata field."""
|
||||
for model in [BomItem]:
|
||||
|
|
|
|||
Loading…
Reference in New Issue