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