Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions netbox_config_diff/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,24 @@ class Meta:

def validate(self, data):
if data.get("devices"):
if devices := data["devices"].filter(platform__platform_setting__isnull=True):
if devices := Device.objects.filter(id__in=[d.id for d in data["devices"]]).filter(
platform__platform_setting__isnull=True
):
platforms = {d.platform.name for d in devices}
raise ValidationError({"devices": f"Assign PlatformSetting for platform(s): {', '.join(platforms)}"})

if drivers := {
device.platform.platform_setting.driver
for device in data["devices"]
if device.platform.platform_setting.driver not in ACCEPTABLE_DRIVERS
}:
raise ValidationError({"devices": f"Driver(s) not supported: {', '.join(drivers)}"})

if devices := list(filter(lambda x: x.get_config_template() is None, data["devices"])):
raise ValidationError(
{"devices": f"Define config template for device(s): {', '.join(d.name for d in devices)}"}
)
unsupported_drivers = {
d.platform.platform_setting.driver
for d in data["devices"]
if d.platform.platform_setting.driver not in ACCEPTABLE_DRIVERS
}
if unsupported_drivers:
raise ValidationError({"devices": f"Driver(s) not supported: {', '.join(unsupported_drivers)}"})

devices_without_template = [d for d in data["devices"] if d.get_config_template() is None]
if devices_without_template:
names = ", ".join(d.name for d in devices_without_template)
raise ValidationError({"devices": f"Define config template for device(s): {names}"})

return super().validate(data)

Expand Down
14 changes: 14 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.db import connection
from django.test.utils import setup_databases
from faker import Faker
from rest_framework.test import APIClient
from typing_extensions import Unpack

from netbox_config_diff.compliance.base import ConfigDiffBase
Expand Down Expand Up @@ -125,3 +126,16 @@ def factory(**fields: Unpack["DeviceDataClassData"]) -> "DeviceDataClassData":
def devicedataclass_data(devicedataclass_factory: "DeviceDataClassDataFactory") -> ConplianceDeviceDataClass:
data = devicedataclass_factory()
return ConplianceDeviceDataClass(**data)


@pytest.fixture
def api_client():
"""Basic API client fixture."""
return APIClient()


@pytest.fixture
def authenticated_api_client(api_client, admin_user):
"""Authenticated API client fixture using admin user."""
api_client.force_authenticate(user=admin_user)
return api_client
13 changes: 13 additions & 0 deletions tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@ class Meta:
model = ConfigCompliance


class ConfigurationRequestFactory(DjangoModelFactory):
class Meta:
model = "netbox_config_diff.ConfigurationRequest"

