From 03c4c9989a6e75a9acbc4247bdfe332aa11d948b Mon Sep 17 00:00:00 2001
From: Magdalena GomezFayren Medina <magdalena.gomez@ext.ec.europa.eu>
Date: Mon, 20 Jan 2025 16:22:45 +0000
Subject: [PATCH 01/11] UI view tests

---
 .../tests/vm_machine_type/__init__.py         |  0
 .../test_vm_machine_type_view.py              | 89 +++++++++++++++++++
 2 files changed, 89 insertions(+)
 create mode 100644 netbox_sys_plugin/tests/vm_machine_type/__init__.py
 create mode 100644 netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_view.py

diff --git a/netbox_sys_plugin/tests/vm_machine_type/__init__.py b/netbox_sys_plugin/tests/vm_machine_type/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_view.py b/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_view.py
new file mode 100644
index 0000000..1038a09
--- /dev/null
+++ b/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_view.py
@@ -0,0 +1,89 @@
+"""VM Maintenance Views Test Case Class"""
+
+from django.contrib.contenttypes.models import ContentType
+from users.models import ObjectPermission
+from virtualization.models import Cluster, ClusterType
+from netbox_sys_plugin.models import VirtualMachineType
+from netbox_sys_plugin.forms import VmTypeForm
+from .. base import BaseModelViewTestCase
+
+
+
+class VmMachineTypeFormTestCase(
+    BaseModelViewTestCase,
+):
+    """VM Machine Type Test Case Class"""
+
+    model = VirtualMachineType
+    form = VmTypeForm
+
+
+    @classmethod
+    # pylint: disable=invalid-name
+    def setUpTestData(cls):
+        """ SetUp Test Data """
+        #Create Cluster type
+        cluster_type_content_type = ContentType.objects.get_for_model(ClusterType)
+        cluster_type1 = ClusterType.objects.create(name="Test ClusterType1", slug="ClusterType1")
+        cluster_type2 = ClusterType.objects.create(name="Test ClusterType2", slug="ClusterType2")
+
+        vmMachineType1 = VirtualMachineType.objects.create(virtual_machine_type_name='vm_type_name1',virtual_machine_type_desc='vm_type_desc_1',assigned_object_type=cluster_type_content_type, assigned_object_id=cluster_type1.pk)
+        vmMachineType2 = VirtualMachineType.objects.create(virtual_machine_type_name='vm_type_name2',virtual_machine_type_desc='vm_type_desc_2',assigned_object_type=cluster_type_content_type, assigned_object_id=cluster_type2.pk)
+
+        cls.form_data = {
+            "virtual_machine_type_name": "vm_type_name3",
+            "cluster_type": cluster_type2.pk,
+            "virtual_machine_type_desc":"vm_type_desc_3"
+
+        }
+        cls.csv_data = (
+            "virtual_machine_type_name","virtual_machine_type_desc","assigned_object_type","assigned_object_id\n"
+            "vm_type_name4,vm_type_desc_4," + str(cluster_type_content_type.pk) + ", " + str(cluster_type1.pk) + "\n"
+        )
+
+        cls.csv_update_data = (
+            "id,virtual_machine_type_name","virtual_machine_type_desc","assigned_object_type","assigned_object_id\n"
+            f"{vmMachineType1.pk},vm_type_new_name1,vm_type_desc_new,{cluster_type_content_type.pk},{cluster_type1.pk}\n"
+            f"{vmMachineType2.pk},vm_type_new_name2,vm_type_desc_new2,{cluster_type_content_type.pk},{cluster_type2.pk}\n"
+        )
+
+    def test_create_already_existing_vm_type(self):
+        """Test the creation of 2 vm types with same name and cluster type"""
+
+        #Create Cluster type for testing
+        # pylint: disable=W0201
+        cluster_type_content_type = ContentType.objects.get_for_model(ClusterType)
+        cluster_type = ClusterType.objects.create(name="Test ClusterType3", slug="ClusterType3")
+
+        # pylint: disable=W0201
+        self.vmMachineType1 = VirtualMachineType.objects.create(
+            virtual_machine_type_name='vm_type_name',
+            virtual_machine_type_desc='vm_type_desc',
+            assigned_object_type=cluster_type_content_type,
+            assigned_object_id=cluster_type.pk)
+
+        form = VmTypeForm(data= {
+            "virtual_machine_type_name": "vm_type_name",
+            "cluster_type": cluster_type.pk,
+            "virtual_machine_type_desc":"vm_type_desc"
+        })
+
+        self.assertFalse(form.is_valid())
+        # Setup object permissions for the test user
+        obj_perm = ObjectPermission(
+            name='Test permission',
+            actions=['add', 'change']
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)  # pylint: disable=no-member
+        obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # pylint: disable=no-member
+
+        self.assertIn(
+            "A Type with this name and cluster type already exists",
+            form.errors.get("__all__",[])
+        )
+
+    def tearDown(self) -> None:# pylint: disable=invalid-name
+        """Method called immediately after the test method has been called and the result recorded."""
+        VirtualMachineType.objects.all().delete()
+        super().tearDown()
-- 
GitLab


From d5c0020f5947377ee4686c61dcfff7ffe8aa8858 Mon Sep 17 00:00:00 2001
From: Magdalena GomezFayren Medina <magdalena.gomez@ext.ec.europa.eu>
Date: Mon, 20 Jan 2025 16:36:21 +0000
Subject: [PATCH 02/11] Fixed lint

---
 .../vm_machine_type/test_vm_machine_type_view.py     | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_view.py b/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_view.py
index 1038a09..aa991c4 100644
--- a/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_view.py
+++ b/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_view.py
@@ -2,7 +2,7 @@
 
 from django.contrib.contenttypes.models import ContentType
 from users.models import ObjectPermission
-from virtualization.models import Cluster, ClusterType
+from virtualization.models import ClusterType
 from netbox_sys_plugin.models import VirtualMachineType
 from netbox_sys_plugin.forms import VmTypeForm
 from .. base import BaseModelViewTestCase
