From 1c3ddcd97ac447a470434f807f03b3887aa1dcf1 Mon Sep 17 00:00:00 2001 From: bctiemann Date: Thu, 28 May 2026 11:41:37 -0400 Subject: [PATCH] Closes #22305: Allow test cases to declare a stable query-count key prefix (#22306) Co-authored-by: Claude Sonnet 4.6 --- netbox/utilities/testing/query_counts.py | 12 ++- netbox/utilities/tests/test_query_counts.py | 84 +++++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 netbox/utilities/tests/test_query_counts.py diff --git a/netbox/utilities/testing/query_counts.py b/netbox/utilities/testing/query_counts.py index fcd1e8920..b9975be31 100644 --- a/netbox/utilities/testing/query_counts.py +++ b/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 in the per-app baseline file (`/tests/query_counts.json`). - The baseline key is `:`, derived from `test_case.model`. + The baseline key is `:`. By default `` 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 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 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}' if _is_update_mode(): diff --git a/netbox/utilities/tests/test_query_counts.py b/netbox/utilities/tests/test_query_counts.py new file mode 100644 index 000000000..d55d4fe98 --- /dev/null +++ b/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')