@factory.post_generation
def devices(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
for device in extracted:
obj.devices.add(device)


class DataSourceFactory(DjangoModelFactory):
name = factory.Sequence(lambda n: f"datasource-{n}")
source_url = factory.Sequence(lambda n: f"/tmp/{n}")
Expand Down
229 changes: 229 additions & 0 deletions tests/test_configurtion_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
from http import HTTPStatus
from typing import TYPE_CHECKING

import pytest
from django.urls import reverse
from users.models import User

from netbox_config_diff.choices import ConfigurationRequestStatusChoices
from tests.factories import (
ConfigurationRequestFactory,
DeviceFactory,
PlatformSettingFactory,
)

if TYPE_CHECKING:
pass


@pytest.mark.django_db()
def test_configuration_request_list(authenticated_api_client):
device = DeviceFactory.create()
PlatformSettingFactory.create(platform=device.platform)
ConfigurationRequestFactory.create(devices=[device])
ConfigurationRequestFactory.create(devices=[device])

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
response = authenticated_api_client.get(url)

assert response.status_code == HTTPStatus.OK
assert response.json()["count"] == 2
assert len(response.json()["results"]) == 2


@pytest.mark.django_db()
def test_configuration_request_retrieve(authenticated_api_client):
device = DeviceFactory.create()
PlatformSettingFactory.create(platform=device.platform)
cr = ConfigurationRequestFactory.create(devices=[device])

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-detail", args=[cr.pk])
response = authenticated_api_client.get(url)

assert response.status_code == HTTPStatus.OK
data = response.json()
assert data["id"] == cr.pk
assert data["status"]["value"] == ConfigurationRequestStatusChoices.CREATED
assert len(data["devices"]) == 1
assert data["devices"][0]["id"] == device.pk


@pytest.mark.django_db()
def test_configuration_request_create_success(authenticated_api_client, admin_user):
device = DeviceFactory.create()
PlatformSettingFactory.create(platform=device.platform)

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {
"devices": [device.pk],
"description": "Test configuration request",
"comments": "Test comments",
}
response = authenticated_api_client.post(url, payload, format="json")

assert response.status_code == HTTPStatus.CREATED
data = response.json()
assert data["status"]["value"] == ConfigurationRequestStatusChoices.CREATED
assert data["description"] == "Test configuration request"
assert data["comments"] == "Test comments"
assert len(data["devices"]) == 1
assert data["created_by"]["id"] == admin_user.pk


@pytest.mark.django_db()
def test_configuration_request_create_multiple_devices(authenticated_api_client):
device1 = DeviceFactory.create()
device2 = DeviceFactory.create()
PlatformSettingFactory.create(platform=device1.platform)
PlatformSettingFactory.create(platform=device2.platform)

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {
"devices": [device1.pk, device2.pk],
"description": "Multi-device request",
}
response = authenticated_api_client.post(url, payload, format="json")

assert response.status_code == HTTPStatus.CREATED
data = response.json()
assert len(data["devices"]) == 2
device_ids = {d["id"] for d in data["devices"]}
assert device1.pk in device_ids
assert device2.pk in device_ids


@pytest.mark.django_db()
def test_configuration_request_create_no_platform_setting(authenticated_api_client):
device = DeviceFactory.create()

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {
"devices": [device.pk],
}
response = authenticated_api_client.post(url, payload, format="json")

assert response.status_code == HTTPStatus.BAD_REQUEST
data = response.json()
assert "devices" in data
assert "Assign PlatformSetting" in str(data["devices"])


@pytest.mark.django_db()
def test_configuration_request_create_unsupported_driver(authenticated_api_client):
device = DeviceFactory.create()
PlatformSettingFactory.create(platform=device.platform, driver="unsupported_driver")

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {
"devices": [device.pk],
}
response = authenticated_api_client.post(url, payload, format="json")

assert response.status_code == HTTPStatus.BAD_REQUEST
data = response.json()
assert "devices" in data
assert "Driver(s) not supported" in str(data["devices"])


@pytest.mark.django_db()
def test_configuration_request_create_no_config_template(authenticated_api_client):
device = DeviceFactory.create(config_template=None)
PlatformSettingFactory.create(platform=device.platform)

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {
"devices": [device.pk],
}
response = authenticated_api_client.post(url, payload, format="json")

assert response.status_code == HTTPStatus.BAD_REQUEST
data = response.json()
assert "devices" in data
assert "Define config template" in str(data["devices"])


@pytest.mark.django_db()
def test_configuration_request_create_mixed_valid_invalid_devices(authenticated_api_client):
valid_device = DeviceFactory.create()
PlatformSettingFactory.create(platform=valid_device.platform)
invalid_device = DeviceFactory.create()

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {
"devices": [valid_device.pk, invalid_device.pk],
}
response = authenticated_api_client.post(url, payload, format="json")

assert response.status_code == HTTPStatus.BAD_REQUEST
data = response.json()
assert "devices" in data


@pytest.mark.django_db()
def test_configuration_request_create_empty_devices(authenticated_api_client):
url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {
"devices": [],
}
response = authenticated_api_client.post(url, payload, format="json")

assert response.status_code == HTTPStatus.CREATED
data = response.json()
assert len(data["devices"]) == 0


@pytest.mark.django_db()
def test_configuration_request_create_without_authentication(api_client):
device = DeviceFactory.create()
PlatformSettingFactory.create(platform=device.platform)

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {
"devices": [device.pk],
}
response = api_client.post(url, payload, format="json")
assert response.status_code == HTTPStatus.FORBIDDEN


@pytest.mark.django_db()
def test_configuration_request_list_filtering(authenticated_api_client):
device = DeviceFactory.create()
PlatformSettingFactory.create(platform=device.platform)

cr = ConfigurationRequestFactory.create(devices=[device], status=ConfigurationRequestStatusChoices.CREATED)
ConfigurationRequestFactory.create(devices=[device], status=ConfigurationRequestStatusChoices.APPROVED)

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")

response = authenticated_api_client.get(url, {"status": ConfigurationRequestStatusChoices.CREATED})

assert response.status_code == HTTPStatus.OK
data = response.json()

assert any(r["id"] == cr.pk for r in data["results"])


@pytest.mark.django_db()
def test_configuration_request_retrieve_nonexistent(authenticated_api_client):
url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-detail", args=[9999])
response = authenticated_api_client.get(url)

assert response.status_code == HTTPStatus.NOT_FOUND


@pytest.mark.django_db()
def test_configuration_request_create_sets_created_by(authenticated_api_client, admin_user):
user2 = User.objects.create_user(username="user2", password="pass")

device = DeviceFactory.create()
PlatformSettingFactory.create(platform=device.platform)

url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
payload = {"devices": [device.pk]}
response = authenticated_api_client.post(url, payload, format="json")

assert response.status_code == HTTPStatus.CREATED
data = response.json()

assert data["created_by"]["id"] == admin_user.pk
assert data["created_by"]["id"] != user2.pk
Loading