Parcourir la source

Closes #22305: Allow test cases to declare a stable query-count key prefix (#22306)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
bctiemann il y a 2 jours
Parent
commit
1c3ddcd97a

+ 10 - 2
netbox/utilities/testing/query_counts.py

@@ -86,7 +86,14 @@ def assert_expected_query_count(test_case, name):
     Assert that the wrapped block performs the number of SQL queries recorded
     Assert that the wrapped block performs the number of SQL queries recorded
     in the per-app baseline file (`<app>/tests/query_counts.json`).
     in the per-app baseline file (`<app>/tests/query_counts.json`).
 
 
-    The baseline key is `<model_name>:<name>`, derived from `test_case.model`.
+    The baseline key is `<model_label>:<name>`. By default `<model_label>` is
+    derived from `test_case.model._meta.model_name`. Test cases that use
+    runtime-generated models with unstable names (e.g. names derived from a
+    database primary-key sequence) can declare a ``query_count_model_label``
+    class attribute to provide a stable, human-assigned label instead:
+
+        class MyViewTestCase(ViewTestCases.PrimaryObjectViewTestCase):
+            query_count_model_label = 'my-stable-label'
 
 
     When the `UPDATE_QUERY_COUNTS` environment variable is set, the assertion
     When the `UPDATE_QUERY_COUNTS` environment variable is set, the assertion
     is skipped and the observed count is written back to the baseline file
     is skipped and the observed count is written back to the baseline file
@@ -94,7 +101,8 @@ def assert_expected_query_count(test_case, name):
     """
     """
     model = test_case.model
     model = test_case.model
     app_label = model._meta.app_label
     app_label = model._meta.app_label
-    model_name = model._meta.model_name
+    label = getattr(test_case, 'query_count_model_label', None)
+    model_name = label if label is not None else model._meta.model_name
     key = f'{model_name}:{name}'
     key = f'{model_name}:{name}'
 
 
     if _is_update_mode():
     if _is_update_mode():

+ 84 - 0
netbox/utilities/tests/test_query_counts.py

@@ -0,0 +1,84 @@
+import os
+
+from django.test import TestCase
+
+import utilities.testing.query_counts as qc_mod
+from utilities.testing.query_counts import assert_expected_query_count
+
+
+class _FakeModel:
+    """Minimal stand-in for a Django model class with _meta."""
+
+    class _meta:
+        app_label = 'utilities'
+        model_name = 'fakemodel'
+
+
+class _BaseTestCase:
+    """Shared base for fake test-case objects passed to assert_expected_query_count."""
+    model = _FakeModel
+
+
+class AssertExpectedQueryCountLabelTestCase(TestCase):
+    """
+    Verify that assert_expected_query_count uses query_count_model_label when
+    set on the test case and falls back to model._meta.model_name otherwise.
+    """
+
+    def _make_test_case(self, label_value=None, set_attr=False):
+        """Return a minimal fake test case object."""
+        tc = type('FakeTestCase', (_BaseTestCase,), {'fail': self.fail})()
+        if set_attr:
+            tc.query_count_model_label = label_value
+        return tc
+
+    def _recorded_key(self, test_case):
+        """
+        Drive assert_expected_query_count in UPDATE mode and return the key it
+        wrote.  We patch _record_update to capture the key without touching the
+        filesystem.
+        """
+        captured = {}
+
+        original = qc_mod._record_update
+
+        def fake_record(app_label, key, count):
+            captured['key'] = key
+
+        original_parallel = qc_mod._is_parallel_test_run
+        qc_mod._record_update = fake_record
+        qc_mod._is_parallel_test_run = lambda: False
+        try:
+            old_env = os.environ.get(qc_mod.UPDATE_ENV_VAR)
+            os.environ[qc_mod.UPDATE_ENV_VAR] = '1'
+            try:
+                with assert_expected_query_count(test_case, 'test_name'):
+                    pass
+            finally:
+                if old_env is None:
+                    del os.environ[qc_mod.UPDATE_ENV_VAR]
+                else:
+                    os.environ[qc_mod.UPDATE_ENV_VAR] = old_env
+        finally:
+            qc_mod._record_update = original
+            qc_mod._is_parallel_test_run = original_parallel
+
+        return captured['key']
+
+    def test_default_uses_model_name(self):
+        """Without query_count_model_label the key prefix is model._meta.model_name."""
+        tc = self._make_test_case()
+        key = self._recorded_key(tc)
+        self.assertEqual(key, 'fakemodel:test_name')
+
+    def test_custom_label_overrides_model_name(self):
+        """A set query_count_model_label is used as the key prefix."""
+        tc = self._make_test_case(label_value='my-stable-label', set_attr=True)
+        key = self._recorded_key(tc)
+        self.assertEqual(key, 'my-stable-label:test_name')
+
+    def test_empty_string_label_is_used_as_prefix(self):
+        """An empty-string query_count_model_label is used as-is, not treated as falsy."""
+        tc = self._make_test_case(label_value='', set_attr=True)
+        key = self._recorded_key(tc)
+        self.assertEqual(key, ':test_name')