From 256451d82b590fce0daeba7c5c84f28216c8b76e Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 11:52:42 +1000 Subject: [PATCH 1/4] Fix context such that build output can be deleted --- InvenTree/build/api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index c8c54b3a43..c6c1c73128 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -282,6 +282,13 @@ class BuildOutputDelete(BuildOrderContextMixin, generics.CreateAPIView): API endpoint for deleting multiple build outputs """ + def get_serializer_context(self): + ctx = super().get_serializer_context() + + ctx['to_complete'] = False + + return ctx + queryset = Build.objects.none() serializer_class = build.serializers.BuildOutputDeleteSerializer From 5a0acedce6994333a9cf98f307afa0ba64d244c3 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 13:00:53 +1000 Subject: [PATCH 2/4] Add unit tests for BuildOutputCreate serializer --- InvenTree/build/serializers.py | 5 +- InvenTree/build/test_api.py | 96 ++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index ebd98fefc9..e4919f1433 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -199,7 +199,7 @@ class BuildOutputCreateSerializer(serializers.Serializer): def validate_quantity(self, quantity): - if quantity < 0: + if quantity <= 0: raise ValidationError(_("Quantity must be greater than zero")) part = self.get_part() @@ -209,7 +209,7 @@ class BuildOutputCreateSerializer(serializers.Serializer): if part.trackable: raise ValidationError(_("Integer quantity required for trackable parts")) - if part.has_trackable_parts(): + if part.has_trackable_parts: raise ValidationError(_("Integer quantity required, as the bill of materials contains trackable parts")) return quantity @@ -232,7 +232,6 @@ class BuildOutputCreateSerializer(serializers.Serializer): serial_numbers = serial_numbers.strip() - # TODO: Field level validation necessary here? return serial_numbers auto_allocate = serializers.BooleanField( diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index a54a92dda8..2c2afa1842 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -305,6 +305,102 @@ class BuildTest(BuildAPITest): self.assertEqual(bo.status, BuildStatus.CANCELLED) + def test_create_delete_output(self): + """ + Test that we can create and delete build outputs via the API + """ + + bo = Build.objects.get(pk=1) + + n_outputs = bo.output_count + + create_url = reverse('api-build-output-create', kwargs={'pk': 1}) + + # Attempt to create outputs with invalid data + response = self.post( + create_url, + { + 'quantity': 'not a number', + }, + expected_code=400 + ) + + self.assertIn('A valid number is required', str(response.data)) + + for q in [-100, -10.3, 0]: + + response = self.post( + create_url, + { + 'quantity': q, + }, + expected_code=400 + ) + + if q == 0: + self.assertIn('Quantity must be greater than zero', str(response.data)) + else: + self.assertIn('Ensure this value is greater than or equal to 0', str(response.data)) + + # Mark the part being built as 'trackable' (requires integer quantity) + bo.part.trackable = True + bo.part.save() + + response = self.post( + create_url, + { + 'quantity': 12.3, + }, + expected_code=400 + ) + + self.assertIn('Integer quantity required for trackable parts', str(response.data)) + + # Erroneous serial numbers + response = self.post( + create_url, + { + 'quantity': 5, + 'serial_numbers': '1, 2, 3, 4, 5, 6', + 'batch': 'my-batch', + }, + expected_code=400 + ) + + self.assertIn('Number of unique serial numbers (6) must match quantity (5)', str(response.data)) + + # At this point, no new build outputs should have been created + self.assertEqual(n_outputs, bo.output_count) + + # Now, create with *good* data + response = self.post( + create_url, + { + 'quantity': 5, + 'serial_numbers': '1, 2, 3, 4, 5', + 'batch': 'my-batch', + }, + expected_code=201, + ) + + # 5 new outputs have been created + self.assertEqual(n_outputs + 5, bo.output_count) + + # Attempt to create with identical serial numbers + response = self.post( + create_url, + { + 'quantity': 3, + 'serial_numbers': '1-3', + }, + expected_code=400, + ) + + self.assertIn('The following serial numbers already exist : 1,2,3', str(response.data)) + + # Double check no new outputs have been created + self.assertEqual(n_outputs + 5, bo.output_count) + class BuildAllocationTest(BuildAPITest): """ From 01a30935f08199d96fe875ab349401ff400e4a36 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 14:20:41 +1000 Subject: [PATCH 3/4] Add unit tests for BuildOutputDelete serializer --- InvenTree/build/test_api.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index 2c2afa1842..d66313c197 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -401,6 +401,54 @@ class BuildTest(BuildAPITest): # Double check no new outputs have been created self.assertEqual(n_outputs + 5, bo.output_count) + # Now, let's delete each build output individually via the API + outputs = bo.build_outputs.all() + + delete_url = reverse('api-build-output-delete', kwargs={'pk': 1}) + + # Mark 1 build output as complete + bo.complete_build_output(outputs[0], self.user) + + self.assertEqual(n_outputs + 5, bo.output_count) + self.assertEqual(1, bo.complete_count) + + # Delete all outputs at once + # Note: One has been completed, so this should fail! + response = self.post( + delete_url, + { + 'outputs': [ + { + 'output': output.pk, + } for output in outputs + ] + }, + expected_code=400 + ) + + self.assertIn('This build output has already been completed', str(response.data)) + + # No change to the build outputs + self.assertEqual(n_outputs + 5, bo.output_count) + self.assertEqual(1, bo.complete_count) + + # Let's delete 2 build outputs + response = self.post( + delete_url, + { + 'outputs': [ + { + 'output': output.pk, + } for output in outputs[1:3] + ] + }, + expected_code=201 + ) + + # Two build outputs have been removed + self.assertEqual(n_outputs + 3, bo.output_count) + self.assertEqual(1, bo.complete_count) + class BuildAllocationTest(BuildAPITest): """ From e7b458978c197b780f75358f443d6e6a72070a86 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 17 May 2022 15:10:48 +1000 Subject: [PATCH 4/4] More unit tests --- InvenTree/build/test_api.py | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index d66313c197..be64aa28c7 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -406,6 +406,16 @@ class BuildTest(BuildAPITest): delete_url = reverse('api-build-output-delete', kwargs={'pk': 1}) + response = self.post( + delete_url, + { + 'outputs': [], + }, + expected_code=400 + ) + + self.assertIn('A list of build outputs must be provided', str(response.data)) + # Mark 1 build output as complete bo.complete_build_output(outputs[0], self.user) @@ -449,6 +459,61 @@ class BuildTest(BuildAPITest): self.assertEqual(n_outputs + 3, bo.output_count) self.assertEqual(1, bo.complete_count) + # Tests for BuildOutputComplete serializer + complete_url = reverse('api-build-output-complete', kwargs={'pk': 1}) + + # Let's mark the remaining outputs as complete + response = self.post( + complete_url, + { + 'outputs': [], + 'location': 4, + }, + expected_code=400, + ) + + self.assertIn('A list of build outputs must be provided', str(response.data)) + + for output in outputs[3:]: + output.refresh_from_db() + self.assertTrue(output.is_building) + + response = self.post( + complete_url, + { + 'outputs': [ + { + 'output': output.pk + } for output in outputs[3:] + ], + 'location': 4, + }, + expected_code=201, + ) + + # Check that the outputs have been completed + self.assertEqual(3, bo.complete_count) + + for output in outputs[3:]: + output.refresh_from_db() + self.assertEqual(output.location.pk, 4) + self.assertFalse(output.is_building) + + # Try again, with an output which has already been completed + response = self.post( + complete_url, + { + 'outputs': [ + { + 'output': outputs.last().pk, + } + ] + }, + expected_code=400, + ) + + self.assertIn('This build output has already been completed', str(response.data)) + class BuildAllocationTest(BuildAPITest): """