Arthur пре 11 часа
родитељ
комит
7cff6a27d4
2 измењених фајлова са 102 додато и 1 уклоњено
  1. 50 0
      netbox/extras/tests/test_api.py
  2. 52 1
      netbox/extras/tests/test_views.py

+ 50 - 0
netbox/extras/tests/test_api.py

@@ -1907,6 +1907,7 @@ class ScriptRunExecutionTestCase(APITestCase):
         site = ObjectVar(model=Site)
         sites = MultiObjectVar(model=Site, required=False)
         label = StringVar(default='hello')
+        flag = BooleanVar(default=True)
 
         def run(self, data, commit=True):
             return 'ok'
@@ -2046,6 +2047,55 @@ class ScriptRunExecutionTestCase(APITestCase):
         self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
         mock_enqueue.assert_not_called()
 
+    @patch('extras.jobs.ScriptJob.enqueue')
+    def test_run_ignores_undeclared_keys(self, mock_enqueue):
+        # Binding 'data' to the script's form means keys which don't correspond to a declared
+        # variable are dropped rather than forwarded to run(). This is the contract documented
+        # under "Running Custom Scripts > Via the API"; pin it so it can't regress silently.
+        payload = {
+            'data': {'site': self.sites[0].pk, 'bogus': 'ignored', 'id': 99},
+            'commit': True,
+        }
+
+        response = self.client.post(self.url, payload, format='json', **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        data = mock_enqueue.call_args.kwargs['data']
+        self.assertEqual(data['site'], self.sites[0])
+        self.assertNotIn('bogus', data)
+        self.assertNotIn('id', data)
+
+    @patch('extras.jobs.ScriptJob.enqueue')
+    def test_run_rejects_omitted_required_var(self, mock_enqueue):
+        # 'site' is required and declares no default, so unlike 'label' it cannot be
+        # back-filled: omitting it must 400 rather than enqueue a job that fails at runtime.
+        payload = {'data': {'label': 'hi'}, 'commit': True}
+
+        response = self.client.post(self.url, payload, format='json', **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+        self.assertIn('site', response.data['data'])
+        mock_enqueue.assert_not_called()
+
+    @patch('extras.jobs.ScriptJob.enqueue')
+    def test_run_resolves_booleanvar_default_and_explicit_values(self, mock_enqueue):
+        # BooleanVar renders as a checkbox, and CheckboxInput reads a missing key as False.
+        # An omitted BooleanVar must therefore pick up its declared default, while an
+        # explicitly supplied False must not be overwritten by that default.
+        for case, supplied, expected in (
+            ('omitted', {}, True),
+            ('explicit False', {'flag': False}, False),
+            ('explicit True', {'flag': True}, True),
+        ):
+            with self.subTest(case=case):
+                mock_enqueue.reset_mock()
+                payload = {'data': {'site': self.sites[0].pk, **supplied}, 'commit': True}
+
+                response = self.client.post(self.url, payload, format='json', **self.header)
+
+                self.assertHttpStatus(response, status.HTTP_200_OK)
+                self.assertIs(mock_enqueue.call_args.kwargs['data']['flag'], expected)
+
 
 class CreatedUpdatedFilterTestCase(APITestCase):
 

+ 52 - 1
netbox/extras/tests/test_views.py

@@ -14,7 +14,7 @@ from core.models import Job, ObjectType
 from dcim.models import DeviceType, Manufacturer, Site
 from extras.choices import *
 from extras.models import *
-from extras.scripts import BooleanVar, IntegerVar
+from extras.scripts import BooleanVar, IntegerVar, StringVar
 from extras.scripts import Script as PythonClass
 from users.models import Group, ObjectPermission, User
 from utilities.testing import TestCase, ViewTestCases
@@ -1279,6 +1279,57 @@ class ScriptModuleCreateViewTestCase(TestCase):
         self.assertEqual(response.context['return_url'], reverse('extras:script_list'))
 
 
+class ScriptDefaultBackfillTestCase(TestCase):
+    """
+    The UI and the REST API now share prepare_script_form(), so the UI's back-filling of
+    declared defaults (previously inline in ScriptView.post()) must keep working after
+    that logic moved into the helper.
+    """
+    user_permissions = ['extras.view_script', 'extras.run_script']
+
+    class TestScriptClass(PythonClass):
+        class Meta:
+            name = 'Backfill test'
+            commit_default = False
+
+        label = StringVar(default='hello')
+        flag = BooleanVar(default=True)
+
+        def run(self, data, commit):
+            return 'Complete'
+
+    @classmethod
+    def setUpTestData(cls):
+        with patch.object(ScriptModule, 'sync_classes'):
+            module = ScriptModule.objects.create(
+                file_root=ManagedFileRootPathChoices.SCRIPTS,
+                file_path='backfill_script.py',
+            )
+        cls.script = Script.objects.create(module=module, name='Backfill test', is_executable=True)
+
+    def setUp(self):
+        super().setUp()
+        python_class_patch = patch.object(Script, 'python_class', new=self.TestScriptClass)
+        python_class_patch.start()
+        self.addCleanup(python_class_patch.stop)
+
+    @tag('regression')
+    def test_ui_backfills_declared_defaults(self):
+        url = reverse('extras:script', kwargs={'pk': self.script.pk})
+
+        with (
+            patch('extras.views.any_workers_for_queue', return_value=True),
+            patch('extras.jobs.ScriptJob.enqueue') as mock_enqueue,
+        ):
+            mock_enqueue.return_value.pk = 1
+            response = self.client.post(url, {'_commit': 'true'})
+
+        self.assertEqual(response.status_code, 302)
+        data = mock_enqueue.call_args.kwargs['data']
+        self.assertEqual(data['label'], 'hello')
+        self.assertIs(data['flag'], True)
+
+
 class ScriptValidationErrorTestCase(TestCase):
     user_permissions = ['extras.view_script', 'extras.run_script']