Skip to content

Resolve every gated object through a restricted queryset - #127

Merged
marcinpsk merged 32 commits into
developfrom
fix/object-scoped-lookups
Aug 18, 2026
Merged

Resolve every gated object through a restricted queryset#127
marcinpsk merged 32 commits into
developfrom
fix/object-scoped-lookups

Conversation

@marcinpsk

@marcinpsk marcinpsk commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

Makes each protected view find objects through a queryset that applies the user permissions.

Motivation / Problem

Bug: A limited NetBox permission could pass the first check. The view could then use a raw ID lookup to read or change an object outside that permission.

Scope of Change

  • Sync/Import logic
  • NetBox models / ORM
  • Tests

How Was This Tested?

  • Unit tests: yes. The permission tests use the real view, database, user, limited ObjectPermission, and NetBox restrict() behavior. Each test failed before the fix and passed after it.
  • Manual testing: no.

Risk Assessment

  • Existing users: Users with limited permissions can no longer access objects outside those permissions. These requests now return 404 or omit the restricted row. Other users are not affected.
  • Unintended imports or updates: no. Device selection does not change.

Backwards Compatibility

There are no breaking changes for authorized use. The fix stops access that should not have been allowed.

Other Notes

The first change touched 41 lookups in eight view modules. Two static checks prevent new raw ID lookups in protected views and cache keys without server_key.

Added after this description was written

Review rounds found the same defect on paths the first change did not cover:

  • Three more view modules (bulk-import actions, location sync, legacy-ID migration), plus the secondary lookups and the delete targets that take an ID from the client.
  • Derived targets. An interface, an IP address, or a module component that the sync finds by name or by relationship is now checked against a change-scoped queryset before it is written, and locked so a concurrent request cannot change it after the check.
  • Module adoption. NetBox adopts standalone components when it installs a module. Each adoptable component is now locked and authorized first, in the replace path too, before the old module is deleted. The adoption name is resolved the way the running NetBox version resolves it.
  • A posted selection that fails its restricted lookup (a VRF, an interface, a VLAN) now reports the error. It does not continue with no selection and does not fall back to another object.
  • Two more static checks, four in total: a posted selection must not fail open, and a restricted row lock must use select_for_update(of=("self",)) so it does not lock the permission join.
  • The tests for these views now run against real objects, a real database, and a real permission gate instead of ORM mocks.

Summary by CodeRabbit

  • Security & Permissions

    • Enforced object-level access checks across synchronization, imports, module management, cables, interfaces, IPs, VLANs, and mappings.
    • Restricted inaccessible collision targets and concealed sensitive identifiers.
  • Bug Fixes

    • Improved protection against race conditions during synchronization, imports, replacements, and migrations.
    • Prevented unauthorized reuse, updates, deletions, and adoptions.
    • Corrected VLAN identifier normalization and invalid-input reporting.
    • Prevented user preference changes from affecting shared defaults.
    • Improved validation for server-key form submissions.
  • Import Improvements

    • Added permission-aware collision previews, clearer skipped-row messages, and safer background-job handling.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f189b93a-5c6e-4322-8eb3-58cc393ae5b1

📥 Commits

Reviewing files that changed from the base of the PR and between d076daa and 6f3e7cb.

📒 Files selected for processing (4)
  • netbox_librenms_plugin/import_utils/bulk_import.py
  • netbox_librenms_plugin/tests/test_collisions.py
  • netbox_librenms_plugin/tests/test_vlan_sync_concurrency.py
  • netbox_librenms_plugin/tests/view_test_helpers.py

📝 Walkthrough

Walkthrough

This PR applies permission-scoped lookup and relocking across synchronization and import flows. It adds collision redaction, row-lock checks, transaction-race coverage, and database-backed regression tests. It also prevents shared default preference mutation.

Changes

Permission-scoped synchronization and import flows

Layer / File(s) Summary
Shared lookup and relocking
netbox_librenms_plugin/views/base/*, netbox_librenms_plugin/views/mixins.py, netbox_librenms_plugin/views/sync/migrate.py
Base views use restricted object lookup. Shared relocking supports later mutation paths.
Sync target permissions and locking
netbox_librenms_plugin/views/sync/device_fields.py, interfaces.py, ip_addresses.py, vlans.py
Sync paths apply action-specific permissions, restricted querysets, conflict reporting, validation, and row locking.
Module synchronization and replacement
netbox_librenms_plugin/views/sync/modules.py, netbox_librenms_plugin/tests/test_sync_modules.py, netbox_librenms_plugin/tests/test_module_replace.py
Module installation, replacement, movement, normalization, and bay-template workflows use permission-filtered dependencies.
Scoped collision handling and import mutations
netbox_librenms_plugin/import_utils/*, netbox_librenms_plugin/views/imports/actions.py, netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/bulk_import_collision.html
Collision targets are scoped to the requesting user. Hidden target details are redacted. Mapping and OOB mutations recheck access before writes.
Database-backed regression coverage
netbox_librenms_plugin/tests/*
Tests cover persistence, rollback, permission boundaries, row-lock order, deletion races, concurrent changes, and server-key validation.
Preference persistence
netbox_librenms_plugin/utils.py
User preference writes deep-copy configuration data before saving.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ImportDevicesJob
  participant bulk_import
  participant collisions
  participant CollisionTemplate

  ImportDevicesJob->>bulk_import: detect_collisions_for_device_ids(..., user)
  bulk_import->>collisions: scope_bulk_collisions(collisions, user)
  collisions-->>bulk_import: visible and redacted collision groups
  bulk_import->>CollisionTemplate: render visible links or restricted labels
Loading
sequenceDiagram
  participant SyncView
  participant RestrictedQueryset
  participant relock_scoped_row
  participant Database

  SyncView->>RestrictedQueryset: resolve permitted target
  RestrictedQueryset->>Database: fetch scoped row
  SyncView->>relock_scoped_row: relock resolved row
  relock_scoped_row->>Database: SELECT ... FOR UPDATE
  Database-->>SyncView: locked row or None
Loading

Poem

A rabbit checks each row with care,
And locks the records found there.
Hidden keys stay out of sight,
Visible targets render right.
Races report their changing state,
While copied preferences stay separate.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: permission-restricted queryset resolution for gated objects.
Description check ✅ Passed The description covers the problem, scope, testing, risks, compatibility, and expanded implementation details required by the template.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/object-scoped-lookups
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/object-scoped-lookups

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.

@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: 3

Caution

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

⚠️ Outside diff range comments (2)
netbox_librenms_plugin/views/sync/interfaces.py (1)

450-457: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the owner's view permission to DeleteNetBoxInterfacesView's declared permissions, matching SyncInterfacesView in this same file.

SyncInterfacesView.get_required_permissions_for_object_type (lines 40-49) declares ("view", Device)/("view", VirtualMachine) because the owner is resolved through restrict_object_or_404, so a missing grant surfaces as an explicit 403. DeleteNetBoxInterfacesView.get_required_permissions_for_object_type resolves its owner the same way (line 471/473) but omits the equivalent declaration, so the identical missing-grant case here surfaces as a bare 404 instead.

♻️ Proposed fix
     def get_required_permissions_for_object_type(self, object_type):
         """Return the required permissions based on object type."""
         if object_type == "device":
-            return [("delete", Interface)]
+            return [("view", Device), ("delete", Interface)]
         elif object_type == "virtualmachine":
-            return [("delete", VMInterface)]
+            return [("view", VirtualMachine), ("delete", VMInterface)]
         else:
             raise Http404(f"Invalid object type: {object_type}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbox_librenms_plugin/views/sync/interfaces.py` around lines 450 - 457,
Update DeleteNetBoxInterfacesView.get_required_permissions_for_object_type to
include the owner's view permission alongside the existing delete permission:
use Device for "device" and VirtualMachine for "virtualmachine", matching
SyncInterfacesView so owner resolution through restrict_object_or_404 enforces
the declared grant.
netbox_librenms_plugin/tests/test_coverage_device_fields.py (1)

41-60: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Bind the request before calling post() directly.

view.post(_make_request(), pk=1) on line 58 calls post() directly without view.setup(request). Every other direct-call test in this file uses the _post() helper for exactly this reason: _bind_and_call's own docstring states that a direct post() call "leaves self.request unset, which the object-scoped lookups read."

Here restrict_object_or_404 is fully mocked, so the current mock likely tolerates the missing binding. But this test is inconsistent with the rest of the file and with the PR's stated goal of exercising dispatch-equivalent request binding. Route this call through _post(view, _make_request(), pk=1) for consistency and to guard against a future change that reads self.request elsewhere in post().

🔧 Proposed fix
-        view.post(_make_request(), pk=1)
+        _post(view, _make_request(), pk=1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbox_librenms_plugin/tests/test_coverage_device_fields.py` around lines 41
- 60, Update test_write_views_fetch_device_info_live_not_cached to invoke the
view through the file’s _post helper instead of calling view.post directly,
passing the request and pk=1 so request setup occurs before post execution.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbox_librenms_plugin/tests/test_coverage_device_fields.py`:
- Around line 17-35: Extract the duplicated _bind_and_call, _post, and _get
helpers into one shared test helper module, preserving their current
setup-and-invoke behavior and docstrings. In
netbox_librenms_plugin/tests/test_coverage_device_fields.py#L17-L35,
netbox_librenms_plugin/tests/test_coverage_devices.py#L8-L26,
netbox_librenms_plugin/tests/test_device_fields_server_scoping.py#L26-L45,
netbox_librenms_plugin/tests/test_module_replace.py#L8-L27,
netbox_librenms_plugin/tests/test_sync_devices.py#L6-L25,
netbox_librenms_plugin/tests/test_sync_modules.py#L16-L35,
netbox_librenms_plugin/tests/test_coverage_sync_views.py#L10-L29, and
netbox_librenms_plugin/tests/test_coverage_sync_views2.py#L15-L34, remove the
local definitions and import the shared helpers instead.

In `@netbox_librenms_plugin/tests/test_view_wiring.py`:
- Around line 504-523: Refactor _scan to accept a parsed AST tree, then update
test_the_scan_recognizes_a_raw_lookup to pass its ast.parse result through _scan
and assert the raw lookup is reported. Remove the duplicated inline predicate
and avoid indexing call.args before verifying arguments, so argument-less calls
are safely skipped by the real scan implementation.
- Around line 692-701: Update the cache-key scan around the helper-call
detection to treat any keyword with arg equal to None (`**kwargs`) as scoped,
while preserving the existing explicit server_key and positional-argument
checks. Add the requested limitations to the scanner class docstring,
documenting that indirect helper calls through local or callable aliases are not
detected and that the scan does not inspect forwarded keyword contents.

---

Outside diff comments:
In `@netbox_librenms_plugin/tests/test_coverage_device_fields.py`:
- Around line 41-60: Update test_write_views_fetch_device_info_live_not_cached
to invoke the view through the file’s _post helper instead of calling view.post
directly, passing the request and pk=1 so request setup occurs before post
execution.

In `@netbox_librenms_plugin/views/sync/interfaces.py`:
- Around line 450-457: Update
DeleteNetBoxInterfacesView.get_required_permissions_for_object_type to include
the owner's view permission alongside the existing delete permission: use Device
for "device" and VirtualMachine for "virtualmachine", matching
SyncInterfacesView so owner resolution through restrict_object_or_404 enforces
the declared grant.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 82513f83-a0e6-4d91-a33d-9492361f1d10

📥 Commits

Reviewing files that changed from the base of the PR and between bcb8fd2 and a015fe0.

📒 Files selected for processing (21)
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/vlans.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: NetBox v4.6.5 / Python 3.13
  • GitHub Check: Analyze (python)
  • GitHub Check: NetBox main / Python 3.13
  • GitHub Check: NetBox v4.4.0 / Python 3.12
  • GitHub Check: NetBox main / Python 3.14
  • GitHub Check: NetBox v4.6.5 / Python 3.12
🧰 Additional context used
📓 Path-based instructions (3)
**/views/sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Sync action views must follow the pattern: check permissions with LibreNMSPermissionMixin and NetBoxObjectPermissionMixin, read selected items from request.POST.getlist('select'), load cached data using CacheMixin.get_cache_key(), apply changes inside transaction.atomic(), and redirect to the sync tab with ?tab=<resource>.

Files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
**/views/object_sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Object sync view methods must create instances of concrete table views, copy the request object, and call get_context_data(). VMs must skip cables and VLANs by returning None from those get_*_context() methods.

Files:

  • netbox_librenms_plugin/views/object_sync/devices.py
🧠 Learnings (27)
📚 Learning: 2026-03-07T17:17:04.217Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/cables.py:160-165
Timestamp: 2026-03-07T17:17:04.217Z
Learning: In Python views under netbox_librenms_plugin/views/sync, when obtaining a server_key for cache namespace scoping, read it from request.POST with a fallback to self.librenms_api.server_key (e.g., server_key = request.POST.get("server_key") or self.librenms_api.server_key) and assign it to an attribute (e.g., self._post_server_key) used by get_cached_links_data to build the cache key. Do not flag or remove this POST-read pattern, as it ensures consistent, future-proof cache namespace scoping for link data lookups. Apply this guidance to similar Sync views in the same module where server_key-based cache scoping is used.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-04-15T12:38:49.280Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/views/base/modules_view.py:133-141
Timestamp: 2026-04-15T12:38:49.280Z
Learning: Do not require or flag an explicit `permission_required = PERM_VIEW_PLUGIN` on views that inherit from `LibreNMSPermissionMixin` (e.g., those ultimately including `BaseModuleTableView` in `views/base/modules_view.py`). `LibreNMSPermissionMixin` is the authoritative place where `permission_required` is set to `PERM_VIEW_PLUGIN`; inherited values are already enforced via the mixin, making a redundant override unnecessary.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-05-17T11:32:40.631Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 76
File: netbox_librenms_plugin/views/object_sync/devices.py:178-180
Timestamp: 2026-05-17T11:32:40.631Z
Learning: In this plugin’s JSON endpoint views (e.g., NetBox view classes/functions under netbox_librenms_plugin/views/**), do not require a try/except JSONDecodeError around `json.loads(request.body)` by default if the endpoint is already protected by CSRF + session authentication and has object-level permission gates. Treat missing JSONDecodeError handling as acceptable when the only expected downside is a noisy log line. If you identify any additional security or availability impact from invalid JSON (e.g., unhandled exceptions leading to user-visible 500s, potential DoS amplification, or lack of appropriate throttling/validation), then flag it and recommend adding guarded parsing/validation.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-07-02T13:36:15.226Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/interfaces_view.py:226-226
Timestamp: 2026-07-02T13:36:15.226Z
Learning: When reviewing netbox-librenms-plugin view code that handles POSTed `server_key`, treat the plugin-wide convention as intentional: if the posted `server_key` is not present in `LibreNMSAPI.get_available_servers()`, the request should fall back to the currently configured default/active server key (not reject/fail-closed and not treat it as an error for that single view). Do not flag individual instances of this fallback pattern as incorrect “invalid server_key” validation. Any change to fail-closed behavior must be a coordinated cross-cutting change applied uniformly across all affected sites/views, not a one-view patch.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-06-01T15:12:26.824Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/ip_addresses.py:94-103
Timestamp: 2026-06-01T15:12:26.824Z
Learning: For any redirect/tab URL building in netbox_librenms_plugin/views/sync, views/base, and views/object_sync, propagate the active multi-server `server_key` as a `?server_key=<key>` query parameter so users return to the same server’s tab after POST actions. When handling POST requests, read the POST-scoped `server_key` from `request.POST` and store it (e.g., `self._post_server_key`) with a fallback to `self.librenms_api.server_key`; use this POST-scoped key for both cache-key scoping and for constructing the redirect/tab URLs. Treat this as the intentional codebase-wide convention—do not flag the presence/usage of the `server_key` query parameter (or the corresponding POST-scoped `_post_server_key` pattern) in these views as an error.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
📚 Learning: 2026-03-08T08:57:43.392Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/object_sync/devices.py:83-100
Timestamp: 2026-03-08T08:57:43.392Z
Learning: In views under netbox_librenms_plugin/views/object_sync, server_key values come from admin-controlled PLUGINS_CONFIG dict keys (e.g., "default", "production") and are not user input. Therefore URL-encoding them via urlencode() is unnecessary defensiveness. Do not flag direct string interpolation of server_key into query strings as a URL-injection or encoding issue. This guidance should apply to similar views in the same directory.

Applied to files:

  • netbox_librenms_plugin/views/object_sync/devices.py
📚 Learning: 2026-05-05T09:58:50.179Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/object_sync/devices.py:57-75
Timestamp: 2026-05-05T09:58:50.179Z
Learning: In object_sync view classes that pass Django/NetBox `request` into child table context helpers (e.g., for interfaces/cables/IPs/vlans/modules), ensure the child view stores `copy.copy(request)` rather than the original `request` object. Apply this consistently across similar sync views (such as the pattern used in `VMLibreNMSSyncView` in `vms.py`) to prevent cross-view request mutation when the child view modifies the request.

Applied to files:

  • netbox_librenms_plugin/views/object_sync/devices.py
📚 Learning: 2026-03-07T10:40:38.106Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/interfaces.py:241-244
Timestamp: 2026-03-07T10:40:38.106Z
Learning: In netbox_librenms_plugin/views/sync/interfaces.py, ensure that set_librenms_device_id does not apply the legacy bare-integer guard to Interface/VMInterface objects. The guard is only relevant for Device/VM objects with pre-existing bare integers from before multi-server support. Interfaces/VMInterfaces have librenms_id starting empty and their port_id is always written from the LibreNMS API JSON response, so there is no migration concern. Do not treat the warning-log path as a silent no-op for interfaces; keep appropriate logging/alerts active. Add or adjust tests to verify that interfaces paths write port_id correctly and do not trigger the legacy-bare-int logic, and document this distinction in code comments.

Applied to files:

  • netbox_librenms_plugin/views/sync/interfaces.py
📚 Learning: 2026-06-25T07:14:19.587Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/modules_view.py:1073-1074
Timestamp: 2026-06-25T07:14:19.587Z
Learning: In netbox_librenms_plugin/views/base/modules_view.py and netbox_librenms_plugin/views/sync/modules.py, treat LibreNMS `entPhysicalIndex` as an end-to-end integer invariant (it originates from an LibreNMS int DB column and is preserved as an int through the module inventory/sync pipeline). When reviewing code, do not flag mixed string/int `entPhysicalIndex` handling or request additional `int()` normalization solely as a defensive measure against string indices. Only recommend `int()` conversion/normalization if there is concrete evidence in the code path that values are actually being converted to strings (e.g., explicit casts, JSON serialization/deserialization steps that coerce to strings, or external inputs known to provide strings).

Applied to files:

  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-12T12:14:03.173Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 25
File: netbox_librenms_plugin/tests/test_coverage_device_fields.py:643-673
Timestamp: 2026-03-12T12:14:03.173Z
Learning: In netbox_librenms_plugin/tests/test_coverage_device_fields.py, strengthen the CreateAndAssignPlatformView success-path tests (test_manufacturer_not_found around lines 642–672 and the adjacent test around lines 674–697) by asserting that the newly created Platform instance is assigned to the locked device object (mock_locked.platform is mock_platform_instance) and that mock_locked.save() is called. This validates the FK assignment and persistence, rather than only checking that messages.success() was invoked. This improvement is a known backlog item (low priority); do not re-raise as a new finding.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
🔇 Additional comments (25)
netbox_librenms_plugin/views/mixins.py (1)

347-367: LGTM!

netbox_librenms_plugin/views/object_sync/devices.py (1)

366-475: LGTM!

Also applies to: 504-559

netbox_librenms_plugin/views/sync/cables.py (1)

10-10: LGTM!

Also applies to: 30-33, 207-207

netbox_librenms_plugin/views/sync/device_fields.py (1)

10-10: LGTM!

Also applies to: 68-68, 163-163, 227-227, 305-305, 418-418, 748-748, 863-863

netbox_librenms_plugin/views/sync/devices.py (1)

47-51: LGTM!

netbox_librenms_plugin/views/sync/interfaces.py (1)

9-9: LGTM!

Also applies to: 40-49, 127-133, 470-473

netbox_librenms_plugin/views/sync/ip_addresses.py (1)

9-9: LGTM!

Also applies to: 42-59, 85-89, 122-123

netbox_librenms_plugin/views/sync/modules.py (3)

12-12: LGTM!

Also applies to: 572-600, 687-687, 1244-1244, 1375-1375, 1428-1449, 1559-1578, 1673-1690, 1733-1755, 2021-2023, 2219-2219, 2281-2281


1925-1954: 🗄️ Data Integrity & Integration

No scoping issue with _resolve_target_device_with_validation.

selected_device_id is only accepted when it matches page_device.pk or a member from page_device.virtual_chassis.members.filter(pk=...); all other inputs return page_device with an invalid-selection warning.

			> Likely an incorrect or invalid review comment.

1940-1990: 🔒 Security & Privacy | 🏗️ Heavy lift

Authorization Bypass (CWE-639): Authorization Bypass Through User-Controlled Key (IDOR)

Reachability: External

Reachability path
● Entry
  netbox_librenms_plugin/views/sync/devices.py:53
  post
│
▼
● Hop
  netbox_librenms_plugin/views/sync/cables.py:201
  post
│
▼
● Hop
  netbox_librenms_plugin/views/object_sync/devices.py:130
  post
│
▼
● Sink
  netbox_librenms_plugin/views/sync/modules.py

Secondary object lookups bypass the new restricted-queryset control in two views. Both sites resolve a primary target through restrict_object_or_404 correctly, but then resolve a second, client-supplied object ID through a raw, unrestricted queryset and mutate it — bypassing the exact object-level permission control this PR introduces.

  • netbox_librenms_plugin/views/sync/modules.py#L1940-L1990: MoveModuleView.post resolves conflict_module from request.POST.get("conflict_module_id") via Module.objects.select_for_update().filter(pk=conflict_module_id) with no scoping at all, then reassigns its module_bay/device and saves it. Route this lookup through restrict_object_or_404 (or add explicit ownership/permission scoping) before locking and mutating it.
  • netbox_librenms_plugin/views/sync/device_fields.py#L693-L719: AssignVCSerialView.post resolves member from request.POST.get(f"member_id_{counter}") via Device.objects.get(pk=member_id), guarded only by a virtual-chassis-membership check (not a permission check), then overwrites and saves member.serial. Route this lookup through self.restrict_object_or_404(Device, "change", pk=member_id) before mutating it.

Both allow a user with a constrained (site/device-scoped) change/delete grant to mutate an object outside their granted scope, provided they can supply or guess the target's pk. This is precisely the vulnerability class restrict_object_or_404 was added to close in this PR; verify whether the project's new AST guard ("gated views do not resolve objects by raw primary key lookups") is scoped to catch secondary lookups nested inside a method body, not just the primary per-view lookup.

netbox_librenms_plugin/views/sync/vlans.py (1)

8-8: LGTM!

Also applies to: 28-30, 64-64

netbox_librenms_plugin/tests/test_coverage_device_fields.py (1)

100-3247: LGTM!

netbox_librenms_plugin/tests/test_coverage_devices.py (1)

27-992: LGTM!

netbox_librenms_plugin/tests/test_device_fields_server_scoping.py (1)

46-133: LGTM!

netbox_librenms_plugin/tests/test_module_replace.py (1)

74-671: LGTM!

netbox_librenms_plugin/tests/test_sync_devices.py (2)

26-243: LGTM!


140-200: 🔒 Security & Privacy | ⚡ Quick win

IDOR (CWE-639): Authorization Bypass Through User-Controlled Key (IDOR)

Reachability: External

Verify UpdateDeviceLocationView uses a permission-scoped object lookup. Across three independent test files, UpdateDeviceLocationView tests mock netbox_librenms_plugin.views.sync.devices.get_object_or_404 (Django's unrestricted shortcut) rather than NetBoxObjectPermissionMixin.restrict_object_or_404, while every sibling view in the same module (for example AddDeviceToLibreNMSView.get_object) was migrated. If the view only checks require_write_permission() (model-level) before resolving the device by raw pk, a caller whose NetBox object permission is scoped to a subset of devices could still push a LibreNMS location update for any device.

  • netbox_librenms_plugin/tests/test_sync_devices.py#L140-L200: confirm whether UpdateDeviceLocationView.post() resolves its device through restrict_object_or_404; if not, migrate it and update this test.
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py#L800-L868: same verification and update once the production code is confirmed.
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py#L925-L1007: same verification and update once the production code is confirmed.
#!/bin/bash
# Description: Inspect UpdateDeviceLocationView's object-resolution and permission-check pattern.
rg -n -C 8 'class UpdateDeviceLocationView' netbox_librenms_plugin/views/sync/devices.py
rg -n 'restrict_object_or_404|get_object_or_404|required_object_permissions|require_write_permission' netbox_librenms_plugin/views/sync/devices.py
netbox_librenms_plugin/tests/test_sync_modules.py (1)

36-6110: LGTM!

netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py (1)

74-1690: LGTM!

netbox_librenms_plugin/tests/test_coverage_sync_views.py (1)

30-2555: LGTM!

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (1)

35-2218: LGTM!

netbox_librenms_plugin/tests/test_coverage_sync_views3.py (1)

83-796: LGTM!

netbox_librenms_plugin/tests/test_permissions.py (1)

976-1173: LGTM!

netbox_librenms_plugin/tests/test_view_wiring.py (2)

526-660: LGTM!

Also applies to: 711-721


474-493: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Authorization Bypass (CWE-639): Authorization Bypass Through User-Controlled Key (IDOR)

Reachability: External

The gate detection misses views gated only by plugin write permission.

declares_gate requires an ast.Assign to required_object_permissions. A view that gates with require_write_permission() alone and then calls a raw get_object_or_404 is invisible to the scan. The graph context for netbox_librenms_plugin/views/sync/devices.py shows one such live instance: SyncSiteLocationView.post checks require_write_permission() and then executes device = get_object_or_404(Device, pk=pk) before writing the site name to LibreNMS. The class docstring lists other limits but not this one.

Two additional narrow gaps: ast.AnnAssign targets (required_object_permissions: dict = {...}) are not matched either.

Extend the scan to also treat classes that call require_write_permission or require_all_permissions as gated, or document this limit and fix the raw lookup in SyncSiteLocationView.

#!/bin/bash
# Find raw model lookups in view classes and show how each enclosing class gates the request.
rg -nP --type=py -C4 'get_object_or_404\(\s*(Device|VirtualMachine|Module|ModuleBay|ModuleType|Interface|VLAN|VLANGroup|IPAddress)\b' netbox_librenms_plugin/views

# Show which of those files declare a gate at all.
rg -nP --type=py -C2 'required_object_permissions|require_write_permission|require_all_permissions|require_object_permissions' netbox_librenms_plugin/views/sync/devices.py

Comment thread netbox_librenms_plugin/tests/test_coverage_device_fields.py Outdated
Comment thread netbox_librenms_plugin/tests/test_view_wiring.py Outdated
Comment thread netbox_librenms_plugin/tests/test_view_wiring.py
marcinpsk added a commit that referenced this pull request Aug 4, 2026
Follow-up to the object-scoping pass, from review of PR #127. Three more sites
resolved a client-supplied pk without scoping, and the AST guard could not see
any of them:

- MoveModuleView took conflict_module_id straight from the POST, locked the row
  with a plain manager and then reassigned its module_bay/device. A constrained
  change_module grant could move a module it cannot see.
- AssignVCSerialView resolved each member_id_<n> with Device.objects.get and
  overwrote the serial, guarded only by a same-virtual-chassis check, which is
  not a permission check.
- UpdateDeviceLocationView gated on the plugin write permission alone and then
  resolved the device by raw pk before pushing its site to LibreNMS. It now
  carries NetBoxObjectPermissionMixin with a declared view_device gate.

DeleteNetBoxInterfacesView now declares the owner's view permission like its
sibling SyncInterfacesView, so a missing grant is a stated 403 rather than a
404 at the lookup.

The guard grew to match:

- "gated" now also means a class that calls require_write_permission /
  require_all_permissions / require_object_permissions (and an annotated
  required_object_permissions), which is exactly what surfaces the location
  push; the docstring records the remaining limits (inherited lookups, and the
  Model.objects.get(pk=...) secondary form).
- the fixture tests run through the REAL scan (_scan_tree) instead of
  re-implementing its predicate, and a positive control asserts a scoped lookup
  is not flagged.
- the cache-key scan no longer reports a **kwargs-forwarding call as unscoped,
  and documents that a helper passed as a callable escapes it (modules.py does
  this in three places).

The request-binding test driver moves to tests/view_test_helpers.py instead of
being copy-pasted into eight files, and the tests whose stubbed seam moved now
patch the scoped queryset rather than the plain manager.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbox_librenms_plugin/tests/test_view_wiring.py`:
- Around line 769-777: Update the test request in the virtual-chassis serial
guard case to use 1-based POST keys, changing the sibling assignment fields from
serial_0/member_id_0 to serial_1/member_id_1 so AssignVCSerialView.post enters
its assignment loop and exercises the scoped member lookup.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4f6e775d-5bd6-4fe9-944a-9db460f57a4a

📥 Commits

Reviewing files that changed from the base of the PR and between a015fe0 and b081247.

📒 Files selected for processing (15)
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/modules.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: NetBox v4.4.0 / Python 3.12
  • GitHub Check: NetBox main / Python 3.14
  • GitHub Check: NetBox v4.6.5 / Python 3.13
  • GitHub Check: NetBox v4.6.5 / Python 3.12
  • GitHub Check: NetBox main / Python 3.13
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
**/views/sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Sync action views must follow the pattern: check permissions with LibreNMSPermissionMixin and NetBoxObjectPermissionMixin, read selected items from request.POST.getlist('select'), load cached data using CacheMixin.get_cache_key(), apply changes inside transaction.atomic(), and redirect to the sync tab with ?tab=<resource>.

Files:

  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
🧠 Learnings (25)
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-07T10:40:38.106Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/interfaces.py:241-244
Timestamp: 2026-03-07T10:40:38.106Z
Learning: In netbox_librenms_plugin/views/sync/interfaces.py, ensure that set_librenms_device_id does not apply the legacy bare-integer guard to Interface/VMInterface objects. The guard is only relevant for Device/VM objects with pre-existing bare integers from before multi-server support. Interfaces/VMInterfaces have librenms_id starting empty and their port_id is always written from the LibreNMS API JSON response, so there is no migration concern. Do not treat the warning-log path as a silent no-op for interfaces; keep appropriate logging/alerts active. Add or adjust tests to verify that interfaces paths write port_id correctly and do not trigger the legacy-bare-int logic, and document this distinction in code comments.

Applied to files:

  • netbox_librenms_plugin/views/sync/interfaces.py
📚 Learning: 2026-03-07T17:17:04.217Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/cables.py:160-165
Timestamp: 2026-03-07T17:17:04.217Z
Learning: In Python views under netbox_librenms_plugin/views/sync, when obtaining a server_key for cache namespace scoping, read it from request.POST with a fallback to self.librenms_api.server_key (e.g., server_key = request.POST.get("server_key") or self.librenms_api.server_key) and assign it to an attribute (e.g., self._post_server_key) used by get_cached_links_data to build the cache key. Do not flag or remove this POST-read pattern, as it ensures consistent, future-proof cache namespace scoping for link data lookups. Apply this guidance to similar Sync views in the same module where server_key-based cache scoping is used.

Applied to files:

  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-04-15T12:38:49.280Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/views/base/modules_view.py:133-141
Timestamp: 2026-04-15T12:38:49.280Z
Learning: Do not require or flag an explicit `permission_required = PERM_VIEW_PLUGIN` on views that inherit from `LibreNMSPermissionMixin` (e.g., those ultimately including `BaseModuleTableView` in `views/base/modules_view.py`). `LibreNMSPermissionMixin` is the authoritative place where `permission_required` is set to `PERM_VIEW_PLUGIN`; inherited values are already enforced via the mixin, making a redundant override unnecessary.

Applied to files:

  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-05-17T11:32:40.631Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 76
File: netbox_librenms_plugin/views/object_sync/devices.py:178-180
Timestamp: 2026-05-17T11:32:40.631Z
Learning: In this plugin’s JSON endpoint views (e.g., NetBox view classes/functions under netbox_librenms_plugin/views/**), do not require a try/except JSONDecodeError around `json.loads(request.body)` by default if the endpoint is already protected by CSRF + session authentication and has object-level permission gates. Treat missing JSONDecodeError handling as acceptable when the only expected downside is a noisy log line. If you identify any additional security or availability impact from invalid JSON (e.g., unhandled exceptions leading to user-visible 500s, potential DoS amplification, or lack of appropriate throttling/validation), then flag it and recommend adding guarded parsing/validation.

Applied to files:

  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-07-02T13:36:15.226Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/interfaces_view.py:226-226
Timestamp: 2026-07-02T13:36:15.226Z
Learning: When reviewing netbox-librenms-plugin view code that handles POSTed `server_key`, treat the plugin-wide convention as intentional: if the posted `server_key` is not present in `LibreNMSAPI.get_available_servers()`, the request should fall back to the currently configured default/active server key (not reject/fail-closed and not treat it as an error for that single view). Do not flag individual instances of this fallback pattern as incorrect “invalid server_key” validation. Any change to fail-closed behavior must be a coordinated cross-cutting change applied uniformly across all affected sites/views, not a one-view patch.

Applied to files:

  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-06-01T15:12:26.824Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/ip_addresses.py:94-103
Timestamp: 2026-06-01T15:12:26.824Z
Learning: For any redirect/tab URL building in netbox_librenms_plugin/views/sync, views/base, and views/object_sync, propagate the active multi-server `server_key` as a `?server_key=<key>` query parameter so users return to the same server’s tab after POST actions. When handling POST requests, read the POST-scoped `server_key` from `request.POST` and store it (e.g., `self._post_server_key`) with a fallback to `self.librenms_api.server_key`; use this POST-scoped key for both cache-key scoping and for constructing the redirect/tab URLs. Treat this as the intentional codebase-wide convention—do not flag the presence/usage of the `server_key` query parameter (or the corresponding POST-scoped `_post_server_key` pattern) in these views as an error.

Applied to files:

  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-06-25T07:14:19.587Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/modules_view.py:1073-1074
Timestamp: 2026-06-25T07:14:19.587Z
Learning: In netbox_librenms_plugin/views/base/modules_view.py and netbox_librenms_plugin/views/sync/modules.py, treat LibreNMS `entPhysicalIndex` as an end-to-end integer invariant (it originates from an LibreNMS int DB column and is preserved as an int through the module inventory/sync pipeline). When reviewing code, do not flag mixed string/int `entPhysicalIndex` handling or request additional `int()` normalization solely as a defensive measure against string indices. Only recommend `int()` conversion/normalization if there is concrete evidence in the code path that values are actually being converted to strings (e.g., explicit casts, JSON serialization/deserialization steps that coerce to strings, or external inputs known to provide strings).

Applied to files:

  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-12T12:14:03.173Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 25
File: netbox_librenms_plugin/tests/test_coverage_device_fields.py:643-673
Timestamp: 2026-03-12T12:14:03.173Z
Learning: In netbox_librenms_plugin/tests/test_coverage_device_fields.py, strengthen the CreateAndAssignPlatformView success-path tests (test_manufacturer_not_found around lines 642–672 and the adjacent test around lines 674–697) by asserting that the newly created Platform instance is assigned to the locked device object (mock_locked.platform is mock_platform_instance) and that mock_locked.save() is called. This validates the FK assignment and persistence, rather than only checking that messages.success() was invoked. This improvement is a known backlog item (low priority); do not re-raise as a new finding.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
🔇 Additional comments (16)
netbox_librenms_plugin/views/sync/device_fields.py (1)

693-697: LGTM!

netbox_librenms_plugin/views/sync/interfaces.py (1)

452-457: LGTM!

netbox_librenms_plugin/tests/test_coverage_device_fields.py (1)

17-17: LGTM!

Also applies to: 1659-1665, 1691-1697, 1726-1732, 1774-1780, 3189-3194

netbox_librenms_plugin/tests/test_device_fields_server_scoping.py (1)

26-26: LGTM!

netbox_librenms_plugin/tests/test_permissions.py (1)

1012-1030: LGTM!

netbox_librenms_plugin/tests/test_view_wiring.py (2)

436-565: LGTM!


808-841: LGTM!

netbox_librenms_plugin/views/sync/modules.py (1)

1966-1972: 🔒 Security & Privacy | 🏗️ Heavy lift

Authorization Bypass (CWE-639): Authorization Bypass Through User-Controlled Key (IDOR)

Reachability: External

Reachability path
● Entry
  netbox_librenms_plugin/tests/test_view_wiring.py
│
▼
● Sink
  netbox_librenms_plugin/views/sync/modules.py

Delete targets taken from the POST are still resolved through plain managers. Both views now restrict the primary object but resolve the rows they delete by client-supplied id against the unmodified manager. The delete gate calls has_perm without an instance, so a constrained delete grant clears it and the delete then reaches rows outside that grant. Location or ownership filters prove where the row lives, not that the grant covers it.

  • netbox_librenms_plugin/views/sync/modules.py#L1966-L1972: resolve the target-bay occupant at lines 1979-1983 through self.restricted_queryset(Module, "delete") before occupant.delete().
  • netbox_librenms_plugin/views/sync/interfaces.py#L472-L475: resolve the interfaces at lines 494 and 509 through self.restricted_queryset(Interface, "delete") and self.restricted_queryset(VMInterface, "delete") before interface.delete().
netbox_librenms_plugin/views/sync/devices.py (1)

4-4: LGTM!

Also applies to: 48-50, 144-158

netbox_librenms_plugin/tests/view_test_helpers.py (1)

1-22: LGTM!

netbox_librenms_plugin/tests/test_coverage_devices.py (1)

8-10: LGTM!

Also applies to: 722-724, 799-799, 898-900, 948-948

netbox_librenms_plugin/tests/test_coverage_sync_views.py (1)

10-12: LGTM!

Also applies to: 470-473, 490-493, 512-515, 588-591, 600-606, 809-815, 826-832, 844-850, 1659-1659, 1671-1671, 2521-2521

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (1)

15-17: LGTM!

Also applies to: 58-377, 843-868, 939-988, 1021-1092, 1218-1233, 1374-1377, 1714-1777, 1911-1999, 2029-2064, 2100-2171

netbox_librenms_plugin/tests/test_module_replace.py (1)

8-10: LGTM!

Also applies to: 73-91, 106-113, 128-135, 155-155, 196-196, 268-284, 366-369, 388-400, 419-422, 462-462, 489-492, 526-526, 546-549, 577-580, 603-628, 646-649

netbox_librenms_plugin/tests/test_sync_devices.py (1)

6-8: LGTM!

Also applies to: 146-152, 174-180, 189-203

netbox_librenms_plugin/tests/test_sync_modules.py (1)

16-18: LGTM!

Also applies to: 1751-2446, 2960-3337, 4171-4468, 4678-5418, 5576-5676, 5997-6073

Comment thread netbox_librenms_plugin/tests/test_view_wiring.py
Comment thread netbox_librenms_plugin/views/sync/modules.py
Comment thread netbox_librenms_plugin/views/sync/interfaces.py
@marcinpsk
marcinpsk force-pushed the fix/object-scoped-lookups branch from 919c0c1 to ee0b72c Compare August 5, 2026 12:09

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbox_librenms_plugin/tests/test_view_wiring.py`:
- Around line 487-493: Refine `_is_scoped` so it no longer treats every
descendant call as scoping evidence. Accept `restricted_queryset`/`restrict`
only when it belongs to the queried call’s receiver chain or appears in a
relationship-oriented keyword value such as `__in` or `__contains`; preserve the
documented argument-based scoping case while ignoring unrelated keyword
expressions passed to the outer call.

In `@netbox_librenms_plugin/views/sync/locations.py`:
- Line 18: Update SyncSiteLocationView’s GET queryset to use
self.restricted_queryset(Site) instead of Site.objects.all(), matching
get_site_by_pk() permission scoping. Add the appropriate GET object-permission
check so the page is available only to users with a Site grant, while preserving
existing sync behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d32059a6-58e4-4a39-bcec-8422ed11b6eb

📥 Commits

Reviewing files that changed from the base of the PR and between c0be261 and ee0b72c.

📒 Files selected for processing (22)
  • netbox_librenms_plugin/tests/test_coverage_actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/vlans.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: NetBox main / Python 3.13
  • GitHub Check: NetBox main / Python 3.14
  • GitHub Check: NetBox v4.6.5 / Python 3.13
  • GitHub Check: NetBox v4.4.0 / Python 3.12
  • GitHub Check: NetBox v4.6.5 / Python 3.12
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
**/views/sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Sync action views must follow the pattern: check permissions with LibreNMSPermissionMixin and NetBoxObjectPermissionMixin, read selected items from request.POST.getlist('select'), load cached data using CacheMixin.get_cache_key(), apply changes inside transaction.atomic(), and redirect to the sync tab with ?tab=<resource>.

Files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/device_fields.py
**/views/imports/**

📄 CodeRabbit inference engine (.github/instructions/background-jobs.instructions.md)

**/views/imports/**: NetBox's /api/core/background-tasks/ endpoint requires superuser (IsSuperuser in BaseRQViewSet). Non-superuser users cannot poll job status (403 Forbidden). Plugin automatically falls back to synchronous mode for non-superusers via should_use_background_job() in list.py and actions.py
Import page filter fields: librenms_location, librenms_type, librenms_os, librenms_hostname, librenms_sysname, librenms_hardware, enable_vc_detection, show_disabled, exclude_existing

Files:

  • netbox_librenms_plugin/views/imports/actions.py
**/views/imports/actions.py

📄 CodeRabbit inference engine (.github/instructions/background-jobs.instructions.md)

**/views/imports/actions.py: DeviceImportHelperMixin provides get_validated_device_with_selections() and render_device_row() for HTMX row rendering, shared by update views
BulkImportConfirmView (POST) — renders confirmation modal with selected device list via htmx/bulk_import_confirm.html
BulkImportDevicesView (POST) — executes import. Background mode enqueues ImportDevicesJob; sync mode calls bulk_import_devices() + bulk_import_vms() and returns OOB row swaps with HX-Trigger: closeModal
DeviceValidationDetailsView (GET) — renders expandable validation details via htmx/device_validation_details.html
DeviceVCDetailsView (GET) — renders VC member details via htmx/device_vc_details.html
DeviceRoleUpdateView, DeviceClusterUpdateView, DeviceRackUpdateView (POST) — per-device dropdown updates. Apply selection to validation state and return re-rendered row via render_device_row()

Files:

  • netbox_librenms_plugin/views/imports/actions.py
🧠 Learnings (28)
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
📚 Learning: 2026-03-07T17:17:04.217Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/cables.py:160-165
Timestamp: 2026-03-07T17:17:04.217Z
Learning: In Python views under netbox_librenms_plugin/views/sync, when obtaining a server_key for cache namespace scoping, read it from request.POST with a fallback to self.librenms_api.server_key (e.g., server_key = request.POST.get("server_key") or self.librenms_api.server_key) and assign it to an attribute (e.g., self._post_server_key) used by get_cached_links_data to build the cache key. Do not flag or remove this POST-read pattern, as it ensures consistent, future-proof cache namespace scoping for link data lookups. Apply this guidance to similar Sync views in the same module where server_key-based cache scoping is used.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/device_fields.py
📚 Learning: 2026-04-15T12:38:49.280Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/views/base/modules_view.py:133-141
Timestamp: 2026-04-15T12:38:49.280Z
Learning: Do not require or flag an explicit `permission_required = PERM_VIEW_PLUGIN` on views that inherit from `LibreNMSPermissionMixin` (e.g., those ultimately including `BaseModuleTableView` in `views/base/modules_view.py`). `LibreNMSPermissionMixin` is the authoritative place where `permission_required` is set to `PERM_VIEW_PLUGIN`; inherited values are already enforced via the mixin, making a redundant override unnecessary.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/device_fields.py
📚 Learning: 2026-05-17T11:32:40.631Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 76
File: netbox_librenms_plugin/views/object_sync/devices.py:178-180
Timestamp: 2026-05-17T11:32:40.631Z
Learning: In this plugin’s JSON endpoint views (e.g., NetBox view classes/functions under netbox_librenms_plugin/views/**), do not require a try/except JSONDecodeError around `json.loads(request.body)` by default if the endpoint is already protected by CSRF + session authentication and has object-level permission gates. Treat missing JSONDecodeError handling as acceptable when the only expected downside is a noisy log line. If you identify any additional security or availability impact from invalid JSON (e.g., unhandled exceptions leading to user-visible 500s, potential DoS amplification, or lack of appropriate throttling/validation), then flag it and recommend adding guarded parsing/validation.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/device_fields.py
📚 Learning: 2026-07-02T13:36:15.226Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/interfaces_view.py:226-226
Timestamp: 2026-07-02T13:36:15.226Z
Learning: When reviewing netbox-librenms-plugin view code that handles POSTed `server_key`, treat the plugin-wide convention as intentional: if the posted `server_key` is not present in `LibreNMSAPI.get_available_servers()`, the request should fall back to the currently configured default/active server key (not reject/fail-closed and not treat it as an error for that single view). Do not flag individual instances of this fallback pattern as incorrect “invalid server_key” validation. Any change to fail-closed behavior must be a coordinated cross-cutting change applied uniformly across all affected sites/views, not a one-view patch.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/device_fields.py
📚 Learning: 2026-08-05T06:48:35.761Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 127
File: netbox_librenms_plugin/views/sync/interfaces.py:494-494
Timestamp: 2026-08-05T06:48:35.761Z
Learning: In permission-scoped NetBox views, do not replace re-locks that use an already resolved object primary key (for example, `pk=already_resolved.pk`) with `restricted_queryset()`. NetBox `restrict()` may return `none()` when no model-level grant exists, even if the view-level permission gate allows the operation, causing valid rows to be removed from the lock set and producing an erroneous “no longer exists” result. AST guards for raw client-supplied primary-key lookups should distinguish and exempt these re-locks based on the ID-expression shape.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/device_fields.py
📚 Learning: 2026-06-01T15:12:26.824Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/ip_addresses.py:94-103
Timestamp: 2026-06-01T15:12:26.824Z
Learning: For any redirect/tab URL building in netbox_librenms_plugin/views/sync, views/base, and views/object_sync, propagate the active multi-server `server_key` as a `?server_key=<key>` query parameter so users return to the same server’s tab after POST actions. When handling POST requests, read the POST-scoped `server_key` from `request.POST` and store it (e.g., `self._post_server_key`) with a fallback to `self.librenms_api.server_key`; use this POST-scoped key for both cache-key scoping and for constructing the redirect/tab URLs. Treat this as the intentional codebase-wide convention—do not flag the presence/usage of the `server_key` query parameter (or the corresponding POST-scoped `_post_server_key` pattern) in these views as an error.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/device_fields.py
📚 Learning: 2026-03-07T10:40:38.106Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/interfaces.py:241-244
Timestamp: 2026-03-07T10:40:38.106Z
Learning: In netbox_librenms_plugin/views/sync/interfaces.py, ensure that set_librenms_device_id does not apply the legacy bare-integer guard to Interface/VMInterface objects. The guard is only relevant for Device/VM objects with pre-existing bare integers from before multi-server support. Interfaces/VMInterfaces have librenms_id starting empty and their port_id is always written from the LibreNMS API JSON response, so there is no migration concern. Do not treat the warning-log path as a silent no-op for interfaces; keep appropriate logging/alerts active. Add or adjust tests to verify that interfaces paths write port_id correctly and do not trigger the legacy-bare-int logic, and document this distinction in code comments.

Applied to files:

  • netbox_librenms_plugin/views/sync/interfaces.py
📚 Learning: 2026-03-08T09:30:45.499Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/views/imports/actions.py:1031-1035
Timestamp: 2026-03-08T09:30:45.499Z
Learning: In netbox_librenms_plugin/views/imports/actions.py, ensure that DeviceConflictActionView.post() explicitly rejects boolean values for librenms_id via isinstance(librenms_id, bool) before coercing to int, returning HTTP 400. Do not remove or consolidate this pre-coercion boolean check. This guard is intentional and consistent with the similar bool-guard pattern used in set_librenms_device_id, get_librenms_device_id, and find_by_librenms_id; preserve this behavior to avoid ambiguity and misinterpretation of truthy/falsey booleans.

Applied to files:

  • netbox_librenms_plugin/views/imports/actions.py
📚 Learning: 2026-06-01T13:32:29.984Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/imports/list.py:0-0
Timestamp: 2026-06-01T13:32:29.984Z
Learning: When reviewing the NetBox LibreNMS plugin’s import/IPAM code (including files under netbox_librenms_plugin/**/imports/), do not flag issues about missing propagation of the `auto_create_ipam` flag (or `resolve_auto_create_ipam()` usage) into cache keys or `FilterDevicesJob` payloads. The `auto_create_ipam` feature was removed entirely during the IPAM rework merged to `develop` (PR `#303`), so the absence of this flag/payload data in current code is expected.

Applied to files:

  • netbox_librenms_plugin/views/imports/actions.py
📚 Learning: 2026-06-25T07:14:19.587Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/modules_view.py:1073-1074
Timestamp: 2026-06-25T07:14:19.587Z
Learning: In netbox_librenms_plugin/views/base/modules_view.py and netbox_librenms_plugin/views/sync/modules.py, treat LibreNMS `entPhysicalIndex` as an end-to-end integer invariant (it originates from an LibreNMS int DB column and is preserved as an int through the module inventory/sync pipeline). When reviewing code, do not flag mixed string/int `entPhysicalIndex` handling or request additional `int()` normalization solely as a defensive measure against string indices. Only recommend `int()` conversion/normalization if there is concrete evidence in the code path that values are actually being converted to strings (e.g., explicit casts, JSON serialization/deserialization steps that coerce to strings, or external inputs known to provide strings).

Applied to files:

  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-12T12:14:03.173Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 25
File: netbox_librenms_plugin/tests/test_coverage_device_fields.py:643-673
Timestamp: 2026-03-12T12:14:03.173Z
Learning: In netbox_librenms_plugin/tests/test_coverage_device_fields.py, strengthen the CreateAndAssignPlatformView success-path tests (test_manufacturer_not_found around lines 642–672 and the adjacent test around lines 674–697) by asserting that the newly created Platform instance is assigned to the locked device object (mock_locked.platform is mock_platform_instance) and that mock_locked.save() is called. This validates the FK assignment and persistence, rather than only checking that messages.success() was invoked. This improvement is a known backlog item (low priority); do not re-raise as a new finding.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
🔇 Additional comments (22)
netbox_librenms_plugin/views/sync/ip_addresses.py (1)

42-64: LGTM!

Also applies to: 76-76, 91-95, 128-129

netbox_librenms_plugin/tests/test_coverage_actions.py (1)

3042-3045: LGTM!

Also applies to: 3162-3165, 3242-3245, 3453-3456, 3686-3689, 3767-3770, 3976-3980, 4916-4916, 4974-4974, 5033-5033, 5094-5094, 5143-5143, 5183-5183, 5877-5880, 6924-6927, 7349-7355, 7395-7401, 7432-7439

netbox_librenms_plugin/tests/test_coverage_device_fields.py (1)

17-43: LGTM!

Also applies to: 85-233, 256-445, 471-643, 669-874, 895-1587, 1627-1860, 1884-2312, 2335-3042, 3058-3117, 3338-3353

netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py (1)

81-97: LGTM!

Also applies to: 366-366, 383-393, 432-446, 464-481, 500-517, 540-557, 587-590, 741-743, 772-774, 801-803, 895-895, 965-1026, 1384-1384, 1399-1403, 1419-1419, 1442-1457, 1485-1500, 1527-1542, 1572-1587, 1612-1623, 1649-1664, 1708-1718

netbox_librenms_plugin/tests/test_coverage_sync_views.py (1)

10-12: LGTM!

Also applies to: 470-515, 588-606, 809-850, 1009-1033, 1327-1363, 1442-1545, 1597-1611, 1673-1685, 2258-2268, 2296-2344, 2542-2542, 2613-2627

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (1)

15-17: LGTM!

Also applies to: 58-380, 523-815, 853-878, 949-998, 1031-1102, 1228-1292, 1388-1402, 1729-1792, 1926-2051, 2080-2194, 2274-2502, 2590-2593

netbox_librenms_plugin/tests/test_coverage_sync_views3.py (1)

87-99: LGTM!

Also applies to: 191-254, 301-301, 321-385, 397-426, 445-483, 571-591, 609-617, 637-645, 671-679, 705-713, 731-739, 758-766, 785-796, 824-832, 1013-1016, 1037-1040

netbox_librenms_plugin/tests/test_import_utils.py (1)

2285-2286: LGTM!

Also applies to: 2333-2334, 2378-2379, 2430-2431, 2471-2472, 2516-2517, 2558-2559, 2600-2601, 2635-2636, 2679-2680, 2735-2736, 2791-2792, 2827-2828, 2868-2869, 2905-2906

netbox_librenms_plugin/tests/test_migrate_views.py (1)

301-726: LGTM!

Also applies to: 745-889, 907-987, 1270-1309, 1346-1369, 1401-1683

netbox_librenms_plugin/tests/test_permissions.py (1)

981-999: LGTM!

Also applies to: 1014-1030, 1085-1104, 1122-1131, 1154-1172

netbox_librenms_plugin/views/sync/locations.py (1)

113-116: LGTM!

netbox_librenms_plugin/tests/test_module_replace.py (1)

57-660: LGTM!

netbox_librenms_plugin/tests/test_sync_devices.py (1)

220-308: LGTM!

netbox_librenms_plugin/tests/test_sync_modules.py (1)

20-6188: LGTM!

netbox_librenms_plugin/tests/test_view_wiring.py (1)

437-479: LGTM!

Also applies to: 510-522, 565-697, 700-1096, 1099-1165

netbox_librenms_plugin/views/imports/actions.py (1)

1600-1958: LGTM!

Also applies to: 1961-2144, 2147-2496, 2499-3077, 3631-3750

netbox_librenms_plugin/views/sync/cables.py (1)

25-259: LGTM!

netbox_librenms_plugin/views/sync/device_fields.py (1)

63-372: LGTM!

Also applies to: 374-742, 745-1021

netbox_librenms_plugin/views/sync/interfaces.py (1)

35-134: LGTM! This confirms the fix already discussed and resolved in the prior review thread on this file (DeleteNetBoxInterfacesView interface-delete authorization bypass), plus the new Device/VirtualMachine view-permission declarations for the object owner lookups.

Also applies to: 199-230, 450-541

netbox_librenms_plugin/views/sync/migrate.py (1)

432-438: LGTM!

Also applies to: 557-562, 783-783

netbox_librenms_plugin/views/sync/modules.py (1)

605-605: LGTM!

Also applies to: 1395-1396, 1796-1797, 1962-1964, 1970-1975, 1985-1989

netbox_librenms_plugin/views/sync/vlans.py (1)

28-30: LGTM!

Also applies to: 64-64, 122-122

Comment thread netbox_librenms_plugin/tests/test_view_wiring.py
Comment thread netbox_librenms_plugin/views/sync/locations.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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbox_librenms_plugin/tests/test_coverage_sync_views2.py`:
- Around line 558-569: Remove the unused poller_choices parameter and its view
assignment from _add_device_view. Update the docstring to state that
poller-group choices are seeded through the _poller_groups patching path, so
callers do not pass ineffective options.
- Around line 298-325: Update
test_a_remote_interface_outside_the_grant_is_reported_missing to assert cable
absence using the generic CableTermination predicate already used by
SyncCablesView.check_existing_cable, filtering termination_type and
termination_id for remote_iface instead of traversing terminations__interface.
Preserve the existing missing-interface error assertion.

In `@netbox_librenms_plugin/tests/test_permissions.py`:
- Around line 1055-1061: Remove the unused device parameter from the _make_view
helper and update all three call sites to stop passing it; retain the existing
pk=dev.pk arguments so the view continues receiving the device identifier
explicitly.

In `@netbox_librenms_plugin/tests/view_test_helpers.py`:
- Around line 99-101: Remove the unused plugin_perms() helper from
view_test_helpers.py and remove the now-unneeded PERM_VIEW_PLUGIN and
PERM_CHANGE_PLUGIN imports, unless a test is added that meaningfully uses the
helper.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ff23f269-d920-4499-9331-3a6b6bf23b22

📥 Commits

Reviewing files that changed from the base of the PR and between ee0b72c and 4c25d79.

📒 Files selected for processing (13)
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: NetBox main / Python 3.14
  • GitHub Check: Analyze (python)
  • GitHub Check: NetBox main / Python 3.13
  • GitHub Check: NetBox v4.6.5 / Python 3.12
  • GitHub Check: NetBox v4.4.0 / Python 3.12
  • GitHub Check: NetBox v4.6.5 / Python 3.13
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
🧠 Learnings (18)
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-12T12:14:03.173Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 25
File: netbox_librenms_plugin/tests/test_coverage_device_fields.py:643-673
Timestamp: 2026-03-12T12:14:03.173Z
Learning: In netbox_librenms_plugin/tests/test_coverage_device_fields.py, strengthen the CreateAndAssignPlatformView success-path tests (test_manufacturer_not_found around lines 642–672 and the adjacent test around lines 674–697) by asserting that the newly created Platform instance is assigned to the locked device object (mock_locked.platform is mock_platform_instance) and that mock_locked.save() is called. This validates the FK assignment and persistence, rather than only checking that messages.success() was invoked. This improvement is a known backlog item (low priority); do not re-raise as a new finding.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
🪛 ast-grep (0.45.0)
netbox_librenms_plugin/tests/test_permissions.py

[warning] 1103-1103: Do not make http calls without encryption
Context: "http://x"
Note: [CWE-319] Cleartext Transmission of Sensitive Information.

(requests-http)

🔇 Additional comments (14)
netbox_librenms_plugin/tests/conftest.py (1)

9-26: LGTM!

netbox_librenms_plugin/tests/test_coverage_device_fields.py (1)

10-24: LGTM!

Also applies to: 42-51, 61-169, 179-340, 350-548, 559-747, 758-980, 990-1446, 1454-1615, 1623-1683, 1693-1898, 1906-1966, 2056-2364, 2584-2600

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (1)

12-38: LGTM!

Also applies to: 233-296, 511-530, 583-785, 1229-1256, 1325-1361, 1857-1989, 2037-2280, 2326-2347

netbox_librenms_plugin/tests/test_permissions.py (1)

3-4: LGTM!

Also applies to: 1063-1127

netbox_librenms_plugin/tests/test_sync_interfaces.py (2)

7-9: LGTM!

Also applies to: 254-305, 327-336


307-325: 📐 Maintainability & Code Quality

No change needed for the NetBox floor. The plugin declares min_version = "4.4.0", which covers NetBox 4.2+ where VMInterface.primary_mac_address is available.

netbox_librenms_plugin/tests/test_view_seam.py (1)

1-191: LGTM!

netbox_librenms_plugin/tests/view_test_helpers.py (1)

6-16: LGTM!

Also applies to: 35-96, 104-166, 169-202

netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py (1)

12-39: LGTM!

Also applies to: 724-773, 842-872, 885-937

netbox_librenms_plugin/tests/test_coverage_sync_views.py (1)

10-10: LGTM!

Also applies to: 2115-2134

netbox_librenms_plugin/tests/test_coverage_sync_views3.py (1)

14-47: LGTM!

Also applies to: 92-112, 295-424, 434-475, 552-730, 839-871, 880-920

netbox_librenms_plugin/tests/test_coverage_tables.py (1)

2574-2574: LGTM!

Also applies to: 2583-2632

netbox_librenms_plugin/tests/test_platform_mapping.py (2)

10-11: LGTM!

Also applies to: 320-406


413-431: 📐 Maintainability & Code Quality

Pin the NetBox version before relying on platform-case-ambiguity fixtures.

These tests create "ios" and "IOS" Platform rows without an explicit NetBox version constraint. NetBox supports both case-sensitive and case-insensitive name uniqueness across versions, so these fixtures may raise IntegrityError instead of reaching the ambiguity branches. Add a supporting NetBox version check to conftest.py or replace the Platform ambiguity in tests/test_platform_mapping.py with _duplicate_mappings; use the same explicit fix for tests/test_coverage_utils.py.

Comment thread netbox_librenms_plugin/tests/test_coverage_sync_views2.py Outdated
Comment thread netbox_librenms_plugin/tests/test_coverage_sync_views2.py
Comment thread netbox_librenms_plugin/tests/test_permissions.py
Comment thread netbox_librenms_plugin/tests/view_test_helpers.py
Comment thread netbox_librenms_plugin/views/sync/interfaces.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: 2

Caution

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

⚠️ Outside diff range comments (2)
netbox_librenms_plugin/tests/test_coverage_devices.py (1)

959-962: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a real authorized user for the restricted group lookup.

VerifyVlanSyncGroupView.post() now resolves VLANGroup through request.user. This request leaves user as an implicit MagicMock. Its truthiness can simulate a superuser and does not validate the real restricted-queryset path. Set request.user = _make_verify_superuser(...), as the analogous test does at Line 804.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbox_librenms_plugin/tests/test_coverage_devices.py` around lines 959 -
962, Update the test request setup in the VerifyVlanSyncGroupView.post scenario
to assign request.user using the existing _make_verify_superuser(...) helper,
matching the analogous test, so the restricted VLANGroup lookup runs with a real
authorized user instead of an implicit MagicMock.
netbox_librenms_plugin/views/sync/modules.py (1)

1807-1812: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Authorization Bypass (CWE-862): Missing Authorization

Reachability: External

Reachability path
● Entry
  netbox_librenms_plugin/tests/test_coverage_sync_views.py
│
▼
● Hop
  netbox_librenms_plugin/views/sync/locations.py:97
  post
│
▼
● Hop
  netbox_librenms_plugin/views/imports/actions.py:160
  get
│
▼
● Sink
  netbox_librenms_plugin/views/sync/modules.py

Scope the serial-conflict module deletion by the caller’s delete grant.

ReplaceModuleView declares ("delete", Module) without an instance, and conflict_qs resolves a conflicting Module through plain Module.objects.select_for_update() before deleting it. A single serial match outside the caller’s device/device-type scope can be removed unless the conflict lookup uses self.restricted_queryset(Module, "delete").

🔒️ Proposed fix to scope the conflict lookup
                 conflict_module = None
                 if serial:
                     conflict_qs = (
-                        Module.objects.select_for_update()
+                        self.restricted_queryset(Module, "delete")
+                        .select_for_update(of=("self",))
                         .filter(serial=serial)
                         .exclude(pk=installed_module.pk)
                         .select_related("module_type", "module_bay", "device")
                     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbox_librenms_plugin/views/sync/modules.py` around lines 1807 - 1812,
Update the serial-conflict lookup in ReplaceModuleView’s conflict_qs to use
self.restricted_queryset(Module, "delete") before select_for_update and
filtering, so the conflicting module can only be deleted when permitted by the
caller’s delete scope. Preserve the existing module_id, target_device,
related-object loading, and first-result behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbox_librenms_plugin/tests/test_view_wiring.py`:
- Around line 495-498: The primary-lookup scanner around
_has_scoped_relationship_filter and the get_object_or_404 handling must reject
unrelated descendant restricted_queryset() calls. Add a regression fixture for a
restricted queryset used inside an unrelated relationship expression, and
require either a scoped queryset receiver or a qualifying __in relationship
filter before accepting the primary lookup.

In `@netbox_librenms_plugin/views/sync/cables.py`:
- Around line 175-191: Update the cable-sync handling around
_selected_device_is_in_page_context so a posted device selection that is not in
page context is rejected or surfaced using the same
_warn_invalid_selected_device(request) treatment as modules.py, instead of
silently retaining the cached local interface. Preserve the existing lookup and
invalid result for valid in-page selections, and update
test_unrelated_posted_device_cannot_redirect_the_local_termination to assert the
new refusal behavior.

---

Outside diff comments:
In `@netbox_librenms_plugin/tests/test_coverage_devices.py`:
- Around line 959-962: Update the test request setup in the
VerifyVlanSyncGroupView.post scenario to assign request.user using the existing
_make_verify_superuser(...) helper, matching the analogous test, so the
restricted VLANGroup lookup runs with a real authorized user instead of an
implicit MagicMock.

In `@netbox_librenms_plugin/views/sync/modules.py`:
- Around line 1807-1812: Update the serial-conflict lookup in
ReplaceModuleView’s conflict_qs to use self.restricted_queryset(Module,
"delete") before select_for_update and filtering, so the conflicting module can
only be deleted when permitted by the caller’s delete scope. Preserve the
existing module_id, target_device, related-object loading, and first-result
behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a3523bdf-d2ef-4aff-ae3c-11d56dc01142

📥 Commits

Reviewing files that changed from the base of the PR and between 4c25d79 and 462e650.

📒 Files selected for processing (16)
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/vlans.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Analyze (python)
  • GitHub Check: NetBox v4.6.5 / Python 3.12
  • GitHub Check: NetBox main / Python 3.14
  • GitHub Check: NetBox v4.6.5 / Python 3.13
  • GitHub Check: NetBox main / Python 3.13
  • GitHub Check: NetBox v4.4.0 / Python 3.12
🧰 Additional context used
📓 Path-based instructions (5)
**/views/sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Sync action views must follow the pattern: check permissions with LibreNMSPermissionMixin and NetBoxObjectPermissionMixin, read selected items from request.POST.getlist('select'), load cached data using CacheMixin.get_cache_key(), apply changes inside transaction.atomic(), and redirect to the sync tab with ?tab=<resource>.

Files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
**/views/object_sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Object sync view methods must create instances of concrete table views, copy the request object, and call get_context_data(). VMs must skip cables and VLANs by returning None from those get_*_context() methods.

Files:

  • netbox_librenms_plugin/views/object_sync/devices.py
**/views/imports/**

📄 CodeRabbit inference engine (.github/instructions/background-jobs.instructions.md)

**/views/imports/**: NetBox's /api/core/background-tasks/ endpoint requires superuser (IsSuperuser in BaseRQViewSet). Non-superuser users cannot poll job status (403 Forbidden). Plugin automatically falls back to synchronous mode for non-superusers via should_use_background_job() in list.py and actions.py
Import page filter fields: librenms_location, librenms_type, librenms_os, librenms_hostname, librenms_sysname, librenms_hardware, enable_vc_detection, show_disabled, exclude_existing

Files:

  • netbox_librenms_plugin/views/imports/actions.py
**/views/imports/actions.py

📄 CodeRabbit inference engine (.github/instructions/background-jobs.instructions.md)

**/views/imports/actions.py: DeviceImportHelperMixin provides get_validated_device_with_selections() and render_device_row() for HTMX row rendering, shared by update views
BulkImportConfirmView (POST) — renders confirmation modal with selected device list via htmx/bulk_import_confirm.html
BulkImportDevicesView (POST) — executes import. Background mode enqueues ImportDevicesJob; sync mode calls bulk_import_devices() + bulk_import_vms() and returns OOB row swaps with HX-Trigger: closeModal
DeviceValidationDetailsView (GET) — renders expandable validation details via htmx/device_validation_details.html
DeviceVCDetailsView (GET) — renders VC member details via htmx/device_vc_details.html
DeviceRoleUpdateView, DeviceClusterUpdateView, DeviceRackUpdateView (POST) — per-device dropdown updates. Apply selection to validation state and return re-rendered row via render_device_row()

Files:

  • netbox_librenms_plugin/views/imports/actions.py
🧠 Learnings (29)
📚 Learning: 2026-03-07T17:17:04.217Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/cables.py:160-165
Timestamp: 2026-03-07T17:17:04.217Z
Learning: In Python views under netbox_librenms_plugin/views/sync, when obtaining a server_key for cache namespace scoping, read it from request.POST with a fallback to self.librenms_api.server_key (e.g., server_key = request.POST.get("server_key") or self.librenms_api.server_key) and assign it to an attribute (e.g., self._post_server_key) used by get_cached_links_data to build the cache key. Do not flag or remove this POST-read pattern, as it ensures consistent, future-proof cache namespace scoping for link data lookups. Apply this guidance to similar Sync views in the same module where server_key-based cache scoping is used.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-15T12:38:49.280Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/views/base/modules_view.py:133-141
Timestamp: 2026-04-15T12:38:49.280Z
Learning: Do not require or flag an explicit `permission_required = PERM_VIEW_PLUGIN` on views that inherit from `LibreNMSPermissionMixin` (e.g., those ultimately including `BaseModuleTableView` in `views/base/modules_view.py`). `LibreNMSPermissionMixin` is the authoritative place where `permission_required` is set to `PERM_VIEW_PLUGIN`; inherited values are already enforced via the mixin, making a redundant override unnecessary.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
📚 Learning: 2026-05-17T11:32:40.631Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 76
File: netbox_librenms_plugin/views/object_sync/devices.py:178-180
Timestamp: 2026-05-17T11:32:40.631Z
Learning: In this plugin’s JSON endpoint views (e.g., NetBox view classes/functions under netbox_librenms_plugin/views/**), do not require a try/except JSONDecodeError around `json.loads(request.body)` by default if the endpoint is already protected by CSRF + session authentication and has object-level permission gates. Treat missing JSONDecodeError handling as acceptable when the only expected downside is a noisy log line. If you identify any additional security or availability impact from invalid JSON (e.g., unhandled exceptions leading to user-visible 500s, potential DoS amplification, or lack of appropriate throttling/validation), then flag it and recommend adding guarded parsing/validation.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
📚 Learning: 2026-07-02T13:36:15.226Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/interfaces_view.py:226-226
Timestamp: 2026-07-02T13:36:15.226Z
Learning: When reviewing netbox-librenms-plugin view code that handles POSTed `server_key`, treat the plugin-wide convention as intentional: if the posted `server_key` is not present in `LibreNMSAPI.get_available_servers()`, the request should fall back to the currently configured default/active server key (not reject/fail-closed and not treat it as an error for that single view). Do not flag individual instances of this fallback pattern as incorrect “invalid server_key” validation. Any change to fail-closed behavior must be a coordinated cross-cutting change applied uniformly across all affected sites/views, not a one-view patch.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
📚 Learning: 2026-08-05T06:48:35.761Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 127
File: netbox_librenms_plugin/views/sync/interfaces.py:494-494
Timestamp: 2026-08-05T06:48:35.761Z
Learning: In permission-scoped NetBox views, do not replace re-locks that use an already resolved object primary key (for example, `pk=already_resolved.pk`) with `restricted_queryset()`. NetBox `restrict()` may return `none()` when no model-level grant exists, even if the view-level permission gate allows the operation, causing valid rows to be removed from the lock set and producing an erroneous “no longer exists” result. AST guards for raw client-supplied primary-key lookups should distinguish and exempt these re-locks based on the ID-expression shape.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/imports/actions.py
📚 Learning: 2026-06-01T15:12:26.824Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/ip_addresses.py:94-103
Timestamp: 2026-06-01T15:12:26.824Z
Learning: For any redirect/tab URL building in netbox_librenms_plugin/views/sync, views/base, and views/object_sync, propagate the active multi-server `server_key` as a `?server_key=<key>` query parameter so users return to the same server’s tab after POST actions. When handling POST requests, read the POST-scoped `server_key` from `request.POST` and store it (e.g., `self._post_server_key`) with a fallback to `self.librenms_api.server_key`; use this POST-scoped key for both cache-key scoping and for constructing the redirect/tab URLs. Treat this as the intentional codebase-wide convention—do not flag the presence/usage of the `server_key` query parameter (or the corresponding POST-scoped `_post_server_key` pattern) in these views as an error.

Applied to files:

  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-08T08:57:43.392Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/object_sync/devices.py:83-100
Timestamp: 2026-03-08T08:57:43.392Z
Learning: In views under netbox_librenms_plugin/views/object_sync, server_key values come from admin-controlled PLUGINS_CONFIG dict keys (e.g., "default", "production") and are not user input. Therefore URL-encoding them via urlencode() is unnecessary defensiveness. Do not flag direct string interpolation of server_key into query strings as a URL-injection or encoding issue. This guidance should apply to similar views in the same directory.

Applied to files:

  • netbox_librenms_plugin/views/object_sync/devices.py
📚 Learning: 2026-05-05T09:58:50.179Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/object_sync/devices.py:57-75
Timestamp: 2026-05-05T09:58:50.179Z
Learning: In object_sync view classes that pass Django/NetBox `request` into child table context helpers (e.g., for interfaces/cables/IPs/vlans/modules), ensure the child view stores `copy.copy(request)` rather than the original `request` object. Apply this consistently across similar sync views (such as the pattern used in `VMLibreNMSSyncView` in `vms.py`) to prevent cross-view request mutation when the child view modifies the request.

Applied to files:

  • netbox_librenms_plugin/views/object_sync/devices.py
📚 Learning: 2026-06-25T07:14:19.587Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/modules_view.py:1073-1074
Timestamp: 2026-06-25T07:14:19.587Z
Learning: In netbox_librenms_plugin/views/base/modules_view.py and netbox_librenms_plugin/views/sync/modules.py, treat LibreNMS `entPhysicalIndex` as an end-to-end integer invariant (it originates from an LibreNMS int DB column and is preserved as an int through the module inventory/sync pipeline). When reviewing code, do not flag mixed string/int `entPhysicalIndex` handling or request additional `int()` normalization solely as a defensive measure against string indices. Only recommend `int()` conversion/normalization if there is concrete evidence in the code path that values are actually being converted to strings (e.g., explicit casts, JSON serialization/deserialization steps that coerce to strings, or external inputs known to provide strings).

Applied to files:

  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-08T09:30:45.499Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/views/imports/actions.py:1031-1035
Timestamp: 2026-03-08T09:30:45.499Z
Learning: In netbox_librenms_plugin/views/imports/actions.py, ensure that DeviceConflictActionView.post() explicitly rejects boolean values for librenms_id via isinstance(librenms_id, bool) before coercing to int, returning HTTP 400. Do not remove or consolidate this pre-coercion boolean check. This guard is intentional and consistent with the similar bool-guard pattern used in set_librenms_device_id, get_librenms_device_id, and find_by_librenms_id; preserve this behavior to avoid ambiguity and misinterpretation of truthy/falsey booleans.

Applied to files:

  • netbox_librenms_plugin/views/imports/actions.py
📚 Learning: 2026-06-01T13:32:29.984Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/imports/list.py:0-0
Timestamp: 2026-06-01T13:32:29.984Z
Learning: When reviewing the NetBox LibreNMS plugin’s import/IPAM code (including files under netbox_librenms_plugin/**/imports/), do not flag issues about missing propagation of the `auto_create_ipam` flag (or `resolve_auto_create_ipam()` usage) into cache keys or `FilterDevicesJob` payloads. The `auto_create_ipam` feature was removed entirely during the IPAM rework merged to `develop` (PR `#303`), so the absence of this flag/payload data in current code is expected.

Applied to files:

  • netbox_librenms_plugin/views/imports/actions.py
📚 Learning: 2026-03-12T12:14:03.173Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 25
File: netbox_librenms_plugin/tests/test_coverage_device_fields.py:643-673
Timestamp: 2026-03-12T12:14:03.173Z
Learning: In netbox_librenms_plugin/tests/test_coverage_device_fields.py, strengthen the CreateAndAssignPlatformView success-path tests (test_manufacturer_not_found around lines 642–672 and the adjacent test around lines 674–697) by asserting that the newly created Platform instance is assigned to the locked device object (mock_locked.platform is mock_platform_instance) and that mock_locked.save() is called. This validates the FK assignment and persistence, rather than only checking that messages.success() was invoked. This improvement is a known backlog item (low priority); do not re-raise as a new finding.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
🪛 ast-grep (0.45.0)
netbox_librenms_plugin/tests/test_view_wiring.py

[error] 732-732: Lack of sanitization of user data
Context: HttpResponse()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(http-response-from-request)

netbox_librenms_plugin/views/sync/modules.py

[error] 2242-2242: Lack of sanitization of user data
Context: HttpResponse("Invalid target_kind.", status=400)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(http-response-from-request)

🔇 Additional comments (22)
netbox_librenms_plugin/tests/test_coverage_device_fields.py (1)

1022-1035: LGTM!

Also applies to: 2597-2614, 2640-2652

netbox_librenms_plugin/tests/test_coverage_devices.py (1)

730-735: LGTM!

Also applies to: 806-806, 890-895, 912-914

netbox_librenms_plugin/tests/test_permissions.py (1)

342-367: LGTM!

netbox_librenms_plugin/tests/test_view_wiring.py (1)

721-869: LGTM!

Also applies to: 965-990, 1222-1253, 1274-1340

netbox_librenms_plugin/tests/view_test_helpers.py (1)

19-22: LGTM!

Also applies to: 147-157

netbox_librenms_plugin/views/object_sync/devices.py (2)

8-8: LGTM!

Also applies to: 408-408, 514-514, 537-537


377-377: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Authorization Bypass (CWE-862): Missing Authorization

Reachability: External

Reachability path
● Entry
  netbox_librenms_plugin/views/sync/locations.py:97
  post
│
▼
● Hop
  netbox_librenms_plugin/tests/test_view_wiring.py
│
▼
● Sink
  netbox_librenms_plugin/views/object_sync/devices.py

Require view_vlan before reading VLAN membership.

A caller can submit vid and vlan_group_id. The POST gate permits a user with Device and VLANGroup view permissions but no VLAN view permission. The endpoint then queries VLAN rows and returns is_missing, which exposes VLAN membership.

Add ("view", VLAN) to required_object_permissions["POST"]. Add a denial test for a user without VLAN view permission. The sibling VerifyVlanSyncGroupView already applies this permission contract.

netbox_librenms_plugin/views/sync/device_fields.py (1)

406-409: LGTM!

Also applies to: 422-422, 440-441, 502-502, 755-755, 821-821, 870-870, 961-961

netbox_librenms_plugin/views/sync/devices.py (1)

48-50: LGTM!

Also applies to: 56-74, 149-155

netbox_librenms_plugin/views/sync/modules.py (4)

2242-2243: 📐 Maintainability & Code Quality

The static-analysis XSS hint on this line is a false positive.

HttpResponse("Invalid target_kind.", status=400) returns a fixed literal. No request data reaches the response body, so no injection sink exists. No change is required.

Source: Linters/SAST tools


566-580: LGTM!

Also applies to: 605-613, 686-696, 1244-1254


1941-1948: LGTM!

Also applies to: 1974-1996, 2001-2010, 2052-2054


2239-2258: LGTM!

Also applies to: 2310-2331

netbox_librenms_plugin/tests/test_coverage_sync_views.py (1)

2389-2398: LGTM!

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (2)

182-258: LGTM!

Also applies to: 404-404


679-692: LGTM!

Also applies to: 2153-2164, 2442-2460

netbox_librenms_plugin/views/imports/actions.py (3)

2029-2039: LGTM!

Also applies to: 3687-3700


2276-2279: LGTM!

Also applies to: 2297-2297, 2984-2991


1698-1702: 🩺 Stability & Availability

No change needed. RestrictedQuerySet.restrict() applies constrained object permissions through a primary-key filter rather than a global DISTINCT, so these select_for_update(of=("self",)) rows remain PostgreSQL-compatible.

netbox_librenms_plugin/views/sync/cables.py (1)

28-40: LGTM!

Also applies to: 147-159, 231-231

netbox_librenms_plugin/views/sync/locations.py (1)

27-36: LGTM!

Also applies to: 53-53, 99-101, 122-127

netbox_librenms_plugin/views/sync/vlans.py (1)

31-31: LGTM!

Also applies to: 65-65, 123-123

Comment thread netbox_librenms_plugin/tests/test_view_wiring.py
Comment thread netbox_librenms_plugin/views/sync/cables.py Outdated
Comment thread netbox_librenms_plugin/tests/test_coverage_devices.py
@marcinpsk

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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 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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html`:
- Line 128: Update the “Update Serial Only” form condition in
module_mismatch_modal.html to also require not serial_conflict_hidden, matching
the guard used by the Replace form. Preserve the existing serial_conflict and
serial_conflict_ambiguous checks.

In `@netbox_librenms_plugin/views/sync/cables.py`:
- Around line 175-181: Update handle_cable_creation so out-of-page-context
selections return a distinct rejected_selection result instead of invalid, then
update display_sync_results to render that bucket with a clear
selection-rejection message. Preserve the existing invalid result handling for
genuine missing LibreNMS link data.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cae328fe-8449-4bd8-b7f6-c865bb3ca52e

📥 Commits

Reviewing files that changed from the base of the PR and between 462e650 and abdb792.

📒 Files selected for processing (8)
  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/modules.py
📜 Review details
⚠️ CI failures not shown inline (1)

GitHub Actions: Test with all supported NetBox versions / 3_NetBox v4.4.0 _ Python 3.12.txt: Resolve every gated object through a restricted queryset

Conclusion: failure

View job details

test_template_compiles[devicetypemapping.html] PASSED [ 96%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[devicetypemapping_list.html] PASSED [ 96%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/_add_as_oob_form.html] PASSED [ 96%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/_dt_mapping_form.html] PASSED [ 96%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/_existing_librenms_link_status.html] PASSED [ 96%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/_oob_interface_select.html] PASSED [ 97%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/_platform_manage_icon.html] PASSED [ 97%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/_platform_mapping_form.html] PASSED [ 97%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/add_bay_template_modal.html] PASSED [ 97%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/bulk_import_confirm.html] PASSED [ 97%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/create_platform_modal.html] PASSED [ 97%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring.py::TestTemplateSyntax::test_template_compiles[htmx/device_import_row.html] PASSED [ 97%]
 ../../netbox-librenms-plugin/netbox_librenms_plugin/tests/test_view_wiring...
🧰 Additional context used
📓 Path-based instructions (7)
netbox_librenms_plugin/templates/**/*.html

📄 CodeRabbit inference engine (.github/instructions/frontend.instructions.md)

netbox_librenms_plugin/templates/**/*.html: HTMX 2.x is the primary async layer. Table row updates should return <tr hx-swap-oob="true">.
Avoid outerHTML swaps in HTMX; use OOB or targeted innerHTML swaps to keep table layout intact.
Do not reintroduce data-bs-toggle or duplicate modal IDs in modal implementation.
Keep <select class="device-role-select"> markup stable to preserve JavaScript hook-up for TomSelect decorators.
Do not re-add table-responsive wrappers as their removal was deliberate to prevent dropdown clipping.
Templates live in templates/netbox_librenms_plugin/; reuse and includes go under inc/ subdirectory.

Files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
netbox_librenms_plugin/**/*.{html,js}

📄 CodeRabbit inference engine (.github/instructions/frontend.instructions.md)

All HTMX requests and fetch() calls must include a CSRF token. Prefer extracting from hidden form input via document.querySelector('[name=csrfmiddlewaretoken]').value rather than cookie-based approach.

Files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
netbox_librenms_plugin/**/*.{html,css}

📄 CodeRabbit inference engine (.github/instructions/frontend.instructions.md)

Styling assumes Tabler defaults for the netbox_librenms_plugin frontend.

Files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/*.html

📄 CodeRabbit inference engine (.github/instructions/frontend.instructions.md)

HTMX fragments live in templates/netbox_librenms_plugin/htmx/ including: device_import_row.html, device_validation_details.html, device_vc_details.html, bulk_import_confirm.html.

Files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
**/views/sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Sync action views must follow the pattern: check permissions with LibreNMSPermissionMixin and NetBoxObjectPermissionMixin, read selected items from request.POST.getlist('select'), load cached data using CacheMixin.get_cache_key(), apply changes inside transaction.atomic(), and redirect to the sync tab with ?tab=<resource>.

Files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/modules.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
**/views/object_sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Object sync view methods must create instances of concrete table views, copy the request object, and call get_context_data(). VMs must skip cables and VLANs by returning None from those get_*_context() methods.

Files:

  • netbox_librenms_plugin/views/object_sync/devices.py
🧠 Learnings (33)
📚 Learning: 2026-03-13T11:16:36.294Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/device_validation_details.html:136-137
Timestamp: 2026-03-13T11:16:36.294Z
Learning: In Django templates under netbox_librenms_plugin/templates/**/*.html, do not suggest adding explicit parentheses to {% if %} expressions for readability. The project favors compact expressions using implicit operator precedence (and binds tighter than or). Treat parentheses as cosmetic and avoid guidance to insert them for style reasons.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-05-01T08:25:06.260Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/device_validation_details.html:245-246
Timestamp: 2026-05-01T08:25:06.260Z
Learning: In netbox_librenms_plugin template HTML/HTMX code, only require an X-CSRFToken header for state-changing requests made via fetch() or HTMX (POST, PUT, PATCH, DELETE). Do not require X-CSRFToken on read-only fetch() GET calls (e.g., autocomplete/lookup endpoints like dcim-api:devicetype-list); Django/DRF exempt GET requests from CSRF validation. Therefore, code reviews should not flag missing CSRF headers on GET fetch() calls used for lookups/autocomplete.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-06-14T22:58:16.581Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/librenms_sync_base.html:196-196
Timestamp: 2026-06-14T22:58:16.581Z
Learning: In Django template files under netbox_librenms_plugin/templates/**/*.html, do NOT flag template expressions like accessing a chained attribute on a possibly-None variable (e.g., `librenms_sync_device.pk` when `librenms_sync_device` may be None) as a NullPointerError/AttributeError. Django’s template attribute lookup resolves failed lookups to `TEMPLATE_STRING_IF_INVALID` (empty string by default), so comparisons such as `object.pk == librenms_sync_device.pk` will evaluate against `''` and safely result in False rather than raising a template error.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-03-13T20:03:16.435Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api2.py:319-355
Timestamp: 2026-03-13T20:03:16.435Z
Learning: Do not propose replacing server_info with module_sync.server_key in the templates located under netbox_librenms_plugin/templates/netbox_librenms_plugin (specifically _module_sync.html and inc/_module_sync.html). These templates rely on server_info being present in the parent template context (librenms_sync_base.html) and server_key may be absent on initial load. Treat the correct usage of server_info for populating the value as the intended pattern; only flag issues if server_key is incorrectly used in these templates. This guideline applies to all files under the templates path for this plugin.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-05-25T21:48:19.264Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/_platform_mapping_form.html:12-12
Timestamp: 2026-05-25T21:48:19.264Z
Learning: In this plugin’s HTMX form templates, `hx-include` selectors that target toggle preference wrapper `<span>` element IDs (e.g., `#use-sysname-toggle`, `#strip-domain-toggle`, `#auto-create-ipam-toggle`) are intentional. Those wrapper spans contain both the hidden `off` fallback input and the checkbox; HTMX must include the wrapper so the correct value is serialized, including the unchecked/off state. Do not recommend changing `hx-include` to the checkbox IDs with the `-cb` suffix, since it would omit the hidden fallback and break unchecked/off state submission.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-06-01T20:22:57.975Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/device_validation_details.html:695-700
Timestamp: 2026-06-01T20:22:57.975Z
Learning: Do not recommend adding or propagating the removed `auto_create_ipam` toggle/preference via HTMX (e.g., `hx-include="`#auto-create-ipam-toggle`"`) or by introducing hidden `auto_create_ipam` inputs in out-of-band (OOB) / “promote” POST forms. Since the `auto_create_ipam` feature has been removed from the import page, any review suggestions attempting to wire it into `device_validation_details.html` or other import-flow templates should be ignored.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-06-14T22:58:16.581Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/librenms_sync_base.html:196-196
Timestamp: 2026-06-14T22:58:16.581Z
Learning: In Django templates, the `{% if %}` tag does not support parenthetical grouping. Do not suggest adding parentheses like `{% if (not x) %}` or `{% if (a or b) %}`—these can raise `TemplateSyntaxError` (e.g., “Could not parse the remainder”). Instead, express the logic using Django template operator precedence rules (not binds tighter than and, and binds tighter than or) and refactor (e.g., via separate conditions/`{% if %}` blocks) when precedence alone can’t express the intended grouping.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-03-07T17:17:04.217Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/cables.py:160-165
Timestamp: 2026-03-07T17:17:04.217Z
Learning: In Python views under netbox_librenms_plugin/views/sync, when obtaining a server_key for cache namespace scoping, read it from request.POST with a fallback to self.librenms_api.server_key (e.g., server_key = request.POST.get("server_key") or self.librenms_api.server_key) and assign it to an attribute (e.g., self._post_server_key) used by get_cached_links_data to build the cache key. Do not flag or remove this POST-read pattern, as it ensures consistent, future-proof cache namespace scoping for link data lookups. Apply this guidance to similar Sync views in the same module where server_key-based cache scoping is used.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-15T12:38:49.280Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/views/base/modules_view.py:133-141
Timestamp: 2026-04-15T12:38:49.280Z
Learning: Do not require or flag an explicit `permission_required = PERM_VIEW_PLUGIN` on views that inherit from `LibreNMSPermissionMixin` (e.g., those ultimately including `BaseModuleTableView` in `views/base/modules_view.py`). `LibreNMSPermissionMixin` is the authoritative place where `permission_required` is set to `PERM_VIEW_PLUGIN`; inherited values are already enforced via the mixin, making a redundant override unnecessary.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-05-17T11:32:40.631Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 76
File: netbox_librenms_plugin/views/object_sync/devices.py:178-180
Timestamp: 2026-05-17T11:32:40.631Z
Learning: In this plugin’s JSON endpoint views (e.g., NetBox view classes/functions under netbox_librenms_plugin/views/**), do not require a try/except JSONDecodeError around `json.loads(request.body)` by default if the endpoint is already protected by CSRF + session authentication and has object-level permission gates. Treat missing JSONDecodeError handling as acceptable when the only expected downside is a noisy log line. If you identify any additional security or availability impact from invalid JSON (e.g., unhandled exceptions leading to user-visible 500s, potential DoS amplification, or lack of appropriate throttling/validation), then flag it and recommend adding guarded parsing/validation.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-07-02T13:36:15.226Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/interfaces_view.py:226-226
Timestamp: 2026-07-02T13:36:15.226Z
Learning: When reviewing netbox-librenms-plugin view code that handles POSTed `server_key`, treat the plugin-wide convention as intentional: if the posted `server_key` is not present in `LibreNMSAPI.get_available_servers()`, the request should fall back to the currently configured default/active server key (not reject/fail-closed and not treat it as an error for that single view). Do not flag individual instances of this fallback pattern as incorrect “invalid server_key” validation. Any change to fail-closed behavior must be a coordinated cross-cutting change applied uniformly across all affected sites/views, not a one-view patch.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-08-05T06:48:35.761Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 127
File: netbox_librenms_plugin/views/sync/interfaces.py:494-494
Timestamp: 2026-08-05T06:48:35.761Z
Learning: In permission-scoped NetBox views, do not replace re-locks that use an already resolved object primary key (for example, `pk=already_resolved.pk`) with `restricted_queryset()`. NetBox `restrict()` may return `none()` when no model-level grant exists, even if the view-level permission gate allows the operation, causing valid rows to be removed from the lock set and producing an erroneous “no longer exists” result. AST guards for raw client-supplied primary-key lookups should distinguish and exempt these re-locks based on the ID-expression shape.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-06-01T15:12:26.824Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/ip_addresses.py:94-103
Timestamp: 2026-06-01T15:12:26.824Z
Learning: For any redirect/tab URL building in netbox_librenms_plugin/views/sync, views/base, and views/object_sync, propagate the active multi-server `server_key` as a `?server_key=<key>` query parameter so users return to the same server’s tab after POST actions. When handling POST requests, read the POST-scoped `server_key` from `request.POST` and store it (e.g., `self._post_server_key`) with a fallback to `self.librenms_api.server_key`; use this POST-scoped key for both cache-key scoping and for constructing the redirect/tab URLs. Treat this as the intentional codebase-wide convention—do not flag the presence/usage of the `server_key` query parameter (or the corresponding POST-scoped `_post_server_key` pattern) in these views as an error.

Applied to files:

  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-08T08:57:43.392Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/object_sync/devices.py:83-100
Timestamp: 2026-03-08T08:57:43.392Z
Learning: In views under netbox_librenms_plugin/views/object_sync, server_key values come from admin-controlled PLUGINS_CONFIG dict keys (e.g., "default", "production") and are not user input. Therefore URL-encoding them via urlencode() is unnecessary defensiveness. Do not flag direct string interpolation of server_key into query strings as a URL-injection or encoding issue. This guidance should apply to similar views in the same directory.

Applied to files:

  • netbox_librenms_plugin/views/object_sync/devices.py
📚 Learning: 2026-05-05T09:58:50.179Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/object_sync/devices.py:57-75
Timestamp: 2026-05-05T09:58:50.179Z
Learning: In object_sync view classes that pass Django/NetBox `request` into child table context helpers (e.g., for interfaces/cables/IPs/vlans/modules), ensure the child view stores `copy.copy(request)` rather than the original `request` object. Apply this consistently across similar sync views (such as the pattern used in `VMLibreNMSSyncView` in `vms.py`) to prevent cross-view request mutation when the child view modifies the request.

Applied to files:

  • netbox_librenms_plugin/views/object_sync/devices.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-25T07:14:19.587Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/modules_view.py:1073-1074
Timestamp: 2026-06-25T07:14:19.587Z
Learning: In netbox_librenms_plugin/views/base/modules_view.py and netbox_librenms_plugin/views/sync/modules.py, treat LibreNMS `entPhysicalIndex` as an end-to-end integer invariant (it originates from an LibreNMS int DB column and is preserved as an int through the module inventory/sync pipeline). When reviewing code, do not flag mixed string/int `entPhysicalIndex` handling or request additional `int()` normalization solely as a defensive measure against string indices. Only recommend `int()` conversion/normalization if there is concrete evidence in the code path that values are actually being converted to strings (e.g., explicit casts, JSON serialization/deserialization steps that coerce to strings, or external inputs known to provide strings).

Applied to files:

  • netbox_librenms_plugin/views/sync/modules.py
🪛 GitHub Actions: Test with all supported NetBox versions / 0_NetBox main _ Python 3.13.txt
netbox_librenms_plugin/tests/test_view_wiring.py

[warning] 1-1: PytestRemovedIn10Warning: Class-scoped fixture defined as an instance method is deprecated; use @classmethod.

netbox_librenms_plugin/views/sync/modules.py

[error] 1856-1856: Test failed in TestSingleInstallInterfaceBinding::test_replace_module_view_binds_interface_after_replace: comparing MagicMock conflict_count with integer using '>' raised TypeError. Failed test command exited with code 1.

🪛 GitHub Actions: Test with all supported NetBox versions / 1_NetBox v4.6.5 _ Python 3.12.txt
netbox_librenms_plugin/tests/test_view_wiring.py

[warning] 1-1: PytestRemovedIn10Warning: A class-scoped fixture is defined as an instance method. Use @classmethod to avoid deprecated behavior.

netbox_librenms_plugin/views/sync/modules.py

[error] 1856-1856: Test failed in TestSingleInstallInterfaceBinding::test_replace_module_view_binds_interface_after_replace: comparison conflict_count > 1 raised TypeError because conflict_count was a MagicMock rather than a number.

🪛 GitHub Actions: Test with all supported NetBox versions / 2_NetBox v4.6.5 _ Python 3.13.txt
netbox_librenms_plugin/tests/test_view_wiring.py

[warning] 1-1: PytestRemovedIn10Warning: A class-scoped fixture is defined as an instance method; use @classmethod instead.

netbox_librenms_plugin/views/sync/modules.py

[error] 1856-1856: Test failure in TestSingleInstallInterfaceBinding::test_replace_module_view_binds_interface_after_replace: comparison conflict_count > 1 raises TypeError because conflict_count is a MagicMock rather than a number.

🪛 GitHub Actions: Test with all supported NetBox versions / 3_NetBox v4.4.0 _ Python 3.12.txt
netbox_librenms_plugin/tests/test_view_wiring.py

[warning] 1-1: PytestRemovedIn10Warning: Class-scoped fixture is defined as an instance method; use @classmethod.

netbox_librenms_plugin/views/sync/modules.py

[error] 1856-1856: Test failure: ReplaceModuleView.post compares conflict_count > 1, but conflict_count is a MagicMock, causing TypeError: '>' not supported between instances of 'MagicMock' and 'int'. Failing test: TestSingleInstallInterfaceBinding::test_replace_module_view_binds_interface_after_replace.

🪛 GitHub Actions: Test with all supported NetBox versions / 4_NetBox main _ Python 3.14.txt
netbox_librenms_plugin/tests/test_view_wiring.py

[warning] 1-1: PytestRemovedIn10Warning: A class-scoped fixture is defined as an instance method. Use @classmethod to avoid deprecated fixture behavior.

netbox_librenms_plugin/views/sync/modules.py

[error] 1856-1856: Test failed in TestSingleInstallInterfaceBinding::test_replace_module_view_binds_interface_after_replace. The code compares conflict_count, which is a MagicMock, with an integer, causing TypeError: '>' not supported between instances of 'MagicMock' and 'int'.

🔇 Additional comments (21)
netbox_librenms_plugin/views/object_sync/devices.py (3)

377-377: LGTM!


407-416: LGTM!


515-515: LGTM!

Also applies to: 537-543

netbox_librenms_plugin/views/sync/cables.py (1)

28-40: LGTM!

Also applies to: 233-233

netbox_librenms_plugin/views/sync/modules.py (5)

161-168: LGTM!

Also applies to: 574-621


1389-1393: LGTM!

Also applies to: 1443-1468


1642-1653: LGTM!

Also applies to: 1673-1673


1745-1780: LGTM!

Also applies to: 1820-1826, 1848-1862, 1936-1941


1968-2034: LGTM!

Also applies to: 2079-2081, 2266-2290, 2337-2362

netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html (1)

71-76: LGTM!

netbox_librenms_plugin/tests/test_module_replace.py (3)

73-91: LGTM!

Also applies to: 106-135, 155-170


196-212: LGTM!

Also applies to: 367-376, 390-402


421-424: LGTM!

Also applies to: 462-466, 493-496, 528-538, 553-559

netbox_librenms_plugin/tests/test_coverage_devices.py (3)

722-728: LGTM!

Also applies to: 730-735, 737-766


768-805: LGTM!

Also applies to: 875-875, 959-965, 981-983


1029-1030: LGTM!

Also applies to: 1048-1049, 1058-1087

netbox_librenms_plugin/tests/test_view_wiring.py (3)

488-516: LGTM!

Also applies to: 567-567


652-692: LGTM!


1147-1210: LGTM!

Also applies to: 1212-1276, 1278-1339

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (2)

182-223: LGTM!


159-162: LGTM!

Also applies to: 2149-2158, 2449-2467

Comment thread netbox_librenms_plugin/views/sync/cables.py Outdated

@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

Caution

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

⚠️ Outside diff range comments (2)
netbox_librenms_plugin/tests/test_view_wiring.py (1)

866-895: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Bind the target permission declaration to the permission gate.

declared_read only finds a ("view", target_model) tuple anywhere in post(). It does not verify that the tuple is added to required_object_permissions before require_object_permissions() runs.

A future edit can leave an unrelated or later tuple in the method. This test will pass while the target read is no longer gated. Inspect the assignment or mutation of required_object_permissions["POST"] and require it to occur before gate_line.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbox_librenms_plugin/tests/test_view_wiring.py` around lines 866 - 895,
Update test_target_read_is_declared_and_gated_before_restricted_lookup to
inspect the required_object_permissions["POST"] assignment or mutation, confirm
it includes the ("view", target_model) tuple, and require that declaration to
occur before gate_line. Remove the unrestricted whole-method declared_read check
while preserving the existing gate and restricted lookup ordering assertions.
netbox_librenms_plugin/views/sync/ip_addresses.py (1)

74-78: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Catch ValueError for a non-numeric posted VRF id.

vrf_id comes straight from request.POST. A non-numeric value (for example vrf_10.0.0.1=abc) makes Django fail while converting the value for the pk lookup, and that raises ValueError instead of VRF.DoesNotExist. The row then propagates out of get_vrf_selection into process_ip_sync, where the broad except Exception marks the address failed with a raw type name. CreateAndAssignPlatformView in netbox_librenms_plugin/views/sync/device_fields.py (Line 441) already catches (Manufacturer.DoesNotExist, ValueError) for the same client-supplied-id shape. Align this site with that precedent.

🛡️ Proposed fix
         if vrf_id:
             try:
                 return self.restricted_queryset(VRF).get(pk=vrf_id)
-            except VRF.DoesNotExist:
+            except (VRF.DoesNotExist, ValueError):
                 pass
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbox_librenms_plugin/views/sync/ip_addresses.py` around lines 74 - 78,
Update the VRF lookup in get_vrf_selection to catch ValueError alongside
VRF.DoesNotExist when resolving the client-supplied vrf_id, matching the
existing CreateAndAssignPlatformView precedent and preserving the current
fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbox_librenms_plugin/views/sync/modules.py`:
- Around line 621-626: Update the locked bay re-fetch in the module sync flow to
filter by both pk=module_bay_id and device=target_device, matching the
pre-transaction check and MoveModuleView behavior. Keep the existing
select_for_update, missing-bay message, and redirect handling unchanged.

---

Outside diff comments:
In `@netbox_librenms_plugin/tests/test_view_wiring.py`:
- Around line 866-895: Update
test_target_read_is_declared_and_gated_before_restricted_lookup to inspect the
required_object_permissions["POST"] assignment or mutation, confirm it includes
the ("view", target_model) tuple, and require that declaration to occur before
gate_line. Remove the unrestricted whole-method declared_read check while
preserving the existing gate and restricted lookup ordering assertions.

In `@netbox_librenms_plugin/views/sync/ip_addresses.py`:
- Around line 74-78: Update the VRF lookup in get_vrf_selection to catch
ValueError alongside VRF.DoesNotExist when resolving the client-supplied vrf_id,
matching the existing CreateAndAssignPlatformView precedent and preserving the
current fallback behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fdc82157-04b5-4c25-a6eb-36241de0f4b9

📥 Commits

Reviewing files that changed from the base of the PR and between abdb792 and 7915a6c.

📒 Files selected for processing (10)
  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Analyze (python)
  • GitHub Check: NetBox main / Python 3.14
  • GitHub Check: NetBox v4.6.5 / Python 3.13
  • GitHub Check: NetBox v4.6.5 / Python 3.12
  • GitHub Check: NetBox v4.4.0 / Python 3.12
  • GitHub Check: NetBox main / Python 3.13
🧰 Additional context used
📓 Path-based instructions (6)
netbox_librenms_plugin/templates/**/*.html

📄 CodeRabbit inference engine (.github/instructions/frontend.instructions.md)

netbox_librenms_plugin/templates/**/*.html: HTMX 2.x is the primary async layer. Table row updates should return <tr hx-swap-oob="true">.
Avoid outerHTML swaps in HTMX; use OOB or targeted innerHTML swaps to keep table layout intact.
Do not reintroduce data-bs-toggle or duplicate modal IDs in modal implementation.
Keep <select class="device-role-select"> markup stable to preserve JavaScript hook-up for TomSelect decorators.
Do not re-add table-responsive wrappers as their removal was deliberate to prevent dropdown clipping.
Templates live in templates/netbox_librenms_plugin/; reuse and includes go under inc/ subdirectory.

Files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
netbox_librenms_plugin/**/*.{html,js}

📄 CodeRabbit inference engine (.github/instructions/frontend.instructions.md)

All HTMX requests and fetch() calls must include a CSRF token. Prefer extracting from hidden form input via document.querySelector('[name=csrfmiddlewaretoken]').value rather than cookie-based approach.

Files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
netbox_librenms_plugin/**/*.{html,css}

📄 CodeRabbit inference engine (.github/instructions/frontend.instructions.md)

Styling assumes Tabler defaults for the netbox_librenms_plugin frontend.

Files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/*.html

📄 CodeRabbit inference engine (.github/instructions/frontend.instructions.md)

HTMX fragments live in templates/netbox_librenms_plugin/htmx/ including: device_import_row.html, device_validation_details.html, device_vc_details.html, bulk_import_confirm.html.

Files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
**/views/sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Sync action views must follow the pattern: check permissions with LibreNMSPermissionMixin and NetBoxObjectPermissionMixin, read selected items from request.POST.getlist('select'), load cached data using CacheMixin.get_cache_key(), apply changes inside transaction.atomic(), and redirect to the sync tab with ?tab=<resource>.

Files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
🧠 Learnings (32)
📚 Learning: 2026-03-13T11:16:36.294Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/device_validation_details.html:136-137
Timestamp: 2026-03-13T11:16:36.294Z
Learning: In Django templates under netbox_librenms_plugin/templates/**/*.html, do not suggest adding explicit parentheses to {% if %} expressions for readability. The project favors compact expressions using implicit operator precedence (and binds tighter than or). Treat parentheses as cosmetic and avoid guidance to insert them for style reasons.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-05-01T08:25:06.260Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/device_validation_details.html:245-246
Timestamp: 2026-05-01T08:25:06.260Z
Learning: In netbox_librenms_plugin template HTML/HTMX code, only require an X-CSRFToken header for state-changing requests made via fetch() or HTMX (POST, PUT, PATCH, DELETE). Do not require X-CSRFToken on read-only fetch() GET calls (e.g., autocomplete/lookup endpoints like dcim-api:devicetype-list); Django/DRF exempt GET requests from CSRF validation. Therefore, code reviews should not flag missing CSRF headers on GET fetch() calls used for lookups/autocomplete.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-06-14T22:58:16.581Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/librenms_sync_base.html:196-196
Timestamp: 2026-06-14T22:58:16.581Z
Learning: In Django template files under netbox_librenms_plugin/templates/**/*.html, do NOT flag template expressions like accessing a chained attribute on a possibly-None variable (e.g., `librenms_sync_device.pk` when `librenms_sync_device` may be None) as a NullPointerError/AttributeError. Django’s template attribute lookup resolves failed lookups to `TEMPLATE_STRING_IF_INVALID` (empty string by default), so comparisons such as `object.pk == librenms_sync_device.pk` will evaluate against `''` and safely result in False rather than raising a template error.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-03-13T20:03:16.435Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api2.py:319-355
Timestamp: 2026-03-13T20:03:16.435Z
Learning: Do not propose replacing server_info with module_sync.server_key in the templates located under netbox_librenms_plugin/templates/netbox_librenms_plugin (specifically _module_sync.html and inc/_module_sync.html). These templates rely on server_info being present in the parent template context (librenms_sync_base.html) and server_key may be absent on initial load. Treat the correct usage of server_info for populating the value as the intended pattern; only flag issues if server_key is incorrectly used in these templates. This guideline applies to all files under the templates path for this plugin.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-05-25T21:48:19.264Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/_platform_mapping_form.html:12-12
Timestamp: 2026-05-25T21:48:19.264Z
Learning: In this plugin’s HTMX form templates, `hx-include` selectors that target toggle preference wrapper `<span>` element IDs (e.g., `#use-sysname-toggle`, `#strip-domain-toggle`, `#auto-create-ipam-toggle`) are intentional. Those wrapper spans contain both the hidden `off` fallback input and the checkbox; HTMX must include the wrapper so the correct value is serialized, including the unchecked/off state. Do not recommend changing `hx-include` to the checkbox IDs with the `-cb` suffix, since it would omit the hidden fallback and break unchecked/off state submission.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-06-01T20:22:57.975Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/device_validation_details.html:695-700
Timestamp: 2026-06-01T20:22:57.975Z
Learning: Do not recommend adding or propagating the removed `auto_create_ipam` toggle/preference via HTMX (e.g., `hx-include="`#auto-create-ipam-toggle`"`) or by introducing hidden `auto_create_ipam` inputs in out-of-band (OOB) / “promote” POST forms. Since the `auto_create_ipam` feature has been removed from the import page, any review suggestions attempting to wire it into `device_validation_details.html` or other import-flow templates should be ignored.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-06-14T22:58:16.581Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/templates/netbox_librenms_plugin/librenms_sync_base.html:196-196
Timestamp: 2026-06-14T22:58:16.581Z
Learning: In Django templates, the `{% if %}` tag does not support parenthetical grouping. Do not suggest adding parentheses like `{% if (not x) %}` or `{% if (a or b) %}`—these can raise `TemplateSyntaxError` (e.g., “Could not parse the remainder”). Instead, express the logic using Django template operator precedence rules (not binds tighter than and, and binds tighter than or) and refactor (e.g., via separate conditions/`{% if %}` blocks) when precedence alone can’t express the intended grouping.

Applied to files:

  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
📚 Learning: 2026-03-07T17:17:04.217Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/cables.py:160-165
Timestamp: 2026-03-07T17:17:04.217Z
Learning: In Python views under netbox_librenms_plugin/views/sync, when obtaining a server_key for cache namespace scoping, read it from request.POST with a fallback to self.librenms_api.server_key (e.g., server_key = request.POST.get("server_key") or self.librenms_api.server_key) and assign it to an attribute (e.g., self._post_server_key) used by get_cached_links_data to build the cache key. Do not flag or remove this POST-read pattern, as it ensures consistent, future-proof cache namespace scoping for link data lookups. Apply this guidance to similar Sync views in the same module where server_key-based cache scoping is used.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-04-15T12:38:49.280Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/views/base/modules_view.py:133-141
Timestamp: 2026-04-15T12:38:49.280Z
Learning: Do not require or flag an explicit `permission_required = PERM_VIEW_PLUGIN` on views that inherit from `LibreNMSPermissionMixin` (e.g., those ultimately including `BaseModuleTableView` in `views/base/modules_view.py`). `LibreNMSPermissionMixin` is the authoritative place where `permission_required` is set to `PERM_VIEW_PLUGIN`; inherited values are already enforced via the mixin, making a redundant override unnecessary.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-05-17T11:32:40.631Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 76
File: netbox_librenms_plugin/views/object_sync/devices.py:178-180
Timestamp: 2026-05-17T11:32:40.631Z
Learning: In this plugin’s JSON endpoint views (e.g., NetBox view classes/functions under netbox_librenms_plugin/views/**), do not require a try/except JSONDecodeError around `json.loads(request.body)` by default if the endpoint is already protected by CSRF + session authentication and has object-level permission gates. Treat missing JSONDecodeError handling as acceptable when the only expected downside is a noisy log line. If you identify any additional security or availability impact from invalid JSON (e.g., unhandled exceptions leading to user-visible 500s, potential DoS amplification, or lack of appropriate throttling/validation), then flag it and recommend adding guarded parsing/validation.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-07-02T13:36:15.226Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/interfaces_view.py:226-226
Timestamp: 2026-07-02T13:36:15.226Z
Learning: When reviewing netbox-librenms-plugin view code that handles POSTed `server_key`, treat the plugin-wide convention as intentional: if the posted `server_key` is not present in `LibreNMSAPI.get_available_servers()`, the request should fall back to the currently configured default/active server key (not reject/fail-closed and not treat it as an error for that single view). Do not flag individual instances of this fallback pattern as incorrect “invalid server_key” validation. Any change to fail-closed behavior must be a coordinated cross-cutting change applied uniformly across all affected sites/views, not a one-view patch.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-08-05T06:48:35.761Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 127
File: netbox_librenms_plugin/views/sync/interfaces.py:494-494
Timestamp: 2026-08-05T06:48:35.761Z
Learning: In permission-scoped NetBox views, do not replace re-locks that use an already resolved object primary key (for example, `pk=already_resolved.pk`) with `restricted_queryset()`. NetBox `restrict()` may return `none()` when no model-level grant exists, even if the view-level permission gate allows the operation, causing valid rows to be removed from the lock set and producing an erroneous “no longer exists” result. AST guards for raw client-supplied primary-key lookups should distinguish and exempt these re-locks based on the ID-expression shape.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-06-01T15:12:26.824Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/ip_addresses.py:94-103
Timestamp: 2026-06-01T15:12:26.824Z
Learning: For any redirect/tab URL building in netbox_librenms_plugin/views/sync, views/base, and views/object_sync, propagate the active multi-server `server_key` as a `?server_key=<key>` query parameter so users return to the same server’s tab after POST actions. When handling POST requests, read the POST-scoped `server_key` from `request.POST` and store it (e.g., `self._post_server_key`) with a fallback to `self.librenms_api.server_key`; use this POST-scoped key for both cache-key scoping and for constructing the redirect/tab URLs. Treat this as the intentional codebase-wide convention—do not flag the presence/usage of the `server_key` query parameter (or the corresponding POST-scoped `_post_server_key` pattern) in these views as an error.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📚 Learning: 2026-06-25T07:14:19.587Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/modules_view.py:1073-1074
Timestamp: 2026-06-25T07:14:19.587Z
Learning: In netbox_librenms_plugin/views/base/modules_view.py and netbox_librenms_plugin/views/sync/modules.py, treat LibreNMS `entPhysicalIndex` as an end-to-end integer invariant (it originates from an LibreNMS int DB column and is preserved as an int through the module inventory/sync pipeline). When reviewing code, do not flag mixed string/int `entPhysicalIndex` handling or request additional `int()` normalization solely as a defensive measure against string indices. Only recommend `int()` conversion/normalization if there is concrete evidence in the code path that values are actually being converted to strings (e.g., explicit casts, JSON serialization/deserialization steps that coerce to strings, or external inputs known to provide strings).

Applied to files:

  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-03-12T12:14:03.173Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 25
File: netbox_librenms_plugin/tests/test_coverage_device_fields.py:643-673
Timestamp: 2026-03-12T12:14:03.173Z
Learning: In netbox_librenms_plugin/tests/test_coverage_device_fields.py, strengthen the CreateAndAssignPlatformView success-path tests (test_manufacturer_not_found around lines 642–672 and the adjacent test around lines 674–697) by asserting that the newly created Platform instance is assigned to the locked device object (mock_locked.platform is mock_platform_instance) and that mock_locked.save() is called. This validates the FK assignment and persistence, rather than only checking that messages.success() was invoked. This improvement is a known backlog item (low priority); do not re-raise as a new finding.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
🔇 Additional comments (10)
netbox_librenms_plugin/tests/test_view_wiring.py (1)

484-590: LGTM!

Also applies to: 822-838, 900-930, 934-1576

netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html (1)

71-76: LGTM!

Also applies to: 88-88, 128-128

netbox_librenms_plugin/tests/test_coverage_device_fields.py (1)

19-19: LGTM!

Also applies to: 1023-1035, 1148-1185

netbox_librenms_plugin/views/sync/cables.py (1)

181-181: LGTM!

Also applies to: 214-221, 272-277

netbox_librenms_plugin/views/sync/device_fields.py (1)

406-409: LGTM!

Also applies to: 431-431, 440-447, 507-507

netbox_librenms_plugin/views/sync/ip_addresses.py (1)

62-64: LGTM!

Also applies to: 90-96, 126-131

netbox_librenms_plugin/views/sync/modules.py (1)

1647-1658: LGTM!

Also applies to: 1678-1678, 1778-1785, 1824-1834, 1852-1866, 1941-1946, 2012-2020, 2037-2046, 2275-2299

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (1)

223-226: LGTM!

Also applies to: 1275-1289

netbox_librenms_plugin/tests/test_module_replace.py (1)

631-631: LGTM!

Also applies to: 644-676

netbox_librenms_plugin/tests/test_sync_modules.py (1)

1762-1762: LGTM!

Also applies to: 1847-1847, 1936-1936, 2389-2454, 2948-2948, 4344-4344, 4371-4413, 4448-4448

Comment thread netbox_librenms_plugin/views/sync/modules.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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbox_librenms_plugin/tests/test_view_wiring.py`:
- Around line 886-900: Update the declaration_line search in the test to
consider only assignments targeting required_object_permissions, resolving any
intermediate permission variable explicitly when the mapping is built across
multiple assignments. Keep the existing tuple match and ordering assertion, but
ensure unrelated ("view", target_model) tuples cannot satisfy the declaration
check.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b042021d-205f-46dc-9c2c-37fae50a9ba8

📥 Commits

Reviewing files that changed from the base of the PR and between 7915a6c and 9f44d67.

📒 Files selected for processing (5)
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
**/views/sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Sync action views must follow the pattern: check permissions with LibreNMSPermissionMixin and NetBoxObjectPermissionMixin, read selected items from request.POST.getlist('select'), load cached data using CacheMixin.get_cache_key(), apply changes inside transaction.atomic(), and redirect to the sync tab with ?tab=<resource>.

Files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
🧠 Learnings (24)
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-07T17:17:04.217Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/cables.py:160-165
Timestamp: 2026-03-07T17:17:04.217Z
Learning: In Python views under netbox_librenms_plugin/views/sync, when obtaining a server_key for cache namespace scoping, read it from request.POST with a fallback to self.librenms_api.server_key (e.g., server_key = request.POST.get("server_key") or self.librenms_api.server_key) and assign it to an attribute (e.g., self._post_server_key) used by get_cached_links_data to build the cache key. Do not flag or remove this POST-read pattern, as it ensures consistent, future-proof cache namespace scoping for link data lookups. Apply this guidance to similar Sync views in the same module where server_key-based cache scoping is used.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-04-15T12:38:49.280Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/views/base/modules_view.py:133-141
Timestamp: 2026-04-15T12:38:49.280Z
Learning: Do not require or flag an explicit `permission_required = PERM_VIEW_PLUGIN` on views that inherit from `LibreNMSPermissionMixin` (e.g., those ultimately including `BaseModuleTableView` in `views/base/modules_view.py`). `LibreNMSPermissionMixin` is the authoritative place where `permission_required` is set to `PERM_VIEW_PLUGIN`; inherited values are already enforced via the mixin, making a redundant override unnecessary.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-05-17T11:32:40.631Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 76
File: netbox_librenms_plugin/views/object_sync/devices.py:178-180
Timestamp: 2026-05-17T11:32:40.631Z
Learning: In this plugin’s JSON endpoint views (e.g., NetBox view classes/functions under netbox_librenms_plugin/views/**), do not require a try/except JSONDecodeError around `json.loads(request.body)` by default if the endpoint is already protected by CSRF + session authentication and has object-level permission gates. Treat missing JSONDecodeError handling as acceptable when the only expected downside is a noisy log line. If you identify any additional security or availability impact from invalid JSON (e.g., unhandled exceptions leading to user-visible 500s, potential DoS amplification, or lack of appropriate throttling/validation), then flag it and recommend adding guarded parsing/validation.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-07-02T13:36:15.226Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/interfaces_view.py:226-226
Timestamp: 2026-07-02T13:36:15.226Z
Learning: When reviewing netbox-librenms-plugin view code that handles POSTed `server_key`, treat the plugin-wide convention as intentional: if the posted `server_key` is not present in `LibreNMSAPI.get_available_servers()`, the request should fall back to the currently configured default/active server key (not reject/fail-closed and not treat it as an error for that single view). Do not flag individual instances of this fallback pattern as incorrect “invalid server_key” validation. Any change to fail-closed behavior must be a coordinated cross-cutting change applied uniformly across all affected sites/views, not a one-view patch.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-08-05T06:48:35.761Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 127
File: netbox_librenms_plugin/views/sync/interfaces.py:494-494
Timestamp: 2026-08-05T06:48:35.761Z
Learning: In permission-scoped NetBox views, do not replace re-locks that use an already resolved object primary key (for example, `pk=already_resolved.pk`) with `restricted_queryset()`. NetBox `restrict()` may return `none()` when no model-level grant exists, even if the view-level permission gate allows the operation, causing valid rows to be removed from the lock set and producing an erroneous “no longer exists” result. AST guards for raw client-supplied primary-key lookups should distinguish and exempt these re-locks based on the ID-expression shape.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-06-01T15:12:26.824Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/ip_addresses.py:94-103
Timestamp: 2026-06-01T15:12:26.824Z
Learning: For any redirect/tab URL building in netbox_librenms_plugin/views/sync, views/base, and views/object_sync, propagate the active multi-server `server_key` as a `?server_key=<key>` query parameter so users return to the same server’s tab after POST actions. When handling POST requests, read the POST-scoped `server_key` from `request.POST` and store it (e.g., `self._post_server_key`) with a fallback to `self.librenms_api.server_key`; use this POST-scoped key for both cache-key scoping and for constructing the redirect/tab URLs. Treat this as the intentional codebase-wide convention—do not flag the presence/usage of the `server_key` query parameter (or the corresponding POST-scoped `_post_server_key` pattern) in these views as an error.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/modules.py
📚 Learning: 2026-06-25T07:14:19.587Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/modules_view.py:1073-1074
Timestamp: 2026-06-25T07:14:19.587Z
Learning: In netbox_librenms_plugin/views/base/modules_view.py and netbox_librenms_plugin/views/sync/modules.py, treat LibreNMS `entPhysicalIndex` as an end-to-end integer invariant (it originates from an LibreNMS int DB column and is preserved as an int through the module inventory/sync pipeline). When reviewing code, do not flag mixed string/int `entPhysicalIndex` handling or request additional `int()` normalization solely as a defensive measure against string indices. Only recommend `int()` conversion/normalization if there is concrete evidence in the code path that values are actually being converted to strings (e.g., explicit casts, JSON serialization/deserialization steps that coerce to strings, or external inputs known to provide strings).

Applied to files:

  • netbox_librenms_plugin/views/sync/modules.py
🔇 Additional comments (5)
netbox_librenms_plugin/views/sync/ip_addresses.py (1)

42-65: LGTM!

Also applies to: 76-77, 91-95, 128-129

netbox_librenms_plugin/views/sync/modules.py (1)

575-588: LGTM!

Also applies to: 613-629

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (1)

228-268: LGTM!

Also applies to: 748-760, 1275-1311, 1335-1369, 2253-2263, 2474-2492

netbox_librenms_plugin/tests/test_sync_modules.py (1)

4413-4413: LGTM!

netbox_librenms_plugin/tests/test_view_wiring.py (1)

484-590: LGTM!

Also applies to: 1555-1580

Comment thread netbox_librenms_plugin/tests/test_view_wiring.py
@marcinpsk

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

@marcinpsk

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbox_librenms_plugin/tests/test_coverage_device_fields.py`:
- Around line 1303-1332: Document the ordering assumption in
test_integrity_error_reuses_concurrently_created_platform: _skip_once must skip
the view’s Platform insert, not the rival Platform.objects.create call, because
the test models the IntegrityError reuse branch. Add a concise comment or
assertion tying the existing surviving-row, device-assignment, and
no-error-message checks to confirmation that the view reached its IntegrityError
recovery path.

In `@netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py`:
- Around line 890-937: Remove the duplicate
TestSyncInterfacesViewHandleMacAddress coverage from
netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py lines 890-937,
retaining only assertions not already covered. Keep TestHandleMacAddress in
netbox_librenms_plugin/tests/test_sync_interfaces.py lines 254-336 as the single
home for handle_mac_address behavior, moving any genuinely unique case there.

In `@netbox_librenms_plugin/tests/test_coverage_sync_views2.py`:
- Line 2007: The repeated maximum-primary-key arithmetic should be centralized
in a shared helper. Add missing_pk(model, *, offset=1000) to
view_test_helpers.py, returning a safe nonexistent primary key for empty or
populated tables, then replace the VLANGroup, Site, and VRF expressions in
netbox_librenms_plugin/tests/test_coverage_sync_views2.py#L2007-L2007, the Site
expression in
netbox_librenms_plugin/tests/test_coverage_sync_views.py#L2132-L2132, the
Interface expression in
netbox_librenms_plugin/tests/test_coverage_sync_views3.py#L707-L707, and the
Device expression in
netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py#L768-L768 with
missing_pk(model).
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9cde21bd-b631-435b-aecb-5a409526b4e3

📥 Commits

Reviewing files that changed from the base of the PR and between bcb8fd2 and 1b55ed3.

📒 Files selected for processing (35)
  • netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.html
  • netbox_librenms_plugin/tests/conftest.py
  • netbox_librenms_plugin/tests/test_coverage_actions.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_devices.py
  • netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_tables.py
  • netbox_librenms_plugin/tests/test_coverage_utils.py
  • netbox_librenms_plugin/tests/test_device_fields_server_scoping.py
  • netbox_librenms_plugin/tests/test_import_utils.py
  • netbox_librenms_plugin/tests/test_migrate_views.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_permissions.py
  • netbox_librenms_plugin/tests/test_platform_mapping.py
  • netbox_librenms_plugin/tests/test_sync_devices.py
  • netbox_librenms_plugin/tests/test_sync_interfaces.py
  • netbox_librenms_plugin/tests/test_sync_modules.py
  • netbox_librenms_plugin/tests/test_view_seam.py
  • netbox_librenms_plugin/tests/test_view_wiring.py
  • netbox_librenms_plugin/tests/view_test_helpers.py
  • netbox_librenms_plugin/views/imports/actions.py
  • netbox_librenms_plugin/views/mixins.py
  • netbox_librenms_plugin/views/object_sync/devices.py
  • netbox_librenms_plugin/views/sync/cables.py
  • netbox_librenms_plugin/views/sync/device_fields.py
  • netbox_librenms_plugin/views/sync/devices.py
  • netbox_librenms_plugin/views/sync/interfaces.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/locations.py
  • netbox_librenms_plugin/views/sync/migrate.py
  • netbox_librenms_plugin/views/sync/modules.py
  • netbox_librenms_plugin/views/sync/vlans.py

Comment thread netbox_librenms_plugin/tests/test_coverage_device_fields.py
Comment thread netbox_librenms_plugin/tests/test_coverage_sync_interfaces.py Outdated
Comment thread netbox_librenms_plugin/tests/test_coverage_sync_views2.py Outdated
@marcinpsk

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

@marcinpsk

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

Caution

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

⚠️ Outside diff range comments (2)
netbox_librenms_plugin/views/sync/ip_addresses.py (1)

70-80: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

An out-of-scope VRF id silently degrades to the global scope.

get_vrf_selection returns None for three distinct cases: no VRF posted, an unresolvable id, and a VRF the caller cannot see. The caller at Line 368 cannot tell them apart. process_ip_sync then matches with vrf=None at Line 391 and creates the address with vrf=None at Line 407. A user who posts a restricted VRF id therefore writes the address into the global table instead of being refused.

SyncVLANsView._handle_create_vlans handles the same shape of input differently: a requested-but-unresolvable VLANGroup fails closed, emits a per-row error, and skips the row (see netbox_librenms_plugin/views/sync/vlans.py lines 128-141). Align this view with that contract.

🛡️ Proposed fix to distinguish "no VRF" from "unresolvable VRF"
     def get_vrf_selection(self, request, ip_address):
-        """Return the VRF selected for a given IP address, or None."""
+        """Return the VRF selected for a given IP address.
+
+        Returns None when no VRF was posted. Raises LookupError when a VRF was posted but
+        cannot be resolved in the caller's scope, so the caller fails the row closed instead
+        of writing the address into the global scope.
+        """
         vrf_id = request.POST.get(f"vrf_{ip_address}")
 
         if vrf_id:
             try:
                 return self.restricted_queryset(VRF).get(pk=vrf_id)
             except (VRF.DoesNotExist, TypeError, ValueError):
-                pass
+                raise LookupError(vrf_id)
 
         return None

Then handle it per row in process_ip_sync:

-                    vrf = self.get_vrf_selection(request, ip_address)
+                    try:
+                        vrf = self.get_vrf_selection(request, ip_address)
+                    except LookupError:
+                        results["failed"].append(ip_address)
+                        results["errors"][ip_address] = (
+                            "The selected VRF no longer exists or is outside your permissions."
+                        )
+                        continue

Note: the existing tests test_get_vrf_selection_not_found_returns_none and test_get_vrf_selection_returns_none_for_a_vrf_outside_the_grant in netbox_librenms_plugin/tests/test_coverage_sync_views2.py pin the current return contract and need updating with any fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbox_librenms_plugin/views/sync/ip_addresses.py` around lines 70 - 80,
Update get_vrf_selection to distinguish an absent vrf_{ip_address} POST value
from a requested VRF that cannot be resolved within restricted_queryset(VRF),
preserving None only for no selection and signaling the latter case explicitly.
In process_ip_sync, handle that unresolved selection per row like
SyncVLANsView._handle_create_vlans: emit the row error and skip matching or
creating the address. Update the affected get_vrf_selection tests to assert the
new contract.
netbox_librenms_plugin/views/sync/vlans.py (1)

209-249: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the skip reasons once.

The four skip-reason lines appear twice, at Lines 222-229 and Lines 239-246. A fifth skip counter must be added in both places. If an author updates only one branch, the success message and the warning message report different reasons for the same batch.

♻️ Proposed refactor to build the reason list once
+        skips = [
+            (group_missing_count, "VLAN group missing"),
+            (permission_skipped_count, "change permission missing"),
+            (ambiguous_count, "VLAN match ambiguous"),
+            (concurrent_change_count, "concurrent VLAN change"),
+        ]
+        reasons = [f"{count} skipped ({label})" for count, label in skips if count > 0]
+
         if parts:
-            if group_missing_count > 0:
-                parts.append(f"{group_missing_count} skipped (VLAN group missing)")
-            if permission_skipped_count > 0:
-                parts.append(f"{permission_skipped_count} skipped (change permission missing)")
-            if ambiguous_count > 0:
-                parts.append(f"{ambiguous_count} skipped (VLAN match ambiguous)")
-            if concurrent_change_count > 0:
-                parts.append(f"{concurrent_change_count} skipped (concurrent VLAN change)")
+            parts.extend(reasons)
             messages.success(request, f"VLANs synced: {', '.join(parts)}.")
-        elif (
-            group_missing_count > 0
-            or permission_skipped_count > 0
-            or ambiguous_count > 0
-            or concurrent_change_count > 0
-        ):
+        elif reasons:
             # Nothing actually synced. Do not claim success for rows rejected by a scope check.
-            reasons = []
-            if group_missing_count > 0:
-                reasons.append(f"{group_missing_count} skipped (VLAN group missing)")
-            if permission_skipped_count > 0:
-                reasons.append(f"{permission_skipped_count} skipped (change permission missing)")
-            if ambiguous_count > 0:
-                reasons.append(f"{ambiguous_count} skipped (VLAN match ambiguous)")
-            if concurrent_change_count > 0:
-                reasons.append(f"{concurrent_change_count} skipped (concurrent VLAN change)")
             messages.warning(request, f"No VLANs synced: {', '.join(reasons)}.")
         else:
             messages.warning(request, "No VLANs were created or updated.")

This preserves the exact message text asserted by test_duplicate_global_vid_is_skipped_without_aborting_the_batch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbox_librenms_plugin/views/sync/vlans.py` around lines 209 - 249, The
summary logic duplicates construction of the four skip-reason messages, so
adding another skip counter can make success and warning outputs inconsistent.
In the surrounding sync summary flow, build a single shared reasons list before
the parts/branching logic, include all existing counters and the new fifth skip
counter in that list, then reuse it for both success and warning messages while
preserving the existing text and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@netbox_librenms_plugin/views/sync/ip_addresses.py`:
- Around line 70-80: Update get_vrf_selection to distinguish an absent
vrf_{ip_address} POST value from a requested VRF that cannot be resolved within
restricted_queryset(VRF), preserving None only for no selection and signaling
the latter case explicitly. In process_ip_sync, handle that unresolved selection
per row like SyncVLANsView._handle_create_vlans: emit the row error and skip
matching or creating the address. Update the affected get_vrf_selection tests to
assert the new contract.

In `@netbox_librenms_plugin/views/sync/vlans.py`:
- Around line 209-249: The summary logic duplicates construction of the four
skip-reason messages, so adding another skip counter can make success and
warning outputs inconsistent. In the surrounding sync summary flow, build a
single shared reasons list before the parts/branching logic, include all
existing counters and the new fifth skip counter in that list, then reuse it for
both success and warning messages while preserving the existing text and
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: deae9f62-5e8c-4d24-8a83-7da4830f0a52

📥 Commits

Reviewing files that changed from the base of the PR and between f24982f and f3cb66e.

📒 Files selected for processing (7)
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
💤 Files with no reviewable changes (2)
  • netbox_librenms_plugin/tests/test_module_replace.py
  • netbox_librenms_plugin/tests/test_coverage_device_fields.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/views/sync/**/*.py

📄 CodeRabbit inference engine (.github/instructions/sync.instructions.md)

Sync action views must follow the pattern: check permissions with LibreNMSPermissionMixin and NetBoxObjectPermissionMixin, read selected items from request.POST.getlist('select'), load cached data using CacheMixin.get_cache_key(), apply changes inside transaction.atomic(), and redirect to the sync tab with ?tab=<resource>.

Files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When building HttpResponse from Django-template-rendered HTML in views, use format_html() to compose the envelope and mark_safe() on the inner HTML to clear CodeQL py/reflected-xss false positives. Example: format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))

Files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
🧠 Learnings (23)
📚 Learning: 2026-03-07T17:17:04.217Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 20
File: netbox_librenms_plugin/views/sync/cables.py:160-165
Timestamp: 2026-03-07T17:17:04.217Z
Learning: In Python views under netbox_librenms_plugin/views/sync, when obtaining a server_key for cache namespace scoping, read it from request.POST with a fallback to self.librenms_api.server_key (e.g., server_key = request.POST.get("server_key") or self.librenms_api.server_key) and assign it to an attribute (e.g., self._post_server_key) used by get_cached_links_data to build the cache key. Do not flag or remove this POST-read pattern, as it ensures consistent, future-proof cache namespace scoping for link data lookups. Apply this guidance to similar Sync views in the same module where server_key-based cache scoping is used.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
📚 Learning: 2026-03-07T22:46:57.537Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 21
File: netbox_librenms_plugin/tests/test_librenms_id.py:184-184
Timestamp: 2026-03-07T22:46:57.537Z
Learning: Do not flag or request style-only changes (such as removing redundant imports or cosmetic cleanup) in Python code reviews. Focus on issues that affect correctness, functionality, or security. This guideline applies to Python files across the repository, including tests (e.g., netbox_librenms_plugin/tests/test_librenms_id.py).

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-03-08T13:09:49.031Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/forms.py:75-79
Timestamp: 2026-03-08T13:09:49.031Z
Learning: In multi-server caching within the codebase, ensure poller group choices and similar cache discriminators use api.server_key as the cache key component (e.g., cache_key = f"librenms_poller_group_choices_{api.server_key}") rather than api.librenms_url. This aligns with the fixed approach seen in commit bf37f07 and with other modules. Apply this pattern consistently to Python files under netbox_librenms_plugin (and similar multi-server cache keys) to maintain correct cross-server caching behavior.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:51:15.707Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/views/base/modules_view.py:311-313
Timestamp: 2026-05-05T09:51:15.707Z
Learning: In this repository, if you see `timezone.timedelta` used from `django.utils.timezone`, do not request a diff to replace it with `datetime.timedelta` solely on style grounds. This usage is functionally correct because Django exposes `timedelta` via `django.utils.timezone`; treat this as intentional and avoid churn unless there is evidence of changed/incorrect behavior (e.g., `timezone` is not Django’s module or `timedelta` is unavailable).

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-01T13:35:47.228Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/migrate.py:177-181
Timestamp: 2026-06-01T13:35:47.228Z
Learning: When reviewing this plugin’s permission checks, note that `check_object_permissions` / `NetBoxObjectPermissionMixin` enforce only **model-level** permissions: they call `request.user.has_perm(perm)` without any object/row instance, and the plugin does not currently implement per-object (row-level) permission scoping. Therefore, do **not** flag “missing winner-side/per-object object-permission checks” in sync/migrate views (or elsewhere in the plugin) as a defect; per-object permission scoping is an intentional plugin-wide design gap to be addressed in a dedicated future PR.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-01T16:41:50.451Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tables/cables.py:62-76
Timestamp: 2026-07-01T16:41:50.451Z
Learning: When rendering Bootstrap/Tabler badges in this repo (netbox-librenms-plugin), always pair a solid background utility `bg-*` with an explicit text utility that provides appropriate contrast (e.g., `bg-danger` + `text-white`, `bg-warning` + `text-dark`, `bg-purple` + `text-white`). Follow the existing badge contrast convention enforced by `netbox_librenms_plugin/tests/test_badge_contrast.py`. Do not suggest Tabler’s semantic `text-*-fg` token classes (e.g., `text-purple-fg`), since they are not used in this codebase and are not covered by the contrast test.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-30T02:40:53.531Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 123
File: netbox_librenms_plugin/views/base/modules_view.py:0-0
Timestamp: 2026-07-30T02:40:53.531Z
Learning: In the NetBox LibreNMS plugin, normalize all LibreNMS serial values via `netbox_librenms_plugin.utils.normalize_serial()` before storing/comparing them. Treat only `None` as “absent”; for any other value (including numeric/falsey values like `0` or `False`), convert using `str(value).strip()` inside the normalizer. For identity/conflict checks against existing NetBox `Device` rows, compare using trimmed serial matching (i.e., compare normalized/stripped serial strings) so legacy device serials with surrounding whitespace still match normalized incoming serials.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-08-03T19:16:41.198Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 87
File: netbox_librenms_plugin/views/base/ip_addresses_view.py:502-502
Timestamp: 2026-08-03T19:16:41.198Z
Learning: For migration-marker handling in the NetBox LibreNMS plugin, use `get_migrated_to_marker()` and `mark_librenms_migrated()` as the centralized read/write chokepoints rather than duplicating marker logic. Normalize blank or `None` `server_key` values to `"default"`. Derive migration UI context with `build_migrated_context()` and pass it through `render_sync_partial()` to the interface, IP, cable, module, and VLAN sync partials.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-15T12:38:49.280Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/views/base/modules_view.py:133-141
Timestamp: 2026-04-15T12:38:49.280Z
Learning: Do not require or flag an explicit `permission_required = PERM_VIEW_PLUGIN` on views that inherit from `LibreNMSPermissionMixin` (e.g., those ultimately including `BaseModuleTableView` in `views/base/modules_view.py`). `LibreNMSPermissionMixin` is the authoritative place where `permission_required` is set to `PERM_VIEW_PLUGIN`; inherited values are already enforced via the mixin, making a redundant override unnecessary.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
📚 Learning: 2026-05-17T11:32:40.631Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 76
File: netbox_librenms_plugin/views/object_sync/devices.py:178-180
Timestamp: 2026-05-17T11:32:40.631Z
Learning: In this plugin’s JSON endpoint views (e.g., NetBox view classes/functions under netbox_librenms_plugin/views/**), do not require a try/except JSONDecodeError around `json.loads(request.body)` by default if the endpoint is already protected by CSRF + session authentication and has object-level permission gates. Treat missing JSONDecodeError handling as acceptable when the only expected downside is a noisy log line. If you identify any additional security or availability impact from invalid JSON (e.g., unhandled exceptions leading to user-visible 500s, potential DoS amplification, or lack of appropriate throttling/validation), then flag it and recommend adding guarded parsing/validation.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
📚 Learning: 2026-07-02T13:36:15.226Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/views/base/interfaces_view.py:226-226
Timestamp: 2026-07-02T13:36:15.226Z
Learning: When reviewing netbox-librenms-plugin view code that handles POSTed `server_key`, treat the plugin-wide convention as intentional: if the posted `server_key` is not present in `LibreNMSAPI.get_available_servers()`, the request should fall back to the currently configured default/active server key (not reject/fail-closed and not treat it as an error for that single view). Do not flag individual instances of this fallback pattern as incorrect “invalid server_key” validation. Any change to fail-closed behavior must be a coordinated cross-cutting change applied uniformly across all affected sites/views, not a one-view patch.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
📚 Learning: 2026-08-05T06:48:35.761Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 127
File: netbox_librenms_plugin/views/sync/interfaces.py:494-494
Timestamp: 2026-08-05T06:48:35.761Z
Learning: In permission-scoped NetBox views, do not replace re-locks that use an already resolved object primary key (for example, `pk=already_resolved.pk`) with `restricted_queryset()`. NetBox `restrict()` may return `none()` when no model-level grant exists, even if the view-level permission gate allows the operation, causing valid rows to be removed from the lock set and producing an erroneous “no longer exists” result. AST guards for raw client-supplied primary-key lookups should distinguish and exempt these re-locks based on the ID-expression shape.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
📚 Learning: 2026-06-01T15:12:26.824Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/views/sync/ip_addresses.py:94-103
Timestamp: 2026-06-01T15:12:26.824Z
Learning: For any redirect/tab URL building in netbox_librenms_plugin/views/sync, views/base, and views/object_sync, propagate the active multi-server `server_key` as a `?server_key=<key>` query parameter so users return to the same server’s tab after POST actions. When handling POST requests, read the POST-scoped `server_key` from `request.POST` and store it (e.g., `self._post_server_key`) with a fallback to `self.librenms_api.server_key`; use this POST-scoped key for both cache-key scoping and for constructing the redirect/tab URLs. Treat this as the intentional codebase-wide convention—do not flag the presence/usage of the `server_key` query parameter (or the corresponding POST-scoped `_post_server_key` pattern) in these views as an error.

Applied to files:

  • netbox_librenms_plugin/views/sync/ip_addresses.py
  • netbox_librenms_plugin/views/sync/vlans.py
📚 Learning: 2026-03-27T02:04:22.276Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 22
File: netbox_librenms_plugin/tests/test_coverage_api.py:893-939
Timestamp: 2026-03-27T02:04:22.276Z
Learning: For unit tests in this repo (e.g., coverage API tests), when testing a happy-path call like `add_device()`, assert both the success flag and the expected success message (e.g., `assert ok is True` and `assert msg == "Device added successfully."`). This ensures the test fails if `add_device()` returns `(False, ...)`. If a related assertion is explicitly tracked as a known deferred follow-up for a prior PR, do not treat the missing `ok is True` assertion as a new review finding in subsequent reviews.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T11:11:56.131Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_actions.py:4773-4776
Timestamp: 2026-06-02T11:11:56.131Z
Learning: When application code performs a function-local import inside a method body (e.g., `from utilities.permissions import get_permission_for_model`), unit tests should patch the original source attribute (`utilities.permissions.get_permission_for_model`). Do not patch the consumer module’s name (e.g., `netbox_librenms_plugin.views.imports.actions.get_permission_for_model`) unless the function is imported at module scope and exposed as a module attribute—local imports re-resolve the attribute at call time.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-02T20:43:51.604Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 79
File: netbox_librenms_plugin/tests/test_coverage_device_operations.py:2466-2478
Timestamp: 2026-06-02T20:43:51.604Z
Learning: When reviewing tests under netbox_librenms_plugin/tests, don’t treat intentional stubs/mocks of lower-layer helper functions as a “coverage hole” if the test’s goal is to isolate and verify only the validate-layer (or another single unit of behavior). If the stubbed helper’s actual logic is exercised in dedicated tests at the helper/service layer (e.g., test_*_helper* / test_librenms_id.py), it’s acceptable for the validate-layer test to control helper outputs (via side_effect/return values) and assert the validate-layer mapping/selection logic only. Flag only when the stub hides untested logic that should belong to the unit under test (i.e., the test asserts behavior from the helper without actually verifying the unit’s own responsibility).

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-15T18:49:04.201Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_coverage_actions.py:1866-1870
Timestamp: 2026-06-15T18:49:04.201Z
Learning: When reviewing tests related to the LibreNMS device ID migration flow (e.g., `migrate_librenms_id` / `migrate_legacy_librenms_id`), do not require `validation["librenms_id_needs_migration"] == True` solely for test setup. That flag is only used for UI visibility in `device_status.py` / `device_validation_details.html`; the backend migration action is gated by the instance’s legacy raw value (`custom_field_data["librenms_id"]` matching the active LibreNMS device id) plus the `serial_confirmed` or `force` condition. If the test already pins/executes migration by asserting the migration function was called with the locked instance (or otherwise directly forces execution), it should be acceptable even when `librenms_id_needs_migration` is not set to True.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-17T07:31:54.849Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 104
File: netbox_librenms_plugin/tests/test_librenms_api.py:2258-2258
Timestamp: 2026-06-17T07:31:54.849Z
Learning: When reviewing Python test code in netbox_librenms_plugin/tests, treat “develop-owned” scaffold lines as off-limits for in-PR rewrites. A line is “develop-owned” if `git blame` for that line attributes it to a commit that is an ancestor of `origin/develop` (i.e., the commit is contained in `origin/develop`). For such lines, reviewers should acknowledge the findings as valid but defer the change by creating/using a follow-up issue targeting the `develop` branch (e.g., `#112`), rather than requesting modifications in the current feature/PR stack.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-19T14:03:09.440Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 113
File: netbox_librenms_plugin/tests/test_coverage_base_views2.py:555-567
Timestamp: 2026-06-19T14:03:09.440Z
Learning: In tests under netbox_librenms_plugin/tests, don’t rely on “pure” MagicMock setups that stub chained calls like `interfaces.filter.return_value.first.return_value` when the code under test is supposed to distinguish between (1) a librenms_id custom-field lookup and (2) a name-based fallback lookup. If the mock returns the same interface regardless of filter arguments, the test cannot detect which lookup path matched (renaming variables like `remote_port` doesn’t fix this). Use a real-DB hardening test instead: create/seed a `remote_port` value that is deliberately different from the actual interface name so only the librenms_id CF lookup can produce a match. If an existing MagicMock-masked test file is develop-inherited (identical on origin/develop), don’t modify it in feature PRs; add a new real-DB hardening test file (e.g., `test_enrich_remote_port_realdb.py`) on the develop-targeted branch.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-06-25T07:07:59.192Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 114
File: netbox_librenms_plugin/tests/test_oob_sync_review_fixes.py:0-0
Timestamp: 2026-06-25T07:07:59.192Z
Learning: When writing/adjusting tests (and any review/test helper code) that interact with NetBox’s custom User model, do not assume the user model has an `is_staff` field. If you need to check or set user privileges/eligibility, use fields that are known to exist on the NetBox User model (e.g., `is_superuser` and `is_active`) instead. Avoid setting `is_staff` (it can raise Django `FieldError` if that field doesn’t exist on the custom model).

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-07-02T21:46:46.384Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 116
File: netbox_librenms_plugin/tests/test_coverage_bulk_import.py:132-140
Timestamp: 2026-07-02T21:46:46.384Z
Learning: In netbox_librenms_plugin/tests, keep the `_stub_norm_preload` autouse fixture (which patches `netbox_librenms_plugin.import_utils.bulk_import.preload_normalization_rules` to return `{}`) intentionally duplicated per mock-based test class/module. Do NOT hoist it into a global `conftest.py` autouse fixture, because that would apply the patch repo-wide and mask the real `preload_normalization_rules` behavior in real-DB tests that are meant to exercise it (see issue `#90`). If you need this patch, scope it to the specific mock-based tests that should stub normalization rules.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-04-01T15:55:42.180Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 50
File: netbox_librenms_plugin/tests/test_coverage_actions.py:88-171
Timestamp: 2026-04-01T15:55:42.180Z
Learning: When unit/integration testing actions that indirectly use a function imported at module import time, patch the function where it is *used* (the consumer’s import path), e.g. `netbox_librenms_plugin.views.imports.actions.resolve_naming_preferences`, rather than its original definition. For tests that target the function itself directly, patch the original dependency/definition (e.g. `netbox_librenms_plugin.utils.get_user_pref` or patch `resolve_naming_preferences` at `netbox_librenms_plugin.utils`) so the function under test sees the mocked behavior.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
📚 Learning: 2026-05-05T09:46:17.700Z
Learnt from: marcinpsk
Repo: marcinpsk/netbox-librenms-plugin PR: 58
File: netbox_librenms_plugin/tests/test_vm_operations.py:46-46
Timestamp: 2026-05-05T09:46:17.700Z
Learning: When the code under test performs *lazy imports* inside function bodies (i.e., the imported symbol is not bound at the module scope), mock/patch the *source module path that the function imports from*, not the consumer module path. The correct patch target is where the imported name is resolved at runtime (e.g., `virtualization.models.VirtualMachine`), because patching `netbox_librenms_plugin.import_utils.vm_operations.VirtualMachine` can fail with `AttributeError` since `VirtualMachine` is never a `vm_operations` module attribute.

Applied to files:

  • netbox_librenms_plugin/tests/test_coverage_sync_views.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views3.py
  • netbox_librenms_plugin/tests/test_coverage_sync_views2.py
🔇 Additional comments (5)
netbox_librenms_plugin/views/sync/ip_addresses.py (1)

42-64: LGTM!

Also applies to: 90-96, 128-129

netbox_librenms_plugin/views/sync/vlans.py (1)

26-35: LGTM!

Also applies to: 102-109, 145-207

netbox_librenms_plugin/tests/test_coverage_sync_views3.py (1)

302-308: LGTM!

Also applies to: 349-404

netbox_librenms_plugin/tests/test_coverage_sync_views.py (1)

1251-1274: LGTM!

Also applies to: 1276-1293, 2388-2398

netbox_librenms_plugin/tests/test_coverage_sync_views2.py (1)

27-39: LGTM!

Also applies to: 345-347, 1328-1337, 2025-2027, 2116-2162

NetBox creates every UserConfig with the DEFAULT_USER_PREFERENCES dict itself
(users/signals.py passes it by reference) and UserConfig.set() edits data in
place. Writing straight through it therefore mutated the process-wide default:
one user's choice of ifName/ifDescr became the value every user created later in
that worker inherited, and the fallback every preference-less user read through
UserConfig.get(). Copy the row's data before writing.

The same aliasing made the test suite order-dependent: the interface-name
preference written by one test leaked into every user built afterwards, so ten
relationship-sync tests resolved port names by ifDescr and stopped linking
parents and LAGs whenever they ran after it.
LibreNMSPermissionMixin extends Django's PermissionRequiredMixin, which checks
only the model-level plugin permission and never evaluates NetBox object
permissions. The six base table views then resolved the URL pk with a plain
get_object_or_404, so a user holding view_librenmssettings reached any Device or
VirtualMachine by pk, whatever its dcim.view_device grant allowed.

Resolve through restrict_object_or_404 instead, so an out-of-scope id 404s like a
missing one. SingleCableVerifyView no longer lists NetBoxObjectPermissionMixin
itself: BaseCableTableView now supplies it, and naming it first made the MRO
unlinearizable.

The static scan in TestGatedViewsResolveThroughRestrictedQuerysets could not see
this. It treats a class as gated only when the gate appears lexically in the
class body, and these classes declare none.
The raw-pk scan exempted any lookup written as pk=<expr>.<name>_id. That reads a
spelling as provenance, and it was wrong in both directions: it flagged a
legitimate re-lock keyed by a local variable, which was silenced by rewriting the
expression rather than by scoping anything, and it waved through
Device.objects.get(pk=payload.device_id) on a request-derived object.

Add relock_scoped_row(), one named chokepoint for the deliberate case: re-locking
a row whose id came from an object the request already resolved through a
restricted queryset. It is not a <Model>.objects chain, so the scan never reaches
it, and every such lookup is now greppable. Route the seven existing re-locks
through it, including the module-level one in migrate.py that the scan could
never see, then drop the suffix exemption.

Two guard-the-guard fixtures pin the new contract: a client-derived *_id
attribute must be reported, and a re-lock spelled as a raw manager chain must be
reported. The class docstring and the assertion message no longer claim to cover
every gated view, because a class gated only through an inherited base and any
module-level helper both remain invisible to it.
Scoping the base getters broke seventeen tests, all of them asserting the old
implementation rather than the behaviour: twelve patched the module-level
get_object_or_404 the fix removed, four built a request with no user (or a view
with no request) that the scoped lookup now needs, and one used AnonymousUser,
for whom restrict() returns none().

Three of them existed only to assert that get_object called get_object_or_404
with the model and pk. They now assert what callers actually depend on, against
real rows and a real constrained grant: the permitted object comes back and one
outside the grant raises Http404.

The rest keep their original subject. Tests about VC delegation and server-key
rebinding patch the view's get_object seam instead of a module function, and the
ones that need a user get a real granted one rather than a mock.

BaseLibreNMSSyncView gains the get_object seam its five sibling base views
already had, so object resolution is overridable and patchable in one place
instead of inline in get().
The cable verify gate test patched BaseCableTableView.get_object, but
SingleCableVerifyView.post resolves through restrict_object_or_404
(cables_view.py:865) and never calls get_object, so assert_not_called passed even
with the gate deleted. Patch what the view actually calls. Removing the gate now
fails the test; it did not before.

The stale-server-key test built a real constrained user and then replaced
request.user with a MagicMock superuser on the next line, so get_object never ran
against the real grant. Drop the override.

Job.queue_name does not exist on NetBox 4.4, which failed the 4.4 CI job. The
field is blank=True where it exists and this test calls run() directly instead of
enqueueing, so the queue never takes part: drop the argument.
…locking

Two lookups authorized a row and then acted on it without holding it.

process_ip_sync checked change permission on an existing IPAddress and then
rewrote its assigned_object and vrf. The check takes no lock, so a concurrent
assignment or VRF change could commit in between and be overwritten. Re-read the
authorized row through relock_scoped_row and decide from that; a row that
disappears between the two now reports the same refusal as an unauthorized one.

_resolve_oob_interface locked the (device, name) candidate and only then hid it,
so a caller could hold a lock on an interface outside its view scope and stall
whoever owns it. Lock within the scoped queryset instead, and fall back to an
unlocked existence probe to keep refusing a name taken by an invisible row.

Both are covered: the IP test asserts the FOR UPDATE precedes the rewrite, and
the interface test holds the row from a second connection and shows the resolver
returns instead of blocking on it.
changed_during_sync came from membership_changed.wait(0.5), and
membership_changed is set only after the UPDATE returns. The window therefore
also covered thread start-up and the two SET statements, so the negative
assertion could pass even with no lock held. Signal readiness immediately before
the UPDATE and wait for it, so the window measures only the interval where the
UPDATE is genuinely contending for the row.
restrict() joins the permission tables, so the bare select_for_update() added
with the scoped interface lookup would have tried to lock those joined rows.
TestScopedRowLocks already enforces of=("self",) for restricted locks and
caught it.
…on types

_ModuleComponentAdoptionUnavailable carried no model, so all three handlers
emitted the same sentence and an operator could not tell which of the eight
adoptable component types they lacked change permission on. Carry the model and
report its verbose name.

Only interfaces and module bays had standalone-adoption coverage. Drive all eight
specs from _module_component_specs() against the real ORM: each asserts that the
template name resolves, that a matching standalone component is authorized, and
that a component outside the change scope aborts the write naming that component.

This also puts _module_template_adoption_name under test for every template type
for the first time, so the version-dependent resolve_name(module) call it makes
is now exercised across the CI matrix rather than assumed.
…older

The FrontPort spec failed on NetBox 4.4 only. FrontPortTemplate there has a
mandatory rear_port foreign key, and so does FrontPort. NetBox 4.5 replaced it
with PortTemplateMapping and gave both models a positions field instead. The
setup now detects the rear_port field and builds the rear port template and the
rear port when it exists, so the test follows the model instead of a version
number.

The template names also carried no placeholder. _resolve_all_placeholders
returns the name unchanged when it holds no token, so the module argument was
never read and the seven passing specs said nothing about the call under
review. Template names now carry the {module} token, and the test asserts the
resolved name equals the installed bay position joined to the template name.
Drop the module argument in the production helper and seven of the eight specs
fail with the token unresolved.

Names are built once for the template and once for the standalone component
through shared helpers, so the two sides cannot drift.

Checked against NetBox sources: resolve_name(self, module) on v4.4.0 and
v4.5.0, resolve_name(self, module=None, device=None) on 4.6. A single
positional module argument is correct on all three, and device is only a
fallback for a missing module.
NetBox 4.4 ModuleBayTemplate.instantiate builds the component with name=self.name
and never calls resolve_name, so the {module} token survives. NetBox's own
adoption loop matches on that same unresolved name, which is why the helper uses
instantiate() for module bays: the prediction has to equal what adoption will
look for, not what the name would resolve to.

The assertion added with the placeholder coverage assumed all eight types
resolve, so it failed spec 7 on the 4.4 leg while the production path was right.
Scope it to the seven types that go through resolve_name(module), which is the
call the review questioned.

Specs 0 through 6 now pass on 4.4, so the token does resolve there through a
single positional module argument.
Three write paths locked their candidate through an unrestricted queryset and
only then checked the caller's grant. No unauthorized write was possible, since
every mutation stays behind a restricted check, but a caller with no grant on
the row still pinned it for the rest of the enclosing transaction. The OOB
address path holds that lock through the later Device save, so an out-of-scope
caller could stall the request that owns the address.

The same file already answered this at _resolve_oob_interface: scope the lock to
the caller, then use an unrestricted non-locking read for the questions that
need to see every row. All three sites now follow it.

_attach_oob_ip additionally judges ownership BEFORE it locks. A row that belongs
to another device is refused without ever being pinned, which the suggested fix
would still have locked. Ownership is re-read from the locked row afterwards,
and both readings share _oob_ip_is_reassignable so they cannot drift. The
post-lock change check is gone: the lock now runs through the change scope, so
that check could no longer fail.

The duplicate detection still has to see rows the caller cannot, so it reads
unlocked and refuses when a second row shares the host address or the mapping
key.

test_locks_candidate_row_with_select_for_update asserted on a patched manager
and stayed green while the lock ran unrestricted. It now drives the real ORM and
asserts on the emitted SQL.

Both new tests fail against the unfixed code: the concurrency test holds the
hidden row from a second connection and the old path dies with "canceling
statement due to lock timeout ... while locking tuple in relation
ipam_ipaddress".
The VLAN sync read the row through the change-scoped queryset, compared the
name and saved, all without a row lock. The only lock in the flow is the
advisory lock over global VIDs, and that set is built from the VIDs with no
`vlan_group_<vid>` POST value, so a grouped VLAN was never covered.

A grouped VLAN deleted between the scope check and the save made
`save(update_fields=["name"])` raise `VLAN.NotUpdated: Save with update_fields
did not affect any rows` (a 500). The row is now re-locked through
`relock_scoped_row`, and a row that is gone is skipped with the existing
concurrent-change message and counter.

`relock_scoped_row` also clears the model ordering. VLAN orders by site and
group, both nullable, so the lock ran into `FOR UPDATE cannot be applied to the
nullable side of an outer join`. `first()` then orders by pk, which joins
nothing. Every other call site kept working by accident: their models order on
plain fields.
The test granted view on its only collision target, so the block message named
that pk whether or not the job passed its user into the collision scoping: it
could not fail. The batch now collides on a second target the job user cannot
view. Only the visible pk may appear in the message, and the assertion pins the
list to exactly that one pk. Verified red with `user=self.job.user` removed from
the job's collision pre-check.
`_resolve_oob_interface` refused a `__new__` name already taken by an interface
outside the caller's view scope, but returned the same `(None, None)` pair as
"no selection made". `AddAsOOBView.post` maps that pair to "Choose an interface
in the OOB form", which tells the operator to do what they just did and hides
the real cause. Both refusal sites (the pre-create check and the
IntegrityError fallback whose winner is invisible) now return
`name_out_of_scope`, and the message chain names it.

Also:
- Cover DeviceCableTableView in the routed-device scope matrix. It resolves the
  URL pk through `restrict_object_or_404` already, so this is coverage only.
- Assert the refusal text in TestMappingChangeScope. Both tests discarded the
  response and checked only that the hidden mapping was unchanged, which a
  failed permission gate, a rebind failure or the broad except satisfy too.
The test stubbed `Device.objects.select_for_update()` and pinned the exact
`.filter().first()` chain, so it broke when `relock_scoped_row` gained an
`order_by()` even though the behaviour was unchanged. The owner row is now
deleted for real from inside a query wrapper, right before the lock runs, and
the test asserts the lock was reached at all.
Both lock tests asserted only that a SELECT ... FOR UPDATE appeared somewhere in
the captured queries. That passes if the lock moves after the UPDATE, which is
the ordering the lock exists to guarantee.

assert_locked_before_update() compares the query indices and also fails when no
UPDATE is captured, so the ordering check cannot pass vacuously. Both call sites
share the helper, so the two tests cannot drift.
@marcinpsk
marcinpsk force-pushed the fix/object-scoped-lookups branch from 6f3e7cb to 29c4545 Compare August 13, 2026 02:02
Comment thread netbox_librenms_plugin/tests/view_test_helpers.py
Comment thread netbox_librenms_plugin/tests/test_vlan_sync_concurrency.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant