Skip to content

fix: update tests, update interface name conflict, support isolated test db#47

Open
marcinpsk wants to merge 5 commits into
mainfrom
chore/isolated-test-db
Open

fix: update tests, update interface name conflict, support isolated test db#47
marcinpsk wants to merge 5 commits into
mainfrom
chore/isolated-test-db

Conversation

@marcinpsk

@marcinpsk marcinpsk commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added isolated Django test runner for per-session concurrent execution.
  • Bug Fixes
    • Improved interface rename safety by detecting target-name collisions, skipping conflicting renames, and continuing the batch.
    • Added warnings and conflict reporting when interfaces are skipped due to name collisions (including breakout channel handling).
  • Documentation
    • Documented standard vs isolated test commands in the README.
  • Tests
    • Expanded advanced and view test coverage using real ORM-backed scenarios.
  • Chores
    • Enabled branch coverage reporting.

…runs

Two 'manage.py test' runs in the shared devcontainer collide on the single
test_netbox database — they corrupt each other's migrations, and a crashed run
can leave an 'idle in transaction' connection holding locks that wedges every
later run. Add netbox-test-isolated: runs the suite on a per-session test DB
(name derived from the app arg, or TEST_DB_NAME=...) via a small
isolated_test_settings shim (--settings + PYTHONPATH). Default falls back to
test_netbox, so existing workflows are unchanged. Documented in README.
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 339425ab-f9db-42f7-8594-83a7744111b8

📥 Commits

Reviewing files that changed from the base of the PR and between fdd08f9 and 15f1874.

📒 Files selected for processing (3)
  • README.md
  • netbox_interface_name_rules/engine.py
  • netbox_interface_name_rules/tests/test_engine_advanced.py

Walkthrough

Adds collision detection to the interface renaming engine: new helper functions pre-check name uniqueness, record conflicts, and skip colliding renames without aborting the batch. Public API functions (apply_rule_to_existing, apply_interface_name_rules) gain an optional conflicts parameter. Jobs and views surface skipped counts. Tests are rewritten to use real ORM paths. A separate devcontainer change adds an isolated per-session test database helper.

Changes

Collision-safe interface renaming

Layer / File(s) Summary
Engine collision helpers and updated signatures
netbox_interface_name_rules/engine.py
Adds _name_exists_on_device, _record_conflict, _rename_in_place, and _create_channel helpers. Updates _try_rename_device_interface with a pre-check for name uniqueness and downgrades full_clean failures to warnings. Extends _apply_rule_to_interface, apply_rule_to_existing, and apply_interface_name_rules to propagate an optional conflicts collector.
Job and view conflict reporting
netbox_interface_name_rules/jobs.py, netbox_interface_name_rules/views.py
ApplyRuleJob.run and RuleApplyDetailView.post each initialize a conflicts list, pass it into apply_rule_to_existing, and conditionally emit a warning with the skipped-interface count when conflicts are present.
Engine and view tests for collision handling
netbox_interface_name_rules/tests/test_engine_advanced.py, netbox_interface_name_rules/tests/test_views.py, pyproject.toml
Removes MagicMock usage; rewrites multiple tests to use real ORM/template evaluation. Adds NameCollisionTest suite covering apply_interface_name_rules, apply_rule_to_existing, breakout-channel partial skipping, and idempotent re-application. Replaces mocked view test with a DB-backed integration test. Enables branch coverage.

Isolated test database devcontainer infrastructure

