diff --git a/changes/1184.changed b/changes/1184.changed new file mode 100644 index 000000000..096a66330 --- /dev/null +++ b/changes/1184.changed @@ -0,0 +1 @@ +Deferred the missing python-magic warning in pysnow to only emit when attachment upload is attempted, rather than at import time. diff --git a/nautobot_ssot/integrations/servicenow/third_party/pysnow/attachment.py b/nautobot_ssot/integrations/servicenow/third_party/pysnow/attachment.py index e927a3256..6a28c4927 100644 --- a/nautobot_ssot/integrations/servicenow/third_party/pysnow/attachment.py +++ b/nautobot_ssot/integrations/servicenow/third_party/pysnow/attachment.py @@ -9,10 +9,6 @@ HAS_MAGIC = True except ImportError: # pragma: no cover HAS_MAGIC = False - warnings.warn( - "Missing the python-magic library; attachment content-type will be set to text/plain. " - "Try installing the python-magic-bin package if running on Mac or Windows" - ) from .exceptions import InvalidUsage @@ -68,7 +64,14 @@ def upload(self, sys_id, file_path, name=None, multipart=False): headers["Content-Type"] = "multipart/form-data" path_append = "/upload" else: - headers["Content-Type"] = magic.from_file(file_path, mime=True) if HAS_MAGIC else "text/plain" + if HAS_MAGIC: + headers["Content-Type"] = magic.from_file(file_path, mime=True) + else: + warnings.warn( + "Missing the python-magic library; attachment content-type will be set to text/plain. " + "Try installing the python-magic-bin package if running on Mac or Windows" + ) + headers["Content-Type"] = "text/plain" path_append = "/file" return resource.request(method="POST", data=data, headers=headers, path_append=path_append) diff --git a/nautobot_ssot/tests/servicenow/test_pysnow.py b/nautobot_ssot/tests/servicenow/test_pysnow.py new file mode 100644 index 000000000..a34d69c7d --- /dev/null +++ b/nautobot_ssot/tests/servicenow/test_pysnow.py @@ -0,0 +1,45 @@ +"""Unit tests for the pysnow module.""" + +import warnings +from unittest.mock import MagicMock, mock_open, patch + +from nautobot.core.testing import TestCase + +from nautobot_ssot.integrations.servicenow.third_party.pysnow.attachment import Attachment + + +class TestAttachmentUploadContentType(TestCase): + """Test that the upload method sets Content-Type based on HAS_MAGIC.""" + + def setUp(self): + """Set up a minimal Attachment instance with a mocked resource.""" + self.resource = MagicMock() + self.attachment = Attachment(resource=self.resource, table_name="incident") + + @patch("nautobot_ssot.integrations.servicenow.third_party.pysnow.attachment.HAS_MAGIC", True) + @patch("nautobot_ssot.integrations.servicenow.third_party.pysnow.attachment.magic", create=True) + @patch("builtins.open", mock_open(read_data=b"file content")) + def test_upload_uses_magic_when_available(self, mock_magic): + """When python-magic is available, Content-Type should be determined by magic.""" + file_path = "/tmp/test.pdf" + mock_magic.from_file.return_value = "application/pdf" + + self.attachment.upload(sys_id="abc123", file_path=file_path) + + mock_magic.from_file.assert_called_once_with(file_path, mime=True) + call_kwargs = self.resource.request.call_args[1] + self.assertEqual(call_kwargs["headers"]["Content-Type"], "application/pdf") + + @patch("nautobot_ssot.integrations.servicenow.third_party.pysnow.attachment.HAS_MAGIC", False) + @patch("builtins.open", mock_open(read_data=b"file content")) + def test_upload_falls_back_to_text_plain_without_magic(self): + """When python-magic is missing, Content-Type should fall back to text/plain with a warning.""" + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + self.attachment.upload(sys_id="abc123", file_path="/tmp/test.txt") + + self.assertEqual(len(caught_warnings), 1) + self.assertIn("python-magic", str(caught_warnings[0].message)) + + call_kwargs = self.resource.request.call_args[1] + self.assertEqual(call_kwargs["headers"]["Content-Type"], "text/plain")