diff --git a/src/backend/InvenTree/plugin/base/label/mixins.py b/src/backend/InvenTree/plugin/base/label/mixins.py index 78182e343c..2b1150140f 100644 --- a/src/backend/InvenTree/plugin/base/label/mixins.py +++ b/src/backend/InvenTree/plugin/base/label/mixins.py @@ -51,6 +51,9 @@ class LabelPrintingMixin: """ try: return label.render(instance, request=request, user=user) + except ValidationError: + log_error('render_to_pdf', plugin=self.slug) + raise except Exception: log_error('render_to_pdf', plugin=self.slug) raise ValidationError(_('Error rendering label to PDF')) @@ -68,6 +71,9 @@ class LabelPrintingMixin: """ try: return label.render_as_string(instance, request=request, user=user) + except ValidationError: + log_error('render_to_html', plugin=self.slug) + raise except Exception: log_error('render_to_html', plugin=self.slug) raise ValidationError(_('Error rendering label to HTML')) diff --git a/src/backend/InvenTree/plugin/base/label/test_label_mixin.py b/src/backend/InvenTree/plugin/base/label/test_label_mixin.py index f64734ee53..a75e933c14 100644 --- a/src/backend/InvenTree/plugin/base/label/test_label_mixin.py +++ b/src/backend/InvenTree/plugin/base/label/test_label_mixin.py @@ -5,6 +5,8 @@ import os from unittest import mock from django.apps import apps +from django.core.exceptions import ValidationError +from django.test import SimpleTestCase from django.urls import reverse from pdfminer.high_level import extract_text @@ -24,6 +26,65 @@ from report.tests import PrintTestMixins from stock.models import StockItem, StockLocation +class LabelRenderingTests(SimpleTestCase): + """Test error handling in the label rendering wrappers.""" + + def setUp(self): + """Create a label plugin without loading the plugin registry.""" + + class TestLabelPlugin(LabelPrintingMixin, InvenTreePlugin): + NAME = 'Test Label Printer' + + self.plugin = TestLabelPlugin() + + @mock.patch('plugin.base.label.mixins.log_error') + def test_validation_errors(self, log_error): + """Log validation errors while preserving messages, codes, and parameters.""" + for wrapper, renderer in [ + ('render_to_pdf', 'render'), + ('render_to_html', 'render_as_string'), + ]: + with self.subTest(wrapper=wrapper): + log_error.reset_mock() + error = ValidationError({ + 'serial': ValidationError( + 'Missing serial number for %(part)s', + code='missing_serial', + params={'part': 'Test part'}, + ) + }) + label = mock.Mock(spec=LabelTemplate) + getattr(label, renderer).side_effect = error + + with self.assertRaises(ValidationError) as raised: + getattr(self.plugin, wrapper)(label, mock.sentinel.instance, None) + + self.assertIs(raised.exception, error) + self.assertEqual( + raised.exception.message_dict, + {'serial': ['Missing serial number for Test part']}, + ) + log_error.assert_called_once_with(wrapper, plugin=self.plugin.slug) + + @mock.patch('plugin.base.label.mixins.log_error') + def test_unexpected_errors(self, log_error): + """Log unexpected errors and return the existing generic messages.""" + for wrapper, renderer, message in [ + ('render_to_pdf', 'render', 'Error rendering label to PDF'), + ('render_to_html', 'render_as_string', 'Error rendering label to HTML'), + ]: + with self.subTest(wrapper=wrapper): + log_error.reset_mock() + label = mock.Mock(spec=LabelTemplate) + getattr(label, renderer).side_effect = RuntimeError('Rendering failed') + + with self.assertRaises(ValidationError) as raised: + getattr(self.plugin, wrapper)(label, mock.sentinel.instance, None) + + self.assertEqual(raised.exception.messages, [message]) + log_error.assert_called_once_with(wrapper, plugin=self.plugin.slug) + + class LabelMixinTests(PrintTestMixins, InvenTreeAPITestCase): """Test that the Label mixin operates correctly."""