summaryrefslogtreecommitdiff
path: root/tests/unit/modules
diff options
context:
space:
mode:
authoromnom62 <omnom62@outlook.com>2026-05-27 21:38:51 +1000
committeromnom62 <omnom62@outlook.com>2026-05-27 21:38:51 +1000
commitaeedc73003ddcf84bbf5c2880f982b6d4925e0c9 (patch)
treeb2bf9705e2fe6db7dbaa51afc5fd60ca0f7878da /tests/unit/modules
parent5a6f6976b49fd5ca8e964b2342493bc5ceee85d5 (diff)
downloadrest.vyos-aeedc73003ddcf84bbf5c2880f982b6d4925e0c9.tar.gz
rest.vyos-aeedc73003ddcf84bbf5c2880f982b6d4925e0c9.zip
Test framework
Diffstat (limited to 'tests/unit/modules')
-rw-r--r--tests/unit/modules/base.py53
-rw-r--r--tests/unit/modules/conftest.py2
-rw-r--r--tests/unit/modules/test_vyos_logging_global.py229
-rw-r--r--tests/unit/modules/test_vyos_ntp_global.py169
-rw-r--r--tests/unit/modules/test_vyos_prefix_lists.py209
-rw-r--r--tests/unit/modules/test_vyos_route_maps.py180
6 files changed, 842 insertions, 0 deletions
diff --git a/tests/unit/modules/base.py b/tests/unit/modules/base.py
new file mode 100644
index 0000000..e5a602e
--- /dev/null
+++ b/tests/unit/modules/base.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock, patch # noqa: F401
+
+
+def load_fixture(filename):
+ """Load a JSON fixture file from tests/unit/fixtures/."""
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ path = os.path.join(fixtures_dir, filename)
+ with open(path) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ """
+ Base class for vyos.rest module unit tests.
+
+ Provides a mock VyOSModule that returns fixture data from
+ get_config() without any device connection.
+
+ Usage:
+ class TestVyOSNtpGlobal(VyOSModuleTestCase):
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("ntp_global_running.json")
+
+ def test_merged_adds_new_server(self):
+ have = self.module.get_running_config_from_fixture(self.fixture)
+ commands = build_commands(want, have, "merged")
+ self.assertIn(("set", ["service", "ntp", "server", "1.2.3.4"]), commands)
+ """
+
+ def setUp(self):
+ self.mock_module = MagicMock()
+ self.mock_module.params = {}
+ self.mock_module.check_mode = False
+
+ self.mock_vyos = MagicMock()
+ self.mock_vyos.get_config = MagicMock(return_value={})
+
+ def set_running_config(self, data):
+ """Configure mock get_config to return given data."""
+ self.mock_vyos.get_config.return_value = data
diff --git a/tests/unit/modules/conftest.py b/tests/unit/modules/conftest.py
new file mode 100644
index 0000000..26b7d8b
--- /dev/null
+++ b/tests/unit/modules/conftest.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+# conftest.py — shared fixtures for vyos.rest unit tests
diff --git a/tests/unit/modules/test_vyos_logging_global.py b/tests/unit/modules/test_vyos_logging_global.py
new file mode 100644
index 0000000..e9ecc8c
--- /dev/null
+++ b/tests/unit/modules/test_vyos_logging_global.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import os
+import sys
+import unittest
+
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_logging_global import (
+ build_commands,
+ normalize_config,
+ normalize_running,
+)
+
+from tests.unit.modules.base import VyOSModuleTestCase, load_fixture
+
+
+class TestVyOSLoggingGlobalNormalize(unittest.TestCase):
+
+ def test_normalize_config_console(self):
+ cfg = {
+ "console": {
+ "facilities": [{"facility": "local7", "severity": "err"}],
+ },
+ }
+ result = normalize_config(cfg)
+ self.assertIn("local7", result["console"]["facilities"])
+ self.assertEqual(
+ result["console"]["facilities"]["local7"],
+ {"severity": "err", "protocol": None},
+ )
+
+ def test_normalize_config_hosts(self):
+ cfg = {
+ "hosts": [
+ {
+ "hostname": "172.16.0.1",
+ "port": 514,
+ "facilities": [
+ {"facility": "local7", "severity": "all"},
+ {"facility": "all", "protocol": "udp"},
+ ],
+ },
+ ],
+ }
+ result = normalize_config(cfg)
+ self.assertIn("172.16.0.1", result["hosts"])
+ host = result["hosts"]["172.16.0.1"]
+ self.assertEqual(host["port"], 514)
+ self.assertIn("local7", host["facilities"])
+ self.assertEqual(host["facilities"]["local7"]["severity"], "all")
+ self.assertEqual(host["facilities"]["all"]["protocol"], "udp")
+
+ def test_normalize_running_console(self):
+ raw = {
+ "console": {
+ "facility": {
+ "local7": {"level": "err"},
+ "all": {},
+ },
+ },
+ }
+ result = normalize_running(raw)
+ self.assertIn("local7", result["console"]["facilities"])
+ self.assertEqual(result["console"]["facilities"]["local7"]["severity"], "err")
+ self.assertIsNone(result["console"]["facilities"]["all"]["severity"])
+
+ def test_normalize_running_global_archive(self):
+ raw = {
+ "global": {
+ "archive": {"file": "2", "size": "111"},
+ "marker": {"interval": "111"},
+ "preserve-fqdn": {},
+ },
+ }
+ result = normalize_running(raw)
+ self.assertEqual(result["global"]["archive"]["file_num"], 2)
+ self.assertEqual(result["global"]["archive"]["size"], 111)
+ self.assertEqual(result["global"]["marker_interval"], 111)
+ self.assertTrue(result["global"]["preserve_fqdn"])
+
+ def test_normalize_running_host_port_cast(self):
+ raw = {
+ "host": {
+ "172.16.0.1": {
+ "port": "514",
+ "facility": {},
+ },
+ },
+ }
+ result = normalize_running(raw)
+ self.assertEqual(result["hosts"]["172.16.0.1"]["port"], 514)
+
+ def test_normalize_running_empty(self):
+ result = normalize_running({})
+ self.assertEqual(result["console"]["facilities"], {})
+ self.assertEqual(result["hosts"], {})
+
+
+class TestVyOSLoggingGlobalBuildCommands(unittest.TestCase):
+
+ def _empty_have(self):
+ return {
+ "console": {"facilities": {}},
+ "global": {"facilities": {}},
+ "hosts": {},
+ "files": {},
+ "users": {},
+ }
+
+ def test_merged_adds_console_facility(self):
+ want = self._empty_have()
+ want["console"]["facilities"]["local7"] = {"severity": "err", "protocol": None}
+ cmds = build_commands(want, self._empty_have(), "merged")
+ self.assertIn(
+ ("set", ["system", "syslog", "console", "facility", "local7", "level", "err"]),
+ cmds,
+ )
+
+ def test_merged_idempotent_console(self):
+ facs = {"local7": {"severity": "err", "protocol": None}}
+ want = self._empty_have()
+ have = self._empty_have()
+ want["console"]["facilities"] = facs
+ have["console"]["facilities"] = facs.copy()
+ cmds = build_commands(want, have, "merged")
+ self.assertEqual(cmds, [])
+
+ def test_merged_adds_host_with_port(self):
+ want = self._empty_have()
+ want["hosts"]["172.16.0.1"] = {
+ "port": 514,
+ "facilities": {"local7": {"severity": "all", "protocol": None}},
+ }
+ cmds = build_commands(want, self._empty_have(), "merged")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["system", "syslog", "host", "172.16.0.1", "port", "514"], paths)
+ self.assertIn(
+ ["system", "syslog", "host", "172.16.0.1", "facility", "local7", "level", "all"],
+ paths,
+ )
+
+ def test_merged_adds_host_facility_protocol(self):
+ want = self._empty_have()
+ want["hosts"]["172.16.0.1"] = {
+ "port": None,
+ "facilities": {"all": {"severity": None, "protocol": "udp"}},
+ }
+ cmds = build_commands(want, self._empty_have(), "merged")
+ self.assertIn(
+ (
+ "set",
+ ["system", "syslog", "host", "172.16.0.1", "facility", "all", "protocol", "udp"],
+ ),
+ cmds,
+ )
+
+ def test_replaced_removes_extra_host(self):
+ want = self._empty_have()
+ have = self._empty_have()
+ have["hosts"]["172.16.0.1"] = {"port": None, "facilities": {}}
+ cmds = build_commands(want, have, "replaced")
+ self.assertIn(("delete", ["system", "syslog", "host", "172.16.0.1"]), cmds)
+
+ def test_deleted_issues_single_delete(self):
+ have = self._empty_have()
+ have["console"]["facilities"]["all"] = {"severity": None, "protocol": None}
+ cmds = build_commands(self._empty_have(), have, "deleted")
+ self.assertIn(("delete", ["system", "syslog"]), cmds)
+
+ def test_overridden_deletes_then_merges(self):
+ want = self._empty_have()
+ have = self._empty_have()
+ have["console"]["facilities"]["all"] = {"severity": None, "protocol": None}
+ cmds = build_commands(want, have, "overridden")
+ self.assertIn(("delete", ["system", "syslog"]), cmds)
+
+ def test_global_preserve_fqdn_added(self):
+ want = self._empty_have()
+ want["global"]["preserve_fqdn"] = True
+ cmds = build_commands(want, self._empty_have(), "merged")
+ self.assertIn(("set", ["system", "syslog", "global", "preserve-fqdn"]), cmds)
+
+ def test_global_archive(self):
+ want = self._empty_have()
+ want["global"]["archive"] = {"file_num": 2, "size": 111}
+ cmds = build_commands(want, self._empty_have(), "merged")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["system", "syslog", "global", "archive", "file", "2"], paths)
+ self.assertIn(["system", "syslog", "global", "archive", "size", "111"], paths)
+
+
+class TestVyOSLoggingGlobalFixture(VyOSModuleTestCase):
+ """Test parsing against the confirmed device fixture."""
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("logging_global_running.json")
+
+ def test_fixture_parses_console(self):
+ result = normalize_running(self.fixture)
+ self.assertIn("local7", result["console"]["facilities"])
+ self.assertEqual(result["console"]["facilities"]["local7"]["severity"], "err")
+
+ def test_fixture_parses_host_port(self):
+ result = normalize_running(self.fixture)
+ self.assertEqual(result["hosts"]["172.16.0.1"]["port"], 223)
+
+ def test_fixture_parses_global_archive(self):
+ result = normalize_running(self.fixture)
+ self.assertEqual(result["global"]["archive"]["file_num"], 2)
+ self.assertEqual(result["global"]["archive"]["size"], 111)
+
+ def test_fixture_parses_preserve_fqdn(self):
+ result = normalize_running(self.fixture)
+ self.assertTrue(result["global"]["preserve_fqdn"])
+
+ def test_fixture_parses_marker_interval(self):
+ result = normalize_running(self.fixture)
+ self.assertEqual(result["global"]["marker_interval"], 111)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_ntp_global.py b/tests/unit/modules/test_vyos_ntp_global.py
new file mode 100644
index 0000000..d646b1a
--- /dev/null
+++ b/tests/unit/modules/test_vyos_ntp_global.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import os
+import sys
+import unittest
+
+
+# Allow importing collection modules without full ansible-test runner
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_ntp_global import (
+ build_commands,
+ get_running_config,
+ normalize_config,
+ normalize_servers,
+)
+
+from tests.unit.modules.base import VyOSModuleTestCase, load_fixture
+
+
+class TestVyOSNtpGlobalNormalize(unittest.TestCase):
+ """Test normalize_config and normalize_servers — no device needed."""
+
+ def test_normalize_config_empty(self):
+ result = normalize_config({})
+ self.assertEqual(result["allow_clients"], [])
+ self.assertEqual(result["listen_addresses"], [])
+ self.assertEqual(result["servers"], {})
+
+ def test_normalize_config_servers_sorted(self):
+ config = {
+ "servers": [
+ {"server": "b.example.com", "options": ["prefer", "noselect"]},
+ {"server": "a.example.com"},
+ ],
+ }
+ result = normalize_config(config)
+ self.assertIn("a.example.com", result["servers"])
+ self.assertIn("b.example.com", result["servers"])
+ self.assertEqual(result["servers"]["b.example.com"], ["noselect", "prefer"])
+
+ def test_normalize_servers_dict_with_options(self):
+ raw = {
+ "time1.vyos.net": {},
+ "203.0.113.0": {"prefer": {}},
+ }
+ result = normalize_servers(raw)
+ self.assertEqual(result["time1.vyos.net"], [])
+ self.assertEqual(result["203.0.113.0"], ["prefer"])
+
+ def test_normalize_servers_list(self):
+ raw = ["time1.vyos.net", "time2.vyos.net"]
+ result = normalize_servers(raw)
+ self.assertEqual(result["time1.vyos.net"], [])
+
+ def test_normalize_servers_string(self):
+ result = normalize_servers("time1.vyos.net")
+ self.assertEqual(result["time1.vyos.net"], [])
+
+
+class TestVyOSNtpGlobalGetRunning(VyOSModuleTestCase):
+ """Test get_running_config parsing against fixture API responses."""
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("ntp_global_running.json")
+
+ def test_parses_allow_clients(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ self.assertIn("10.6.6.0/24", result["allow_clients"])
+
+ def test_parses_listen_addresses(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ self.assertIn("10.1.3.1", result["listen_addresses"])
+
+ def test_parses_servers(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ self.assertIn("time1.vyos.net", result["servers"])
+ self.assertIn("203.0.113.0", result["servers"])
+ self.assertIn("prefer", result["servers"]["203.0.113.0"])
+
+ def test_empty_config_returns_empty(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result["allow_clients"], [])
+ self.assertEqual(result["servers"], {})
+
+
+class TestVyOSNtpGlobalBuildCommands(unittest.TestCase):
+ """Test build_commands diff logic — no device needed."""
+
+ def _have(self, **kwargs):
+ base = {"allow_clients": [], "listen_addresses": [], "servers": {}}
+ base.update(kwargs)
+ return base
+
+ def _want(self, **kwargs):
+ return self._have(**kwargs)
+
+ def test_merged_adds_new_server(self):
+ want = self._want(servers={"new.server.com": []})
+ have = self._have(servers={})
+ cmds = build_commands(want, have, "merged")
+ self.assertIn(("set", ["service", "ntp", "server", "new.server.com"]), cmds)
+
+ def test_merged_idempotent_existing_server(self):
+ want = self._want(servers={"time1.vyos.net": []})
+ have = self._have(servers={"time1.vyos.net": []})
+ cmds = build_commands(want, have, "merged")
+ self.assertEqual(cmds, [])
+
+ def test_merged_adds_server_option(self):
+ want = self._want(servers={"time1.vyos.net": ["prefer"]})
+ have = self._have(servers={"time1.vyos.net": []})
+ cmds = build_commands(want, have, "merged")
+ self.assertIn(("set", ["service", "ntp", "server", "time1.vyos.net", "prefer"]), cmds)
+
+ def test_replaced_removes_extra_server(self):
+ want = self._want(servers={"time1.vyos.net": []})
+ have = self._have(servers={"time1.vyos.net": [], "time2.vyos.net": []})
+ cmds = build_commands(want, have, "replaced")
+ self.assertIn(("delete", ["service", "ntp", "server", "time2.vyos.net"]), cmds)
+
+ def test_replaced_removes_extra_allow_client(self):
+ want = self._want(allow_clients=["10.1.0.0/24"])
+ have = self._have(allow_clients=["10.1.0.0/24", "10.2.0.0/24"])
+ cmds = build_commands(want, have, "replaced")
+ self.assertIn(
+ ("delete", ["service", "ntp", "allow-client", "address", "10.2.0.0/24"]),
+ cmds,
+ )
+
+ def test_deleted_removes_all(self):
+ want = self._want(servers={}, allow_clients=[], listen_addresses=[])
+ have = self._have(
+ servers={"time1.vyos.net": []},
+ allow_clients=["10.0.0.0/24"],
+ listen_addresses=["192.168.1.1"],
+ )
+ cmds = build_commands(want, have, "deleted")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["service", "ntp", "server", "time1.vyos.net"], paths)
+ self.assertIn(["service", "ntp", "allow-client", "address", "10.0.0.0/24"], paths)
+
+ def test_overridden_deletes_then_merges(self):
+ want = self._want(servers={"new.server.com": []})
+ have = self._have(servers={"old.server.com": []})
+ cmds = build_commands(want, have, "overridden")
+ paths = [c[1] for c in cmds]
+ # overridden deletes existing server subtree first
+ self.assertIn(["service", "ntp", "server"], paths)
+ # then adds new server
+ self.assertIn(["service", "ntp", "server", "new.server.com"], paths)
+
+ def test_no_commands_when_already_correct(self):
+ state = {"allow_clients": ["10.0.0.0/24"], "listen_addresses": [], "servers": {}}
+ cmds = build_commands(state, state, "merged")
+ self.assertEqual(cmds, [])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_prefix_lists.py b/tests/unit/modules/test_vyos_prefix_lists.py
new file mode 100644
index 0000000..d8b75df
--- /dev/null
+++ b/tests/unit/modules/test_vyos_prefix_lists.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import os
+import sys
+import unittest
+
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_prefix_lists import (
+ _normalize,
+ build_commands,
+ get_running_config,
+)
+
+from tests.unit.modules.base import VyOSModuleTestCase, load_fixture
+
+
+class TestVyOSPrefixListsGetRunning(VyOSModuleTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("prefix_lists_running.json")
+
+ def test_parses_ipv4_prefix_list(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ ipv4 = next((e for e in result if e["afi"] == "ipv4"), None)
+ self.assertIsNotNone(ipv4)
+ pl = next((p for p in ipv4["prefix_lists"] if p["name"] == "AnsibleIPv4PrefixList"), None)
+ self.assertIsNotNone(pl)
+ self.assertEqual(pl["description"], "PL configured by ansible")
+
+ def test_parses_ipv4_rules(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ ipv4 = next(e for e in result if e["afi"] == "ipv4")
+ pl = ipv4["prefix_lists"][0]
+ seqs = [r["sequence"] for r in pl["entries"]]
+ self.assertIn(2, seqs)
+ self.assertIn(3, seqs)
+
+ def test_parses_ipv4_rule_fields(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ ipv4 = next(e for e in result if e["afi"] == "ipv4")
+ rule2 = next(r for r in ipv4["prefix_lists"][0]["entries"] if r["sequence"] == 2)
+ self.assertEqual(rule2["action"], "permit")
+ self.assertEqual(rule2["prefix"], "92.168.10.0/26")
+ self.assertEqual(rule2["le"], 32)
+
+ def test_parses_ipv6_prefix_lists(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ ipv6 = next((e for e in result if e["afi"] == "ipv6"), None)
+ self.assertIsNotNone(ipv6)
+ names = [p["name"] for p in ipv6["prefix_lists"]]
+ self.assertIn("AllowIPv6Prefix", names)
+ self.assertIn("DenyIPv6Prefix", names)
+
+ def test_empty_returns_empty_list(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+
+class TestVyOSPrefixListsNormalize(unittest.TestCase):
+
+ def test_normalize_ipv4(self):
+ config = [
+ {
+ "afi": "ipv4",
+ "prefix_lists": [
+ {
+ "name": "PL1",
+ "entries": [{"sequence": 10, "action": "permit", "prefix": "10.0.0.0/8"}],
+ },
+ ],
+ },
+ ]
+ result = _normalize(config)
+ self.assertIn("PL1", result["ipv4"])
+ self.assertIn(10, result["ipv4"]["PL1"]["rules"])
+ self.assertEqual(result["ipv4"]["PL1"]["rules"][10]["action"], "permit")
+
+ def test_normalize_filters_none_values(self):
+ config = [
+ {
+ "afi": "ipv4",
+ "prefix_lists": [
+ {
+ "name": "PL1",
+ "entries": [
+ {
+ "sequence": 10,
+ "action": "permit",
+ "prefix": "10.0.0.0/8",
+ "ge": None,
+ "le": None,
+ },
+ ],
+ },
+ ],
+ },
+ ]
+ result = _normalize(config)
+ rule = result["ipv4"]["PL1"]["rules"][10]
+ self.assertNotIn("ge", rule)
+ self.assertNotIn("le", rule)
+
+
+class TestVyOSPrefixListsBuildCommands(unittest.TestCase):
+
+ def _have_empty(self):
+ return []
+
+ def _have_with_ipv4_pl(self):
+ return [
+ {
+ "afi": "ipv4",
+ "prefix_lists": [
+ {
+ "name": "PL1",
+ "entries": [
+ {
+ "sequence": 10,
+ "action": "permit",
+ "prefix": "10.0.0.0/8",
+ },
+ ],
+ },
+ ],
+ },
+ ]
+
+ def test_merged_adds_new_prefix_list(self):
+ config = [
+ {
+ "afi": "ipv4",
+ "prefix_lists": [
+ {
+ "name": "PL-NEW",
+ "entries": [
+ {
+ "sequence": 5,
+ "action": "permit",
+ "prefix": "192.168.0.0/24",
+ },
+ ],
+ },
+ ],
+ },
+ ]
+ cmds = build_commands(config, self._have_empty(), "merged")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["policy", "prefix-list", "PL-NEW", "rule", "5", "action", "permit"], paths)
+ self.assertIn(
+ ["policy", "prefix-list", "PL-NEW", "rule", "5", "prefix", "192.168.0.0/24"],
+ paths,
+ )
+
+ def test_merged_idempotent_existing_rule(self):
+ config = self._have_with_ipv4_pl()
+ cmds = build_commands(config, self._have_with_ipv4_pl(), "merged")
+ self.assertEqual(cmds, [])
+
+ def test_deleted_no_config_deletes_all(self):
+ cmds = build_commands([], self._have_with_ipv4_pl(), "deleted")
+ self.assertIn(("delete", ["policy", "prefix-list"]), cmds)
+
+ def test_deleted_with_config_deletes_named(self):
+ config = [{"afi": "ipv4", "prefix_lists": [{"name": "PL1"}]}]
+ cmds = build_commands(config, self._have_with_ipv4_pl(), "deleted")
+ self.assertIn(("delete", ["policy", "prefix-list", "PL1"]), cmds)
+
+ def test_replaced_deletes_then_resets(self):
+ config = [
+ {
+ "afi": "ipv4",
+ "prefix_lists": [
+ {
+ "name": "PL1",
+ "entries": [
+ {
+ "sequence": 10,
+ "action": "deny",
+ "prefix": "10.0.0.0/8",
+ },
+ ],
+ },
+ ],
+ },
+ ]
+ cmds = build_commands(config, self._have_with_ipv4_pl(), "replaced")
+ # Should delete PL1 first then re-add
+ ops = [(c[0], c[1]) for c in cmds]
+ delete_idx = next(
+ i for i, c in enumerate(ops) if c == ("delete", ["policy", "prefix-list", "PL1"])
+ )
+ set_idx = next(i for i, c in enumerate(ops) if c[0] == "set" and "deny" in c[1])
+ self.assertLess(delete_idx, set_idx)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_route_maps.py b/tests/unit/modules/test_vyos_route_maps.py
new file mode 100644
index 0000000..2d45bb7
--- /dev/null
+++ b/tests/unit/modules/test_vyos_route_maps.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import os
+import sys
+import unittest
+
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_route_maps import (
+ _want_to_api_match,
+ _want_to_api_set,
+ build_commands,
+ get_running_config,
+)
+
+from tests.unit.modules.base import VyOSModuleTestCase, load_fixture
+
+
+class TestVyOSRouteMapsGetRunning(VyOSModuleTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("route_maps_running.json")
+
+ def test_unwraps_route_map_nesting(self):
+ """API returns {"route-map": {"NAME": {...}}} — must unwrap."""
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ names = [e["route_map"] for e in result]
+ self.assertIn("RM-TEST-EXPORT-POLICY", names)
+ self.assertIn("rm1", names)
+ # "route-map" itself must NOT appear as a route map name
+ self.assertNotIn("route-map", names)
+
+ def test_parses_rule_action(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ rm = next(e for e in result if e["route_map"] == "RM-TEST-EXPORT-POLICY")
+ rule = rm["entries"][0]
+ self.assertEqual(rule["action"], "permit")
+ self.assertEqual(rule["sequence"], 10)
+
+ def test_parses_match_peer(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ rm = next(e for e in result if e["route_map"] == "RM-TEST-EXPORT-POLICY")
+ rule = rm["entries"][0]
+ self.assertEqual(rule["match"]["peer"], "192.0.2.32")
+
+ def test_parses_set_fields(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ rm = next(e for e in result if e["route_map"] == "RM-TEST-EXPORT-POLICY")
+ rule = rm["entries"][0]
+ self.assertEqual(rule["set"]["metric"], "5")
+ self.assertEqual(rule["set"]["aggregator"]["as"], "100")
+ self.assertEqual(rule["set"]["as-path"]["exclude"], "111")
+
+ def test_empty_returns_empty_list(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+
+class TestVyOSRouteMapsWantToApi(unittest.TestCase):
+
+ def test_as_path_exclude_nested(self):
+ """as_path_exclude maps to nested as-path.exclude."""
+ result = _want_to_api_set({"as_path_exclude": "111"})
+ self.assertEqual(result["as-path"]["exclude"], "111")
+
+ def test_metric_flat(self):
+ result = _want_to_api_set({"metric": "5"})
+ self.assertEqual(result["metric"], "5")
+
+ def test_aggregator_as(self):
+ result = _want_to_api_set({"aggregator": {"as": 100}})
+ self.assertEqual(result["aggregator"]["as"], "100")
+
+ def test_aggregator_as_underscore(self):
+ """aggregator.as_ is an alias for aggregator.as."""
+ result = _want_to_api_set({"aggregator": {"as_": 100}})
+ self.assertEqual(result["aggregator"]["as"], "100")
+
+ def test_large_community_presence_node(self):
+ result = _want_to_api_set({"large_community": "none"})
+ self.assertEqual(result["large-community"], {"none": {}})
+
+ def test_match_peer(self):
+ result = _want_to_api_match({"peer": "192.0.2.32"})
+ self.assertEqual(result["peer"], "192.0.2.32")
+
+
+class TestVyOSRouteMapsBuildCommands(unittest.TestCase):
+
+ def _have_empty(self):
+ return []
+
+ def _have_with_rm(self):
+ return [
+ {
+ "route_map": "RM1",
+ "entries": [
+ {
+ "sequence": 10,
+ "action": "permit",
+ "match": {"peer": "192.0.2.32"},
+ "set": {"metric": "5", "as-path": {"exclude": "111"}},
+ },
+ ],
+ },
+ ]
+
+ def test_merged_adds_new_rm(self):
+ config = [
+ {
+ "route_map": "RM-NEW",
+ "entries": [{"sequence": 10, "action": "permit"}],
+ },
+ ]
+ cmds = build_commands(config, self._have_empty(), "merged")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["policy", "route-map", "RM-NEW", "rule", "10", "action", "permit"], paths)
+
+ def test_merged_idempotent(self):
+ config = [
+ {
+ "route_map": "RM1",
+ "entries": [
+ {
+ "sequence": 10,
+ "action": "permit",
+ "match": {"peer": "192.0.2.32"},
+ "set": {"metric": "5", "as_path_exclude": "111"},
+ },
+ ],
+ },
+ ]
+ cmds = build_commands(config, self._have_with_rm(), "merged")
+ self.assertEqual(cmds, [])
+
+ def test_deleted_no_config_deletes_all(self):
+ cmds = build_commands([], self._have_with_rm(), "deleted")
+ self.assertIn(("delete", ["policy", "route-map"]), cmds)
+
+ def test_deleted_with_config_deletes_named(self):
+ config = [{"route_map": "RM1"}]
+ cmds = build_commands(config, self._have_with_rm(), "deleted")
+ self.assertIn(("delete", ["policy", "route-map", "RM1"]), cmds)
+
+ def test_replaced_deletes_then_resets(self):
+ config = [
+ {
+ "route_map": "RM1",
+ "entries": [{"sequence": 10, "action": "deny"}],
+ },
+ ]
+ cmds = build_commands(config, self._have_with_rm(), "replaced")
+ ops = [c[0] for c in cmds]
+ # delete must come before set
+ self.assertIn("delete", ops)
+ self.assertIn("set", ops)
+ delete_idx = ops.index("delete")
+ set_idx = ops.index("set")
+ self.assertLess(delete_idx, set_idx)
+
+ def test_overridden_removes_extra_rm(self):
+ config = [{"route_map": "RM-NEW", "entries": []}]
+ have = self._have_with_rm() # has RM1
+ cmds = build_commands(config, have, "overridden")
+ self.assertIn(("delete", ["policy", "route-map", "RM1"]), cmds)
+
+
+if __name__ == "__main__":
+ unittest.main()