@@ -27,8 +27,14 @@ class VmMachineTypeFormTestCase(
         cluster_type1 = ClusterType.objects.create(name="Test ClusterType1", slug="ClusterType1")
         cluster_type2 = ClusterType.objects.create(name="Test ClusterType2", slug="ClusterType2")
 
-        vmMachineType1 = VirtualMachineType.objects.create(virtual_machine_type_name='vm_type_name1',virtual_machine_type_desc='vm_type_desc_1',assigned_object_type=cluster_type_content_type, assigned_object_id=cluster_type1.pk)
-        vmMachineType2 = VirtualMachineType.objects.create(virtual_machine_type_name='vm_type_name2',virtual_machine_type_desc='vm_type_desc_2',assigned_object_type=cluster_type_content_type, assigned_object_id=cluster_type2.pk)
+        vmMachineType1 = VirtualMachineType.objects.create(virtual_machine_type_name='vm_type_name1',
+                                                           virtual_machine_type_desc='vm_type_desc_1',
+                                                           assigned_object_type=cluster_type_content_type,
+                                                           assigned_object_id=cluster_type1.pk)
+        vmMachineType2 = VirtualMachineType.objects.create(virtual_machine_type_name='vm_type_name2',
+                                                           virtual_machine_type_desc='vm_type_desc_2',
+                                                           assigned_object_type=cluster_type_content_type,
+                                                           assigned_object_id=cluster_type2.pk)
 
         cls.form_data = {
             "virtual_machine_type_name": "vm_type_name3",
-- 
GitLab


From cc00426f968b770875649dc74425d8f21eb67404 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 16 Jan 2025 11:17:47 +0000
Subject: [PATCH 03/11] =?UTF-8?q?=E2=9C=85=20Add=20API=20test=20for=20prov?=
 =?UTF-8?q?ider=20type=20extra=20config?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/api/serializers.py          |  10 +
 ...traconfig_assigned_object_type_and_more.py |  25 ++
 netbox_sys_plugin/models/provider.py          |   2 +-
 .../provider_type_extra_config/__init__.py    |   0
 .../test_provider_type_extra_config_api.py    | 233 ++++++++++++++++++
 5 files changed, 269 insertions(+), 1 deletion(-)
 create mode 100644 netbox_sys_plugin/migrations/0003_alter_providertypeextraconfig_assigned_object_type_and_more.py
 create mode 100644 netbox_sys_plugin/tests/provider_type_extra_config/__init__.py
 create mode 100644 netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_api.py

diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py
index 5ca6a09..2c4bcbd 100644
--- a/netbox_sys_plugin/api/serializers.py
+++ b/netbox_sys_plugin/api/serializers.py
@@ -130,11 +130,21 @@ class WebhookSettingsSerializer(NetBoxModelSerializer):
 class ProviderTypeExtraConfigSerializer(NetBoxModelSerializer):
     id = serializers.IntegerField(read_only=True)
     extra_config_structure = serializers.JSONField()
+    assigned_object_type = serializers.PrimaryKeyRelatedField(
+        queryset=ContentType.objects.all(),
+        write_only=True,
+        )
+    display = serializers.CharField(source="__str__",read_only=True)
 
     class Meta:
         model = ProviderTypeExtraConfig
         fields = '__all__'
 
+    def create(self, validated_data):
+        assigned_object_type = validated_data.pop("assigned_object_type")
+        validated_data["assigned_object_type"] = assigned_object_type
+        return super().create(validated_data)
+
 class VmAssignedExtraConfigSerializer(NetBoxModelSerializer):
     id = serializers.IntegerField(read_only=True)
     virtual_machine = NestedVirtualMachineSerializer(source='assigned_object', read_only=True)
diff --git a/netbox_sys_plugin/migrations/0003_alter_providertypeextraconfig_assigned_object_type_and_more.py b/netbox_sys_plugin/migrations/0003_alter_providertypeextraconfig_assigned_object_type_and_more.py
new file mode 100644
index 0000000..f684ab6
--- /dev/null
+++ b/netbox_sys_plugin/migrations/0003_alter_providertypeextraconfig_assigned_object_type_and_more.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.2.16 on 2025-01-16 11:16
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contenttypes', '0002_remove_content_type_name'),
+        ('netbox_sys_plugin', '0002_alter_providercredentials_assigned_object_type'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='providertypeextraconfig',
+            name='assigned_object_type',
+            field=models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(('app_label', 'virtualization'), ('model', 'clustertype'))), null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype'),
+        ),
+        migrations.AlterField(
+            model_name='virtualmachinetype',
+            name='assigned_object_type',
+            field=models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(('app_label', 'virtualization'), ('model', 'clustertype'))), null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype'),
+        ),
+    ]
diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py
index 12a7871..e6770e0 100644
--- a/netbox_sys_plugin/models/provider.py
+++ b/netbox_sys_plugin/models/provider.py
@@ -9,7 +9,7 @@ from netbox.models import NetBoxModel
 from ..validators import validate_extra_config_structure
 
 CLUSTER_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="cluster"))
-CLUSTER_TYPE_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="ClusterType"))
+CLUSTER_TYPE_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="clustertype"))
 
 #Provider Credentials
 class ProviderCredentials(NetBoxModel):
diff --git a/netbox_sys_plugin/tests/provider_type_extra_config/__init__.py b/netbox_sys_plugin/tests/provider_type_extra_config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_api.py b/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_api.py
new file mode 100644
index 0000000..fb3071f
--- /dev/null
+++ b/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_api.py
@@ -0,0 +1,233 @@
+"""SYS Plugin Provider Type Extra Config API Test Case Class"""
+
+from users.models import ObjectPermission
+from django.contrib.contenttypes.models import ContentType
+from rest_framework import status
+from virtualization.models import ClusterType
+from netbox_sys_plugin.models import ProviderTypeExtraConfig
+from ..base import BaseAPITestCase
+
+class ProviderTypeExtraConfigApiTestCase(BaseAPITestCase):
+    """Test suite for ProviderTypeExtraConfig API"""
+    model = ProviderTypeExtraConfig
+    brief_fields = ["extra_config_name","extra_config_structure","extra_config_description", "assigned_object_id", "assigned_object_type"]
+
+
+    @classmethod
+    def setUpTestData(cls):
+
+        """Set up test data for ProviderTypeExtraConfig API"""
+        cls.ct_ct = ContentType.objects.get_for_model(ClusterType)
+
+        # Create a ClusterType
+        cls.cluster_type = ClusterType.objects.create(name="Test ClusterType", slug="CT")
+
+        # Create a ClusterType
+        cls.cluster_type2 = ClusterType.objects.create(name="Test ClusterType 2", slug="CT2")
+
+        # Create a ClusterType
+        cls.cluster_type3 = ClusterType.objects.create(name="Test ClusterType 3", slug="CT3")
+
+        # Create a ClusterType
+        cls.cluster_type4 = ClusterType.objects.create(name="Test ClusterType 4", slug="CT4")
+
+        # Create a ClusterType
+        cls.cluster_type5 = ClusterType.objects.create(name="Test ClusterType 5", slug="CT5")
+
+        # Create a ClusterType
+        cls.cluster_type6 = ClusterType.objects.create(name="Test ClusterType 6", slug="CT6")
+
+        # Create a ClusterType
+        cls.cluster_type7 = ClusterType.objects.create(name="Test ClusterType 7", slug="CT8")
+
+        # Create a ClusterType
+        cls.cluster_type8 = ClusterType.objects.create(name="Test ClusterType 8", slug="CT7")
+
+        # Create Extra Configs entries linked to the Cluster Types
+        ProviderTypeExtraConfig.objects.create(
+            extra_config_name="TEST_1",
+            extra_config_structure={'test_extra1': [{'id': {'required': 'true','type': 'String'}}]},
+            extra_config_description="Test Extra1",
+            assigned_object=cls.cluster_type7
+        )
+        ProviderTypeExtraConfig.objects.create(
+            extra_config_name="TEST_2",
+            extra_config_structure={'test_extra2': [{'id': {'required': 'true','type': 'String'}}]},
+            extra_config_description="Test Extra2",
+            assigned_object=cls.cluster_type8
+        )
+
+        # Data for valid creation
+        cls.valid_create_data = [
+            {
+                "extra_config_name": "Test Config",
+                "extra_config_structure": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+                "extra_config_description": "Test structure",
+                "assigned_object_type": cls.ct_ct.id,
+                "assigned_object_id": cls.cluster_type.pk,
+            }
+        ]
+
+        # Data for invalid creation
+        cls.invalid_create_data = [
+            {
+                "extra_config_name": "Config Invalid 1",
+                "extra_config_structure": {'test_invalid': {'id': {'required': 'true'}}}, #No dict root key
+                "extra_config_description": "Test structure",
+                "assigned_object_id": cls.cluster_type2.pk,
+                "assigned_object_type": cls.ct_ct.id,
+            },
+            {
+                "extra_config_name": "Config Invalid 2",
+                "extra_config_structure": {'test_valid': [{'id': {'type': 'String'}}]}, #No required field
+                "extra_config_description": "Test structure",
+                "assigned_object_id": cls.cluster_type3.pk,
+                "assigned_object_type": cls.ct_ct.id,
+
+            },
+            {
+                "extra_config_name": "Config Invalid 4",
+                "extra_config_structure": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+                "extra_config_description": "Test structure",
+                "assigned_object_id": cls.cluster_type5.pk,
+                "assigned_object_type": None #No assignment field
+            },
+        ]
+
+        # Data for checking  unique key
+        cls.valid_check_unique_data = [
+            {
+                "extra_config_name": "Test Config uniq",
+                "extra_config_structure": {'test_uniq': [{'id': {'required': 'true','type': 'String'}}]},
+                "extra_config_description": "Test structure",
+                "assigned_object_type": cls.ct_ct.pk,
+                "assigned_object_id": cls.cluster_type6.id, # Same Virtual Machine
+            },
+            {
+                "extra_config_name": "Test Config uniq2",
+                "extra_config_structure": {'test_uniq2': [{'id': {'required': 'true','type': 'String'}}]},
+                "extra_config_description": "Test structure",
+                "assigned_object_type": cls.ct_ct.pk,
+                "assigned_object_id": cls.cluster_type6.id, # Same Virtual Machine
+            }
+        ]
+
+    def test_create_valid_extra_config(self):
+        """Test creating a valid extra config"""
+        obj_perm = ObjectPermission(
+            name="Create Extra Config Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(ProviderTypeExtraConfig))
+
+        form_data = self.valid_create_data[0]
+        response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_201_CREATED)
+        self.assertEqual(response.data["extra_config_name"], form_data["extra_config_name"])
+        self.assertEqual(response.data["extra_config_structure"], form_data["extra_config_structure"])
+        self.assertEqual(response.data["extra_config_description"], form_data["extra_config_description"])
+
+    def test_extra_config_unique_key(self):
+        """Test extra config unique key"""
+        obj_perm = ObjectPermission(
+            name="Invalid Extra Config Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(ProviderTypeExtraConfig))
+
+        for form_data in self.valid_check_unique_data:
+            response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+        self.assertIn('The fields assigned_object_id must make a unique set.',str(response.data))
+
+    def test_create_invalid_extra_config(self):
+        """Test creating invalid extra config"""
+        obj_perm = ObjectPermission(
+            name="Invalid Extra Config Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(ProviderTypeExtraConfig))
+
+        form_data = self.invalid_create_data[0]
+        response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+        self.assertIn('the value of the root key must be a list',str(response.data))
+
+        form_data = self.invalid_create_data[1]
+        response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+        self.assertIn('must contain `type` and `required` keys',str(response.data))
+
+        form_data = self.invalid_create_data[2]
+        response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+        self.assertIn('This field may not be null.',str(response.data))
+
+    def test_update_extra_config(self):
+        """Test updating an existing extra config"""
+        extra_config = ProviderTypeExtraConfig.objects.first()
+        obj_perm = ObjectPermission(
+            name="Update Extra Config Permission",
+            actions=["change", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(ProviderTypeExtraConfig))
+
+        update_data = {"extra_config_name": "Update Data"}
+        response = self.client.patch(self._get_detail_url(extra_config), update_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(response.data["extra_config_name"], update_data["extra_config_name"])
+
+    def test_get_all_extra_config(self):
+        """Test fetching all extra config"""
+        obj_perm = ObjectPermission(
+            name="Create Extra Config Permission",
+            actions=["view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(ProviderTypeExtraConfig))
+
+        response = self.client.get(self._get_list_url(), **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertGreaterEqual(len(response.data), 2)
+
+    def test_get_single_extra_config(self):
+        """Test fetching a single extra config"""
+        obj_perm = ObjectPermission(
+            name="View Extra Config Permission",
+            actions=["view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(ProviderTypeExtraConfig))
+
+        extra_config = ProviderTypeExtraConfig.objects.first()
+        response = self.client.get(self._get_detail_url(extra_config), **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(response.data["extra_config_name"], extra_config.extra_config_name)
+        self.assertEqual(response.data["extra_config_structure"], extra_config.extra_config_structure)
+        self.assertEqual(response.data["extra_config_description"], extra_config.extra_config_description)
+
+    def test_delete_extra_config(self):
+        """Test deleting a extra config"""
+        extra_config = ProviderTypeExtraConfig.objects.first()
+        obj_perm = ObjectPermission(
+            name="Delete Extra Config Permission",
+            actions=["delete"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(ProviderTypeExtraConfig))
+
+        response = self.client.delete(self._get_detail_url(extra_config), **self.header)
+        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
+        self.assertFalse(ProviderTypeExtraConfig.objects.filter(id=extra_config.id).exists())
-- 
GitLab


From d0d17c71fa7c090d68f7bbde38f060ccb6e8c678 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 16 Jan 2025 17:37:24 +0000
Subject: [PATCH 04/11] =?UTF-8?q?=E2=9C=85=20Add=20view=20testing?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../test_provider_type_extra_config_view.py   | 168 ++++++++++++++++++
 1 file changed, 168 insertions(+)
 create mode 100644 netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_view.py

diff --git a/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_view.py b/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_view.py
new file mode 100644
index 0000000..243f050
--- /dev/null
+++ b/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_view.py
@@ -0,0 +1,168 @@
+"""VM Maintenance Views Test Case Class"""
+
+from django.contrib.contenttypes.models import ContentType
+from users.models import ObjectPermission
+from virtualization.models import ClusterType
+from netbox_sys_plugin.models import ProviderTypeExtraConfig
+from netbox_sys_plugin.forms import ProviderTypeExtraConfigForm
+from .. base import BaseModelViewTestCase
+
+
+
+class ProviderTypeExtraConfigFormTestCase(
+    BaseModelViewTestCase,
+):
+    """Provider Type Extra Config Test Case Class"""
+
+    model = ProviderTypeExtraConfig
+    form = ProviderTypeExtraConfigForm
+
+    def test_create_valid_extra_config(self):
+        """Create a valid Extra Config"""
+
+        cluster_type1 = ClusterType.objects.create(name="Test ClusterType3", slug="ClusterType3")
+
+        form = ProviderTypeExtraConfigForm(data= {
+            "extra_config_name": "test_config_1",
+            "extra_config_structure": {'test_extra1': [{'id': {'required': 'true','type': 'String'}}]},
+            "extra_config_description": "Test Config 1",
+            "cluster_type": cluster_type1.pk
+        })
+        self.assertTrue(form.is_valid())
+        # Setup object permissions for the test user
+        obj_perm = ObjectPermission(
+            name='Test permission',
+            actions=['add', 'change']
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)  # pylint: disable=no-member
+        obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # pylint: disable=no-member
+
+    def test_create_cluster_type_with_two_assigment(self):
+        """Test the assignment of 2 extra configs to the same Cluster Type """
+        # pylint: disable=W0201
+        self.clt_content_type = ContentType.objects.get_for_model(ClusterType)
+        # pylint: disable=W0201
+        self.cluster_type = ClusterType.objects.create(name="Test ClusterType3", slug="ClusterType3")
+        # pylint: disable=W0201
+        self.extra_config1 = ProviderTypeExtraConfig.objects.create(
+            extra_config_name='test_config_3',
+            extra_config_structure={"test_extra3": [{"id": {"required": "true","type": "String"}}]},
+            extra_config_description='Test Config 3',
+
+            assigned_object_type=self.clt_content_type,
+            assigned_object_id=self.cluster_type.pk
+            )
+
+        form = ProviderTypeExtraConfigForm(data= {
+            "extra_config_name": "test_config_2",
+            "extra_config_structure": {"test_extra3": [{"id": {"required": "true","type": "String"}}]},
+            "extra_config_description": "Test Config 2",
+            "cluster_type": self.cluster_type.pk,
+        })
+
+        self.assertFalse(form.is_valid())
+        # Setup object permissions for the test user
+        obj_perm = ObjectPermission(
+            name='Test permission',
+            actions=['add', 'change']
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)  # pylint: disable=no-member
+        obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # pylint: disable=no-member
+
+        self.assertIn(
+            "A Provider Type can only have one extra configuration",
+            form.errors.get("__all__",[])
+        )
+
+    def test_invalid_extra_config_format(self):
+        """
+        Test invalid maintenance window invalid format.
+        """
+
+        # Create VLAN and VLANGroup for testing
+        # pylint: disable=W0201
+        self.vm_content_type = ContentType.objects.get_for_model(ClusterType)
+        # pylint: disable=W0201
+        self.cluster_type = ClusterType.objects.create(name="Test ClusterType3", slug="ClusterType3")
+
+        # Set up valid form data
+        self.invalid_valid_form_data = {
+            "extra_config_name": "test_config_format1",
+            "extra_config_structure": {'test_extra1': [{'id': {'required': 'true','type': 'String'}}]},
+            "extra_config_description": "Test Config format1",
+            "cluster_type": self.cluster_type.pk
+        }
+        #Invalid JSON
+        invalid_form_data = self.invalid_valid_form_data.copy()
+        invalid_form_data["extra_config_structure"] = "Invalid"  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "Enter a valid JSON.",
+            form.errors.get("extra_config_structure", []),
+        )
+        #List Validation
+        invalid_form_data["extra_config_structure"] = [{'test_structure': [{'field1': {'type': 'string', 'required': True}}]}]  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "The structure must be a dictionary.",
+            form.errors.get("extra_config_structure", []),
+        )
+        #Missing property Type
+        invalid_form_data["extra_config_structure"] = {'structure1': [], 'structure2': []}  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "The structure must contain exactly one key.",
+            form.errors.get("extra_config_structure", []),
+        )
+        #Missing property Required
+        invalid_form_data["extra_config_structure"] = {'test_structure': {'field1': {'type': 'string', 'required': True}}}  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "For test_structure the value of the root key must be a list.",
+            form.errors.get("extra_config_structure", []),
+        )
+
+        #Dict Validation
+        invalid_form_data["extra_config_structure"] = {'test_structure': ['field1']}  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "Each field in the list must be a dictionary.",
+            form.errors.get("extra_config_structure", []),
+        )
+        #Only one key
+        invalid_form_data["extra_config_structure"] = {'test_structure': [{'field1': 'string'}]}  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "The field `field1` must be a dictionary.",
+            form.errors.get("extra_config_structure", []),
+        )
+        #Missing property Type
+        invalid_form_data["extra_config_structure"] = {'test_structure': [{'field1': {'required': True}}]}  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "The field `field1` must contain `type` and `required` keys.",
+            form.errors.get("extra_config_structure", []),
+        )
+
+        #Missing property Required
+        invalid_form_data["extra_config_structure"] = {'test_structure': [{'field1': {'type': 'string'}}]}  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "The field `field1` must contain `type` and `required` keys.",
+            form.errors.get("extra_config_structure", []),
+        )
+
+    def tearDown(self) -> None:# pylint: disable=invalid-name
+        """Method called immediately after the test method has been called and the result recorded."""
+        ProviderTypeExtraConfig.objects.all().delete()
+        super().tearDown()
-- 
GitLab


From 9d0753d25a6a99970c4d5676de9bb591c088a000 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 17 Jan 2025 16:09:18 +0000
Subject: [PATCH 05/11] =?UTF-8?q?=E2=9C=85=20Add=20API=20tests=20to=20Doma?=
 =?UTF-8?q?in=20names?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/api/serializers.py          |  12 +-
 netbox_sys_plugin/forms/machine.py            |   2 +-
 .../tests/vm_domain_names/__init__.py         |   0
 .../test_vm_domain_names_api.py               | 216 ++++++++++++++++++
 4 files changed, 227 insertions(+), 3 deletions(-)
 create mode 100644 netbox_sys_plugin/tests/vm_domain_names/__init__.py
 create mode 100644 netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_api.py

diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py
index 2c4bcbd..f9086f8 100644
--- a/netbox_sys_plugin/api/serializers.py
+++ b/netbox_sys_plugin/api/serializers.py
@@ -113,13 +113,21 @@ class DomainNamesSerializer(NetBoxModelSerializer):
     id = serializers.IntegerField(read_only=True)
     virtual_machine = NestedVirtualMachineSerializer(source='assigned_object', read_only=True)
     assigned_object_id = serializers.IntegerField(write_only=True)
-    assigned_object_type = serializers.CharField(write_only=True)
-    display = serializers.CharField(source="__str__")
+    assigned_object_type = serializers.PrimaryKeyRelatedField(
+        queryset=ContentType.objects.all(),
+        write_only=True,
+        )
+    display = serializers.CharField(source="__str__",read_only=True)
 
     class Meta:
         model = DomainNames
         fields = '__all__'
 
+    def create(self, validated_data):
+        assigned_object_type = validated_data.pop("assigned_object_type")
+        validated_data["assigned_object_type"] = assigned_object_type
+        return super().create(validated_data)
+
 class WebhookSettingsSerializer(NetBoxModelSerializer):
     id = serializers.IntegerField(read_only=True)
 
diff --git a/netbox_sys_plugin/forms/machine.py b/netbox_sys_plugin/forms/machine.py
index 36d18c3..2d9fb45 100644
--- a/netbox_sys_plugin/forms/machine.py
+++ b/netbox_sys_plugin/forms/machine.py
@@ -190,7 +190,7 @@ class DomainNamesForm(NetBoxModelForm):
         #Check if Virtual Machine is assigned corretly
         if not vm:
             raise ValidationError(
-                {"__all__": "Can't assign more than one Maintenance Window to the same Virtual Machine"},
+                {"__all__": "Can't assign more than one group of domain names to the same Virtual Machine"},
             )
 
     def save(self, *args, **kwargs):
diff --git a/netbox_sys_plugin/tests/vm_domain_names/__init__.py b/netbox_sys_plugin/tests/vm_domain_names/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_api.py b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_api.py
new file mode 100644
index 0000000..9240b0d
--- /dev/null
+++ b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_api.py
@@ -0,0 +1,216 @@
+"""SYS Plugin VM domain names API Test Case Class"""
+
+from users.models import ObjectPermission
+from django.contrib.contenttypes.models import ContentType
+from rest_framework import status
+from virtualization.models import VirtualMachine, Cluster, ClusterType
+from netbox_sys_plugin.models import DomainNames
+from ..base import BaseAPITestCase
+
+class VirtualMachineMaintenanceApiTestCase(BaseAPITestCase):
+    """Test suite for VirtualMachineMaintenance API"""
+    model = DomainNames
+    brief_fields = ["domain_names", "assigned_object_id", "assigned_object_type"]
+
+
+    @classmethod
+    def setUpTestData(cls):
+
+        """Set up test data for DomainNames API"""
+        cls.vm_ct = ContentType.objects.get_for_model(VirtualMachine)
+
+        # Create a ClusterType
+        cls.cluster_type = ClusterType.objects.create(name="Test ClusterType")
+
+        # Create a Cluster linked to the ClusterType
+        cls.cluster = Cluster.objects.create(name="Test Cluster", type=cls.cluster_type)
+
+        # Create a VirtualMachine linked to the Cluster
+        cls.virtual_machine = VirtualMachine.objects.create(
+            name="Test VM",
+            status="active",
+            cluster=cls.cluster
+        )
+
+        # Create a VirtualMachine 2 linked to the Cluster
+        cls.virtual_machine2 = VirtualMachine.objects.create(
+            name="Test VM2",
+            status="active",
+            cluster=cls.cluster
+        )
+
+        # Create a VirtualMachine 3 linked to the Cluster
+        cls.virtual_machine3 = VirtualMachine.objects.create(
+            name="Test VM3",
+            status="active",
+            cluster=cls.cluster
+        )
+
+        # Create a VirtualMachine 4 linked to the Cluster
+        cls.virtual_machine4 = VirtualMachine.objects.create(
+            name="Test VM4",
+            status="active",
+            cluster=cls.cluster
+        )
+        # Create a VirtualMachine 5 linked to the Cluster
+        cls.virtual_machine5 = VirtualMachine.objects.create(
+            name="Test VM5",
+            status="active",
+            cluster=cls.cluster
+        )
+        # Create a VirtualMachine 6 linked to the Cluster
+        cls.virtual_machine6 = VirtualMachine.objects.create(
+            name="Test VM6",
+            status="active",
+            cluster=cls.cluster
+        )
+
+        # Create maintenance entries linked to the VirtualMachine
+        DomainNames.objects.create(
+            domain_names={'domain1':'ec.test.test','domain2':'ec.test2.test2'},
+            assigned_object=cls.virtual_machine
+        )
+        DomainNames.objects.create(
+            domain_names={'domain1':'ec.test.test','domain2':'ec.test2.test2'},
+            assigned_object=cls.virtual_machine2
+        )
+
+        # Data for valid creation
+        cls.valid_create_data = [
+            {
+                "domain_names": {'domain1':'ec.test.test','domain2':'ec.test2.test2'},
+                "assigned_object_type": cls.vm_ct.pk,
+                "assigned_object_id": cls.virtual_machine3.id,
+            }
+        ]
+
+        # Data for invalid creation
+        cls.invalid_create_data = [
+            #{
+            #    "domain_names": "invalid-format",
+            #    "assigned_object_type": cls.vm_ct.pk,
+            #    "assigned_object_id": cls.virtual_machine4.id,
+            #},
+            {
+                "domain_names": {'domain1':'ec.test.test','domain2':'ec.test2.test2'},
+                "assigned_object_type": cls.vm_ct.pk,
+                "assigned_object_id": None,  # Missing VirtualMachine
+            },
+        ]
+
+        # Data for checking  unique key
+        cls.valid_check_unique_data = [
+            {
+                "domain_names": {'domain1':'ec.test.test','domain2':'ec.test2.test2'},
+                "assigned_object_type": cls.vm_ct.pk,
+                "assigned_object_id": cls.virtual_machine6.id, # Same Virtual Machine
+            },
+            {
+                "domain_names": {'domain1':'ec.test.test','domain2':'ec.test2.test2'},
+                "assigned_object_type": cls.vm_ct.pk,
+                "assigned_object_id": cls.virtual_machine6.id, # Same Virtual Machine
+            }
+        ]
+
+    def test_create_valid_domain_names(self):
+        """Test creating a valid Domain Names"""
+        obj_perm = ObjectPermission(
+            name="Create Domain Names Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
+
+        form_data = self.valid_create_data[0]
+        response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_201_CREATED)
+        self.assertEqual(response.data["domain_names"], form_data["domain_names"])
+
+    def test_maintenance_window_unique_key(self):
+        """Test maintenance windows unique key"""
+        obj_perm = ObjectPermission(
+            name="Invalid Maintenance Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
+
+        for form_data in self.valid_check_unique_data:
+            response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_invalid_domain_names(self):
+        """Test creating invalid domain names"""
+        obj_perm = ObjectPermission(
+            name="Invalid domain names Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
+
+        for form_data in self.invalid_create_data:
+            response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+            self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+
+    def test_update_domain_names(self):
+        """Test updating an existing domain names"""
+        domain_names = DomainNames.objects.first()
+        obj_perm = ObjectPermission(
+            name="Update domain names Permission",
+            actions=["change", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
+
+        update_data = {'domain_names': {'domain1':'ec.test.test','domain2':'ec.test2.test2'}}
+        response = self.client.patch(self._get_detail_url(domain_names), update_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(response.data["domain_names"], update_data["domain_names"])
+
+    def test_delete_domain_names(self):
+        """Test deleting a domain names"""
+        maintenance = DomainNames.objects.first()
+        obj_perm = ObjectPermission(
+            name="Delete Maintenance Permission",
+            actions=["delete"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
+
+        response = self.client.delete(self._get_detail_url(maintenance), **self.header)
+        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
+        self.assertFalse(DomainNames.objects.filter(id=maintenance.id).exists())
+
+    def test_get_all_maintenance_windows(self):
+        """Test fetching all maintenance windows"""
+        obj_perm = ObjectPermission(
+            name="View Maintenance Permission",
+            actions=["view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
+
+        response = self.client.get(self._get_list_url(), **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertGreaterEqual(len(response.data), 2)
+
+    def test_get_single_domain_names(self):
+        """Test fetching a single domain names"""
+        obj_perm = ObjectPermission(
+            name="View domain names Permission",
+            actions=["view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
+
+        domain_names = DomainNames.objects.first()
+        response = self.client.get(self._get_detail_url(domain_names), **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(response.data["domain_names"], domain_names.domain_names)
-- 
GitLab


From 7c2c6706a1b42c20c400e091fd3a97642acee95e Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 17 Jan 2025 17:10:02 +0000
Subject: [PATCH 06/11] =?UTF-8?q?=E2=9C=85=20=F0=9F=90=9B=20Add=20working?=
 =?UTF-8?q?=20API=20tests=20and=20bug=20fixes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/machine.py            |  8 ++-
 .../0004_alter_domainnames_domain_names.py    | 19 +++++++
 netbox_sys_plugin/models/machine.py           |  2 +-
 .../test_provider_type_extra_config_view.py   |  2 +-
 .../test_vm_domain_names_api.py               | 51 +++++++++++--------
 5 files changed, 58 insertions(+), 24 deletions(-)
 create mode 100644 netbox_sys_plugin/migrations/0004_alter_domainnames_domain_names.py

diff --git a/netbox_sys_plugin/forms/machine.py b/netbox_sys_plugin/forms/machine.py
index 2d9fb45..e78e861 100644
--- a/netbox_sys_plugin/forms/machine.py
+++ b/netbox_sys_plugin/forms/machine.py
@@ -152,7 +152,7 @@ class DomainNamesForm(NetBoxModelForm):
     )
     domain_names = JSONField(
         label=_('Domain Names'),
-        required=False
+        required=True
     )
 
     def __init__(self, *args, **kwargs):
@@ -186,6 +186,7 @@ class DomainNamesForm(NetBoxModelForm):
         super().clean()
 
         vm = self.cleaned_data.get("virtual_machine")
+        domain_names = self.cleaned_data.get("domain_names")
 
         #Check if Virtual Machine is assigned corretly
         if not vm:
@@ -193,6 +194,11 @@ class DomainNamesForm(NetBoxModelForm):
                 {"__all__": "Can't assign more than one group of domain names to the same Virtual Machine"},
             )
 
+        if not domain_names:
+            raise ValidationError(
+                {"__all__": "Domain names should not be empty"},
+            )
+
     def save(self, *args, **kwargs):
         """Set assigned object and save"""
         # Set assigned object
diff --git a/netbox_sys_plugin/migrations/0004_alter_domainnames_domain_names.py b/netbox_sys_plugin/migrations/0004_alter_domainnames_domain_names.py
new file mode 100644
index 0000000..561c270
--- /dev/null
+++ b/netbox_sys_plugin/migrations/0004_alter_domainnames_domain_names.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.2.16 on 2025-01-17 17:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('netbox_sys_plugin', '0003_alter_providertypeextraconfig_assigned_object_type_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='domainnames',
+            name='domain_names',
+            field=models.JSONField(blank=True, default={}),
+            preserve_default=False,
+        ),
+    ]
diff --git a/netbox_sys_plugin/models/machine.py b/netbox_sys_plugin/models/machine.py
index 6ffd6d9..9dc3495 100644
--- a/netbox_sys_plugin/models/machine.py
+++ b/netbox_sys_plugin/models/machine.py
@@ -120,7 +120,7 @@ class DomainNames(NetBoxModel):
 
     domain_names = models.JSONField(
         blank=True,
-        null=True,
+        null=False,
     )
 
     assigned_object_type = models.ForeignKey(
diff --git a/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_view.py b/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_view.py
index 243f050..d0c1218 100644
--- a/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_view.py
+++ b/netbox_sys_plugin/tests/provider_type_extra_config/test_provider_type_extra_config_view.py
@@ -78,7 +78,7 @@ class ProviderTypeExtraConfigFormTestCase(
 
     def test_invalid_extra_config_format(self):
         """
-        Test invalid maintenance window invalid format.
+        Test invalid extra config invalid format.
         """
 
         # Create VLAN and VLANGroup for testing
diff --git a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_api.py b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_api.py
index 9240b0d..0117333 100644
--- a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_api.py
+++ b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_api.py
@@ -7,8 +7,8 @@ from virtualization.models import VirtualMachine, Cluster, ClusterType
 from netbox_sys_plugin.models import DomainNames
 from ..base import BaseAPITestCase
 
-class VirtualMachineMaintenanceApiTestCase(BaseAPITestCase):
-    """Test suite for VirtualMachineMaintenance API"""
+class VirtualMachinedomain_namesApiTestCase(BaseAPITestCase):
+    """Test suite for VirtualMachinedomain_names API"""
     model = DomainNames
     brief_fields = ["domain_names", "assigned_object_id", "assigned_object_type"]
 
@@ -65,7 +65,7 @@ class VirtualMachineMaintenanceApiTestCase(BaseAPITestCase):
             cluster=cls.cluster
         )
 
-        # Create maintenance entries linked to the VirtualMachine
+        # Create domain_names entries linked to the VirtualMachine
         DomainNames.objects.create(
             domain_names={'domain1':'ec.test.test','domain2':'ec.test2.test2'},
             assigned_object=cls.virtual_machine
@@ -86,11 +86,11 @@ class VirtualMachineMaintenanceApiTestCase(BaseAPITestCase):
 
         # Data for invalid creation
         cls.invalid_create_data = [
-            #{
-            #    "domain_names": "invalid-format",
-            #    "assigned_object_type": cls.vm_ct.pk,
-            #    "assigned_object_id": cls.virtual_machine4.id,
-            #},
+            {
+                "domain_names": None,
+                "assigned_object_type": cls.vm_ct.pk,
+                "assigned_object_id": cls.virtual_machine4.id,
+            },
             {
                 "domain_names": {'domain1':'ec.test.test','domain2':'ec.test2.test2'},
                 "assigned_object_type": cls.vm_ct.pk,
@@ -127,10 +127,10 @@ class VirtualMachineMaintenanceApiTestCase(BaseAPITestCase):
         self.assertHttpStatus(response, status.HTTP_201_CREATED)
         self.assertEqual(response.data["domain_names"], form_data["domain_names"])
 
-    def test_maintenance_window_unique_key(self):
-        """Test maintenance windows unique key"""
+    def test_domain_names_unique_key(self):
+        """Test domain names unique key"""
         obj_perm = ObjectPermission(
-            name="Invalid Maintenance Permission",
+            name="Invalid domain names Permission",
             actions=["add", "view"],
         )
         obj_perm.save()
@@ -151,9 +151,18 @@ class VirtualMachineMaintenanceApiTestCase(BaseAPITestCase):
         obj_perm.users.add(self.user)
         obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
 
-        for form_data in self.invalid_create_data:
-            response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
-            self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+        form_data = self.invalid_create_data[0]
+        print("form_data",form_data)
+        response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        print("RESPONSE API 1",response.data)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+        self.assertIn('This field may not be null.',str(response.data))
+
+        form_data = self.invalid_create_data[1]
+        response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        print("RESPONSE API 2",response.data)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+        self.assertIn('This field may not be null.',str(response.data))
 
     def test_update_domain_names(self):
         """Test updating an existing domain names"""
@@ -173,23 +182,23 @@ class VirtualMachineMaintenanceApiTestCase(BaseAPITestCase):
 
     def test_delete_domain_names(self):
         """Test deleting a domain names"""
-        maintenance = DomainNames.objects.first()
+        domain_names = DomainNames.objects.first()
         obj_perm = ObjectPermission(
-            name="Delete Maintenance Permission",
+            name="Delete domain names Permission",
             actions=["delete"],
         )
         obj_perm.save()
         obj_perm.users.add(self.user)
         obj_perm.object_types.add(ContentType.objects.get_for_model(DomainNames))
 
-        response = self.client.delete(self._get_detail_url(maintenance), **self.header)
+        response = self.client.delete(self._get_detail_url(domain_names), **self.header)
         self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertFalse(DomainNames.objects.filter(id=maintenance.id).exists())
+        self.assertFalse(DomainNames.objects.filter(id=domain_names.id).exists())
 
-    def test_get_all_maintenance_windows(self):
-        """Test fetching all maintenance windows"""
+    def test_get_all_domain_names(self):
+        """Test fetching all domain names"""
         obj_perm = ObjectPermission(
-            name="View Maintenance Permission",
+            name="View domain_names Permission",
             actions=["view"],
         )
         obj_perm.save()
-- 
GitLab


From e191582c69daab803a3bbde135c20a707f8e911a Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 20 Jan 2025 10:24:30 +0000
Subject: [PATCH 07/11] =?UTF-8?q?=E2=9C=85=20Add=20view=20test=20for=20Dom?=
 =?UTF-8?q?ain=20Names?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../test_vm_domain_names_view.py              | 129 ++++++++++++++++++
 1 file changed, 129 insertions(+)
 create mode 100644 netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py

diff --git a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
new file mode 100644
index 0000000..c6f7b99
--- /dev/null
+++ b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
@@ -0,0 +1,129 @@
+"""VM domain names Views Test Case Class"""
+
+from django.contrib.contenttypes.models import ContentType
+from users.models import ObjectPermission
+from virtualization.models import VirtualMachine, Cluster, ClusterType
+from netbox_sys_plugin.models import DomainNames
+from netbox_sys_plugin.forms import DomainNamesForm
+from .. base import BaseModelViewTestCase
+
+
+
+class DomainNamesFormTestCase(
+    BaseModelViewTestCase,
+):
+    """VM domain names Test Case Class"""
+
+    model = DomainNames
+    form = DomainNamesForm
+
+
+    @classmethod
+    # pylint: disable=invalid-name
+    def setUpTestData(cls):
+        """ SetUp Test Data """
+        #Create Cluster type, Cluster and VMs
+        vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
+        cluster_type = ClusterType.objects.create(name="Test ClusterType1", slug="ClusterType1")
+        cluster = Cluster.objects.create(name="Test Cluster1", type=cluster_type)
+        virtual_machine = VirtualMachine.objects.create(name="Test VM",status="active",cluster=cluster)
+        virtual_machine2 = VirtualMachine.objects.create(name="Test VM2",status="active",cluster=cluster)
+        DomainNames.objects.create(domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},assigned_object_type=vm_content_type, assigned_object_id=virtual_machine.pk)
+        DomainNames.objects.create(domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},assigned_object_type=vm_content_type, assigned_object_id=virtual_machine2.pk)
+
+        cls.form_data = {
+            "domain_names": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            "virtual_machine": virtual_machine2.pk,
+
+        }
+
+    def test_create_domain_names_with_no_assigment(self):
+        """Test the creation a VM domain names with no VM assignement"""
+
+        form = DomainNamesForm(data= {
+            "domain_names": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+        })
+
+        self.assertFalse(form.is_valid())
+        # Setup object permissions for the test user
+        obj_perm = ObjectPermission(
+            name='Test permission',
+            actions=['add', 'change']
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)  # pylint: disable=no-member
+        obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # pylint: disable=no-member
+
+
+    def test_create_vm_with_two_assigment(self):
+        """Test the assignment of 2 domain namess to the same VM """
+        # pylint: disable=W0201
+        self.vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
+        # pylint: disable=W0201
+        self.cluster_type = ClusterType.objects.create(name="Test ClusterType2", slug="ClusterType2")
+        # pylint: disable=W0201
+        self.cluster = Cluster.objects.create(name="Test Cluster2", type=self.cluster_type)
+        # pylint: disable=W0201
+        self.virtual_machine = VirtualMachine.objects.create(name="Test VM",status="active",cluster=self.cluster)
+        # pylint: disable=W0201
+        self.domain_names = DomainNames.objects.create(
+            domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            assigned_object_type=self.vm_content_type,
+            assigned_object_id=self.virtual_machine.pk
+            )
+
+        form = DomainNamesForm(data= {
+            "domain_names": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            "virtual_machine": self.virtual_machine.pk,
+        })
+
+        self.assertFalse(form.is_valid())
+        # Setup object permissions for the test user
+        obj_perm = ObjectPermission(
+            name='Test permission',
+            actions=['add', 'change']
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)  # pylint: disable=no-member
+        obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # pylint: disable=no-member
+
+        self.assertIn(
+            "Can't assign more than one group of domain names to the same Virtual Machine",
+            form.errors.get("__all__",[])
+        )
+
+    def test_invalid_domain_names_format(self):
+        """
+        Test invalid domain names invalid format.
+        """
+
+        # Create VLAN and VLANGroup for testing
+        # pylint: disable=W0201
+        self.vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
+        # pylint: disable=W0201
+        self.cluster_type = ClusterType.objects.create(name="Test ClusterType3", slug="ClusterType3")
+        # pylint: disable=W0201
+        self.cluster = Cluster.objects.create(name="Test Cluster3", type=self.cluster_type)
+        # pylint: disable=W0201
+        self.virtual_machine = VirtualMachine.objects.create(name="Test VM",status="active",cluster=self.cluster)
+
+        # Set up valid form data
+        self.valid_form_data = {
+            "virtual_machine": self.virtual_machine.pk,
+            "domain_names": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            "tags": "",
+        }
+
+        invalid_form_data = self.valid_form_data.copy()
+        invalid_form_data["domain_names"] = "invalid"  # Invalid format
+        form = self.form(data=invalid_form_data)
+        self.assertFalse(form.is_valid())
+        self.assertIn(
+            "Enter a valid JSON.",
+            form.errors.get("domain_names", []),
+        )
+
+    def tearDown(self) -> None:# pylint: disable=invalid-name
+        """Method called immediately after the test method has been called and the result recorded."""
+        DomainNames.objects.all().delete()
+        super().tearDown()
-- 
GitLab


From 5736fc05781d8da8dcabb99303e98e3d884f6fe5 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 20 Jan 2025 10:34:24 +0000
Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20lint=20issues?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../tests/vm_domain_names/test_vm_domain_names_view.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
index c6f7b99..fb05def 100644
--- a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
+++ b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
@@ -28,8 +28,14 @@ class DomainNamesFormTestCase(
         cluster = Cluster.objects.create(name="Test Cluster1", type=cluster_type)
         virtual_machine = VirtualMachine.objects.create(name="Test VM",status="active",cluster=cluster)
         virtual_machine2 = VirtualMachine.objects.create(name="Test VM2",status="active",cluster=cluster)
-        DomainNames.objects.create(domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},assigned_object_type=vm_content_type, assigned_object_id=virtual_machine.pk)
-        DomainNames.objects.create(domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},assigned_object_type=vm_content_type, assigned_object_id=virtual_machine2.pk)
+        DomainNames.objects.create(
+            domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            assigned_object_type=vm_content_type, 
+            assigned_object_id=virtual_machine.pk)
+        DomainNames.objects.create(
+            domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            assigned_object_type=vm_content_type,
+            assigned_object_id=virtual_machine2.pk)
 
         cls.form_data = {
             "domain_names": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
-- 
GitLab


From 4ab5325c0c76145494c2f9d2b77d354c3d780747 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 20 Jan 2025 10:46:17 +0000
Subject: [PATCH 09/11] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20Trailing=20whitespac?=
 =?UTF-8?q?e?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../tests/vm_domain_names/test_vm_domain_names_view.py          | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
index fb05def..21f3053 100644
--- a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
+++ b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
@@ -30,7 +30,7 @@ class DomainNamesFormTestCase(
         virtual_machine2 = VirtualMachine.objects.create(name="Test VM2",status="active",cluster=cluster)
         DomainNames.objects.create(
             domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
-            assigned_object_type=vm_content_type, 
+            assigned_object_type=vm_content_type,
             assigned_object_id=virtual_machine.pk)
         DomainNames.objects.create(
             domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
-- 
GitLab


From 8cb3091b9ede76148d95067b5bf621916ef8afaa Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 20 Jan 2025 11:40:07 +0000
Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=85=20Add=20new=20test?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../test_vm_domain_names_view.py              | 45 +++++++++++++++----
 1 file changed, 36 insertions(+), 9 deletions(-)

diff --git a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
index 21f3053..7757766 100644
--- a/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
+++ b/netbox_sys_plugin/tests/vm_domain_names/test_vm_domain_names_view.py
@@ -29,25 +29,52 @@ class DomainNamesFormTestCase(
         virtual_machine = VirtualMachine.objects.create(name="Test VM",status="active",cluster=cluster)
         virtual_machine2 = VirtualMachine.objects.create(name="Test VM2",status="active",cluster=cluster)
         DomainNames.objects.create(
-            domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            domain_names={'test_setup': [{'id': {'required': 'true','type': 'String'}}]},
             assigned_object_type=vm_content_type,
             assigned_object_id=virtual_machine.pk)
         DomainNames.objects.create(
-            domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            domain_names={'test_setup': [{'id': {'required': 'true','type': 'String'}}]},
             assigned_object_type=vm_content_type,
             assigned_object_id=virtual_machine2.pk)
 
         cls.form_data = {
-            "domain_names": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            "domain_names": {'test_setup': [{'id': {'required': 'true','type': 'String'}}]},
             "virtual_machine": virtual_machine2.pk,
 
         }
 
+    def test_create_valid_domain_names(self):
+        """Test domain name valid creation"""
+        # pylint: disable=W0201
+        self.vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
+        # pylint: disable=W0201
+        self.cluster_type = ClusterType.objects.create(name="Test ClusterType2", slug="ClusterType2")
+        # pylint: disable=W0201
+        self.cluster = Cluster.objects.create(name="Test Cluster2", type=self.cluster_type)
+        # pylint: disable=W0201
+        self.virtual_machine_valid = VirtualMachine.objects.create(name="Test VM",status="active",cluster=self.cluster)
+
+        form = DomainNamesForm(data= {
+            "domain_names": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            "virtual_machine": self.virtual_machine_valid.pk,
+        })
+
+        self.assertTrue(form.is_valid())
+        # Setup object permissions for the test user
+        obj_perm = ObjectPermission(
+            name='Test permission',
+            actions=['add', 'change']
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)  # pylint: disable=no-member
+        obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # pylint: disable=no-member
+
+
     def test_create_domain_names_with_no_assigment(self):
         """Test the creation a VM domain names with no VM assignement"""
 
         form = DomainNamesForm(data= {
-            "domain_names": {'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            "domain_names": {'test_no_assignment': [{'id': {'required': 'true','type': 'String'}}]},
         })
 
         self.assertFalse(form.is_valid())
@@ -66,14 +93,14 @@ class DomainNamesFormTestCase(
         # pylint: disable=W0201
         self.vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
         # pylint: disable=W0201
-        self.cluster_type = ClusterType.objects.create(name="Test ClusterType2", slug="ClusterType2")
+        self.cluster_type = ClusterType.objects.create(name="Test ClusterType3", slug="ClusterType3")
         # pylint: disable=W0201
-        self.cluster = Cluster.objects.create(name="Test Cluster2", type=self.cluster_type)
+        self.cluster = Cluster.objects.create(name="Test Cluster3", type=self.cluster_type)
         # pylint: disable=W0201
         self.virtual_machine = VirtualMachine.objects.create(name="Test VM",status="active",cluster=self.cluster)
         # pylint: disable=W0201
         self.domain_names = DomainNames.objects.create(
-            domain_names={'test_valid': [{'id': {'required': 'true','type': 'String'}}]},
+            domain_names={'test_2_assignment': [{'id': {'required': 'true','type': 'String'}}]},
             assigned_object_type=self.vm_content_type,
             assigned_object_id=self.virtual_machine.pk
             )
@@ -107,9 +134,9 @@ class DomainNamesFormTestCase(
         # pylint: disable=W0201
         self.vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
         # pylint: disable=W0201
-        self.cluster_type = ClusterType.objects.create(name="Test ClusterType3", slug="ClusterType3")
+        self.cluster_type = ClusterType.objects.create(name="Test ClusterType4", slug="ClusterType4")
         # pylint: disable=W0201
-        self.cluster = Cluster.objects.create(name="Test Cluster3", type=self.cluster_type)
+        self.cluster = Cluster.objects.create(name="Test Cluster4", type=self.cluster_type)
         # pylint: disable=W0201
         self.virtual_machine = VirtualMachine.objects.create(name="Test VM",status="active",cluster=self.cluster)
 
-- 
GitLab


From 8baea6b5efb9cbbefcbfb247253e25a5b7d354d4 Mon Sep 17 00:00:00 2001
From: Magdalena GomezFayren Medina <magdalena.gomez@ext.ec.europa.eu>
Date: Wed, 22 Jan 2025 12:15:33 +0000
Subject: [PATCH 11/11] Added api tests

---
 netbox_sys_plugin/api/serializers.py          |  11 +-
 .../test_vm_machine_type_api.py               | 184 ++++++++++++++++++
 2 files changed, 194 insertions(+), 1 deletion(-)
 create mode 100644 netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_api.py

diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py
index f9086f8..aeb45da 100644
--- a/netbox_sys_plugin/api/serializers.py
+++ b/netbox_sys_plugin/api/serializers.py
@@ -91,12 +91,21 @@ class VirtualMachineTypeSerializer(NetBoxModelSerializer):
     id = serializers.IntegerField(read_only=True)
     cluster_type = ClusterTypeSerializer(source='assigned_object', read_only=True)
     assigned_object_id = serializers.IntegerField(write_only=True)
-    assigned_object_type = serializers.CharField(write_only=True)
+    assigned_object_type = serializers.PrimaryKeyRelatedField(
+        queryset=ContentType.objects.all(),
+        write_only=True,
+        )
+    display = serializers.CharField(source="__str__",read_only=True)
 
     class Meta:
         model = VirtualMachineType
         fields = '__all__'
 
+    def create(self, validated_data):
+        assigned_object_type = validated_data.pop("assigned_object_type")
+        validated_data["assigned_object_type"] = assigned_object_type
+        return super().create(validated_data)
+
 class VmAssignedVirtualMachineTypeSerializer(NetBoxModelSerializer):
     id = serializers.IntegerField(read_only=True)
     virtual_machine = NestedVirtualMachineSerializer(source='assigned_object', read_only=True)
diff --git a/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_api.py b/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_api.py
new file mode 100644
index 0000000..81b1e81
--- /dev/null
+++ b/netbox_sys_plugin/tests/vm_machine_type/test_vm_machine_type_api.py
@@ -0,0 +1,184 @@
+"""SYS Plugin VM Machine Type API Test Case Class"""
+
+from users.models import ObjectPermission
+from django.contrib.contenttypes.models import ContentType
+from rest_framework import status
+from virtualization.models import ClusterType
+from netbox_sys_plugin.models import VirtualMachineType
+from ..base import BaseAPITestCase
+
+class VmMachineTypeApiTestCase(BaseAPITestCase):
+    """Test suite for VmMachineType API"""
+    model = VirtualMachineType
+    brief_fields = ["virtual_machine_type_name","virtual_machine_type_desc","assigned_object_id", "assigned_object_type"]
+
+
+    @classmethod
+    def setUpTestData(cls):
+
+        """Set up test data for VirtualMachineType API"""
+        cls.cluster_type_ct = ContentType.objects.get_for_model(ClusterType)
+
+        # Create a ClusterType
+        cls.cluster_type = ClusterType.objects.create(name="Test ClusterType1", slug="ClusterType1")
+
+
+        # Create a ClusterType2
+        cls.cluster_type2 = ClusterType.objects.create(name="Test ClusterType2", slug="ClusterType2")
+
+        # Create vm machine type entries linked to the ClusterType
+        VirtualMachineType.objects.create(
+            virtual_machine_type_name='vm_type_name1',
+            virtual_machine_type_desc='vm_type_desc_1',
+            assigned_object=cls.cluster_type
+        )
+        VirtualMachineType.objects.create(
+            virtual_machine_type_name='vm_type_name2',
+            virtual_machine_type_desc='vm_type_desc_2',
+            assigned_object=cls.cluster_type2
+        )
+
+        # Data for valid creation
+        cls.valid_create_data = [
+            {
+                "virtual_machine_type_name": "vm_type_name3",
+                "virtual_machine_type_desc":"vm_type_desc_3",
+                "assigned_object_type": cls.cluster_type_ct.pk,
+                "assigned_object_id": cls.cluster_type.id,
+            }
+        ]
+
+        # Data for invalid creation
+        cls.invalid_create_data = [
+            {
+                "virtual_machine_type_name": "vm_type_name4",
+                "virtual_machine_type_desc":"vm_type_desc_4",
+                "assigned_object_type": cls.cluster_type_ct.pk,
+                "assigned_object_id": None,  # Missing ClusterType
+            },
+        ]
+
+        # Data for checking  unique key
+        cls.valid_check_unique_data = [
+            {
+                "virtual_machine_type_name": "vm_type_name4", # Same name
+                "virtual_machine_type_desc":"vm_type_desc_different",
+                "assigned_object_type": cls.cluster_type_ct.pk,
+                "assigned_object_id": cls.cluster_type2.id, # Same ClusterType
+            },
+            {
+                "virtual_machine_type_name": "vm_type_name4", # Same name
+                "virtual_machine_type_desc":"vm_type_desc_4",
+                "assigned_object_type": cls.cluster_type_ct.pk,
+                "assigned_object_id": cls.cluster_type2.id, # Same ClusterType
+            }
+        ]
+
+    def test_create_valid_machine_type(self):
+        """Test creating a valid machine type"""
+        obj_perm = ObjectPermission(
+            name="Create Machine Type Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(VirtualMachineType))
+
+        form_data = self.valid_create_data[0]
+        response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_201_CREATED)
+        self.assertEqual(response.data["virtual_machine_type_name"], form_data["virtual_machine_type_name"])
+        self.assertEqual(response.data["virtual_machine_type_desc"], form_data["virtual_machine_type_desc"])
+
+
+    def test_machine_type_unique_key(self):
+        """Test machine type unique key"""
+        obj_perm = ObjectPermission(
+            name="Invalid Machine Type Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(VirtualMachineType))
+
+        for form_data in self.valid_check_unique_data:
+            response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_invalid_machine_type(self):
+        """Test creating invalid machine type"""
+        obj_perm = ObjectPermission(
+            name="Invalid Machine Type Permission",
+            actions=["add", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(VirtualMachineType))
+
+        for form_data in self.invalid_create_data:
+            response = self.client.post(self._get_list_url(), form_data, format="json", **self.header)
+            self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
+
+    def test_update_machine_type(self):
+        """Test updating an existing machine type"""
+        machine_type = VirtualMachineType.objects.first()
+        obj_perm = ObjectPermission(
+            name="Update Machine Type Permission",
+            actions=["change", "view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(VirtualMachineType))
+
+        update_data = {"virtual_machine_type_name": "vm_type_name_updated",
+                       "virtual_machine_type_desc":"vm_type_desc_updated"}
+
+        response = self.client.patch(self._get_detail_url(machine_type), update_data, format="json", **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(response.data["virtual_machine_type_name"], update_data["virtual_machine_type_name"])
+        self.assertEqual(response.data["virtual_machine_type_desc"], update_data["virtual_machine_type_desc"])
+
+    def test_delete_machine_type(self):
+        """Test deleting a machine type"""
+        machine_type = VirtualMachineType.objects.first()
+        obj_perm = ObjectPermission(
+            name="Delete Machine Type Permission",
+            actions=["delete"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(VirtualMachineType))
+
+        response = self.client.delete(self._get_detail_url(machine_type), **self.header)
+        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
+        self.assertFalse(VirtualMachineType.objects.filter(id=machine_type.id).exists())
+
+    def test_get_all_machine_type(self):
+        """Test fetching all machine types"""
+        obj_perm = ObjectPermission(
+            name="View Machine Type Permission",
+            actions=["view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(VirtualMachineType))
+
+        response = self.client.get(self._get_list_url(), **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertGreaterEqual(len(response.data), 2)
+
+    def test_get_single_machine_type(self):
+        """Test fetching a single machine type"""
+        obj_perm = ObjectPermission(
+            name="View Machine Type Permission",
+            actions=["view"],
+        )
+        obj_perm.save()
+        obj_perm.users.add(self.user)
+        obj_perm.object_types.add(ContentType.objects.get_for_model(VirtualMachineType))
+
+        machine_type = VirtualMachineType.objects.first()
+        response = self.client.get(self._get_detail_url(machine_type), **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(response.data["virtual_machine_type_name"], machine_type.virtual_machine_type_name)
+        self.assertEqual(response.data["virtual_machine_type_desc"], machine_type.virtual_machine_type_desc)
-- 
GitLab