From dab383a793aede0c331f232497e517e5925f916e Mon Sep 17 00:00:00 2001 From: Jeleel Muibi Date: Sat, 8 Aug 2026 11:49:13 +0100 Subject: cloud-init: T9172: add userdata command regression tests Add focused regression coverage for the VyOS-specific vyos_config_commands handler. Exercise template discovery, supported set/delete parsing, ordinary, multi, and nested tag-node updates, both delete paths, and malformed-command skipping. Use temporary template trees and a mocked ConfigTree interface so the tests run without a VyOS image or installed template hierarchy. --- tests/unittests/config/test_cc_vyos_userdata.py | 186 ++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 tests/unittests/config/test_cc_vyos_userdata.py diff --git a/tests/unittests/config/test_cc_vyos_userdata.py b/tests/unittests/config/test_cc_vyos_userdata.py new file mode 100644 index 00000000..3da5b3d2 --- /dev/null +++ b/tests/unittests/config/test_cc_vyos_userdata.py @@ -0,0 +1,186 @@ +# This file is part of cloud-init. See LICENSE file for license information. + +from pathlib import Path + +from cloudinit.config import cc_vyos_userdata +from tests.unittests.helpers import CiTestCase, mock + + +class TestVyosUserdata(CiTestCase): + def test_get_tag_nodes_discovers_template_paths(self): + templates_dir = Path(self.tmp_dir()) + (templates_dir / "interfaces" / "ethernet" / "node.tag").mkdir( + parents=True + ) + + with mock.patch.object( + cc_vyos_userdata, "TEMPLATES_DIR", str(templates_dir) + ): + self.assertEqual( + [("interfaces", "ethernet")], + cc_vyos_userdata.get_tag_nodes(), + ) + + def test_get_multi_nodes_accepts_trailing_metadata(self): + templates_dir = Path(self.tmp_dir()) + multi_node = templates_dir / "system" / "name-server" + ordinary_node = templates_dir / "system" / "host-name" + multi_node.mkdir(parents=True) + ordinary_node.mkdir(parents=True) + (multi_node / "node.def").write_text("multi: true\n") + (ordinary_node / "node.def").write_text("help: Host name\n") + + with mock.patch.object( + cc_vyos_userdata, "TEMPLATES_DIR", str(templates_dir) + ): + self.assertEqual( + [("system", "name-server")], + cc_vyos_userdata.get_multi_nodes(), + ) + + def test_string_to_command_parses_set_and_delete(self): + self.assertEqual( + { + "cmd_action": "set", + "cmd_path": ["system", "host-name"], + "cmd_value": "edge-a", + }, + cc_vyos_userdata.string_to_command( + "set system host-name 'edge-a'" + ), + ) + self.assertEqual( + { + "cmd_action": "delete", + "cmd_path": ["system", "name-server"], + "cmd_value": "192.0.2.53", + }, + cc_vyos_userdata.string_to_command( + "delete system name-server '192.0.2.53'" + ), + ) + + def test_string_to_command_rejects_malformed_input(self): + self.assertIsNone(cc_vyos_userdata.string_to_command("show version")) + + def test_apply_command_set_replaces_ordinary_node(self): + config = mock.Mock() + command = { + "cmd_path": ["system", "host-name"], + "cmd_value": "edge-a", + } + + cc_vyos_userdata.apply_command_set(config, [], [], command) + + config.set.assert_called_once_with( + ["system", "host-name"], "edge-a", replace=True + ) + + config.set_tag.assert_not_called() + + def test_apply_command_set_appends_multi_node_value(self): + config = mock.Mock() + command = { + "cmd_path": ["system", "name-server"], + "cmd_value": "192.0.2.53", + } + + cc_vyos_userdata.apply_command_set( + config, [], [("system", "name-server")], command + ) + + config.set.assert_called_once_with( + ["system", "name-server"], "192.0.2.53", replace=False + ) + + def test_apply_command_set_preserves_nested_tag_node_values(self): + config = mock.Mock() + marked_paths = [] + config.set_tag.side_effect = lambda path: marked_paths.append( + list(path) + ) + command = { + "cmd_path": [ + "vrf", + "name", + "customer", + "protocols", + "bgp", + "address-family", + "ipv4-unicast", + "network", + ], + "cmd_value": "10.180.0.0/25", + } + tag_nodes = [ + ("vrf", "name"), + ( + "vrf", + "name", + "node.tag", + "protocols", + "bgp", + "address-family", + "ipv4-unicast", + "network", + ), + ] + + cc_vyos_userdata.apply_command_set(config, tag_nodes, [], command) + + config.set.assert_called_once_with( + command["cmd_path"], "10.180.0.0/25", replace=False + ) + self.assertEqual( + [ + ["vrf", "name"], + command["cmd_path"], + ], + marked_paths, + ) + + def test_apply_command_delete_uses_value_or_path(self): + config = mock.Mock() + + cc_vyos_userdata.apply_command_delete( + config, + { + "cmd_path": ["system", "name-server"], + "cmd_value": "192.0.2.53", + }, + ) + cc_vyos_userdata.apply_command_delete( + config, + {"cmd_path": ["system", "host-name"], "cmd_value": None}, + ) + + config.delete_value.assert_called_once_with( + ["system", "name-server"], "192.0.2.53" + ) + config.delete.assert_called_once_with(["system", "host-name"]) + + @mock.patch("cloudinit.config.cc_vyos_userdata.get_multi_nodes") + @mock.patch("cloudinit.config.cc_vyos_userdata.get_tag_nodes") + def test_apply_commands_routes_valid_commands_and_skips_malformed( + self, tag_nodes, multi_nodes + ): + config = mock.Mock() + tag_nodes.return_value = [] + multi_nodes.return_value = [] + + cc_vyos_userdata.apply_commands( + config, + [ + "set system host-name 'edge-a'", + "show version", + "delete system name-server '192.0.2.53'", + ], + ) + + config.set.assert_called_once_with( + ["system", "host-name"], "edge-a", replace=True + ) + config.delete_value.assert_called_once_with( + ["system", "name-server"], "192.0.2.53" + ) + config.delete.assert_not_called() -- cgit v1.2.3