test(extras): Stabilize ScriptModule tests and reduce CI noise

Patch `ScriptModule.sync_classes()` in tests that create
`ScriptModule` records without backing files to avoid setup-time import
attempts for non-existent modules.

For the upload API test, mock both serializer and module storage access
so the uploaded script can be saved and imported during the test, and
assert that the corresponding `Script` object is created.

Fixes #21862
This commit is contained in:
Martin Hauser
2026-04-08 22:31:14 +02:00
parent dbb871b75a
commit f201a54949
2 changed files with 40 additions and 9 deletions

View File

@@ -1,5 +1,6 @@
import datetime
import hashlib
import io
from unittest.mock import MagicMock, patch
from django.contrib.contenttypes.models import ContentType
@@ -1013,10 +1014,14 @@ class ScriptTest(APITestCase):
@classmethod
def setUpTestData(cls):
module = ScriptModule.objects.create(
file_root=ManagedFileRootPathChoices.SCRIPTS,
file_path='script.py',
)
# Avoid trying to import a non-existent on-disk module during setup.
# This test creates the Script row explicitly and monkey-patches
# Script.python_class below.
with patch.object(ScriptModule, 'sync_classes'):
module = ScriptModule.objects.create(
file_root=ManagedFileRootPathChoices.SCRIPTS,
file_path='script.py',
)
script = Script.objects.create(
module=module,
name='Test script',
@@ -1419,9 +1424,20 @@ class ScriptModuleTest(APITestCase):
upload_file = SimpleUploadedFile('test_upload.py', script_content, content_type='text/plain')
mock_storage = MagicMock()
mock_storage.save.return_value = 'test_upload.py'
with patch('extras.api.serializers_.scripts.storages') as mock_storages:
mock_storages.create_storage.return_value = mock_storage
mock_storages.backends = {'scripts': {}}
# The upload serializer writes the file via storages.create_storage(...).save(),
# but ScriptModule.sync_classes() later imports it via storages["scripts"].open().
# Provide both behaviors so the uploaded module can actually be loaded during the test.
mock_storage.open.side_effect = lambda *args, **kwargs: io.BytesIO(script_content)
with (
patch('extras.api.serializers_.scripts.storages') as mock_serializer_storages,
patch('extras.models.mixins.storages') as mock_module_storages,
):
mock_serializer_storages.create_storage.return_value = mock_storage
mock_serializer_storages.backends = {'scripts': {}}
mock_module_storages.__getitem__.return_value = mock_storage
response = self.client.post(
self.url,
{'file': upload_file},
@@ -1432,6 +1448,7 @@ class ScriptModuleTest(APITestCase):
self.assertEqual(response.data['file_path'], 'test_upload.py')
mock_storage.save.assert_called_once()
self.assertTrue(ScriptModule.objects.filter(file_path='test_upload.py').exists())
self.assertTrue(Script.objects.filter(module__file_path='test_upload.py', name='TestScript').exists())
def test_upload_script_module_without_file_fails(self):
self.add_permissions('extras.add_scriptmodule', 'core.add_managedfile')

View File

@@ -924,7 +924,14 @@ class ScriptValidationErrorTest(TestCase):
@classmethod
def setUpTestData(cls):
module = ScriptModule.objects.create(file_root=ManagedFileRootPathChoices.SCRIPTS, file_path='test_script.py')
# Avoid trying to import a non-existent on-disk module during setup.
# This test creates the Script row explicitly and monkey-patches
# Script.python_class below.
with patch.object(ScriptModule, 'sync_classes'):
module = ScriptModule.objects.create(
file_root=ManagedFileRootPathChoices.SCRIPTS,
file_path='test_script.py',
)
cls.script = Script.objects.create(module=module, name='Test script', is_executable=True)
def setUp(self):
@@ -986,7 +993,14 @@ class ScriptDefaultValuesTest(TestCase):
@classmethod
def setUpTestData(cls):
module = ScriptModule.objects.create(file_root=ManagedFileRootPathChoices.SCRIPTS, file_path='test_script.py')
# Avoid trying to import a non-existent on-disk module during setup.
# This test creates the Script row explicitly and monkey-patches
# Script.python_class below.
with patch.object(ScriptModule, 'sync_classes'):
module = ScriptModule.objects.create(
file_root=ManagedFileRootPathChoices.SCRIPTS,
file_path='test_script.py',
)
cls.script = Script.objects.create(module=module, name='Test script', is_executable=True)
def setUp(self):