Skip to content

Add unit tests for osism/tasks/netbox.py#2458

Merged
ideaship merged 4 commits into
mainfrom
unit-tests-tasks-netbox
Jul 15, 2026
Merged

Add unit tests for osism/tasks/netbox.py#2458
ideaship merged 4 commits into
mainfrom
unit-tests-tasks-netbox

Conversation

@berendt

@berendt berendt commented Jul 13, 2026

Copy link
Copy Markdown
Member

Adds tests/unit/tasks/test_netbox.py covering the Celery tasks of the netbox worker as specified in #2355:

  • _update_netbox_device_field: happy path (custom-field update + save), device not found, all four requests exception handlers (ConnectTimeout, ReadTimeout, ConnectionError, HTTPError), errors raised by device.save(), and the semaphore being created from nb.base_url and entered/exited around the API call.
  • _matches_netbox_filter: empty filter, primary filter variants (case-insensitive, substring), URL/name/site matching, missing netbox_name/netbox_site attributes, and the no-match fall-through.
  • set_maintenance / set_provision_state / set_power_state: one parametrised body over the three tasks (same approach as QUERY_LIST_VARIANTS in the conductor tests) covering Redlock key/timeouts, unacquired lock, failed update still returning True, filter-based primary/secondary skipping, the utils.secondary_nb_list fallback, lock release on exceptions, the set_power_state None"n/a" conversion, and task-lock abort before Redlock creation.
  • get_location_id / get_rack_id (parametrised): found, missing, and ValueError from ambiguous matches.
  • The four pass-through get_* tasks: delegation to the expected pynetbox endpoints.
  • manage: exact netbox-manager environment with str()-coerced settings, argument/keyword forwarding to run_command, return value pass-through, and no command execution when tasks are locked.
  • ping and setup_periodic_tasks.

utils.nb and utils.secondary_nb_list are lazy attributes resolved via osism.utils.__getattr__; they are always patched with create=True so no real NetBox connection is ever opened. All tasks are invoked directly — Celery binds self on direct calls, so no broker is needed.

Closes #2355

@sourcery-ai sourcery-ai 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.

Hey - I've left some high level feedback:

  • In the _update_netbox_device_field tests (e.g. test_update_field_updates_and_saves and the request‑error tests), the nb MagicMock doesn’t have a base_url attribute even though the implementation calls create_netbox_semaphore(nb.base_url); consider reusing the mock_nb fixture or explicitly setting base_url there to avoid brittle tests if the code starts relying on it more strictly.
  • The semaphore behavior in _update_netbox_device_field is only asserted for the happy path; it may be worth adding a case where the NetBox call raises to confirm the semaphore context manager is exited correctly even on exceptions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `_update_netbox_device_field` tests (e.g. `test_update_field_updates_and_saves` and the request‑error tests), the `nb` MagicMock doesn’t have a `base_url` attribute even though the implementation calls `create_netbox_semaphore(nb.base_url)`; consider reusing the `mock_nb` fixture or explicitly setting `base_url` there to avoid brittle tests if the code starts relying on it more strictly.
- The semaphore behavior in `_update_netbox_device_field` is only asserted for the happy path; it may be worth adding a case where the NetBox call raises to confirm the semaphore context manager is exited correctly even on exceptions.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@berendt berendt force-pushed the unit-tests-tasks-netbox branch from 36642e6 to 6bca113 Compare July 13, 2026 20:38
@berendt berendt moved this from New to Ready for review in Human Board Jul 13, 2026
@berendt berendt requested a review from ideaship July 13, 2026 20:40
Comment thread tests/unit/tasks/test_netbox.py Outdated
def test_get_addresses_by_device_and_interface_delegates_to_filter(mock_nb):
result = netbox.get_addresses_by_device_and_interface("n1", "eth0")

mock_nb.dcim.addresses.filter.assert_called_once_with(device="n1", interface="eth0")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This assertion pins a broken pynetbox path. The production task at osism/tasks/netbox.py:380 calls utils.nb.dcim.addresses.filter(...), but pynetbox exposes IP addresses under ipam.ip_addresses, not dcim.addresses — see osism/tasks/conductor/netbox.py:122,177 in this same repo, which use utils.nb.ipam.ip_addresses.filter(...). Against a real NetBox this 404s; the full MagicMock hides that. Don't land a test that certifies the wrong endpoint: fix the production line to ipam.ip_addresses in a separate commit, then assert the corrected path here. (The device=/interface= filter keys are valid on that endpoint — both filter by name — so only the app/endpoint changes.)

Comment thread tests/unit/tasks/test_netbox.py Outdated
assert netbox._matches_netbox_filter(nb, netbox_filter) is True


@pytest.mark.parametrize("netbox_filter", ["primary", "PRIMARY", "primary-dc"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"primary-dc" in this parametrize list cements a production bug. In _matches_netbox_filter (osism/tasks/netbox.py:83) the primary branch is "primary" in filter_lower — reversed from every other check, which does filter_lower in <field>. As a result any filter containing the substring "primary" (e.g. a secondary at site primary-region) also selects the primary and updates it unintentionally. Don't assert "primary-dc" matches here. Correct the production line (exact-keyword match, or filter_lower in "primary") in a separate commit, then update the test to match.

Comment thread tests/unit/tasks/test_netbox.py Outdated


@pytest.mark.parametrize(SET_TASK_PARAMS, SET_TASK_VARIANTS)
def test_set_task_update_failure_logs_error_but_returns_true(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This asserts result is True when the update failed, which cements a contract bug: all three set_* tasks fall through to return True (osism/tasks/netbox.py:183) even though _update_netbox_device_field returned False (lines 148-153 only log). The docstring — "True if lock was acquired and operation succeeded" (line 132) — establishes the intended contract, so this is a bug, not an ambiguity: a caller (e.g. conductor/ironic.py:1099) can't distinguish success from a silently-failed update. Fix the production code in a separate commit to aggregate update failures into a False return, and change this test to assert False on the failure path as part of that same fix. If the production change isn't happening here, drop this test rather than land one asserting True.

@github-project-automation github-project-automation Bot moved this from Ready for review to In review in Human Board Jul 14, 2026
berendt added 4 commits July 15, 2026 11:15
set_maintenance, set_provision_state and set_power_state acquired the
Redlock, ran the primary and secondary NetBox updates and then
unconditionally returned True -- even when _update_netbox_device_field
had returned False and only logged the failure. Callers that branch on
the return value (e.g. the ironic state sync in
osism/tasks/conductor/ironic.py) therefore treated a silently failed
update as success.

Track the outcome of every matching update and return False if any of
them failed, so the return value matches the documented contract.

Assisted-by: Claude:anthropic-opus-4-8
Signed-off-by: Christian Berendt <berendt@osism.tech>
get_addresses_by_device_and_interface called
utils.nb.dcim.addresses.filter(...), but pynetbox exposes IP addresses
under the ipam app as ipam.ip_addresses, not dcim.addresses. Against a
real NetBox the dcim.addresses endpoint does not exist and the query
404s. Use utils.nb.ipam.ip_addresses.filter(...), consistent with the
lookups in osism/tasks/conductor/netbox.py; the device and interface
filter keys are valid on that endpoint.

Assisted-by: Claude:anthropic-opus-4-8
Signed-off-by: Christian Berendt <berendt@osism.tech>
_matches_netbox_filter tested `"primary" in filter_lower`, the reverse
of every other check (`filter_lower in <field>`). Any filter that merely
contained the substring "primary" -- for example a secondary at a site
named "primary-region" -- therefore also selected the primary instance
and updated it unintentionally. Test `filter_lower in "primary"` instead,
so the keyword is matched consistently with the URL, name and site checks.

Assisted-by: Claude:anthropic-opus-4-8
Signed-off-by: Christian Berendt <berendt@osism.tech>
Covers the Celery tasks of the netbox worker:

- _update_netbox_device_field: successful custom-field update and
  save; device not found returns False without an error log; the
  four requests exception handlers (ConnectTimeout, ReadTimeout via
  the Timeout handler, ConnectionError, HTTPError as generic
  RequestException) each return False and log the matching message,
  including the base_url of the instance; errors raised by
  device.save() take the same path; the NetBox semaphore is created
  from nb.base_url and entered/exited around the API call, and is
  exited both when a requests exception is handled inside the with
  block and when an unexpected exception propagates out of it.

- _matches_netbox_filter: empty/None filter matches everything; the
  'primary' keyword (case-insensitive) selects the primary while a
  filter that merely contains "primary" does not, and 'primary' does
  not select a secondary; case-insensitive URL substring match;
  instances without netbox_name/netbox_site attributes fall through
  without AttributeError; netbox_name and netbox_site matches;
  nothing matching returns False.

- set_maintenance / set_provision_state / set_power_state share one
  parametrised body (same approach as QUERY_LIST_VARIANTS in the
  conductor tests): the happy path asserts the Redlock key, auto
  release time, acquire timeout and the update call against the
  primary; an unacquired lock returns False without update or
  release; a failed update logs "Could not set ..." and the task
  returns False; a non-matching filter skips the primary; only
  matching secondaries are processed; secondary_nb_list=None falls
  back to utils.secondary_nb_list; the lock is released when the
  update raises; set_power_state converts state=None to "n/a"; a
  task-lock SystemExit aborts before the Redlock is created.

- get_location_id / get_rack_id (parametrised): id returned when
  found, None when missing, None on ValueError from ambiguous
  matches.

- get_devices, get_device_by_name, get_interfaces_by_device and
  get_addresses_by_device_and_interface delegate to the expected
  pynetbox endpoints and pass the result through.

- manage: exact netbox-manager environment with str()-coerced
  settings values, command path, argument and keyword forwarding,
  run_command return value passed through, request id None on a
  direct call, and no run_command call when tasks are locked.

- ping returns utils.nb.status() unchanged; setup_periodic_tasks is
  a no-op.

utils.nb and utils.secondary_nb_list are lazy attributes resolved
via osism.utils.__getattr__ and are always patched with create=True
so no real NetBox connection is ever opened.

Closes #2355

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@osism.tech>
@berendt berendt force-pushed the unit-tests-tasks-netbox branch from 6bca113 to 3d14576 Compare July 15, 2026 09:19
@berendt berendt requested a review from ideaship July 15, 2026 09:30
@ideaship ideaship merged commit 7d7434a into main Jul 15, 2026
3 checks passed
@ideaship ideaship deleted the unit-tests-tasks-netbox branch July 15, 2026 19:44
@github-project-automation github-project-automation Bot moved this from In review to Done in Human Board Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Unit tests for osism/tasks/netbox.py

3 participants