Skip to content

Introduce foundation for unit tests (pytest + Zuul CI integration) #233

Description

@berendt

Background

Foundation work for #232. The osism/netbox-manager repository currently has no unit tests at all:

  • No tests/ directory exists
  • No pytest configuration (existing pyproject.toml only configures mypy, setuptools, and setuptools-git-versioning)
  • No test dependencies (Pipfile [dev-packages] only lists python-gilt; requirements.txt is runtime-only)
  • The Zuul check/periodic-daily pipelines (see .zuul.yaml) only run flake8, mypy, python-black, yamllint — no Python tests are executed

The goal of this issue is to establish the foundation so that unit tests for netbox_manager/main.py and netbox_manager/dtl.py can be written and automatically executed in Zuul CI. Actual test coverage of the function groups (settings/role helpers, task / playbook builders, autoconf, validation, DTL, …) is tracked in the follow-up sub-issues under #232.

Repo specifics worth noting

  • The package is properly installable: pyproject.toml declares setuptools build-backend, entry point netbox-manager = "netbox_manager.main:main", and tool.setuptools.packages = ["netbox_manager"]. Tests can from netbox_manager import main, dtl directly — no sys.path munging needed.
  • Lint config in place today: .flake8, [tool.mypy] (exclude = ["doc"]) in pyproject.toml, .yamllint.yml. Tests must comply with the same rules.
  • pyproject.toml declares requires-python = ">=3.8" and classifies 3.8–3.14, but CI today only runs the default Zuul Python. Pinning the test job's Python version is part of this issue (see Open questions).
  • settings.toml is loaded at import time by dynaconf in netbox_manager/main.py:53. Tests must either provide a stub settings.toml (via tmp_path + monkeypatch.chdir) or set the relevant NETBOX_MANAGER_* env vars in conftest.py before importing netbox_manager.main. Document the chosen approach in conftest.py.

Scope

In scope (foundation only):

  • Set up the test framework (pytest)
  • Establish directory structure and configuration
  • One or two minimal smoke tests so the CI pipeline passes and the infrastructure is validated
  • Zuul integration: new job netbox-manager-unit-tests running in check and periodic-daily
  • Documentation on how to run tests locally

Out of scope:

  • Full test coverage of existing modules (tracked in Meta: Unit test coverage for netbox_manager/ #232)
  • Integration / end-to-end tests against a live NetBox or executing real Ansible playbooks via ansible_runner
  • Coverage gates / thresholds (can be added later)
  • A Python-version test matrix (single version for now — see Open questions)

Tasks

1. Test dependencies

Add a new top-level test-requirements.txt (separate from runtime requirements.txt):

  • pytest
  • pytest-cov
  • pytest-mock

Pin versions, consistent with the existing style. Mirror the same entries under [dev-packages] in Pipfile so pipenv install --dev picks them up.

2. Directory structure

tests/
├── __init__.py
├── conftest.py
└── unit/
    ├── __init__.py
    └── netbox_manager/
        ├── __init__.py
        └── test_smoke.py
  • tests/conftest.py configures dynaconf so importing netbox_manager.main does not require a real settings.toml (set NETBOX_MANAGER_URL / NETBOX_MANAGER_TOKEN / etc. via monkeypatch.setenv in an autouse fixture, or chdir to a tmp_path containing a stub settings.toml). Placeholder for shared mocks (pynetbox device / interface / IP / VRF / cluster / config-context / prefix shapes; ansible_runner.run; git.Repo) to be filled in by the per-module sub-issues.
  • tests/unit/netbox_manager/test_smoke.py contains a trivial test (e.g. from netbox_manager.main import deep_merge; assert deep_merge({\"a\": 1}, {\"b\": 2}) == {\"a\": 1, \"b\": 2}) so a failure in the test infrastructure is immediately visible.

3. pytest configuration

Extend the existing top-level pyproject.toml with a [tool.pytest.ini_options] section:

[tool.pytest.ini_options]
testpaths = [\"tests/unit\"]
python_files = [\"test_*.py\"]
python_classes = [\"Test*\"]
python_functions = [\"test_*\"]
addopts = \"-ra --strict-markers\"

Do not set a coverage threshold — only enable optional reporting via --cov flags passed on the command line.

4. Zuul CI integration

In .zuul.yaml:

  • Define a new job netbox-manager-unit-tests (analogous to the existing lint jobs):

    - job:
        name: netbox-manager-unit-tests
        pre-run: playbooks/pre.yml
        run: playbooks/test-unit.yml
  • Add the job to the check and periodic-daily pipelines (alongside flake8, mypy, python-black, yamllint).

Create a new playbook playbooks/test-unit.yml that:

  1. Installs Python (version per Open questions below) and pip
  2. Installs the dependencies via pip install -r requirements.txt -r test-requirements.txt and pip install -e . (so from netbox_manager import … works)
  3. Executes pytest tests/unit from the repo root and uses the exit code as the job result

5. Local usage & documentation

Short section in README.md (or a new CONTRIBUTING.md) describing:

  • pip install -r requirements.txt -r test-requirements.txt && pip install -e . (or pipenv install --dev && pipenv shell)
  • pytest tests/unit for the full run
  • pytest tests/unit/netbox_manager/test_smoke.py to run a single test

Update CLAUDE.md to add the test command alongside the existing flake8 / mypy block.

6. Verification

  • pytest tests/unit runs green locally
  • The new Zuul job appears in check / periodic-daily and succeeds
  • flake8, mypy, python-black, yamllint remain green (new files must comply with the rules in .flake8 and .yamllint.yml; tests under tests/ are inside mypy's scan unless pyproject.toml excludes them — decide explicitly)
  • tests/ is excluded from the built sdist/wheel (verify via python -m build && tar -tf dist/*.tar.gz | grep tests returns nothing); add to MANIFEST.in or [tool.setuptools] exclude list if needed

Open questions for review

  1. Test directory at repo root in tests/ (recommended above) or under netbox_manager/tests/? Recommendation: repo root, keeps tests out of the installed package automatically and matches the convention used by osism/python-osism.
  2. Test dependencies as a separate test-requirements.txt or under [dev-packages] in Pipfile only? Recommendation: both — test-requirements.txt for the Zuul job (independent of pipenv), mirrored into Pipfile for local development convenience.
  3. Pin the Python version for the test job to which minor version? pyproject.toml declares >=3.8 and classifies 3.8–3.14. Recommendation: 3.13 for now (matches osism/python-osism's foundation choice — see Introduce foundation for unit tests (pytest + Zuul CI integration) python-osism#2192) and add a 3.8/3.14 matrix as a follow-up.
  4. Should tests/ be in mypy's scan? Recommendation: yes, but with disable_error_code = [\"no-untyped-def\"] for the test tree so test functions don't need return-type annotations.

Definition of Done

  • All tasks in sections 1–5 implemented
  • Zuul check / periodic-daily pipelines contain and pass the new netbox-manager-unit-tests job
  • A minimal smoke test runs successfully in CI
  • Developers can run tests locally with two commands (pip install -r ..., pytest tests/unit)
  • The built sdist/wheel does not contain tests/

Parent / tracking issue: #232

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions