From ef06c3c6539d330d138ccf0b95434a35c528f095 Mon Sep 17 00:00:00 2001 From: Travis Smith <141754521+tsm1th@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:08:23 +0000 Subject: [PATCH] Resolves 182 --- pynautobot/models/extras.py | 33 +++++++++- tests/fixtures/extras/dynamic_group.json | 20 ++++++ tests/fixtures/extras/dynamic_groups.json | 47 ++++++++++++++ tests/fixtures/extras/members.json | 75 +++++++++++++++++++++++ tests/test_extras.py | 23 +++++++ 5 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/extras/dynamic_group.json create mode 100644 tests/fixtures/extras/dynamic_groups.json create mode 100644 tests/fixtures/extras/members.json create mode 100644 tests/test_extras.py diff --git a/pynautobot/models/extras.py b/pynautobot/models/extras.py index cb10f9a..aad8805 100644 --- a/pynautobot/models/extras.py +++ b/pynautobot/models/extras.py @@ -16,7 +16,7 @@ This file has been modified by NetworktoCode, LLC. """ -from pynautobot.core.endpoint import JobsEndpoint +from pynautobot.core.endpoint import JobsEndpoint, DetailEndpoint from pynautobot.core.response import JsonField, Record @@ -44,3 +44,34 @@ class Jobs(Record): def run(self, **kwargs): """Run a job from within a job instance.""" return JobsEndpoint(self.api, self.api.extras, "jobs").run(class_path=self.id, **kwargs) + + +class DynamicGroups(Record): + def __str__(self): + parent_record_string = super().__str__() + return parent_record_string or str(self.id) + + @property + def members(self): + """Represents the ``members`` detail endpoint. + + Returns a list of DetailEndpoint objects that are + related to the dynamic group + + :returns: :py:class:`.DetailEndpoint` + + :Examples: + + Dynamic group of devices: + + >>> group = nb.extras.dynamic_groups.get("device-group") + >>> group.members.list() + [...] + + Dynamic group of IPs: + + >>> group = nb.extras.dynamic_groups.get("ip-group") + >>> group.members.list() + [...] + """ + return DetailEndpoint(self, "members", custom_return=DynamicGroups) diff --git a/tests/fixtures/extras/dynamic_group.json b/tests/fixtures/extras/dynamic_group.json new file mode 100644 index 0000000..609fa8b --- /dev/null +++ b/tests/fixtures/extras/dynamic_group.json @@ -0,0 +1,20 @@ +{ + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "object_type": "extras.dynamicgroup", + "display": "test-group", + "url": "http://localhost:8000/api/extras/dynamic-groups/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "natural_slug": "test-group_e755", + "content_type": "dcim.device", + "name": "test-group", + "description": "", + "filter": { + "rack": [ + "0a372aaf-b25b-4fdc-be09-cc857981a7bb" + ] + }, + "children": [], + "created": "2024-04-19T12:57:16.695546Z", + "last_updated": "2024-04-19T12:57:49.122196Z", + "notes_url": "http://localhost:8000/api/extras/dynamic-groups/5b39ba88-e5ab-4be2-89f5-5a016473b53c/notes/", + "custom_fields": {} +} \ No newline at end of file diff --git a/tests/fixtures/extras/dynamic_groups.json b/tests/fixtures/extras/dynamic_groups.json new file mode 100644 index 0000000..95d02d1 --- /dev/null +++ b/tests/fixtures/extras/dynamic_groups.json @@ -0,0 +1,47 @@ +{ + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "object_type": "extras.dynamicgroup", + "display": "test-group", + "url": "http://localhost:8000/api/extras/dynamic-groups/5b39ba88-e5ab-4be2-89f5-5a016473b53c/", + "natural_slug": "test-group_e755", + "content_type": "dcim.device", + "name": "test-group", + "description": "", + "filter": { + "rack": [ + "0a372aaf-b25b-4fdc-be09-cc857981a7bb" + ] + }, + "children": [], + "created": "2024-04-19T12:57:16.695546Z", + "last_updated": "2024-04-19T12:57:49.122196Z", + "notes_url": "http://localhost:8000/api/extras/dynamic-groups/5b39ba88-e5ab-4be2-89f5-5a016473b53c/notes/", + "custom_fields": {} + }, + { + "id": "e7555cc1-2224-42f2-ac90-cd4c75f12345", + "object_type": "extras.dynamicgroup", + "display": "test-group-2", + "url": "http://localhost:8000/api/extras/dynamic-groups/e7555cc1-2224-42f2-ac90-cd4c75f12345/", + "natural_slug": "test-group_e789", + "content_type": "dcim.device", + "name": "test-group-2", + "description": "", + "filter": { + "rack": [ + "0a372aaf-b25b-4fdc-be09-cc8579812345" + ] + }, + "children": [], + "created": "2024-04-19T12:57:16.695546Z", + "last_updated": "2024-04-19T12:57:49.122196Z", + "notes_url": "http://localhost:8000/api/extras/dynamic-groups/e7555cc1-2224-42f2-ac90-cd4c75f12345/notes/", + "custom_fields": {} + } + ] + } \ No newline at end of file diff --git a/tests/fixtures/extras/members.json b/tests/fixtures/extras/members.json new file mode 100644 index 0000000..8193531 --- /dev/null +++ b/tests/fixtures/extras/members.json @@ -0,0 +1,75 @@ +[{ + "id": "5b39ba88-e5ab-4be2-89f5-5a016473b53c", + "name": "test1-edge1", + "display_name": "test1-edge1", + "device_type": { + "id": 1, + "url": "http://localhost:8000/api/dcim/device-types/1/", + "manufacturer": { + "id": 1, + "url": "http://localhost:8000/api/dcim/manufacturers/1/", + "name": "Juniper", + "slug": "juniper" + }, + "model": "MX960", + "slug": "mx960" + }, + "device_role": { + "id": 1, + "url": "http://localhost:8000/api/dcim/device-roles/1/", + "name": "Router", + "slug": "router" + }, + "tenant": null, + "platform": { + "id": 1, + "url": "http://localhost:8000/api/dcim/platforms/1/", + "name": "Juniper Junos", + "slug": "juniper-junos" + }, + "serial": "5555555555", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost:8000/api/dcim/sites/1/", + "name": "TEST1", + "slug": "test1" + }, + "rack": { + "id": 1, + "url": "http://localhost:8000/api/dcim/racks/1/", + "name": "A1R1", + "display_name": "A1R1 (T23A01)" + }, + "position": 1, + "face": { + "value": 0, + "label": "Front" + }, + "parent_device": null, + "status": { + "value": true, + "label": "Active" + }, + "primary_ip": { + "id": 1, + "url": "http://localhost:8000/api/ipam/ip-addresses/1/", + "family": 4, + "address": "10.0.255.1/32" + }, + "primary_ip4": { + "id": 1, + "url": "http://localhost:8000/api/ipam/ip-addresses/1/", + "family": 4, + "address": "10.0.255.1/32" + }, + "primary_ip6": null, + "comments": "", + "local_context_data": { + "testing": "test" + }, + "custom_fields": {}, + "config_context": { + "test_key": "test_val" + } +}] diff --git a/tests/test_extras.py b/tests/test_extras.py new file mode 100644 index 0000000..f409cbc --- /dev/null +++ b/tests/test_extras.py @@ -0,0 +1,23 @@ +from unittest.mock import patch + +from . import Generic, HEADERS +from .util import Response + + +class DynamicGroupTestCase(Generic.Tests): + app = "extras" + name = "dynamic_groups" + name_singular = "dynamic_group" + + @patch( + "requests.sessions.Session.get", + side_effect=[Response(fixture="extras/dynamic_group.json"), Response(fixture="extras/members.json")], + ) + def test_get_members(self, mock): + dg = self.endpoint.get(self.uuid) + ret = dg.members.list() + mock.assert_called_with(f"{self.detail_uri}members/", params={}, json=None, headers=HEADERS) + self.assertTrue(ret) + self.assertEqual(len(ret), 1) + for record in ret: + self.assertEqual(record.name, str(record))