Skip to content

[change] Replace global mock decorator with local contextmanager in test_admin.TestAdminTransaction#397

Open
asmodehn wants to merge 3 commits into
openwisp:masterfrom
asmodehn:refactor_test_admin_TestAdminTransaction
Open

[change] Replace global mock decorator with local contextmanager in test_admin.TestAdminTransaction#397
asmodehn wants to merge 3 commits into
openwisp:masterfrom
asmodehn:refactor_test_admin_TestAdminTransaction

Conversation

@asmodehn
Copy link
Copy Markdown
Member

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Description of Changes

Refactoring one test file to allow for simple inheritance.
Part of #362 for simpler check

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Refactors the test module by moving two mock target path strings from module scope into the TestAdminTransaction class as class attributes: _mock_upgrade and _mock_connect. Test methods were rewritten to use per-test mocking (context managers and localized mock.patch) instead of module- or class-level decorators, increasing mock isolation and scoping mocks to individual test cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description addresses the core purpose (refactoring for inheritance) and references the related issue, but lacks detailed explanation of the changes and unchecks critical test-related items. Expand the description to explain the refactoring rationale, why the mock scope changes enable inheritance, and clarify the unchecked 'written/updated tests' item.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: replacing global mock decorators with local context managers in the TestAdminTransaction test class.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_firmware_upgrader/tests/test_admin.py`:
- Around line 658-663: The test uses the class attribute _mock_upgrade in a
decorator which is evaluated at class-definition time and prevents subclasses
from overriding it; change the decorated patch on _mock_upgrade in
test_upgrade_selected_action_perms to use a runtime context manager (with
mock.patch(self._mock_upgrade, return_value=True):) like the existing
self._mock_connect usage so subclass overrides of _mock_upgrade are respected,
and apply the same conversion to any other test methods that currently patch
_mock_upgrade via decorator (search for occurrences of
`@mock.patch`(_mock_upgrade) and replace them with with
mock.patch(self._mock_upgrade, ...): blocks).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 66737650-d830-4b91-9f9e-a9e49dae1714

📥 Commits

Reviewing files that changed from the base of the PR and between 1dee7d9 and b625f24.

📒 Files selected for processing (1)
  • openwisp_firmware_upgrader/tests/test_admin.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 383
File: openwisp_firmware_upgrader/tests/test_admin.py:50-50
Timestamp: 2026-02-27T19:09:01.705Z
Learning: In openwisp-firmware-upgrader tests, the BaseTestAdmin class should derive app_label from Build._meta.app_label and config_app_label from Device._meta.app_label instead of hard-coding them. This ensures tests work correctly when models are swapped.
Learnt from: pandafy
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: runtests.py:27-38
Timestamp: 2026-02-25T11:20:13.903Z
Learning: In openwisp-firmware-upgrader, runtests.py now uses `execute_from_command_line()` to run Django tests in-process rather than via subprocess, so the `patch = subprocess` coverage directive is not needed in pyproject.toml.
📚 Learning: 2026-02-27T19:08:56.218Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 383
File: openwisp_firmware_upgrader/tests/test_admin.py:50-50
Timestamp: 2026-02-27T19:08:56.218Z
Learning: In tests for openwisp-firmware-upgrader, derive app_label values from model meta instead of hard-coding. Specifically, let BaseTestAdmin compute app_label from Build._meta.app_label and config_app_label from Device._meta.app_label to ensure tests remain correct if models are swapped. This pattern should apply to all test files under openwisp_firmware_upgrader/tests.

Applied to files:

  • openwisp_firmware_upgrader/tests/test_admin.py
📚 Learning: 2026-02-25T11:20:13.903Z
Learnt from: pandafy
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: runtests.py:27-38
Timestamp: 2026-02-25T11:20:13.903Z
Learning: In openwisp-firmware-upgrader, runtests.py now uses `execute_from_command_line()` to run Django tests in-process rather than via subprocess, so the `patch = subprocess` coverage directive is not needed in pyproject.toml.

Applied to files:

  • openwisp_firmware_upgrader/tests/test_admin.py
📚 Learning: 2026-03-07T01:16:24.447Z
Learnt from: atif09
Repo: openwisp/openwisp-firmware-upgrader PR: 387
File: openwisp_firmware_upgrader/admin.py:420-423
Timestamp: 2026-03-07T01:16:24.447Z
Learning: In openwisp-firmware-upgrader, UpgradeOperation and BatchUpgradeOperation always share the same organization (BatchUpgradeOperation.build.category.organization). A user who can view an UpgradeOperation can always view its related BatchUpgradeOperation. Model-level has_view_permission(request) is sufficient for batch breadcrumb visibility in UpgradeOperationAdmin.change_view; object-level permission checks are not needed.

Applied to files:

  • openwisp_firmware_upgrader/tests/test_admin.py
📚 Learning: 2026-02-23T21:44:41.470Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: openwisp_firmware_upgrader/websockets.py:233-246
Timestamp: 2026-02-23T21:44:41.470Z
Learning: In openwisp-firmware-upgrader, the `upgrade_operations` attribute on `BatchUpgradeOperation` is a cached property that returns a queryset, so it should be accessed as `batch_operation.upgrade_operations.all()` rather than being called as a method.

Applied to files:

  • openwisp_firmware_upgrader/tests/test_admin.py
🔇 Additional comments (1)
openwisp_firmware_upgrader/tests/test_admin.py (1)

655-659: Good refactoring for inheritance support.

Moving the mock paths from module-level to class attributes enables subclasses to override them, which aligns with the PR objective. The inherited test class in tests/openwisp2/sample_firmware_upgrader/tests.py will correctly access these attributes.

Comment on lines +658 to +663
_mock_upgrade = "openwisp_firmware_upgrader.upgraders.openwrt.OpenWrt.upgrade"
_mock_connect = "openwisp_controller.connection.models.DeviceConnection.connect"

def test_upgrade_related(self, *args):
self._login()
env = self._create_upgrade_env()
self._create_firmwareless_device(organization=env["d1"].organization)
# check state is good before proceeding
fw = DeviceFirmware.objects.filter(
image__build_id=env["build2"].pk
).select_related("image")
self.assertEqual(Device.objects.count(), 3)
self.assertEqual(UpgradeOperation.objects.count(), 0)
self.assertEqual(fw.count(), 0)

with self.subTest("Invalid upgrade_options"):
response = self.client.post(
self.build_list_url,
{
"action": "upgrade_selected",
"upgrade_related": "upgrade_related",
"upgrade_options": "invalid",
ACTION_CHECKBOX_NAME: (env["build2"].pk,),
},
follow=True,
)
id_attr = (
' id="id_upgrade_options_error"' if django.VERSION >= (5, 2) else ""
)
self.assertContains(
response,
f'<ul class="errorlist"{id_attr}><li>Enter a valid JSON.</li></ul>',
@mock.patch(_mock_upgrade, return_value=True)
def test_upgrade_selected_action_perms(self, *args):
with mock.patch(self._mock_connect, return_value=True):
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using self._mock_upgrade in context managers for inheritance consistency.

The refactor successfully moves mock paths to class attributes, but there's an inconsistency that may affect subclass customization:

  • _mock_upgrade used in decorators is evaluated at class definition time and captures the base class value
  • self._mock_connect used in context managers is evaluated at runtime and respects subclass overrides

If a subclass needs to override _mock_upgrade, the inherited decorated methods won't use the overridden value. For full inheritance support, consider converting _mock_upgrade to context manager style as well:

♻️ Proposed change for consistent inheritance behavior
-    `@mock.patch`(_mock_upgrade, return_value=True)
-    def test_upgrade_selected_action_perms(self, *args):
-        with mock.patch(self._mock_connect, return_value=True):
+    def test_upgrade_selected_action_perms(self, *args):
+        with mock.patch(self._mock_upgrade, return_value=True), \
+             mock.patch(self._mock_connect, return_value=True):

Apply this pattern to all test methods that currently use the decorator.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_mock_upgrade = "openwisp_firmware_upgrader.upgraders.openwrt.OpenWrt.upgrade"
_mock_connect = "openwisp_controller.connection.models.DeviceConnection.connect"
def test_upgrade_related(self, *args):
self._login()
env = self._create_upgrade_env()
self._create_firmwareless_device(organization=env["d1"].organization)
# check state is good before proceeding
fw = DeviceFirmware.objects.filter(
image__build_id=env["build2"].pk
).select_related("image")
self.assertEqual(Device.objects.count(), 3)
self.assertEqual(UpgradeOperation.objects.count(), 0)
self.assertEqual(fw.count(), 0)
with self.subTest("Invalid upgrade_options"):
response = self.client.post(
self.build_list_url,
{
"action": "upgrade_selected",
"upgrade_related": "upgrade_related",
"upgrade_options": "invalid",
ACTION_CHECKBOX_NAME: (env["build2"].pk,),
},
follow=True,
)
id_attr = (
' id="id_upgrade_options_error"' if django.VERSION >= (5, 2) else ""
)
self.assertContains(
response,
f'<ul class="errorlist"{id_attr}><li>Enter a valid JSON.</li></ul>',
@mock.patch(_mock_upgrade, return_value=True)
def test_upgrade_selected_action_perms(self, *args):
with mock.patch(self._mock_connect, return_value=True):
_mock_upgrade = "openwisp_firmware_upgrader.upgraders.openwrt.OpenWrt.upgrade"
_mock_connect = "openwisp_controller.connection.models.DeviceConnection.connect"
def test_upgrade_selected_action_perms(self):
with mock.patch(self._mock_upgrade, return_value=True), \
mock.patch(self._mock_connect, return_value=True):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_firmware_upgrader/tests/test_admin.py` around lines 658 - 663, The
test uses the class attribute _mock_upgrade in a decorator which is evaluated at
class-definition time and prevents subclasses from overriding it; change the
decorated patch on _mock_upgrade in test_upgrade_selected_action_perms to use a
runtime context manager (with mock.patch(self._mock_upgrade,
return_value=True):) like the existing self._mock_connect usage so subclass
overrides of _mock_upgrade are respected, and apply the same conversion to any
other test methods that currently patch _mock_upgrade via decorator (search for
occurrences of `@mock.patch`(_mock_upgrade) and replace them with with
mock.patch(self._mock_upgrade, ...): blocks).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but this will make the change in behaviour bigger than it needs to be, so I will abstain until there is an actual need, or a specific request from maintainers to do so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asmodehn, that's a reasonable call — keeping the PR focused on the minimum necessary refactor is sensible, and the inheritance concern only matters if a subclass actually needs to override _mock_upgrade. Feel free to revisit when there's a concrete need.


🧠 Learnings used
Learnt from: atif09
Repo: openwisp/openwisp-firmware-upgrader PR: 387
File: openwisp_firmware_upgrader/admin.py:420-423
Timestamp: 2026-03-07T01:16:24.447Z
Learning: In openwisp-firmware-upgrader, UpgradeOperation and BatchUpgradeOperation always share the same organization (BatchUpgradeOperation.build.category.organization). A user who can view an UpgradeOperation can always view its related BatchUpgradeOperation. Model-level has_view_permission(request) is sufficient for batch breadcrumb visibility in UpgradeOperationAdmin.change_view; object-level permission checks are not needed.

Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 383
File: openwisp_firmware_upgrader/tests/test_admin.py:50-50
Timestamp: 2026-02-27T19:08:56.218Z
Learning: In tests for openwisp-firmware-upgrader, derive app_label values from model meta instead of hard-coding. Specifically, let BaseTestAdmin compute app_label from Build._meta.app_label and config_app_label from Device._meta.app_label to ensure tests remain correct if models are swapped. This pattern should apply to all test files under openwisp_firmware_upgrader/tests.

Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: openwisp_firmware_upgrader/websockets.py:233-246
Timestamp: 2026-02-23T21:44:41.470Z
Learning: In openwisp-firmware-upgrader, the `upgrade_operations` attribute on `BatchUpgradeOperation` is a cached property that returns a queryset, so it should be accessed as `batch_operation.upgrade_operations.all()` rather than being called as a method.

