--- applyTo: "tests/**" description: Testing patterns and conventions for the NetBox LibreNMS plugin --- # Testing Patterns ## General Test Conventions - Use plain **pytest classes**, not Django `TestCase`. Avoid `from django.test import TestCase`. - **Never use `@pytest.mark.django_db`** for unit tests—mock all database interactions with `MagicMock`. - Use **inline imports** inside test methods to avoid Django initialization at module load time. - Mock NetBox models (Device, Job, User) with `MagicMock()` instead of creating real instances. - Use `assert x == y` syntax, not `self.assertEqual(x, y)` (no TestCase inheritance). - See [docs/development/testing.md](../../docs/development/testing.md) for test file structure and running instructions. ## Background Job Tests - Instantiate `JobRunner` subclasses using `object.__new__(JobClass)` to bypass `__init__`, then set `job.job = MagicMock()` and `job.logger = MagicMock()`. See `create_mock_job_runner()` helper in `tests/test_background_jobs.py`. - Patch deferred/inline imports at their **source** module (e.g., `netbox_librenms_plugin.import_utils.process_device_filters`), not the consuming module. - Patch `cache` where imported: `netbox_librenms_plugin.views.imports.list.cache`, not `django.core.cache.cache`. - Test view decision logic by setting `view._filter_form_data = {...}` directly, not via HTTP requests. - **Never use `RequestFactory`**—mock request objects directly or test method logic in isolation. - Cache key tests must patch `get_validated_device_cache_key` from `import_utils.py`; never hardcode key formats like `job_123_device_1`. ## Test File Naming - Follow the `test_{module_name}.py` convention for new test files. - `test_netbox_librenms_plugin.py` is an empty placeholder — do not add tests there. ## Test Coverage by Module - `librenms_api.py` → `test_librenms_api.py`, `test_librenms_api_helpers.py` - `import_utils.py`, `import_validation_helpers.py`, `utils.py` → `test_import_utils.py`, `test_import_validation_helpers.py`, `test_utils.py` - `jobs.py`, `views/imports/list.py` → `test_background_jobs.py` - Permission mixins, API permissions, constants → `test_permissions.py` - VLAN API, mode detection, comparison, sync → `test_vlan_sync.py` - `VlanAssignmentMixin`, VLAN enrichment → `test_interface_vlan_sync.py` - Views (`views/sync/`, `views/object_sync/`, `views/imports/actions.py`) — no dedicated test files yet. Test business logic via the utility modules they call, not via HTTP requests. ## Permission Test Patterns When testing permissions (see `test_permissions.py` for reference): - Create a mock view instance with `object.__new__(ViewClass)`, set `request = MagicMock()` with `request.user.has_perm.side_effect = lambda p: p in allowed_perms`. - For `NetBoxObjectPermissionMixin` tests, set `required_object_permissions` on the instance before calling `check_object_permissions()`. - Test both the individual methods (`has_write_permission()`, `check_object_permissions()`) and the combined `require_all_permissions()` flow. - For JSON variants (`require_all_permissions_json`), assert `isinstance(response, JsonResponse)` and check `response.status_code == 403`. ## Shared Fixtures (`conftest.py`) Reuse fixtures from `tests/conftest.py` instead of creating ad-hoc mocks: - **Configuration**: `mock_multi_server_config`, `mock_legacy_config` - **API client**: `mock_librenms_api` - **NetBox objects**: `mock_netbox_device`, `mock_netbox_vm`, `mock_netbox_site`, `mock_netbox_platform`, `mock_netbox_device_type`, `mock_netbox_device_role`, `mock_netbox_cluster`, `mock_netbox_rack` - **HTTP responses**: `mock_response_factory`, `mock_success_response`, `mock_device_response`, `mock_error_response`, `mock_auth_error_response` - **Import workflow**: `sample_librenms_device`, `sample_librenms_device_minimal`, `sample_validation_state`, `sample_validation_state_vm`