Resolve every gated object through a restricted queryset - #127
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI (base), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis 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. ChangesPermission-scoped synchronization and import flows
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
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
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
There was a problem hiding this comment.
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 winAdd the owner's
viewpermission toDeleteNetBoxInterfacesView's declared permissions, matchingSyncInterfacesViewin this same file.
SyncInterfacesView.get_required_permissions_for_object_type(lines 40-49) declares("view", Device)/("view", VirtualMachine)because the owner is resolved throughrestrict_object_or_404, so a missing grant surfaces as an explicit 403.DeleteNetBoxInterfacesView.get_required_permissions_for_object_typeresolves 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 winBind the request before calling
post()directly.
view.post(_make_request(), pk=1)on line 58 callspost()directly withoutview.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 directpost()call "leavesself.requestunset, which the object-scoped lookups read."Here
restrict_object_or_404is 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 readsself.requestelsewhere inpost().🔧 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
📒 Files selected for processing (21)
netbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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
LibreNMSPermissionMixinandNetBoxObjectPermissionMixin, read selected items fromrequest.POST.getlist('select'), load cached data usingCacheMixin.get_cache_key(), apply changes insidetransaction.atomic(), and redirect to the sync tab with?tab=<resource>.
Files:
netbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/modules.py
**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
When building
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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
requestobject, and callget_context_data(). VMs must skip cables and VLANs by returningNonefrom thoseget_*_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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 & IntegrationNo scoping issue with
_resolve_target_device_with_validation.
selected_device_idis only accepted when it matchespage_device.pkor a member frompage_device.virtual_chassis.members.filter(pk=...); all other inputs returnpage_devicewith an invalid-selection warning.> Likely an incorrect or invalid review comment.
1940-1990: 🔒 Security & Privacy | 🏗️ Heavy liftAuthorization 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.pySecondary object lookups bypass the new restricted-queryset control in two views. Both sites resolve a primary target through
restrict_object_or_404correctly, 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.postresolvesconflict_modulefromrequest.POST.get("conflict_module_id")viaModule.objects.select_for_update().filter(pk=conflict_module_id)with no scoping at all, then reassigns itsmodule_bay/deviceand saves it. Route this lookup throughrestrict_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.postresolvesmemberfromrequest.POST.get(f"member_id_{counter}")viaDevice.objects.get(pk=member_id), guarded only by a virtual-chassis-membership check (not a permission check), then overwrites and savesmember.serial. Route this lookup throughself.restrict_object_or_404(Device, "change", pk=member_id)before mutating it.Both allow a user with a constrained (site/device-scoped)
change/deletegrant to mutate an object outside their granted scope, provided they can supply or guess the target's pk. This is precisely the vulnerability classrestrict_object_or_404was 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 winIDOR (CWE-639): Authorization Bypass Through User-Controlled Key (IDOR)
Reachability: External
Verify
UpdateDeviceLocationViewuses a permission-scoped object lookup. Across three independent test files,UpdateDeviceLocationViewtests mocknetbox_librenms_plugin.views.sync.devices.get_object_or_404(Django's unrestricted shortcut) rather thanNetBoxObjectPermissionMixin.restrict_object_or_404, while every sibling view in the same module (for exampleAddDeviceToLibreNMSView.get_object) was migrated. If the view only checksrequire_write_permission()(model-level) before resolving the device by rawpk, 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 whetherUpdateDeviceLocationView.post()resolves its device throughrestrict_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.pynetbox_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 winAuthorization Bypass (CWE-639): Authorization Bypass Through User-Controlled Key (IDOR)
Reachability: External
The gate detection misses views gated only by plugin write permission.
declares_gaterequires anast.Assigntorequired_object_permissions. A view that gates withrequire_write_permission()alone and then calls a rawget_object_or_404is invisible to the scan. The graph context fornetbox_librenms_plugin/views/sync/devices.pyshows one such live instance:SyncSiteLocationView.postchecksrequire_write_permission()and then executesdevice = 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.AnnAssigntargets (required_object_permissions: dict = {...}) are not matched either.Extend the scan to also treat classes that call
require_write_permissionorrequire_all_permissionsas gated, or document this limit and fix the raw lookup inSyncSiteLocationView.#!/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
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (15)
netbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_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
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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
LibreNMSPermissionMixinandNetBoxObjectPermissionMixin, read selected items fromrequest.POST.getlist('select'), load cached data usingCacheMixin.get_cache_key(), apply changes insidetransaction.atomic(), and redirect to the sync tab with?tab=<resource>.
Files:
netbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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 liftAuthorization 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.pyDelete 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_permwithout 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 throughself.restricted_queryset(Module, "delete")beforeoccupant.delete().netbox_librenms_plugin/views/sync/interfaces.py#L472-L475: resolve the interfaces at lines 494 and 509 throughself.restricted_queryset(Interface, "delete")andself.restricted_queryset(VMInterface, "delete")beforeinterface.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
919c0c1 to
ee0b72c
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (22)
netbox_librenms_plugin/tests/test_coverage_actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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
LibreNMSPermissionMixinandNetBoxObjectPermissionMixin, read selected items fromrequest.POST.getlist('select'), load cached data usingCacheMixin.get_cache_key(), apply changes insidetransaction.atomic(), and redirect to the sync tab with?tab=<resource>.
Files:
netbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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 (IsSuperuserinBaseRQViewSet). Non-superuser users cannot poll job status (403 Forbidden). Plugin automatically falls back to synchronous mode for non-superusers viashould_use_background_job()inlist.pyandactions.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:DeviceImportHelperMixinprovidesget_validated_device_with_selections()andrender_device_row()for HTMX row rendering, shared by update views
BulkImportConfirmView(POST) — renders confirmation modal with selected device list viahtmx/bulk_import_confirm.html
BulkImportDevicesView(POST) — executes import. Background mode enqueuesImportDevicesJob; sync mode callsbulk_import_devices()+bulk_import_vms()and returns OOB row swaps withHX-Trigger: closeModal
DeviceValidationDetailsView(GET) — renders expandable validation details viahtmx/device_validation_details.html
DeviceVCDetailsView(GET) — renders VC member details viahtmx/device_vc_details.html
DeviceRoleUpdateView,DeviceClusterUpdateView,DeviceRackUpdateView(POST) — per-device dropdown updates. Apply selection to validation state and return re-rendered row viarender_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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 (DeleteNetBoxInterfacesViewinterface-delete authorization bypass), plus the newDevice/VirtualMachineview-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
There was a problem hiding this comment.
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
📒 Files selected for processing (13)
netbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_sync_interfaces.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_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
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/tests/test_sync_interfaces.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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 QualityNo change needed for the NetBox floor. The plugin declares
min_version = "4.4.0", which covers NetBox 4.2+ whereVMInterface.primary_mac_addressis 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 QualityPin the NetBox version before relying on platform-case-ambiguity fixtures.
These tests create
"ios"and"IOS"Platformrows without an explicit NetBox version constraint. NetBox supports both case-sensitive and case-insensitive name uniqueness across versions, so these fixtures may raiseIntegrityErrorinstead of reaching the ambiguity branches. Add a supporting NetBox version check toconftest.pyor replace the Platform ambiguity intests/test_platform_mapping.pywith_duplicate_mappings; use the same explicit fix fortests/test_coverage_utils.py.
There was a problem hiding this comment.
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 winUse a real authorized user for the restricted group lookup.
VerifyVlanSyncGroupView.post()now resolvesVLANGroupthroughrequest.user. This request leavesuseras an implicitMagicMock. Its truthiness can simulate a superuser and does not validate the real restricted-queryset path. Setrequest.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 winAuthorization 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.pyScope the serial-conflict module deletion by the caller’s delete grant.
ReplaceModuleViewdeclares("delete", Module)without an instance, andconflict_qsresolves a conflicting Module through plainModule.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 usesself.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
📒 Files selected for processing (16)
netbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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
LibreNMSPermissionMixinandNetBoxObjectPermissionMixin, read selected items fromrequest.POST.getlist('select'), load cached data usingCacheMixin.get_cache_key(), apply changes insidetransaction.atomic(), and redirect to the sync tab with?tab=<resource>.
Files:
netbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/modules.py
**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
When building
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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
requestobject, and callget_context_data(). VMs must skip cables and VLANs by returningNonefrom thoseget_*_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 (IsSuperuserinBaseRQViewSet). Non-superuser users cannot poll job status (403 Forbidden). Plugin automatically falls back to synchronous mode for non-superusers viashould_use_background_job()inlist.pyandactions.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:DeviceImportHelperMixinprovidesget_validated_device_with_selections()andrender_device_row()for HTMX row rendering, shared by update views
BulkImportConfirmView(POST) — renders confirmation modal with selected device list viahtmx/bulk_import_confirm.html
BulkImportDevicesView(POST) — executes import. Background mode enqueuesImportDevicesJob; sync mode callsbulk_import_devices()+bulk_import_vms()and returns OOB row swaps withHX-Trigger: closeModal
DeviceValidationDetailsView(GET) — renders expandable validation details viahtmx/device_validation_details.html
DeviceVCDetailsView(GET) — renders VC member details viahtmx/device_vc_details.html
DeviceRoleUpdateView,DeviceClusterUpdateView,DeviceRackUpdateView(POST) — per-device dropdown updates. Apply selection to validation state and return re-rendered row viarender_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_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 winAuthorization 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.pyRequire
view_vlanbefore reading VLAN membership.A caller can submit
vidandvlan_group_id. The POST gate permits a user with Device and VLANGroup view permissions but no VLAN view permission. The endpoint then queriesVLANrows and returnsis_missing, which exposes VLAN membership.Add
("view", VLAN)torequired_object_permissions["POST"]. Add a denial test for a user without VLAN view permission. The siblingVerifyVlanSyncGroupViewalready 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 QualityThe 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 & AvailabilityNo change needed.
RestrictedQuerySet.restrict()applies constrained object permissions through a primary-key filter rather than a globalDISTINCT, so theseselect_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
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
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
📒 Files selected for processing (8)
netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.htmlnetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/cables.pynetbox_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
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">.
AvoidouterHTMLswaps in HTMX; use OOB or targetedinnerHTMLswaps to keep table layout intact.
Do not reintroducedata-bs-toggleor 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-addtable-responsivewrappers as their removal was deliberate to prevent dropdown clipping.
Templates live intemplates/netbox_librenms_plugin/; reuse and includes go underinc/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 viadocument.querySelector('[name=csrfmiddlewaretoken]').valuerather 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
LibreNMSPermissionMixinandNetBoxObjectPermissionMixin, read selected items fromrequest.POST.getlist('select'), load cached data usingCacheMixin.get_cache_key(), apply changes insidetransaction.atomic(), and redirect to the sync tab with?tab=<resource>.
Files:
netbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/modules.py
**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
When building
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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
requestobject, and callget_context_data(). VMs must skip cables and VLANs by returningNonefrom thoseget_*_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.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_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.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_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
There was a problem hiding this comment.
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 winBind the target permission declaration to the permission gate.
declared_readonly finds a("view", target_model)tuple anywhere inpost(). It does not verify that the tuple is added torequired_object_permissionsbeforerequire_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 beforegate_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 winCatch
ValueErrorfor a non-numeric posted VRF id.
vrf_idcomes straight fromrequest.POST. A non-numeric value (for examplevrf_10.0.0.1=abc) makes Django fail while converting the value for thepklookup, and that raisesValueErrorinstead ofVRF.DoesNotExist. The row then propagates out ofget_vrf_selectionintoprocess_ip_sync, where the broadexcept Exceptionmarks the address failed with a raw type name.CreateAndAssignPlatformViewinnetbox_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
📒 Files selected for processing (10)
netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.htmlnetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_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">.
AvoidouterHTMLswaps in HTMX; use OOB or targetedinnerHTMLswaps to keep table layout intact.
Do not reintroducedata-bs-toggleor 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-addtable-responsivewrappers as their removal was deliberate to prevent dropdown clipping.
Templates live intemplates/netbox_librenms_plugin/; reuse and includes go underinc/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 viadocument.querySelector('[name=csrfmiddlewaretoken]').valuerather 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
LibreNMSPermissionMixinandNetBoxObjectPermissionMixin, read selected items fromrequest.POST.getlist('select'), load cached data usingCacheMixin.get_cache_key(), apply changes insidetransaction.atomic(), and redirect to the sync tab with?tab=<resource>.
Files:
netbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/modules.py
**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
When building
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_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
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
netbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_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
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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
LibreNMSPermissionMixinandNetBoxObjectPermissionMixin, read selected items fromrequest.POST.getlist('select'), load cached data usingCacheMixin.get_cache_key(), apply changes insidetransaction.atomic(), and redirect to the sync tab with?tab=<resource>.
Files:
netbox_librenms_plugin/views/sync/ip_addresses.pynetbox_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.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/views/sync/modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_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.pynetbox_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.pynetbox_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.pynetbox_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.pynetbox_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.pynetbox_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.pynetbox_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
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
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
📒 Files selected for processing (35)
netbox_librenms_plugin/templates/netbox_librenms_plugin/htmx/module_mismatch_modal.htmlnetbox_librenms_plugin/tests/conftest.pynetbox_librenms_plugin/tests/test_coverage_actions.pynetbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_devices.pynetbox_librenms_plugin/tests/test_coverage_sync_interfaces.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_coverage_tables.pynetbox_librenms_plugin/tests/test_coverage_utils.pynetbox_librenms_plugin/tests/test_device_fields_server_scoping.pynetbox_librenms_plugin/tests/test_import_utils.pynetbox_librenms_plugin/tests/test_migrate_views.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/tests/test_permissions.pynetbox_librenms_plugin/tests/test_platform_mapping.pynetbox_librenms_plugin/tests/test_sync_devices.pynetbox_librenms_plugin/tests/test_sync_interfaces.pynetbox_librenms_plugin/tests/test_sync_modules.pynetbox_librenms_plugin/tests/test_view_seam.pynetbox_librenms_plugin/tests/test_view_wiring.pynetbox_librenms_plugin/tests/view_test_helpers.pynetbox_librenms_plugin/views/imports/actions.pynetbox_librenms_plugin/views/mixins.pynetbox_librenms_plugin/views/object_sync/devices.pynetbox_librenms_plugin/views/sync/cables.pynetbox_librenms_plugin/views/sync/device_fields.pynetbox_librenms_plugin/views/sync/devices.pynetbox_librenms_plugin/views/sync/interfaces.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/locations.pynetbox_librenms_plugin/views/sync/migrate.pynetbox_librenms_plugin/views/sync/modules.pynetbox_librenms_plugin/views/sync/vlans.py
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
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 winAn out-of-scope VRF id silently degrades to the global scope.
get_vrf_selectionreturnsNonefor 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_syncthen matches withvrf=Noneat Line 391 and creates the address withvrf=Noneat 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_vlanshandles the same shape of input differently: a requested-but-unresolvableVLANGroupfails closed, emits a per-row error, and skips the row (seenetbox_librenms_plugin/views/sync/vlans.pylines 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 NoneThen 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." + ) + continueNote: the existing tests
test_get_vrf_selection_not_found_returns_noneandtest_get_vrf_selection_returns_none_for_a_vrf_outside_the_grantinnetbox_librenms_plugin/tests/test_coverage_sync_views2.pypin 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 winBuild 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
📒 Files selected for processing (7)
netbox_librenms_plugin/tests/test_coverage_device_fields.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views2.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_librenms_plugin/tests/test_module_replace.pynetbox_librenms_plugin/views/sync/ip_addresses.pynetbox_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
LibreNMSPermissionMixinandNetBoxObjectPermissionMixin, read selected items fromrequest.POST.getlist('select'), load cached data usingCacheMixin.get_cache_key(), apply changes insidetransaction.atomic(), and redirect to the sync tab with?tab=<resource>.
Files:
netbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/vlans.py
**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
When building
HttpResponsefrom Django-template-rendered HTML in views, useformat_html()to compose the envelope andmark_safe()on the inner HTML to clear CodeQLpy/reflected-xssfalse positives. Example:format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
Files:
netbox_librenms_plugin/views/sync/ip_addresses.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/views/sync/vlans.pynetbox_librenms_plugin/tests/test_coverage_sync_views.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_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.pynetbox_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.pynetbox_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.pynetbox_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.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.pynetbox_librenms_plugin/tests/test_coverage_sync_views3.pynetbox_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.
6f3e7cb to
29c4545
Compare
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
How Was This Tested?
ObjectPermission, and NetBoxrestrict()behavior. Each test failed before the fix and passed after it.Risk Assessment
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:
change-scoped queryset before it is written, and locked so a concurrent request cannot change it after the check.select_for_update(of=("self",))so it does not lock the permission join.Summary by CodeRabbit
Security & Permissions
Bug Fixes
Import Improvements