diff --git a/api/tacticalrmm/agents/tests.py b/api/tacticalrmm/agents/tests.py index d14603b8..058d6464 100644 --- a/api/tacticalrmm/agents/tests.py +++ b/api/tacticalrmm/agents/tests.py @@ -12,7 +12,7 @@ from tacticalrmm.test import TacticalTestCase from winupdate.models import WinUpdatePolicy from winupdate.serializers import WinUpdatePolicySerializer -from .models import Agent +from .models import Agent, AgentCustomField from .serializers import AgentSerializer from .tasks import auto_self_agent_update_task @@ -534,6 +534,35 @@ class TestAgentViews(TacticalTestCase): data = WinUpdatePolicySerializer(policy).data self.assertEqual(data["run_time_days"], [2, 3, 6]) + # test adding custom fields + field = baker.make("core.CustomField", model="agent", type="text") + edit = { + "id": self.agent.pk, + "site": site.id, # type: ignore + "description": "asjdk234andasd", + "custom_fields": [{"field": field.id, "value": "new value"}], # type: ignore + } + + r = self.client.patch(url, edit, format="json") + self.assertEqual(r.status_code, 200) + self.assertTrue( + AgentCustomField.objects.filter(agent=self.agent, field=field).exists() + ) + + # test edit custom field + edit = { + "id": self.agent.pk, + "site": site.id, # type: ignore + "description": "asjdk234andasd", + "custom_fields": [{"field": field.id, "value": "another value"}], # type: ignore + } + + r = self.client.patch(url, edit, format="json") + self.assertEqual(r.status_code, 200) + self.assertEqual( + AgentCustomField.objects.get(agent=agent, field=field).value, + "another value", + ) self.check_not_authenticated("patch", url) @patch("agents.models.Agent.get_login_token") diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index 6bbe8f3a..5cd14f9a 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -104,36 +104,36 @@ def edit_agent(request): p_serializer.is_valid(raise_exception=True) p_serializer.save() - if "custom_fields" in request.data.keys(): + if "custom_fields" in request.data.keys(): - for field in request.data["custom_fields"]: + for field in request.data["custom_fields"]: - # get custom field for validation - obj = CustomField.objects.get(pk=field["field"]) + # get custom field for validation + obj = CustomField.objects.get(pk=field["field"]) - if obj.default_value and field.value == obj.default_value: - continue + if obj.default_value and field.value == obj.default_value: + continue - custom_field = { - "value": field["value"], - "field": field["field"], - "agent": agent.id, - } - if AgentCustomField.objects.filter( - field=field["field"], agent=agent.id - ): - value = AgentCustomField.objects.get( - field=field["field"], agent=agent.id - ) - serializer = AgentCustomFieldSerializer( - instance=value, data=custom_field - ) - serializer.is_valid(raise_exception=True) - serializer.save() - else: - serializer = AgentCustomFieldSerializer(data=custom_field) - serializer.is_valid(raise_exception=True) - serializer.save() + custom_field = { + "value": field["value"], + "field": field["field"], + "agent": agent.id, # type: ignore + } + if AgentCustomField.objects.filter( + field=field["field"], agent=agent.id # type: ignore + ): + value = AgentCustomField.objects.get( + field=field["field"], agent=agent.id # type: ignore + ) + serializer = AgentCustomFieldSerializer( + instance=value, data=custom_field + ) + serializer.is_valid(raise_exception=True) + serializer.save() + else: + serializer = AgentCustomFieldSerializer(data=custom_field) + serializer.is_valid(raise_exception=True) + serializer.save() return Response("ok") diff --git a/api/tacticalrmm/clients/tests.py b/api/tacticalrmm/clients/tests.py index 507f4977..65f179b1 100644 --- a/api/tacticalrmm/clients/tests.py +++ b/api/tacticalrmm/clients/tests.py @@ -6,7 +6,7 @@ from rest_framework.serializers import ValidationError from tacticalrmm.test import TacticalTestCase -from .models import Client, Deployment, Site +from .models import Client, Deployment, Site, ClientCustomField, SiteCustomField from .serializers import ( ClientSerializer, ClientTreeSerializer, @@ -94,8 +94,35 @@ class TestClientViews(TacticalTestCase): r = self.client.post(url, payload, format="json") self.assertEqual(r.status_code, 200) + # test add with custom fields + field = baker.make("core.CustomField", model="client", type="text") + payload = { + "client": {"name": "Custom Field Client"}, + "site": {"name": "Setup Site"}, + "custom_fields": [{"field": field.id, "value": "new Value"}], # type: ignore + } + r = self.client.post(url, payload, format="json") + self.assertEqual(r.status_code, 200) + + client = Client.objects.get(name="Custom Field Client") + self.assertTrue( + ClientCustomField.objects.filter(client=client, field=field).exists() + ) + self.check_not_authenticated("post", url) + def test_get_client(self): + # setup data + client = baker.make("clients.Client") + + url = f"/clients/{client.id}/client/" # type: ignore + r = self.client.get(url, format="json") + serializer = ClientSerializer(client) + self.assertEqual(r.status_code, 200) + self.assertEqual(r.data, serializer.data) # type: ignore + + self.check_not_authenticated("get", url) + def test_edit_client(self): # setup data client = baker.make("clients.Client", name="OldClientName") @@ -118,6 +145,39 @@ class TestClientViews(TacticalTestCase): r = self.client.put(url, data, format="json") self.assertEqual(r.status_code, 400) + # test add with custom fields new value + field = baker.make("core.CustomField", model="client", type="text") + payload = { + "client": { + "id": client.id, # type: ignore + "name": "Custom Field Client", + }, + "custom_fields": [{"field": field.id, "value": "new Value"}], # type: ignore + } + r = self.client.put(url, payload, format="json") + self.assertEqual(r.status_code, 200) + + client = Client.objects.get(name="Custom Field Client") + self.assertTrue( + ClientCustomField.objects.filter(client=client, field=field).exists() + ) + + # edit custom field value + payload = { + "client": { + "id": client.id, # type: ignore + "name": "Custom Field Client", + }, + "custom_fields": [{"field": field.id, "value": "another value"}], # type: ignore + } + r = self.client.put(url, payload, format="json") + self.assertEqual(r.status_code, 200) + + self.assertTrue( + ClientCustomField.objects.get(client=client, field=field).value, + "another value", + ) + self.check_not_authenticated("put", url) @patch("automation.tasks.generate_all_agent_checks_task.delay") @@ -201,8 +261,32 @@ class TestClientViews(TacticalTestCase): ): self.assertFalse(serializer.is_valid(raise_exception=True)) + # test add with custom fields + field = baker.make("core.CustomField", model="site", type="text") + payload = { + "site": {"client": client.id, "name": "Custom Field Site"}, # type: ignore + "custom_fields": [{"field": field.id, "value": "new Value"}], # type: ignore + } + r = self.client.post(url, payload, format="json") + self.assertEqual(r.status_code, 200) + + site = Site.objects.get(name="Custom Field Site") + self.assertTrue(SiteCustomField.objects.filter(site=site, field=field).exists()) + self.check_not_authenticated("post", url) + def test_get_site(self): + # setup data + site = baker.make("clients.Site") + + url = f"/clients/sites/{site.id}/" # type: ignore + r = self.client.get(url, format="json") + serializer = SiteSerializer(site) + self.assertEqual(r.status_code, 200) + self.assertEqual(r.data, serializer.data) # type: ignore + + self.check_not_authenticated("get", url) + def test_edit_site(self): # setup data client = baker.make("clients.Client") @@ -224,6 +308,39 @@ class TestClientViews(TacticalTestCase): Site.objects.filter(client=client, name="New Site Name").exists() ) + # test add with custom fields new value + field = baker.make("core.CustomField", model="site", type="text") + payload = { + "site": { + "id": site.id, # type: ignore + "client": site.client.id, # type: ignore + "name": "Custom Field Site", + }, + "custom_fields": [{"field": field.id, "value": "new Value"}], # type: ignore + } + r = self.client.put(url, payload, format="json") + self.assertEqual(r.status_code, 200) + + site = Site.objects.get(name="Custom Field Site") + self.assertTrue(SiteCustomField.objects.filter(site=site, field=field).exists()) + + # edit custom field value + payload = { + "site": { + "id": site.id, # type: ignore + "client": client.id, # type: ignore + "name": "Custom Field Site", + }, + "custom_fields": [{"field": field.id, "value": "another value"}], # type: ignore + } + r = self.client.put(url, payload, format="json") + self.assertEqual(r.status_code, 200) + + self.assertTrue( + SiteCustomField.objects.get(site=site, field=field).value, + "another value", + ) + self.check_not_authenticated("put", url) @patch("automation.tasks.generate_all_agent_checks_task.delay") diff --git a/api/tacticalrmm/core/tests.py b/api/tacticalrmm/core/tests.py index 0e234c27..71e42a6f 100644 --- a/api/tacticalrmm/core/tests.py +++ b/api/tacticalrmm/core/tests.py @@ -1,9 +1,10 @@ from unittest.mock import patch -from model_bakery import baker, seq +from model_bakery import baker -from core.models import CoreSettings -from core.tasks import core_maintenance_tasks +from .models import CoreSettings, CustomField +from .tasks import core_maintenance_tasks +from .serializers import CustomFieldSerializer from tacticalrmm.test import TacticalTestCase @@ -42,7 +43,7 @@ class TestCoreTasks(TacticalTestCase): url = "/core/editsettings/" # setup - policies = baker.make("Policy", _quantity=2) + policies = baker.make("automation.Policy", _quantity=2) # test normal request data = { "smtp_from_email": "newexample@example.com", @@ -59,14 +60,14 @@ class TestCoreTasks(TacticalTestCase): # test adding policy data = { - "workstation_policy": policies[0].id, - "server_policy": policies[1].id, + "workstation_policy": policies[0].id, # type: ignore + "server_policy": policies[1].id, # type: ignore } r = self.client.patch(url, data) self.assertEqual(r.status_code, 200) - self.assertEqual(CoreSettings.objects.first().server_policy.id, policies[1].id) + self.assertEqual(CoreSettings.objects.first().server_policy.id, policies[1].id) # type: ignore self.assertEqual( - CoreSettings.objects.first().workstation_policy.id, policies[0].id + CoreSettings.objects.first().workstation_policy.id, policies[0].id # type: ignore ) self.assertEqual(generate_all_agent_checks_task.call_count, 2) @@ -128,3 +129,97 @@ class TestCoreTasks(TacticalTestCase): remove_orphaned_win_tasks.assert_called() self.check_not_authenticated("post", url) + + def test_get_custom_fields(self): + url = "/core/customfields/" + + # setup + custom_fields = baker.make("core.CustomField", _quantity=2) + + r = self.client.get(url) + serializer = CustomFieldSerializer(custom_fields, many=True) + self.assertEqual(r.status_code, 200) + self.assertEqual(len(r.data), 2) # type: ignore + self.assertEqual(r.data, serializer.data) # type: ignore + + self.check_not_authenticated("get", url) + + def test_get_custom_fields_by_model(self): + url = "/core/customfields/" + + # setup + custom_fields = baker.make("core.CustomField", model="agent", _quantity=5) + baker.make("core.CustomField", model="client", _quantity=5) + + # will error if request invalid + r = self.client.patch(url, {"invalid": ""}) + self.assertEqual(r.status_code, 400) + + data = {"model": "agent"} + r = self.client.patch(url, data) + serializer = CustomFieldSerializer(custom_fields, many=True) + self.assertEqual(r.status_code, 200) + self.assertEqual(len(r.data), 5) # type: ignore + self.assertEqual(r.data, serializer.data) # type: ignore + + self.check_not_authenticated("patch", url) + + def test_add_custom_field(self): + url = "/core/customfields/" + + data = {"model": "client", "type": "text", "name": "Field"} + r = self.client.patch(url, data) + self.assertEqual(r.status_code, 200) + + self.check_not_authenticated("post", url) + + def test_get_custom_field(self): + # setup + custom_field = baker.make("core.CustomField") + + # test not found + r = self.client.get("/core/customfields/500/") + self.assertEqual(r.status_code, 404) + + url = f"/core/customfields/{custom_field.id}/" # type: ignore + r = self.client.get(url) + serializer = CustomFieldSerializer(custom_field) + self.assertEqual(r.status_code, 200) + self.assertEqual(r.data, serializer.data) # type: ignore + + self.check_not_authenticated("get", url) + + def test_update_custom_field(self): + # setup + custom_field = baker.make("core.CustomField") + + # test not found + r = self.client.put("/core/customfields/500/") + self.assertEqual(r.status_code, 404) + + url = f"/core/customfields/{custom_field.id}/" # type: ignore + data = {"type": "single", "options": ["ione", "two", "three"]} + r = self.client.put(url, data) + self.assertEqual(r.status_code, 200) + + new_field = CustomField.objects.get(pk=custom_field.id) # type: ignore + self.assertEqual(new_field.type, data["type"]) + self.assertEqual(new_field.options, data["options"]) + + self.check_not_authenticated("put", url) + + def test_delete_custom_field(self): + # setup + custom_field = baker.make("core.CustomField") + + # test not found + r = self.client.delete("/core/customfields/500/") + self.assertEqual(r.status_code, 404) + + url = f"/core/customfields/{custom_field.id}/" # type: ignore + r = self.client.delete(url) + self.assertEqual(r.status_code, 200) + + self.assertFalse(CustomField.objects.filter(pk=custom_field.id).exists()) # type: ignore + + self.check_not_authenticated("delete", url) \ No newline at end of file diff --git a/web/package.json b/web/package.json index b64f1b9c..060c4de0 100644 --- a/web/package.json +++ b/web/package.json @@ -41,4 +41,4 @@ "last 3 iOS versions", "last 2 Opera versions" ] -} \ No newline at end of file +}