Layer / File(s) Summary
Settings shim, shell alias, and docs
.devcontainer/config/isolated_test_settings.py, .devcontainer/scripts/load-aliases.sh, README.md
Creates isolated_test_settings.py that overrides the test DB name via TEST_DB_NAME. Adds netbox-test-isolated shell function deriving TEST_DB_NAME from the first app argument and a dev-help entry. Adds a Testing section to the README documenting both test helpers.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over RuleApplyDetailView,apply_rule_to_existing: Foreground apply (view)
    RuleApplyDetailView->>apply_rule_to_existing: call(rule, interface_ids=ids, conflicts=conflicts)
    apply_rule_to_existing->>_apply_rule_to_interface: per interface, conflicts=conflicts
    _apply_rule_to_interface->>_name_exists_on_device: check target name
    alt collision
      _name_exists_on_device-->>_apply_rule_to_interface: True
      _apply_rule_to_interface->>_record_conflict: append iface, log WARNING
    else free
      _apply_rule_to_interface->>_rename_in_place: save renamed interface
    end
    apply_rule_to_existing-->>RuleApplyDetailView: renamed count
    RuleApplyDetailView->>RuleApplyDetailView: messages.success(renamed count)
    RuleApplyDetailView->>RuleApplyDetailView: messages.warning(skipped count) if conflicts
  end
  rect rgba(255, 200, 150, 0.5)
    Note over ApplyRuleJob,apply_rule_to_existing: Background apply (job)
    ApplyRuleJob->>apply_rule_to_existing: call(rule, conflicts=conflicts)
    apply_rule_to_existing-->>ApplyRuleJob: renamed count
    ApplyRuleJob->>ApplyRuleJob: log.info(renamed count)
    ApplyRuleJob->>ApplyRuleJob: log.warning(skipped count) if conflicts
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 Hop, hop! No more crashes when names clash and collide,
A conflicts list now catches what used to slip and slide.
The engine checks before it saves — a wise pre-flight indeed,
And isolated test DBs give each run its own freed seed.
With warnings logged and counts displayed, the rabbit hops with pride! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title addresses three distinct changes: test updates, interface name conflict handling, and isolated test database support. It accurately represents the main objectives of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 85.37% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/isolated-test-db

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

❤️ Share

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

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ising

Renaming could fail when a rule's computed name was already taken on the
device. Because renames run mostly non-interactively (module install,
API/module-type change, VC-position change via signals, and the background
ApplyRuleJob), a collision must skip-and-log, never raise or partially abort
the batch.

Engine:
- Add a device-scope pre-check (_name_exists_on_device) so a colliding
  rename/breakout-channel is skipped and logged at WARNING via _record_conflict;
  full_clean() stays the authoritative backstop for the rarer VC cross-member
  case. Idempotent breakout re-apply and atomic rollback on unexpected errors
  are preserved.
- Apply the same pre-check to the device-level path (_try_rename_device_interface)
  so collisions are a clean WARNING rather than an ERROR + traceback; downgrade
  its full_clean backstop log from exception to warning.
- Thread an optional conflicts collector through apply_rule_to_existing /
  _apply_rule_to_interface so interactive callers can report a skipped count.

Surfacing:
- RuleApplyDetailView POST -> messages.warning with the skipped count.
- ApplyRuleJob -> logs the skipped count (non-interactive: log only).
- A collision-driven 0-rename count no longer flags the rule
  potentially-deprecated (that flag means "no-op rule", a different reason).

Tests:
- Add real-object collision tests (install path, apply_rule_to_existing,
  breakout single-channel skip, idempotent re-apply).
- Convert mock-based tests to exercise real behavior: view apply renames a real
  interface; unsafe-AST guard via real {2**3}; invalid-regex rule for
  has_applicable_interfaces; undefined-var template for _find_channel_base;
  real ORM objects for _process_channel_module paths. Drop the now-unused
  MagicMock import. Keep genuine fault-injection mocks (loop/rollback
  resilience, save-IntegrityError race, tag-write failures).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
netbox_interface_name_rules/engine.py (1)

842-927: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Refactor apply_rule_to_existing() to reduce complexity before it grows further.

This method is now carrying branching, filtering, conflict propagation, and per-mode exception handling in one place (Sonar reports cognitive complexity 34). Extracting the channel/plain per-module branches into dedicated helpers would make this path safer to evolve and easier to test.

🤖 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_interface_name_rules/engine.py` around lines 842 - 927, The
apply_rule_to_existing() function has high cognitive complexity due to inline
branching between channel and non-channel rule handling, filtering, and
exception management. Extract the two main processing paths into dedicated
helper functions: one for the channel rule branch (the if rule.channel_count > 0
block that calls _find_channel_base and processes a single base interface) and
one for the plain rule branch (the else block that iterates over all
interfaces). Each helper should encapsulate its id_set filtering, vars_copy
construction, _apply_rule_to_interface invocation, and exception handling.
Replace the branched logic in apply_rule_to_existing() with calls to these
helpers, leaving the module loop, interface batch-loading, and count
accumulation in the main function.

Source: Linters/SAST tools

pyproject.toml (1)

27-27: ⚠️ Potential issue | 🟠 Major

Declare the NetBox minimum version in pyproject.toml.

The dependencies array at line 27 is empty and does not encode a NetBox runtime floor. As per coding guidelines, pyproject.toml must require NetBox ≥ 4.2.0 and Python ≥ 3.12. While Python is correctly specified (line 12), the NetBox constraint must be added to the [project] dependencies to enforce the packaging metadata contract.

🤖 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 `@pyproject.toml` at line 27, The dependencies array in the [project] section
of pyproject.toml is empty and must include the NetBox minimum version
constraint. Add "netbox>=4.2.0" to the dependencies list to enforce the required
NetBox runtime version, matching the Python version constraint already correctly
specified elsewhere in the configuration file.

Source: Coding guidelines

🤖 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_interface_name_rules/engine.py`:
- Around line 564-574: Both the `_rename_in_place()` and `_create_channel()`
functions perform a pre-check for name existence but do not handle race
conditions where a concurrent operation creates the same name between the check
and the save. Wrap the save operation (iface.full_clean() and iface.save() in
`_rename_in_place()`, and the corresponding save calls in `_create_channel()`)
in a try-except block to catch IntegrityError and ValidationError exceptions.
When these exceptions are caught, treat them as collision scenarios by recording
the conflict and returning 0, ensuring the batch is not aborted despite the
concurrent collision.

In `@README.md`:
- Around line 108-109: The README.md documentation at lines 108-109 currently
states that netbox-test-isolated falls back to test_netbox with no override, but
this contradicts the actual implementation in
.devcontainer/scripts/load-aliases.sh (line 135) which always sets TEST_DB_NAME
by default and does not fall back to test_netbox. Update the sentence in
README.md to clarify that netbox-test-isolated always sets TEST_DB_NAME by
default and only falls back to test_netbox if invoked through a different path,
ensuring the documentation accurately reflects the actual behavior of the
helper.

---

Outside diff comments:
In `@netbox_interface_name_rules/engine.py`:
- Around line 842-927: The apply_rule_to_existing() function has high cognitive
complexity due to inline branching between channel and non-channel rule
handling, filtering, and exception management. Extract the two main processing
paths into dedicated helper functions: one for the channel rule branch (the if
rule.channel_count > 0 block that calls _find_channel_base and processes a
single base interface) and one for the plain rule branch (the else block that
iterates over all interfaces). Each helper should encapsulate its id_set
filtering, vars_copy construction, _apply_rule_to_interface invocation, and
exception handling. Replace the branched logic in apply_rule_to_existing() with
calls to these helpers, leaving the module loop, interface batch-loading, and
count accumulation in the main function.

In `@pyproject.toml`:
- Line 27: The dependencies array in the [project] section of pyproject.toml is
empty and must include the NetBox minimum version constraint. Add
"netbox>=4.2.0" to the dependencies list to enforce the required NetBox runtime
version, matching the Python version constraint already correctly specified
elsewhere in the configuration file.
🪄 Autofix (Beta)

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 93e3ebdc-a2be-421b-83f7-ce6b30fba063

📥 Commits

Reviewing files that changed from the base of the PR and between eefc835 and fdd08f9.

📒 Files selected for processing (9)
  • .devcontainer/config/isolated_test_settings.py
  • .devcontainer/scripts/load-aliases.sh
  • README.md
  • netbox_interface_name_rules/engine.py
  • netbox_interface_name_rules/jobs.py
  • netbox_interface_name_rules/tests/test_engine_advanced.py
  • netbox_interface_name_rules/tests/test_views.py
  • netbox_interface_name_rules/views.py
  • pyproject.toml

Comment thread netbox_interface_name_rules/engine.py
Comment thread README.md Outdated
Address CodeRabbit review on PR #47.

- engine: apply_interface_name_rules() now wraps each _apply_rule_to_interface
  call in try/except (ValueError, ValidationError, IntegrityError), mirroring
  apply_rule_to_existing(). The collision pre-check closes the common case, but a
  concurrent insert can still win between the check and the save; the resulting
  exception exits the per-interface atomic block (already rolled back cleanly),
  so catching it at the caller — not inside the helpers, which run inside the
  still-open transaction — lets one racing interface be skipped without aborting
  the whole install batch. (CR I1)
- engine: extract _apply_channel_rule_to_module / _apply_plain_rule_to_module
  from apply_rule_to_existing() to cut its cognitive complexity (Sonar 34); no
  behaviour change, covered by the existing apply_rule_to_existing suite. (CR B1)
- README: correct the netbox-test-isolated default — it derives a per-app
  test DB name (test_<app>), it does not fall back to test_netbox. (CR I2)

Test: new test_install_path_save_race_skips_one_interface_without_aborting_batch
injects an IntegrityError on the first save (a genuine concurrency race we can't
reproduce deterministically) and asserts the batch continues; confirmed it errors
against the unfixed code and passes against the fix. 312 tests pass; ruff clean.
Comment thread netbox_interface_name_rules/engine.py
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant