From aeedc73003ddcf84bbf5c2880f982b6d4925e0c9 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Wed, 27 May 2026 21:38:51 +1000 Subject: Test framework --- tests/unit/modules/base.py | 53 ++++++ tests/unit/modules/conftest.py | 2 + tests/unit/modules/test_vyos_logging_global.py | 229 +++++++++++++++++++++++++ tests/unit/modules/test_vyos_ntp_global.py | 169 ++++++++++++++++++ tests/unit/modules/test_vyos_prefix_lists.py | 209 ++++++++++++++++++++++ tests/unit/modules/test_vyos_route_maps.py | 180 +++++++++++++++++++ 6 files changed, 842 insertions(+) create mode 100644 tests/unit/modules/base.py create mode 100644 tests/unit/modules/conftest.py create mode 100644 tests/unit/modules/test_vyos_logging_global.py create mode 100644 tests/unit/modules/test_vyos_ntp_global.py create mode 100644 tests/unit/modules/test_vyos_prefix_lists.py create mode 100644 tests/unit/modules/test_vyos_route_maps.py (limited to 'tests/unit/modules') 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() -- cgit v1.2.3 From 3d5580b801812dbbeefb37cfb7059b7f2b4adcbb Mon Sep 17 00:00:00 2001 From: omnom62 Date: Mon, 1 Jun 2026 06:12:38 +1000 Subject: Preparing first cut of modules --- .github/PULL_REQUEST_TEMPLATE.md | 104 +++++++++++++++ .github/dependabot.yaml | 10 ++ .github/workflows/ah_token_refresh.yml | 14 ++ .github/workflows/check_label.yaml | 11 ++ .github/workflows/cla-check.yml | 15 +++ .github/workflows/codecoverage.yml | 71 ++++++++++ .github/workflows/release.yml | 13 ++ .github/workflows/tests.yml | 52 ++++++++ .../targets/vyos_prefix_lists.old/aliases | 1 - .../targets/vyos_prefix_lists.old/tasks/main.yaml | 144 --------------------- tests/unit/modules/test_vyos_ntp_global.py | 22 ++-- 11 files changed, 302 insertions(+), 155 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yaml create mode 100644 .github/workflows/ah_token_refresh.yml create mode 100644 .github/workflows/check_label.yaml create mode 100644 .github/workflows/cla-check.yml create mode 100644 .github/workflows/codecoverage.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tests.yml delete mode 100644 tests/integration/targets/vyos_prefix_lists.old/aliases delete mode 100644 tests/integration/targets/vyos_prefix_lists.old/tasks/main.yaml (limited to 'tests/unit/modules') diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..9ff958d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,104 @@ + + + +## Change Summary + + +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Code style update (formatting, renaming) +- [ ] Refactoring (no functional changes) +- [ ] Migration from an old Vyatta component to vyos-1x, please link to related PR inside obsoleted component +- [ ] Other (please describe): + +## Related Task(s) + + + +## Related PR(s) + + +## Component(s) name + + +## Proposed changes + + +## How to test + + +## Test results + +- [ ] Sanity tests passed +- [ ] Unit tests passed + +Tested against VyOS versions: + +- 1.3.8 +- 1.4-rolling-202201010100 + + +## Checklist: + + + +- [ ] I have read the [**CONTRIBUTING**](https://github.com/vyos/vyos-1x/blob/current/CONTRIBUTING.md) document +- [ ] I have linked this PR to one or more Phabricator Task(s) +- [ ] I have run the ansible sanity and unit tests +- [ ] My commit headlines contain a valid Task id +- [ ] My change requires a change to the documentation +- [ ] I have updated the documentation accordingly +- [ ] I have added unit tests to cover my changes +- [ ] I have added a file to `changelogs/fragments` to describe the changes diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..66f19d7 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,10 @@ +--- +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + open-pull-requests-limit: 3 + labels: + - skip-changelog diff --git a/.github/workflows/ah_token_refresh.yml b/.github/workflows/ah_token_refresh.yml new file mode 100644 index 0000000..09d2f9d --- /dev/null +++ b/.github/workflows/ah_token_refresh.yml @@ -0,0 +1,14 @@ +name: Refresh the automation hub token +# the token expires every 30 days, so we need to refresh it +on: + schedule: + - cron: '0 12 1,15 * *' # run 12pm on the 1st and 15th of the month + workflow_dispatch: + +jobs: + refresh: + uses: ansible/team-devtools/.github/workflows/ah_token_refresh.yml@v26.4.0 + with: + environment: release + secrets: + ah_token: ${{ secrets.AH_TOKEN }} diff --git a/.github/workflows/check_label.yaml b/.github/workflows/check_label.yaml new file mode 100644 index 0000000..000578b --- /dev/null +++ b/.github/workflows/check_label.yaml @@ -0,0 +1,11 @@ +--- +name: Check label +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +on: # yamllint disable-line rule:truthy + pull_request_target: + types: [opened, labeled, unlabeled, synchronize] +jobs: + check_label: + uses: ansible/ansible-content-actions/.github/workflows/check_label.yaml@main diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml new file mode 100644 index 0000000..da3e6ef --- /dev/null +++ b/.github/workflows/cla-check.yml @@ -0,0 +1,15 @@ +name: "CLA Check" +permissions: + actions: write + contents: read + pull-requests: write + statuses: write +on: + pull_request_target: + types: [opened, synchronize, closed] + issue_comment: + types: [created] +jobs: + call-cla-assistant: + uses: vyos/vyos-cla-signatures/.github/workflows/cla-reusable.yml@current + secrets: inherit diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml new file mode 100644 index 0000000..8ce3af3 --- /dev/null +++ b/.github/workflows/codecoverage.yml @@ -0,0 +1,71 @@ +--- +name: Code Coverage +# cloned from ansible-network/github_actions/.github/workflows/coverage_network_devices.yml@main +# in order to deal with token issue in codecov + +on: # yamllint disable-line rule:truthy + push: + pull_request: + branches: [main] +jobs: + codecoverage: + env: + PY_COLORS: "1" + source_directory: "./source" + python_version: "3.10" + ansible_version: "latest" + os: "ubuntu-latest" + collection_pre_install: >- + git+https://github.com/ansible-collections/ansible.utils.git + git+https://github.com/ansible-collections/ansible.netcommon.git + runs-on: ubuntu-latest + name: "Code Coverage | Python 3.10" + steps: + - name: Checkout the collection repository + uses: ansible-network/github_actions/.github/actions/checkout_dependency@main + with: + path: ${{ env.source_directory }} + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: "0" + + - name: Set up Python ${{ env.python_version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.python_version }} + + - name: Install ansible-core (${{ env.ansible-version }}) + run: python3 -m pip install ansible-core pytest pytest-cov pytest-ansible-units pytest-forked pytest-xdist + + - name: Read collection metadata from galaxy.yml + id: identify + uses: ansible-network/github_actions/.github/actions/identify_collection@main + with: + source_path: ${{ env.source_directory }} + + - name: Build and install the collection + uses: ansible-network/github_actions/.github/actions/build_install_collection@main + with: + install_python_dependencies: true + source_path: ${{ env.source_directory }} + collection_path: ${{ steps.identify.outputs.collection_path }} + tar_file: ${{ steps.identify.outputs.tar_file }} + + - name: Print the ansible version + run: ansible --version + + - name: Print the python dependencies + run: python3 -m pip list + + - name: Run Coverage tests + run: | + pytest tests/unit -v --cov-report xml --cov=./ + working-directory: ${{ steps.identify.outputs.collection_path }} + + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@v6 + with: + directory: ${{ steps.identify.outputs.collection_path }} + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7a2c493 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,13 @@ +--- +name: Release collection +on: # yamllint disable-line rule:truthy + release: + types: [published] +jobs: + release: + uses: ansible/ansible-content-actions/.github/workflows/release.yaml@main + with: + environment: release + secrets: + ah_token: ${{ secrets.AH_TOKEN }} + ansible_galaxy_api_key: ${{ secrets.ANSIBLE_GALAXY_API_KEY }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..dedbc9e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,52 @@ +--- +name: CI + +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: # yamllint disable-line rule:truthy + pull_request: + branches: [main] + workflow_dispatch: + schedule: + - cron: 0 0 * * * + +jobs: + changelog: + uses: ansible/ansible-content-actions/.github/workflows/changelog.yaml@main + if: github.event_name == 'pull_request' + build-import: + uses: ansible/ansible-content-actions/.github/workflows/build_import.yaml@main + ansible-lint: + uses: ansible/ansible-content-actions/.github/workflows/ansible_lint.yaml@main + sanity: + uses: ansible/ansible-content-actions/.github/workflows/sanity.yaml@main + unit-galaxy: + uses: ansible/ansible-content-actions/.github/workflows/unit.yaml@main + unit-source: + uses: ansible-network/github_actions/.github/workflows/unit_source.yml@main + with: + collection_pre_install: >- + git+https://github.com/ansible-collections/ansible.utils.git + git+https://github.com/ansible-collections/ansible.netcommon.git + all_green: + if: ${{ always() }} + needs: + - changelog + - build-import + - sanity + - unit-galaxy + - unit-source + - ansible-lint + runs-on: ubuntu-latest + steps: + - run: >- + python -c "assert 'failure' not in + set([ + '${{ needs.changelog.result }}', + '${{ needs.sanity.result }}', + '${{ needs.unit-galaxy.result }}' + '${{ needs.ansible-lint.result }}' + '${{ needs.unit-source.result }}' + ])" diff --git a/tests/integration/targets/vyos_prefix_lists.old/aliases b/tests/integration/targets/vyos_prefix_lists.old/aliases deleted file mode 100644 index cc0afef..0000000 --- a/tests/integration/targets/vyos_prefix_lists.old/aliases +++ /dev/null @@ -1 +0,0 @@ -network/vyos diff --git a/tests/integration/targets/vyos_prefix_lists.old/tasks/main.yaml b/tests/integration/targets/vyos_prefix_lists.old/tasks/main.yaml deleted file mode 100644 index e5c62cd..0000000 --- a/tests/integration/targets/vyos_prefix_lists.old/tasks/main.yaml +++ /dev/null @@ -1,144 +0,0 @@ ---- -# tests/integration/targets/vyos_prefix_lists/tasks/main.yaml - -- name: TEARDOWN — delete all prefix lists before tests - vyos.rest.vyos_prefix_lists: - state: deleted - -# ------------------------------------------------------------------ -# MERGED -# ------------------------------------------------------------------ - -- name: MERGED — create prefix lists - register: result - vyos.rest.vyos_prefix_lists: - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - description: PL configured by ansible - entries: - - sequence: 2 - action: permit - prefix: 92.168.10.0/26 - le: 32 - - sequence: 3 - action: deny - prefix: 72.168.2.0/24 - ge: 26 - - afi: ipv6 - prefix_lists: - - name: AllowIPv6Prefix - description: Configured by ansible for allowing IPv6 networks - entries: - - sequence: 5 - action: permit - prefix: 2001:db8:8000::/35 - le: 37 - state: merged - -- name: ASSERT — merged changed - assert: - that: - - result.changed == true - -- name: MERGED — idempotency check - register: result - vyos.rest.vyos_prefix_lists: - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - description: PL configured by ansible - entries: - - sequence: 2 - action: permit - prefix: 92.168.10.0/26 - le: 32 - - sequence: 3 - action: deny - prefix: 72.168.2.0/24 - ge: 26 - state: merged - -- name: ASSERT — merged idempotent - assert: - that: - - result.changed == false - - result.commands | length == 0 - -# ------------------------------------------------------------------ -# GATHERED -# ------------------------------------------------------------------ - -- name: GATHERED — read current prefix lists - register: result - vyos.rest.vyos_prefix_lists: - state: gathered - -- name: ASSERT — gathered has ipv4 entry - assert: - that: - - result.gathered | selectattr('afi','eq','ipv4') | list | length > 0 - -# ------------------------------------------------------------------ -# REPLACED -# ------------------------------------------------------------------ - -- name: REPLACED — replace AnsibleIPv4PrefixList - register: result - vyos.rest.vyos_prefix_lists: - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - entries: - - sequence: 10 - action: permit - prefix: 10.0.0.0/8 - state: replaced - -- name: ASSERT — replaced changed - assert: - that: - - result.changed == true - -- name: REPLACED — idempotency check - register: result - vyos.rest.vyos_prefix_lists: - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - entries: - - sequence: 10 - action: permit - prefix: 10.0.0.0/8 - state: replaced - -- name: ASSERT — replaced idempotent - assert: - that: - - result.changed == false - -# ------------------------------------------------------------------ -# DELETED -# ------------------------------------------------------------------ - -- name: DELETED — remove specific prefix list - register: result - vyos.rest.vyos_prefix_lists: - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - state: deleted - -- name: ASSERT — deleted changed - assert: - that: - - result.changed == true - -- name: DELETED — remove all remaining - vyos.rest.vyos_prefix_lists: - state: deleted diff --git a/tests/unit/modules/test_vyos_ntp_global.py b/tests/unit/modules/test_vyos_ntp_global.py index d646b1a..9aac605 100644 --- a/tests/unit/modules/test_vyos_ntp_global.py +++ b/tests/unit/modules/test_vyos_ntp_global.py @@ -138,26 +138,28 @@ class TestVyOSNtpGlobalBuildCommands(unittest.TestCase): ) 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) + cmds = build_commands({}, have, "deleted") + self.assertEqual(len(cmds), 1) + self.assertEqual(cmds[0], ("delete", ["service", "ntp"])) + + def test_deleted_idempotent_when_empty(self): + have = self._have(servers={}, allow_clients=[], listen_addresses=[]) + cmds = build_commands({}, have, "deleted") + self.assertEqual(cmds, []) 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) + ops_paths = [(c[0], c[1]) for c in cmds] + self.assertIn(("delete", ["service", "ntp", "server", "old.server.com"]), ops_paths) + self.assertIn(("set", ["service", "ntp", "server", "new.server.com"]), ops_paths) + self.assertNotIn(("delete", ["service", "ntp", "server"]), ops_paths) def test_no_commands_when_already_correct(self): state = {"allow_clients": ["10.0.0.0/24"], "listen_addresses": [], "servers": {}} -- cgit v1.2.3 From efcfa5c9922206385888d613385a341d4f3d4d06 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Mon, 1 Jun 2026 20:59:58 +1000 Subject: logging_global UAT --- tests/unit/modules/test_vyos_logging_global.py | 205 +++++++++++++------------ 1 file changed, 108 insertions(+), 97 deletions(-) (limited to 'tests/unit/modules') diff --git a/tests/unit/modules/test_vyos_logging_global.py b/tests/unit/modules/test_vyos_logging_global.py index e9ecc8c..8593f1f 100644 --- a/tests/unit/modules/test_vyos_logging_global.py +++ b/tests/unit/modules/test_vyos_logging_global.py @@ -17,12 +17,10 @@ from ansible_collections.vyos.rest.plugins.modules.vyos_logging_global import ( normalize_running, ) -from tests.unit.modules.base import VyOSModuleTestCase, load_fixture - class TestVyOSLoggingGlobalNormalize(unittest.TestCase): - def test_normalize_config_console(self): + def test_normalize_config_console_severity_is_string(self): cfg = { "console": { "facilities": [{"facility": "local7", "severity": "err"}], @@ -30,10 +28,17 @@ class TestVyOSLoggingGlobalNormalize(unittest.TestCase): } result = normalize_config(cfg) self.assertIn("local7", result["console"]["facilities"]) - self.assertEqual( - result["console"]["facilities"]["local7"], - {"severity": "err", "protocol": None}, - ) + # severity is stored as plain string, not dict + self.assertEqual(result["console"]["facilities"]["local7"], "err") + + def test_normalize_config_console_no_severity(self): + cfg = { + "console": { + "facilities": [{"facility": "all"}], + }, + } + result = normalize_config(cfg) + self.assertIsNone(result["console"]["facilities"]["all"]) def test_normalize_config_hosts(self): cfg = { @@ -53,10 +58,29 @@ class TestVyOSLoggingGlobalNormalize(unittest.TestCase): host = result["hosts"]["172.16.0.1"] self.assertEqual(host["port"], 514) self.assertIn("local7", host["facilities"]) + # host facilities are dicts with severity/protocol self.assertEqual(host["facilities"]["local7"]["severity"], "all") self.assertEqual(host["facilities"]["all"]["protocol"], "udp") - def test_normalize_running_console(self): + def test_normalize_config_global_preserve_fqdn(self): + cfg = {"global_params": {"preserve_fqdn": True}} + result = normalize_config(cfg) + self.assertTrue(result["global"]["preserve_fqdn"]) + + def test_normalize_config_global_archive(self): + cfg = {"global_params": {"archive": {"file_num": 2, "size": 111}}} + result = normalize_config(cfg) + self.assertEqual(result["global"]["archive"]["file_num"], 2) + self.assertEqual(result["global"]["archive"]["size"], 111) + + def test_normalize_config_empty(self): + result = normalize_config({}) + self.assertEqual(result["console"]["facilities"], {}) + self.assertEqual(result["hosts"], {}) + self.assertEqual(result["files"], {}) + self.assertEqual(result["users"], {}) + + def test_normalize_running_console_severity_is_string(self): raw = { "console": { "facility": { @@ -67,10 +91,26 @@ class TestVyOSLoggingGlobalNormalize(unittest.TestCase): } 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"]) + # severity is plain string from "level" key + self.assertEqual(result["console"]["facilities"]["local7"], "err") + self.assertIsNone(result["console"]["facilities"]["all"]) + + def test_normalize_running_host_port_not_cast(self): + """Port is NOT cast to int — stored as-is from API response.""" + raw = { + "host": { + "172.16.0.1": { + "port": "514", + "facility": {}, + }, + }, + } + result = normalize_running(raw) + # port stays as string — module does not cast + self.assertEqual(result["hosts"]["172.16.0.1"]["port"], "514") - def test_normalize_running_global_archive(self): + def test_normalize_running_global_archive_key(self): + """Archive stored under 'archive' key — no file_num remapping.""" raw = { "global": { "archive": {"file": "2", "size": "111"}, @@ -79,27 +119,34 @@ class TestVyOSLoggingGlobalNormalize(unittest.TestCase): }, } 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) + # archive stored as-is from API + self.assertEqual(result["global"]["archive"]["file"], "2") + self.assertEqual(result["global"]["archive"]["size"], "111") + # marker_interval stored as string — no cast + self.assertEqual(result["global"]["marker_interval"], "111") self.assertTrue(result["global"]["preserve_fqdn"]) - def test_normalize_running_host_port_cast(self): + def test_normalize_running_empty(self): + result = normalize_running({}) + self.assertEqual(result["console"]["facilities"], {}) + self.assertEqual(result["hosts"], {}) + + def test_normalize_running_host_facilities(self): raw = { "host": { "172.16.0.1": { - "port": "514", - "facility": {}, + "facility": { + "local7": {"level": "all"}, + "all": {"protocol": "udp"}, + }, + "port": "223", }, }, } 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"], {}) + h = result["hosts"]["172.16.0.1"] + self.assertEqual(h["facilities"]["local7"]["severity"], "all") + self.assertEqual(h["facilities"]["all"]["protocol"], "udp") class TestVyOSLoggingGlobalBuildCommands(unittest.TestCase): @@ -113,25 +160,34 @@ class TestVyOSLoggingGlobalBuildCommands(unittest.TestCase): "users": {}, } - def test_merged_adds_console_facility(self): + def test_merged_adds_console_facility_with_severity(self): want = self._empty_have() - want["console"]["facilities"]["local7"] = {"severity": "err", "protocol": None} + want["console"]["facilities"]["local7"] = "err" cmds = build_commands(want, self._empty_have(), "merged") self.assertIn( ("set", ["system", "syslog", "console", "facility", "local7", "level", "err"]), cmds, ) + def test_merged_adds_console_facility_no_severity(self): + want = self._empty_have() + want["console"]["facilities"]["all"] = None + cmds = build_commands(want, self._empty_have(), "merged") + self.assertIn( + ("set", ["system", "syslog", "console", "facility", "all"]), + cmds, + ) + def test_merged_idempotent_console(self): - facs = {"local7": {"severity": "err", "protocol": None}} + facs = {"local7": "err"} want = self._empty_have() have = self._empty_have() want["console"]["facilities"] = facs - have["console"]["facilities"] = facs.copy() + have["console"]["facilities"] = dict(facs) cmds = build_commands(want, have, "merged") self.assertEqual(cmds, []) - def test_merged_adds_host_with_port(self): + def test_merged_adds_host(self): want = self._empty_have() want["hosts"]["172.16.0.1"] = { "port": 514, @@ -139,26 +195,8 @@ class TestVyOSLoggingGlobalBuildCommands(unittest.TestCase): } 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, - ) + # diff_map only adds the host key, not per-facility details + self.assertIn(["system", "syslog", "host", "172.16.0.1"], paths) def test_replaced_removes_extra_host(self): want = self._empty_have() @@ -167,62 +205,35 @@ class TestVyOSLoggingGlobalBuildCommands(unittest.TestCase): cmds = build_commands(want, have, "replaced") self.assertIn(("delete", ["system", "syslog", "host", "172.16.0.1"]), cmds) - def test_deleted_issues_single_delete(self): + def test_deleted_removes_per_field(self): + """deleted state removes per-facility entries, not single subtree.""" have = self._empty_have() - have["console"]["facilities"]["all"] = {"severity": None, "protocol": None} + have["console"]["facilities"]["all"] = None cmds = build_commands(self._empty_have(), have, "deleted") - self.assertIn(("delete", ["system", "syslog"]), cmds) + self.assertIn( + ("delete", ["system", "syslog", "console", "facility", "all"]), + cmds, + ) - def test_overridden_deletes_then_merges(self): + def test_overridden_deletes_all_then_merges(self): want = self._empty_have() + want["console"]["facilities"]["local7"] = "err" have = self._empty_have() - have["console"]["facilities"]["all"] = {"severity": None, "protocol": None} + have["console"]["facilities"]["all"] = 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"]) + # first command is full syslog delete + self.assertEqual(cmds[0], ("delete", ["system", "syslog"])) + # then adds wanted facility + self.assertIn( + ("set", ["system", "syslog", "console", "facility", "local7", "level", "err"]), + cmds, + ) - def test_fixture_parses_marker_interval(self): - result = normalize_running(self.fixture) - self.assertEqual(result["global"]["marker_interval"], 111) + def test_no_commands_when_already_correct(self): + state = self._empty_have() + state["console"]["facilities"]["local7"] = "err" + cmds = build_commands(state, state, "merged") + self.assertEqual(cmds, []) if __name__ == "__main__": -- cgit v1.2.3 From 710e61bc17a8354c824dba4ea53a71013fe1b0e1 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Mon, 1 Jun 2026 21:33:44 +1000 Subject: Test framework updates --- tests/unit/__init__.py | 0 tests/unit/modules/__init__.py | 0 tests/unit/modules/test_vyos_ntp_global.py | 22 +++++++++++++++++----- tests/unit/modules/test_vyos_route_maps.py | 21 +++++++++++++++++---- 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/modules/__init__.py (limited to 'tests/unit/modules') diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/modules/__init__.py b/tests/unit/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/modules/test_vyos_ntp_global.py b/tests/unit/modules/test_vyos_ntp_global.py index 9aac605..c536141 100644 --- a/tests/unit/modules/test_vyos_ntp_global.py +++ b/tests/unit/modules/test_vyos_ntp_global.py @@ -4,13 +4,11 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import json 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 unittest.mock import MagicMock from ansible_collections.vyos.rest.plugins.modules.vyos_ntp_global import ( build_commands, @@ -19,7 +17,21 @@ from ansible_collections.vyos.rest.plugins.modules.vyos_ntp_global import ( normalize_servers, ) -from tests.unit.modules.base import VyOSModuleTestCase, load_fixture + +def load_fixture(filename): + 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): + def setUp(self): + self.mock_vyos = MagicMock() + self.mock_vyos.get_config = MagicMock(return_value={}) + + def set_running_config(self, data): + self.mock_vyos.get_config.return_value = data class TestVyOSNtpGlobalNormalize(unittest.TestCase): diff --git a/tests/unit/modules/test_vyos_route_maps.py b/tests/unit/modules/test_vyos_route_maps.py index 2d45bb7..97d814b 100644 --- a/tests/unit/modules/test_vyos_route_maps.py +++ b/tests/unit/modules/test_vyos_route_maps.py @@ -4,12 +4,11 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import json import os -import sys import unittest - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) +from unittest.mock import MagicMock from ansible_collections.vyos.rest.plugins.modules.vyos_route_maps import ( _want_to_api_match, @@ -18,7 +17,21 @@ from ansible_collections.vyos.rest.plugins.modules.vyos_route_maps import ( get_running_config, ) -from tests.unit.modules.base import VyOSModuleTestCase, load_fixture + +def load_fixture(filename): + 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): + def setUp(self): + self.mock_vyos = MagicMock() + self.mock_vyos.get_config = MagicMock(return_value={}) + + def set_running_config(self, data): + self.mock_vyos.get_config.return_value = data class TestVyOSRouteMapsGetRunning(VyOSModuleTestCase): -- cgit v1.2.3 From e41f9f0f331c0175fa7f9676b9636131d2cc586a Mon Sep 17 00:00:00 2001 From: omnom62 Date: Mon, 1 Jun 2026 21:36:51 +1000 Subject: diable prefix_lists UAT temp --- tests/unit/modules/test_vyos_prefix_lists.py | 209 --------------------------- 1 file changed, 209 deletions(-) delete mode 100644 tests/unit/modules/test_vyos_prefix_lists.py (limited to 'tests/unit/modules') diff --git a/tests/unit/modules/test_vyos_prefix_lists.py b/tests/unit/modules/test_vyos_prefix_lists.py deleted file mode 100644 index d8b75df..0000000 --- a/tests/unit/modules/test_vyos_prefix_lists.py +++ /dev/null @@ -1,209 +0,0 @@ -# -*- 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() -- cgit v1.2.3 From ba080f20749a7c79dc3033e5e022e3ccb00c65c5 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Wed, 3 Jun 2026 21:47:02 +1000 Subject: vyos_configure tests --- tests/integration/targets/vyos_configure/aliases | 1 + .../targets/vyos_configure/defaults/main.yaml | 3 + .../targets/vyos_configure/tasks/httpapi.yaml | 21 +++++++ .../targets/vyos_configure/tasks/main.yaml | 5 ++ .../vyos_configure/tests/httpapi/configure.yaml | 52 ++++++++++++++++++ .../targets/vyos_configure/vars/main.yaml | 2 + tests/unit/modules/test_vyos_configure.py | 64 ++++++++++++++++++++++ 7 files changed, 148 insertions(+) create mode 100644 tests/integration/targets/vyos_configure/aliases create mode 100644 tests/integration/targets/vyos_configure/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_configure/tasks/httpapi.yaml create mode 100644 tests/integration/targets/vyos_configure/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_configure/tests/httpapi/configure.yaml create mode 100644 tests/integration/targets/vyos_configure/vars/main.yaml create mode 100644 tests/unit/modules/test_vyos_configure.py (limited to 'tests/unit/modules') diff --git a/tests/integration/targets/vyos_configure/aliases b/tests/integration/targets/vyos_configure/aliases new file mode 100644 index 0000000..cc0afef --- /dev/null +++ b/tests/integration/targets/vyos_configure/aliases @@ -0,0 +1 @@ +network/vyos diff --git a/tests/integration/targets/vyos_configure/defaults/main.yaml b/tests/integration/targets/vyos_configure/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_configure/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_configure/tasks/httpapi.yaml b/tests/integration/targets/vyos_configure/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_configure/tasks/httpapi.yaml @@ -0,0 +1,21 @@ +--- +- name: Collect all httpapi test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/httpapi" + patterns: "{{ testcase }}.yaml" + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + ansible.builtin.set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=httpapi) + ansible.builtin.include_tasks: "{{ test_case_to_run }}" + vars: + ansible_connection: ansible.netcommon.httpapi + ansible_network_os: vyos.rest.vyos + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/vyos_configure/tasks/main.yaml b/tests/integration/targets/vyos_configure/tasks/main.yaml new file mode 100644 index 0000000..b1f6193 --- /dev/null +++ b/tests/integration/targets/vyos_configure/tasks/main.yaml @@ -0,0 +1,5 @@ +--- +- name: Run httpapi tests + ansible.builtin.include_tasks: httpapi.yaml + tags: + - httpapi diff --git a/tests/integration/targets/vyos_configure/tests/httpapi/configure.yaml b/tests/integration/targets/vyos_configure/tests/httpapi/configure.yaml new file mode 100644 index 0000000..2f95df3 --- /dev/null +++ b/tests/integration/targets/vyos_configure/tests/httpapi/configure.yaml @@ -0,0 +1,52 @@ +--- +- debug: + msg: START vyos_configure integration tests on connection={{ ansible_connection }} + +- block: + - name: Set a loopback address via vyos_configure + register: result + vyos.rest.vyos_configure: + commands: + - set interfaces loopback lo address 192.0.2.100/32 + + - assert: + that: + - result.changed == true + - result.commands | length == 1 + - result.commands[0][0] == "set" + - result.commands[0][1] == ["interfaces", "loopback", "lo", "address", "192.0.2.100/32"] + + - name: Set multiple commands in one atomic commit + register: result + vyos.rest.vyos_configure: + commands: + - set interfaces loopback lo address 192.0.2.101/32 + - set interfaces loopback lo address 192.0.2.102/32 + + - assert: + that: + - result.changed == true + - result.commands | length == 2 + + - name: Delete loopback addresses + register: result + vyos.rest.vyos_configure: + commands: + - delete interfaces loopback lo address 192.0.2.100/32 + - delete interfaces loopback lo address 192.0.2.101/32 + - delete interfaces loopback lo address 192.0.2.102/32 + + - assert: + that: + - result.changed == true + - result.commands | length == 3 + - result.commands[0][0] == "delete" + + always: + - name: Cleanup — remove test loopback addresses + vyos.rest.vyos_configure: + commands: + - delete interfaces loopback lo address 192.0.2.100/32 + - delete interfaces loopback lo address 192.0.2.101/32 + - delete interfaces loopback lo address 192.0.2.102/32 + ignore_errors: true diff --git a/tests/integration/targets/vyos_configure/vars/main.yaml b/tests/integration/targets/vyos_configure/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_configure/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here diff --git a/tests/unit/modules/test_vyos_configure.py b/tests/unit/modules/test_vyos_configure.py new file mode 100644 index 0000000..af1b325 --- /dev/null +++ b/tests/unit/modules/test_vyos_configure.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import unittest + +from ansible_collections.vyos.rest.plugins.modules.vyos_configure import ( + _parse_command, +) + + +class TestVyOSConfigureParseCommand(unittest.TestCase): + + def test_set_simple(self): + result = _parse_command("set system host-name vyos") + self.assertEqual(result, ("set", ["system", "host-name", "vyos"])) + + def test_set_with_address(self): + result = _parse_command("set interfaces loopback lo address 20.1.1.1/32") + self.assertEqual( + result, + ("set", ["interfaces", "loopback", "lo", "address", "20.1.1.1/32"]), + ) + + def test_delete_simple(self): + result = _parse_command("delete service snmp") + self.assertEqual(result, ("delete", ["service", "snmp"])) + + def test_delete_with_path(self): + result = _parse_command("delete interfaces loopback lo address 20.1.1.1/32") + self.assertEqual( + result, + ("delete", ["interfaces", "loopback", "lo", "address", "20.1.1.1/32"]), + ) + + def test_strips_leading_whitespace(self): + result = _parse_command(" set system host-name vyos") + self.assertEqual(result, ("set", ["system", "host-name", "vyos"])) + + def test_invalid_command_returns_none(self): + result = _parse_command("commit") + self.assertIsNone(result) + + def test_empty_string_returns_none(self): + result = _parse_command("") + self.assertIsNone(result) + + def test_unknown_op_returns_none(self): + result = _parse_command("show interfaces") + self.assertIsNone(result) + + def test_set_single_token_path(self): + result = _parse_command("set service") + self.assertEqual(result, ("set", ["service"])) + + def test_delete_single_token_path(self): + result = _parse_command("delete service") + self.assertEqual(result, ("delete", ["service"])) + + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3 From 63641482f79e6fb81603762f61478969c1ac57f8 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Thu, 4 Jun 2026 06:39:05 +1000 Subject: Sanity check fixes --- plugins/modules/vyos_banner.py | 1 - plugins/modules/vyos_hostname.py | 1 - tests/unit/modules/base.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) (limited to 'tests/unit/modules') diff --git a/plugins/modules/vyos_banner.py b/plugins/modules/vyos_banner.py index 4f734cd..304fa20 100644 --- a/plugins/modules/vyos_banner.py +++ b/plugins/modules/vyos_banner.py @@ -68,7 +68,6 @@ options: api_key: description: REST API key (not needed when ansible_httpapi_api_key is set). type: str - no_log: true timeout: description: Request timeout in seconds. type: int diff --git a/plugins/modules/vyos_hostname.py b/plugins/modules/vyos_hostname.py index 461a552..86444ad 100644 --- a/plugins/modules/vyos_hostname.py +++ b/plugins/modules/vyos_hostname.py @@ -73,7 +73,6 @@ options: description: - API key configured on the device. type: str - no_log: true timeout: description: - Request timeout in seconds. diff --git a/tests/unit/modules/base.py b/tests/unit/modules/base.py index e5a602e..4d49fbb 100644 --- a/tests/unit/modules/base.py +++ b/tests/unit/modules/base.py @@ -10,7 +10,7 @@ import json import os import unittest -from unittest.mock import MagicMock, patch # noqa: F401 +from unittest.mock import MagicMock # noqa: F401 def load_fixture(filename): -- cgit v1.2.3