Learnt from: pandafy
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: runtests.py:27-38
Timestamp: 2026-02-25T11:20:13.903Z
Learning: In openwisp-firmware-upgrader, runtests.py now uses `execute_from_command_line()` to run Django tests in-process rather than via subprocess, so the `patch = subprocess` coverage directive is not needed in pyproject.toml.

Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: openwisp_firmware_upgrader/static/firmware-upgrader/js/upgrade-progress.js:458-463
Timestamp: 2026-02-25T00:17:23.479Z
Learning: In the openwisp/openwisp-firmware-upgrader repository, focus review comments on real, actionable issues rather than theoretical edge cases, trivialities, or potential future problems. Avoid flagging issues that are functionally correct but could theoretically become problems only if the code were significantly refactored or used in different contexts.

@asmodehn asmodehn changed the title Refactor test_admin.TestAdminTransaction [change] Replace global mock decorator with local contextmanager in test_admin.TestAdminTransaction Apr 13, 2026
Copy link
Copy Markdown
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asmodehn the change looks ok to merge, I don't see any problem with it, but I do not understand the need for this change and I think the pull request description fails to explain this. Why is this change needed, what does the current code prevent you to do?

@openwisp openwisp deleted a comment from openwisp-companion Bot May 11, 2026
@nemesifier nemesifier added python Pull requests that update Python code and removed stale labels May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests that update Python code

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants