Skip to content

[tests] Extended controller app tests part one#362

Merged
nemesifier merged 26 commits into
openwisp:masterfrom
asmodehn:extended_controller_app_tests_part_one
Jun 5, 2026
Merged

[tests] Extended controller app tests part one#362
nemesifier merged 26 commits into
openwisp:masterfrom
asmodehn:extended_controller_app_tests_part_one

Conversation

@asmodehn

@asmodehn asmodehn commented Dec 1, 2025

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.

Reference to Existing Issue

This is a part of #303 which was too ambitious. Making a sample_config extend app is trickier than expected, and will be done later.

The goal here is to add a sample_connection extended app, to make sure the case where connection is an extended app is tested.
This happens when someone extends the connection app (to change the connection logic or add complex device command code for instance) and wants to work with it while also using the firmware_upgrader module, which depends on it.

Description of Changes

Added sample_connection extended app.
Based on #303 changes.
Aiming to get all tests to pass with this as a first step.

To achieve this, we need to refactor some mocks in tests to use a contextmanager for the *TestCase._mock_connect attribute instead of a decorator (parsed at parent-class definition time)

@asmodehn asmodehn marked this pull request as ready for review December 2, 2025 10:46
@asmodehn asmodehn force-pushed the extended_controller_app_tests_part_one branch from eb3df37 to 7c1f2a7 Compare December 29, 2025 14:25
@nemesifier nemesifier changed the title Extended controller app tests part one [fix:tests] Extended controller app tests part one Dec 29, 2025
@nemesifier nemesifier added the bug Something isn't working label Dec 29, 2025

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @asmodehn for your patience in working on this, I scanned the PR and noticed a few small things.

Is the change to tests/media/floorplan.jpg intentional? I suggest reverting that.

See also my comments below.

PS: can we close #303 then?

Comment thread tests/openwisp2/urls.py Outdated
Comment thread tests/openwisp2/urls.py Outdated
Comment thread tests/openwisp2/urls.py Outdated
Comment thread tests/openwisp2/urls.py Outdated
@asmodehn

asmodehn commented Dec 30, 2025

Copy link
Copy Markdown
Member Author

Thanks @asmodehn for your patience in working on this, I scanned the PR and noticed a few small things.

Is the change to tests/media/floorplan.jpg intentional? I suggest reverting that.

See also my comments below.

PS: can we close #303 then?

The floorplan,jpg is useful for sample_connection tests.

#303 deals with extending the more troublesome config app. I will rebase #303 on master once this #362 is merged in, and continue working to extend config into a sample_config app for tests.

@asmodehn asmodehn force-pushed the extended_controller_app_tests_part_one branch from 4a19dec to df6a7dc Compare December 30, 2025 14:20
Comment thread tests/openwisp2/sample_connection/tests.py Outdated
Comment thread tests/openwisp2/sample_connection/tests.py Outdated
Comment thread tests/openwisp2/urls.py Outdated
@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 9, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jan 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a sample_connection Django test app that serves as a concrete implementation fixture for OpenWISP connection models. It refactors test mocking infrastructure in openwisp_firmware_upgrader/tests/test_admin.py by extracting hardcoded mock patch target strings into reusable class-level attributes (_mock_upgrade and _mock_connect). The sample app includes concrete Django models (Credentials, DeviceConnection, Command) inheriting from OpenWISP abstract bases, full database migration support with permission assignment and constraint enforcement, API view adapters, and comprehensive test coverage. Django settings and URL routing are conditionally configured via the SAMPLE_APP environment variable to register the sample app, swap in its model implementations, and wire API endpoints. Firmware upgrader tests are updated to use the new mocking pattern and support dynamic app label configuration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

python

Suggested reviewers

  • nemesifier
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title uses 'fix:tests' prefix instead of the required format '[fix]' and does not clearly indicate the main change is adding a sample_connection extended app. Reformat title to '[fix] Added sample_connection extended app for controller tests' or '[feature] Added sample_connection extended app for controller tests' to follow the required '[type] Descriptive title' format and better summarize the PR's main purpose.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Bug Fixes ✅ Passed PR does not fix a bug in core user-facing functionality; it only refactors test infrastructure and adds test coverage for extended apps. The bug-fix check is not applicable.
Description check ✅ Passed The pull request description follows the template structure with all required sections completed: checklist items are marked, issue reference provided, and clear description of changes and objectives documented.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
openwisp_firmware_upgrader/tests/test_selenium.py (2)

21-35: Patch target may miss swapped DeviceConnection implementations.

Since DeviceConnection = swapper.load_model("connection", "DeviceConnection"), patching "openwisp_controller.connection.models.DeviceConnection.connect" might not intercept calls when the connection app/model is swapped. Consider patching the loaded model instead (module + class name).

Proposed fix
 class TestDeviceAdmin(TestUpgraderMixin, SeleniumTestMixin, StaticLiveServerTestCase):
@@
-    _mock_connect = "openwisp_controller.connection.models.DeviceConnection.connect"
+    _mock_connect = f"{DeviceConnection.__module__}.{DeviceConnection.__name__}.connect"

114-331: Address Ruff warnings (ARG002, RUF059) to avoid lint-gated CI failures.

You can keep the patch decorators but make the injected args explicit/ignored, and avoid binding unused unpacked values.

Proposed fix
@@
     @capture_any_output()
     @patch(
         "openwisp_firmware_upgrader.upgraders.openwrt.OpenWrt.upgrade",
         return_value=True,
     )
-    def test_device_firmware_upgrade_options(self, *args):
+    def test_device_firmware_upgrade_options(self, _mock_upgrade):
@@
     @capture_any_output()
     @patch(
         "openwisp_firmware_upgrader.upgraders.openwrt.OpenWrt.upgrade",
         return_value=True,
     )
-    def test_batch_upgrade_upgrade_options(self, *args):
+    def test_batch_upgrade_upgrade_options(self, _mock_upgrade):
@@
     @capture_any_output()
     @patch(
         "openwisp_firmware_upgrader.upgraders.openwrt.OpenWrt.upgrade",
         return_value=True,
     )
     @patch.object(OpenWrt, "SCHEMA", None)
-    def test_upgrader_with_unsupported_upgrade_options(self, *args):
+    def test_upgrader_with_unsupported_upgrade_options(self, _mock_upgrade, _mock_schema):
         with patch(self._mock_connect, return_value=True):
-            org, category, build1, build2, image1, image2, device = self._set_up_env()
+            _org, _category, _build1, build2, _image1, image2, device = self._set_up_env()
tests/openwisp2/sample_firmware_upgrader/tests.py (1)

124-130: TestModels.app_label should be "sample_firmware_upgrader" (not the dotted path).

This is inconsistent with every other test class in the file (TestAdmin, TestAdminTransaction, TestDeviceAdmin) which all use the short app label. The dotted path will break mock patching in the base class tests and violates Django conventions for app labels.

Proposed fix
 class TestModels(BaseTestModels):
-    app_label = "openwisp2.sample_firmware_upgrader"
+    app_label = "sample_firmware_upgrader"
🤖 Fix all issues with AI agents
In @tests/openwisp2/urls.py:
- Around line 10-38: The SAMPLE_APP check uses os.environ.get("SAMPLE_APP",
False) which returns a string so values like "0" or "false" are treated as
truthy; change the condition to explicitly parse the env var string (e.g., read
value = os.environ.get("SAMPLE_APP", "") and test value.lower() in
("1","true","yes") or use a canonical str-to-bool helper) before appending to
urlpatterns; update the conditional around urlpatterns (the if that references
SAMPLE_APP) to use this parsed boolean so only true-like values enable the
sample routes.
🧹 Nitpick comments (7)
tests/media/.gitignore (1)

1-3: Optional: Add a comment explaining the intent.

Consider adding a comment to clarify why floorplan.jpg is tracked, which will help future maintainers understand this is a deliberate test fixture.

✨ Proposed enhancement (optional)
 *
 !.gitignore
+# Track test fixtures for sample_connection tests
 !floorplan.jpg
tests/openwisp2/sample_connection/admin.py (1)

1-1: Use a specific noqa code for clarity.

The blanket noqa directive can be replaced with a more specific one to clarify intent.

Suggested fix
-from openwisp_controller.connection import admin  # noqa
+from openwisp_controller.connection import admin  # noqa: F401
tests/openwisp2/sample_connection/pytest.py (1)

1-10: Consider dropping del BaseTestCommandsConsumer unless it’s solving a real collection issue.

Since the imported class is aliased to BaseTestCommandsConsumer (not Test*), pytest shouldn’t collect it anyway; the del adds a bit of surprise/IDE friction. If you’ve seen unwanted discovery, a short comment explaining why would help.

tests/openwisp2/sample_connection/models.py (1)

10-29: Prefer avoiding null=True on CharField unless you need tri-state semantics.

If details doesn’t need to distinguish NULL vs "", consider null=False + default="" to simplify queries/fixtures.

Proposed change
 class DetailsModel(models.Model):
-    details = models.CharField(max_length=64, blank=True, null=True)
+    details = models.CharField(max_length=64, blank=True, default="")
tests/openwisp2/sample_connection/tests.py (1)

58-96: Use config_app_label (or a class attribute) instead of hardcoding "config" in notification assertions.

This keeps the sample tests aligned with the broader “dynamic app label” direction in the PR.

Proposed tweak (one possible approach)
 class TestNotifications(BaseTestNotifications):
     app_label = "sample_connection"
+    config_app_label = "config"
@@
-        config_app = "config"
-        device_url_path = reverse(f"admin:{config_app}_device_change", args=[self.d.id])
+        device_url_path = reverse(
+            f"admin:{self.config_app_label}_device_change", args=[self.d.id]
+        )
tests/openwisp2/sample_connection/migrations/0001_initial.py (1)

186-193: Inconsistent model reference pattern between DeviceConnection and Command.

DeviceConnection.device uses a hardcoded string "config.Device" (line 191-192), whereas Command.device (line 278-281) uses swapper.get_model_name("config", "Device"). For consistency and to support swappable Device models, consider using swapper here as well.

♻️ Suggested consistency fix
                 (
                     "device",
                     models.ForeignKey(
                         on_delete=django.db.models.deletion.CASCADE,
-                        to="config.Device",
+                        to=swapper.get_model_name("config", "Device"),
                     ),
                 ),
tests/openwisp2/sample_connection/api/views.py (1)

1-18: Consider consolidating imports.

Multiple separate import statements from the same module can be consolidated for improved readability.

♻️ Consolidated imports
-from openwisp_controller.connection.api.views import (
-    CommandDetailsView as BaseCommandDetailsView,
-)
-from openwisp_controller.connection.api.views import (
-    CommandListCreateView as BaseCommandListCreateView,
-)
-from openwisp_controller.connection.api.views import (
-    CredentialDetailView as BaseCredentialDetailView,
-)
-from openwisp_controller.connection.api.views import (
-    CredentialListCreateView as BaseCredentialListCreateView,
-)
-from openwisp_controller.connection.api.views import (
-    DeviceConnectionDetailView as BaseDeviceConnectionDetailView,
-)
-from openwisp_controller.connection.api.views import (
-    DeviceConnenctionListCreateView as BaseDeviceConnenctionListCreateView,
-)
+from openwisp_controller.connection.api.views import (
+    CommandDetailsView as BaseCommandDetailsView,
+    CommandListCreateView as BaseCommandListCreateView,
+    CredentialDetailView as BaseCredentialDetailView,
+    CredentialListCreateView as BaseCredentialListCreateView,
+    DeviceConnectionDetailView as BaseDeviceConnectionDetailView,
+    DeviceConnenctionListCreateView as BaseDeviceConnenctionListCreateView,
+)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc1768d and 87d9f97.

⛔ Files ignored due to path filters (1)
  • tests/media/floorplan.jpg is excluded by !**/*.jpg
📒 Files selected for processing (20)
  • openwisp_firmware_upgrader/tests/test_admin.py
  • openwisp_firmware_upgrader/tests/test_models.py
  • openwisp_firmware_upgrader/tests/test_selenium.py
  • openwisp_firmware_upgrader/tests/test_tasks.py
  • tests/media/.gitignore
  • tests/openwisp2/sample_connection/__init__.py
  • tests/openwisp2/sample_connection/admin.py
  • tests/openwisp2/sample_connection/api/__init__.py
  • tests/openwisp2/sample_connection/api/views.py
  • tests/openwisp2/sample_connection/apps.py
  • tests/openwisp2/sample_connection/migrations/0001_initial.py
  • tests/openwisp2/sample_connection/migrations/0002_default_group_permissions.py
  • tests/openwisp2/sample_connection/migrations/0003_name_unique_per_organization.py
  • tests/openwisp2/sample_connection/migrations/__init__.py
  • tests/openwisp2/sample_connection/models.py
  • tests/openwisp2/sample_connection/pytest.py
  • tests/openwisp2/sample_connection/tests.py
  • tests/openwisp2/sample_firmware_upgrader/tests.py
  • tests/openwisp2/settings.py
  • tests/openwisp2/urls.py
🧰 Additional context used
🧬 Code graph analysis (6)
openwisp_firmware_upgrader/tests/test_selenium.py (2)
openwisp_firmware_upgrader/models.py (3)
  • BatchUpgradeOperation (37-40)
  • UpgradeOperation (43-46)
  • DeviceFirmware (31-34)
openwisp_firmware_upgrader/upgraders/openwrt.py (1)
  • OpenWrt (23-530)
openwisp_firmware_upgrader/tests/test_tasks.py (2)
openwisp_firmware_upgrader/base/models.py (2)
  • upgrade (523-528)
  • upgrade (683-776)
openwisp_firmware_upgrader/tasks.py (2)
  • batch_upgrade_operation (44-59)
  • upgrade_firmware (24-40)
tests/openwisp2/sample_connection/migrations/0002_default_group_permissions.py (2)
tests/openwisp2/sample_connection/migrations/0001_initial.py (1)
  • Migration (21-292)
tests/openwisp2/sample_connection/migrations/0003_name_unique_per_organization.py (1)
  • Migration (6-20)
tests/openwisp2/urls.py (1)
openwisp_firmware_upgrader/api/views.py (1)
  • get (94-105)
tests/openwisp2/sample_firmware_upgrader/tests.py (1)
openwisp_firmware_upgrader/tests/test_selenium.py (1)
  • TestDeviceAdmin (29-331)
tests/openwisp2/sample_connection/migrations/0001_initial.py (3)
tests/openwisp2/sample_connection/migrations/0002_default_group_permissions.py (1)
  • Migration (9-19)
tests/openwisp2/sample_connection/migrations/0003_name_unique_per_organization.py (1)
  • Migration (6-20)
openwisp_firmware_upgrader/swapper.py (1)
  • get_model_name (11-12)
🪛 Ruff (0.14.10)
openwisp_firmware_upgrader/tests/test_admin.py

526-526: Unused method argument: args

(ARG002)


558-558: Unused method argument: args

(ARG002)


612-612: Unused method argument: args

(ARG002)


690-690: Unused method argument: args

(ARG002)


728-728: Unused method argument: args

(ARG002)


742-742: Unused method argument: args

(ARG002)


756-756: Unused method argument: args

(ARG002)


773-773: Unused method argument: args

(ARG002)


793-793: Unused method argument: args

(ARG002)


850-850: Unused method argument: args

(ARG002)

openwisp_firmware_upgrader/tests/test_selenium.py

198-198: Unused method argument: args

(ARG002)


277-277: Unused method argument: args

(ARG002)


279-279: Unpacked variable org is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


279-279: Unpacked variable category is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


279-279: Unpacked variable build1 is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


279-279: Unpacked variable image1 is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

openwisp_firmware_upgrader/tests/test_tasks.py

40-40: Unused method argument: args

(ARG002)


52-52: Unused method argument: args

(ARG002)


62-62: Unused method argument: args

(ARG002)

openwisp_firmware_upgrader/tests/test_models.py

510-510: Unused method argument: args

(ARG002)


534-534: Unused method argument: args

(ARG002)

tests/openwisp2/sample_connection/admin.py

1-1: Unused blanket noqa directive

Remove unused noqa directive

(RUF100)

tests/openwisp2/sample_connection/migrations/0002_default_group_permissions.py

10-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


12-19: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/openwisp2/sample_connection/migrations/0003_name_unique_per_organization.py

7-9: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


11-20: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/openwisp2/sample_connection/migrations/0001_initial.py

24-30: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


32-292: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (16)
tests/media/.gitignore (1)

1-3: LGTM!

The .gitignore correctly excludes all files in the media directory while intentionally tracking the test fixture floorplan.jpg and the configuration file itself. Pattern order is correct for Git semantics.

tests/openwisp2/sample_connection/apps.py (1)

1-9: LGTM!

The SampleConnectionConfig correctly extends ConnectionConfig with appropriate name and label attributes. The del ConnectionConfig pattern is a valid Django convention to prevent namespace pollution after subclassing.

openwisp_firmware_upgrader/tests/test_models.py (2)

491-507: LGTM!

The refactored context-manager pattern for mocking DeviceConnection.connect correctly scopes the mock to the test's active code path. The *args parameter captures decorator-injected mocks and is intentionally unused.


509-587: Consistent mocking pattern applied across all batch upgrade tests.

The context-manager approach is consistently applied in test_upgrade_related_devices, test_upgrade_firmwareless_devices, and test_upgrade_related_devices_existing_fw. Test logic remains unchanged.

tests/openwisp2/settings.py (1)

219-231: LGTM!

The sample_connection app integration follows the established pattern from sample_firmware_upgrader. The app replacement in INSTALLED_APPS, addition to EXTENDED_APPS, and swapper model settings are correctly configured.

tests/openwisp2/sample_connection/migrations/0003_name_unique_per_organization.py (1)

1-20: LGTM!

This migration correctly transitions the credentials.name field from globally unique to organization-scoped uniqueness via unique_together. This is the expected pattern for multi-tenant credential management. The Ruff RUF012 warnings are false positives for Django migration files.

tests/openwisp2/sample_connection/migrations/0002_default_group_permissions.py (1)

1-19: LGTM!

The data migration correctly reuses permission assignment functions from openwisp_controller.connection.migrations. Using migrations.RunPython.noop for reverse is appropriate as permission removal on rollback is typically undesired.

openwisp_firmware_upgrader/tests/test_tasks.py (1)

26-68: LGTM!

The context-manager mocking pattern for DeviceConnection.connect is consistently applied across all tests. The *args parameter intentionally captures decorator-injected mocks and is a common pattern when the mock objects aren't explicitly referenced in test code.

tests/openwisp2/urls.py (1)

10-49: Verify the api/v1/ include ordering and get_connection_api_urls(...) compatibility.

Because this relies on openwisp-controller==1.3.0 URL factory behavior and you now have multiple api/v1/ includes, please confirm the intended routes are reachable and none are shadowed (especially if any patterns overlap).

openwisp_firmware_upgrader/tests/test_admin.py (1)

48-114: Dynamic admin URL construction via config_app_label looks good.

Also applies to: 188-196, 358-413

tests/openwisp2/sample_connection/tests.py (1)

98-112: The test code is likely correct as-is. This code is inheriting from a base test class in an external OpenWISP package and using Django REST Framework, which is installed in this project. REST Framework's test client properly handles JSON serialization when a dict is passed to client.post() with content_type="application/json", or the code may be using the modern json= parameter pattern. The fact that the test passes in CI (as confirmed by the workflow configuration showing successful test runs) strongly indicates the JSON is being serialized correctly. The review concern applies primarily to standard Django TestCase usage, but does not account for REST Framework's APIClient behavior.

Likely an incorrect or invalid review comment.

tests/openwisp2/sample_connection/migrations/0001_initial.py (3)

1-31: LGTM on migration setup and dependencies.

The migration correctly declares dependencies using swapper for flexible model resolution, ensuring proper migration ordering across swappable apps.


246-256: Django version-conditional choices pattern is correct.

The version check django.VERSION < (5, 0) properly handles Django 5.0+'s preference for callable choices via get_command_choices versus the static COMMAND_CHOICES tuple for earlier versions.


206-291: Command model configuration looks good.

The Command model correctly uses swapper for both the connection ForeignKey and the swappable setting, enabling proper model substitution in extended apps.

tests/openwisp2/sample_connection/api/views.py (2)

21-50: LGTM on view class structure.

The pass-through subclasses and as_view() instantiation follow the standard pattern for Django REST framework view extension in sample/test apps. This allows customization points while inheriting all base functionality.


16-18: Inherited typo from upstream base class.

DeviceConnenctionListCreateView contains a typo ("nnection" instead of "nection") from the upstream openwisp_controller.connection.api.views module. This sample app correctly mirrors the upstream class name, and the variable deviceconnection_list_create_view uses the correct spelling.

Consider filing an issue or fixing the typo upstream in openwisp_controller to maintain naming consistency across the codebase.

Comment thread openwisp_firmware_upgrader/tests/test_admin.py
Comment thread tests/openwisp2/urls.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @tests/openwisp2/sample_connection/tests.py:
- Around line 68-81: TestConnectionApi is missing the class attribute app_label
= "sample_connection" which other tests in this file set; add that attribute to
the TestConnectionApi class definition (class
TestConnectionApi(BaseTestConnectionApi)) so it matches the other tests, and
re-run tests to ensure the hardcoded with self.assertNumQueries(13): still
reflects expected DB queries—update or remove the assertNumQueries invocation in
TestConnectionApi if the query count changes.
🧹 Nitpick comments (1)
tests/openwisp2/urls.py (1)

10-15: Unconditional imports used only in conditional block.

The imports for get_connection_api_urls and connection_api_views on lines 10-15 are always executed, but only used when SAMPLE_APP is set. This is fine since this is a test module and sample_connection always exists in the test suite, but consider moving these imports inside the conditional block for clarity and slightly faster startup when SAMPLE_APP is not set.

♻️ Suggested refactor
-from openwisp_controller.connection.api.urls import (
-    get_api_urls as get_connection_api_urls,
-)
 from openwisp_users.api.urls import get_api_urls
 
-from .sample_connection.api import views as connection_api_views
-
 redirect_view = RedirectView.as_view(url=reverse_lazy("admin:index"))
 
 urlpatterns = []
 
 if os.environ.get("SAMPLE_APP", False):
+    from openwisp_controller.connection.api.urls import (
+        get_api_urls as get_connection_api_urls,
+    )
+
+    from .sample_connection.api import views as connection_api_views
+
     urlpatterns += [

Also applies to: 21-37

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87d9f97 and 0aaba03.

📒 Files selected for processing (2)
  • tests/openwisp2/sample_connection/tests.py
  • tests/openwisp2/urls.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/openwisp2/sample_connection/tests.py (2)
openwisp_firmware_upgrader/swapper.py (1)
  • load_model (7-8)
openwisp_firmware_upgrader/api/views.py (1)
  • post (85-92)
⏰ 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.11 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=4.2.0
  • GitHub Check: Python==3.13 | 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.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
🔇 Additional comments (4)
tests/openwisp2/urls.py (1)

39-48: LGTM!

The URL pattern structure is correct. The conditional sample app patterns are prepended, followed by the common admin, redirect, and API routes. The use of += to extend urlpatterns is appropriate.

tests/openwisp2/sample_connection/tests.py (3)

30-52: LGTM!

The test class structure properly extends base test classes with appropriate app_label and config_app_label attributes for the sample_connection app context. The pattern of only overriding necessary attributes is clean.


84-92: Good practice: cleaning up the namespace.

Deleting the base test classes from the module namespace prevents pytest/unittest from discovering and running them as separate test cases in this module.


55-55: I cannot provide a rewritten review comment because no review comment content was provided in the <review_comment> tags. Please provide:

  1. The review comment you want me to rewrite
  2. Any relevant code context or repository information needed for verification

Once you provide these details, I will rewrite the review comment in the required format and output only the rewritten comment with its classification tag between the <!-- result-start --> and <!-- result-end --> markers.

Comment thread tests/openwisp2/sample_connection/tests.py
…t_one

# Conflicts:
#	openwisp_firmware_upgrader/tests/test_admin.py
#	openwisp_firmware_upgrader/tests/test_selenium.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
openwisp_firmware_upgrader/tests/test_admin.py (1)

659-660: ⚠️ Potential issue | 🟠 Major

Patch target should resolve from the swapped DeviceConnection model, not a fixed module path.

Using a literal default-model path can miss the effective model in extended/swap scenarios and let real connect() behavior leak into tests.

Proposed fix
-    _mock_connect = "openwisp_controller.connection.models.DeviceConnection.connect"
+    _mock_connect = (
+        f"{DeviceConnection.__module__}.{DeviceConnection.__name__}.connect"
+    )
#!/bin/bash
set -euo pipefail

# Verify which DeviceConnection model is active in tests and whether connect() is overridden.
rg -n "swapper.load_model\\(\"connection\", \"DeviceConnection\"\\)|CONNECTION_.*MODEL|SWAPPABLE" tests openwisp_firmware_upgrader -g "*.py" -C2

# Inspect sample/extended connection model definitions.
fd "models.py" tests/openwisp2 | xargs -I{} sh -c 'echo "## {}"; rg -n "class .*DeviceConnection|def connect\\(" "{}" -C2'

# Locate hardcoded connect patch targets in tests.
rg -n "openwisp_controller\\.connection\\.models\\.DeviceConnection\\.connect|_mock_connect" openwisp_firmware_upgrader/tests -C1

Also applies to: 664-665

🤖 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 659 - 660, The
test currently patches a hardcoded
"openwisp_controller.connection.models.DeviceConnection.connect" via
_mock_connect which breaks when the DeviceConnection model is swapped; instead,
import swapper (or Django's apps.get_model) in the test and resolve the active
model with swapper.load_model("connection", "DeviceConnection"), then build the
patch target from the resolved model's __module__ and __name__ and patch
"<resolved_module>.<resolved_class>.connect" (use that dynamic target everywhere
_mock_connect is used, e.g., replace references to _mock_connect with the
resolved target string).
openwisp_firmware_upgrader/tests/test_selenium.py (1)

44-44: ⚠️ Potential issue | 🟠 Major

Use a swapped-model-aware _mock_connect target in this class too.

The fixed default-model path is brittle under connection-app extension/swap; resolve from the loaded DeviceConnection class.

Proposed fix
-    _mock_connect = "openwisp_controller.connection.models.DeviceConnection.connect"
+    _mock_connect = (
+        f"{DeviceConnection.__module__}.{DeviceConnection.__name__}.connect"
+    )

Also applies to: 130-130, 209-210, 288-289

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_firmware_upgrader/tests/test_selenium.py` at line 44, _the
_mock_connect target currently uses a hard-coded model path which breaks when
the connection app/model is swapped; replace the fixed string with a dynamic
target built from the loaded DeviceConnection class (e.g. construct the target
as f"{DeviceConnection.__module__}.{DeviceConnection.__name__}.connect" after
importing or resolving DeviceConnection) and use that value for _mock_connect in
this test class; apply the same change to the other occurrences of _mock_connect
in this file (the other two places referenced) so the mock respects swapped
models.
🤖 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`:
- Line 55: Replace the hard-coded connection_app_label = "connection" with a
dynamic lookup from the model meta (e.g., connection_app_label =
Build._meta.app_label or Device._meta.app_label depending on which model the
test uses); import the referenced model (Build or Device) at the top of the test
module if not already imported and use its _meta.app_label to construct admin
URL namespaces so swapped models remain correct.

---

Duplicate comments:
In `@openwisp_firmware_upgrader/tests/test_admin.py`:
- Around line 659-660: The test currently patches a hardcoded
"openwisp_controller.connection.models.DeviceConnection.connect" via
_mock_connect which breaks when the DeviceConnection model is swapped; instead,
import swapper (or Django's apps.get_model) in the test and resolve the active
model with swapper.load_model("connection", "DeviceConnection"), then build the
patch target from the resolved model's __module__ and __name__ and patch
"<resolved_module>.<resolved_class>.connect" (use that dynamic target everywhere
_mock_connect is used, e.g., replace references to _mock_connect with the
resolved target string).

In `@openwisp_firmware_upgrader/tests/test_selenium.py`:
- Line 44: _the _mock_connect target currently uses a hard-coded model path
which breaks when the connection app/model is swapped; replace the fixed string
with a dynamic target built from the loaded DeviceConnection class (e.g.
construct the target as
f"{DeviceConnection.__module__}.{DeviceConnection.__name__}.connect" after
importing or resolving DeviceConnection) and use that value for _mock_connect in
this test class; apply the same change to the other occurrences of _mock_connect
in this file (the other two places referenced) so the mock respects swapped
models.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 25bb800c-3349-4bd5-9826-59695cc40f29

📥 Commits

Reviewing files that changed from the base of the PR and between 0aaba03 and 218ebf5.

📒 Files selected for processing (4)
  • openwisp_firmware_upgrader/tests/test_admin.py
  • openwisp_firmware_upgrader/tests/test_models.py
  • openwisp_firmware_upgrader/tests/test_selenium.py
  • tests/openwisp2/settings.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.12 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
🧰 Additional context used
🧠 Learnings (7)
📓 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: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: tests/openwisp2/sample_firmware_upgrader/migrations/0001_initial.py:61-67
Timestamp: 2026-02-23T21:36:30.581Z
Learning: In the openwisp/openwisp-firmware-upgrader repository, the sample app (tests/openwisp2/sample_firmware_upgrader) can have a different migration path from the main app. New migration files are generally not created for small changes in the sample app since it's only used for demonstration and testing purposes. The sample app's initial migration may include fields or choices that are added incrementally in the main app's migration history.
Learnt from: pandafy
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: docs/developer/installation.rst:123-130
Timestamp: 2026-01-29T18:09:27.769Z
Learning: In openwisp-firmware-upgrader, the `./runtests` script is a wrapper that runs tests for both openwisp_firmware_upgrader and SAMPLE_APP with coverage. To run only the sample app tests, developers should use `SAMPLE_APP=1 ./runtests.py --keepdb --failfast` directly, not the `./runtests` wrapper.
📚 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_selenium.py
  • openwisp_firmware_upgrader/tests/test_models.py
  • 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_selenium.py
  • openwisp_firmware_upgrader/tests/test_models.py
  • openwisp_firmware_upgrader/tests/test_admin.py
📚 Learning: 2026-01-29T18:09:27.769Z
Learnt from: pandafy
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: docs/developer/installation.rst:123-130
Timestamp: 2026-01-29T18:09:27.769Z
Learning: In openwisp-firmware-upgrader, the `./runtests` script is a wrapper that runs tests for both openwisp_firmware_upgrader and SAMPLE_APP with coverage. To run only the sample app tests, developers should use `SAMPLE_APP=1 ./runtests.py --keepdb --failfast` directly, not the `./runtests` wrapper.

Applied to files:

  • openwisp_firmware_upgrader/tests/test_selenium.py
  • tests/openwisp2/settings.py
📚 Learning: 2026-02-23T21:36:30.581Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: tests/openwisp2/sample_firmware_upgrader/migrations/0001_initial.py:61-67
Timestamp: 2026-02-23T21:36:30.581Z
Learning: In the openwisp/openwisp-firmware-upgrader repository, the sample app (tests/openwisp2/sample_firmware_upgrader) can have a different migration path from the main app. New migration files are generally not created for small changes in the sample app since it's only used for demonstration and testing purposes. The sample app's initial migration may include fields or choices that are added incrementally in the main app's migration history.

Applied to files:

  • openwisp_firmware_upgrader/tests/test_selenium.py
  • openwisp_firmware_upgrader/tests/test_admin.py
  • tests/openwisp2/settings.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_selenium.py
  • openwisp_firmware_upgrader/tests/test_models.py
  • openwisp_firmware_upgrader/tests/test_admin.py
📚 Learning: 2026-02-25T00:17:23.479Z
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.

Applied to files:

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

242-254: LGTM!

The sample_connection app extension follows the established pattern used for sample_firmware_upgrader. The implementation correctly:

  • Swaps openwisp_controller.connection with openwisp2.sample_connection in INSTALLED_APPS
  • Adds the original app to EXTENDED_APPS
  • Configures the swapper model settings to point to the sample_connection models

The model settings (CONNECTION_CREDENTIALS_MODEL, CONNECTION_DEVICECONNECTION_MODEL, CONNECTION_COMMAND_MODEL) correctly reference the models defined in tests/openwisp2/sample_connection/models.py.

openwisp_firmware_upgrader/tests/test_models.py (1)

548-562: Good mock scoping improvement.

Switching to per-test with mock.patch(...) blocks improves isolation and avoids patch bleed between transaction tests.

Also applies to: 566-587, 590-610, 622-643

openwisp_firmware_upgrader/tests/test_selenium.py (1)

113-117: Dynamic changelist URL assertion is a solid fix.

Building the expected URL with self.live_server_url + reverse(...) makes the redirect assertion environment-safe.

Comment thread openwisp_firmware_upgrader/tests/test_admin.py Outdated
@github-project-automation github-project-automation Bot moved this from To do (general) to In progress in OpenWISP Contributor's Board Mar 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
openwisp_firmware_upgrader/tests/test_admin.py (2)

659-663: ⚠️ Potential issue | 🟠 Major

Patch the swapped DeviceConnection class, not a hard-coded module path.

Line 659 hard-codes openwisp_controller...DeviceConnection.connect, but this module already resolves DeviceConnection via swapper (Line 45). In extended-app mode, this can patch the wrong class method.

Proposed fix
-    _mock_connect = "openwisp_controller.connection.models.DeviceConnection.connect"
+    _mock_connect = f"{DeviceConnection.__module__}.DeviceConnection.connect"
#!/bin/bash
set -euo pipefail

# Verify swapped DeviceConnection usage in this test module.
rg -n 'swapper\.load_model\("connection",\s*"DeviceConnection"\)' openwisp_firmware_upgrader/tests/test_admin.py -C2

# Verify test settings/model wiring for sample_connection or connection model swaps.
fd -t f "settings.py" tests | while read -r f; do
  rg -n 'CONNECTION_.*DeviceConnection|sample_connection|swapper' "$f" || true
done

# Inspect DeviceConnection class/connect definitions in test apps.
fd -t f "models.py" tests | while read -r f; do
  rg -n 'class\s+DeviceConnection\b|def\s+connect\s*\(' "$f" -C2 || true
done
🤖 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 659 - 663, The
test currently hard-codes _mock_connect to
"openwisp_controller.connection.models.DeviceConnection.connect", which can
patch the wrong class when swaps are used; change the test to patch the actual
swapped class instead: obtain the DeviceConnection class via
swapper.load_model("connection", "DeviceConnection") (or call swapper.load_model
once and reuse it) and then patch its connect method (e.g., patch the attribute
on the returned class or construct the module-qualified path from its __module__
and __name__). Update references in test_upgrade_selected_action_perms and the
_mock_connect setup to use the swapped class rather than the static string.

863-866: 🛠️ Refactor suggestion | 🟠 Major

Avoid calling one test method from another test method.

Line 865 (self.test_upgrade_all()) couples tests and nests decorators/patches, which makes failures harder to localize. Extract shared setup/execution into a helper and call that helper from both tests.

Refactor direction
+    def _run_upgrade_all_flow(self):
+        # shared flow currently inside test_upgrade_all
+        ...

     `@mock.patch`(_mock_upgrade, return_value=True)
     def test_upgrade_all(self, *args):
-        with mock.patch(self._mock_connect, return_value=True):
-            ...
+        self._run_upgrade_all_flow()

     `@mock.patch`(_mock_upgrade, return_value=True)
     def test_massive_upgrade_operation_page(self, *args):
         with mock.patch(self._mock_connect, return_value=True):
-            self.test_upgrade_all()
+            self._run_upgrade_all_flow()
             uo = UpgradeOperation.objects.first()
#!/bin/bash
set -euo pipefail
# Verify test-to-test invocations in this module.
rg -nP 'self\.test_[A-Za-z0-9_]+\(' openwisp_firmware_upgrader/tests/test_admin.py -C2
🤖 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 863 - 866, The
test calls another test method (self.test_upgrade_all())—remove this coupling by
extracting the common setup/execution code from test_upgrade_all into a shared
helper (e.g., a private method like _prepare_and_run_upgrade or
_create_upgrade_operations) and have both test_upgrade_all and
test_massive_upgrade_operation_page call that helper instead; ensure any
mocks/patches (like the one using self._mock_connect) are applied in the
individual tests or passed into the helper as parameters so you do not nest
decorators/patches when invoking the shared logic, then replace the direct
self.test_upgrade_all() call with a call to the new helper and use
UpgradeOperation.objects.first() as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@openwisp_firmware_upgrader/tests/test_admin.py`:
- Around line 659-663: The test currently hard-codes _mock_connect to
"openwisp_controller.connection.models.DeviceConnection.connect", which can
patch the wrong class when swaps are used; change the test to patch the actual
swapped class instead: obtain the DeviceConnection class via
swapper.load_model("connection", "DeviceConnection") (or call swapper.load_model
once and reuse it) and then patch its connect method (e.g., patch the attribute
on the returned class or construct the module-qualified path from its __module__
and __name__). Update references in test_upgrade_selected_action_perms and the
_mock_connect setup to use the swapped class rather than the static string.
- Around line 863-866: The test calls another test method
(self.test_upgrade_all())—remove this coupling by extracting the common
setup/execution code from test_upgrade_all into a shared helper (e.g., a private
method like _prepare_and_run_upgrade or _create_upgrade_operations) and have
both test_upgrade_all and test_massive_upgrade_operation_page call that helper
instead; ensure any mocks/patches (like the one using self._mock_connect) are
applied in the individual tests or passed into the helper as parameters so you
do not nest decorators/patches when invoking the shared logic, then replace the
direct self.test_upgrade_all() call with a call to the new helper and use
UpgradeOperation.objects.first() as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: eab7bd0d-a963-42fb-8cd0-2dbbf2bbb279

📥 Commits

Reviewing files that changed from the base of the PR and between 218ebf5 and 4bb164c.

📒 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.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | 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~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: tests/openwisp2/sample_firmware_upgrader/migrations/0001_initial.py:61-67
Timestamp: 2026-02-23T21:36:30.581Z
Learning: In the openwisp/openwisp-firmware-upgrader repository, the sample app (tests/openwisp2/sample_firmware_upgrader) can have a different migration path from the main app. New migration files are generally not created for small changes in the sample app since it's only used for demonstration and testing purposes. The sample app's initial migration may include fields or choices that are added incrementally in the main app's migration history.
Learnt from: pandafy
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: docs/developer/installation.rst:123-130
Timestamp: 2026-01-29T18:09:27.769Z
Learning: In openwisp-firmware-upgrader, the `./runtests` script is a wrapper that runs tests for both openwisp_firmware_upgrader and SAMPLE_APP with coverage. To run only the sample app tests, developers should use `SAMPLE_APP=1 ./runtests.py --keepdb --failfast` directly, not the `./runtests` wrapper.
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-02-25T00:17:23.479Z
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.

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
📚 Learning: 2026-02-23T21:36:30.581Z
Learnt from: nemesifier
Repo: openwisp/openwisp-firmware-upgrader PR: 377
File: tests/openwisp2/sample_firmware_upgrader/migrations/0001_initial.py:61-67
Timestamp: 2026-02-23T21:36:30.581Z
Learning: In the openwisp/openwisp-firmware-upgrader repository, the sample app (tests/openwisp2/sample_firmware_upgrader) can have a different migration path from the main app. New migration files are generally not created for small changes in the sample app since it's only used for demonstration and testing purposes. The sample app's initial migration may include fields or choices that are added incrementally in the main app's migration history.

Applied to files:

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

661-694: Per-test mock scoping is a good improvement.

Using method-level @mock.patch(_mock_upgrade, ...) and local connect patch contexts reduces cross-test mock leakage.

@asmodehn

asmodehn commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

Switching to draft again, as I need to make it work again with recent changes on master, some tests are failing in non-obvious ways.

@asmodehn

asmodehn commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

On the mock refactoring -- converting test_add_credentials_with_cancelled_upgrade_operation to use self._mock_connect / self._mock_upgrade is correct. But TestAdminTransaction still uses @mock.patch(_mock_connect, ...) decorators, which capture the value at parent-class definition time. Subclassing _mock_connect will not affect them. This is not a regression but should be acknowledged in the description so reviewers know the scope.

Not sure what you mean here, all instances of _mock_connect_ I could find are in context managers.
Anyway I added a line in the description to be more explicit about this.

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@openwisp-companion

Copy link
Copy Markdown

Test Failures and Slow Tests

Hello @asmodehn,
(Analysis for commit 084dafa)

The CI failed due to multiple slow tests. The tests in openwisp_firmware_upgrader.tests.test_admin.TestAdmin and several other test classes are exceeding the acceptable time limits.

Action:
Investigate the slow tests within openwisp_firmware_upgrader.tests.test_admin.TestAdmin and other identified test classes. Optimize the test logic or the code being tested to reduce execution time.

Specific slow tests include (but are not limited to):

  • test_add_credentials_with_cancelled_upgrade_operation (3.13s in JOB 79298314621, 3.36s in JOB 79298314802)
  • test_device_upgrade_operation_inline_delete_by_status (4.90s in JOB 79298314802)
  • test_device_upgrade_operation_inline_delete_mixed_statuses (1.65s in JOB 79298314802)
  • test_device_upgrade_shared_firmware (3.01s in JOB 79298314802)
  • test_view_device_administrator (1.97s in JOB 79298314802)
  • test_api_batch_upgrade (1.06s in JOB 79298314802)

Please address these slow tests to resolve the CI failure.

@asmodehn asmodehn force-pushed the extended_controller_app_tests_part_one branch from 084dafa to 1680bbc Compare June 3, 2026 13:53
@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 4, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 4, 2026
@asmodehn asmodehn requested a review from nemesifier June 4, 2026 07:26

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good, I have a few doubts touching urls.py, I am not 100% sure but worth double checking (I aslso double checking on my side).

Comment thread tests/openwisp2/urls.py Outdated
urlpatterns += [
path(
"",
include(("openwisp_controller.config.urls", "config"), namespace="config"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

isn't this redundant with what we have below?

Comment thread tests/openwisp2/urls.py
urlpatterns += [
path("admin/", admin.site.urls),
path("", redirect_view, name="index"),
path("", include("openwisp_controller.urls")),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here, doesn't this already include the controller URLs? See https://github.com/openwisp/openwisp-controller/blob/master/openwisp_controller/urls.py

Comment thread tests/openwisp2/urls.py
"api/v1/",
include(
(
get_connection_api_urls(connection_api_views),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I understand you're doing this to supply your own views, which seems correct, but wouldn't this be overridden by path("", include("openwisp_controller.urls")), below?

@nemesifier

Copy link
Copy Markdown
Member

GPT-5.5 assisted review note, focused only on the URL doubts.

The URL concerns look valid:

  • tests/openwisp2/urls.py:23-26 is redundant. path("", include("openwisp_controller.urls")) already includes openwisp_controller.config.urls under the config namespace.
  • path("", include("openwisp_controller.urls")) does include the controller URL set, but not the swapped sample_connection API when SAMPLE_APP=1. In settings.py, openwisp_controller.connection is removed from INSTALLED_APPS, so openwisp_controller.urls skips its connection_api entry.
  • The custom connection_api include is therefore needed, and it is not overridden by the later openwisp_controller.urls include. There is no second connection_api namespace in that mode.
  • The current mount point looks wrong: get_connection_api_urls() already returns paths beginning with api/v1/controller/..., so wrapping it in path("api/v1/", ...) produces api/v1/api/v1/controller/....

Suggested fix: remove the explicit openwisp_controller.config.urls include, keep the custom connection API include, but mount it at "" rather than "api/v1/".

@nemesifier nemesifier force-pushed the extended_controller_app_tests_part_one branch from 2c92922 to 75c2024 Compare June 4, 2026 15:46

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@asmodehn I pushed some minor fixes, please double check on your end when you can.

@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@asmodehn

asmodehn commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

@nemesifier LGTM 👍
In a further effort, we should probably look for a (low maintenance) way to keep the same code in the sample apps here and in controller tests for consistency...

@nemesifier

Copy link
Copy Markdown
Member

@nemesifier LGTM 👍 In a further effort, we should probably look for a (low maintenance) way to keep the same code in the sample apps here and in controller tests for consistency...

You mean shared logic for these sample apps? If it's exactly identical we can do it easily.

@nemesifier nemesifier merged commit 90056e4 into openwisp:master Jun 5, 2026
16 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in OpenWISP Contributor's Board Jun 5, 2026
@nemesifier nemesifier changed the title [fix:tests] Extended controller app tests part one [tests] Extended controller app tests part one Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working python Pull requests that update Python code

Projects

Development

Successfully merging this pull request may close these issues.

2 participants