summaryrefslogtreecommitdiff
path: root/tests/unit
diff options
context:
space:
mode:
authoromnom62 <75066712+omnom62@users.noreply.github.com>2026-09-16 22:32:53 +1000
committerGitHub <noreply@github.com>2026-09-16 15:32:53 +0300
commit762c276f61dd8fb599d9600df93c1a2992a8cf2e (patch)
tree1e0d1c381f4dd2ac722661b150d11c0c44221d81 /tests/unit
parent25635b0560863eb4a5b34434bb34dfd7c43d77f9 (diff)
downloadvyos.vyos-762c276f61dd8fb599d9600df93c1a2992a8cf2e.tar.gz
vyos.vyos-762c276f61dd8fb599d9600df93c1a2992a8cf2e.zip
T6837: vyos_config replace (#493)
* T6837: vyos_config replace core
Diffstat (limited to 'tests/unit')
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_config.py208
1 files changed, 207 insertions, 1 deletions
diff --git a/tests/unit/modules/network/vyos/test_vyos_config.py b/tests/unit/modules/network/vyos/test_vyos_config.py
index e732ca60..d6a75f1d 100644
--- a/tests/unit/modules/network/vyos/test_vyos_config.py
+++ b/tests/unit/modules/network/vyos/test_vyos_config.py
@@ -1,4 +1,3 @@
-#
# (c) 2016 Red Hat Inc.
#
# This file is part of Ansible
@@ -19,6 +18,7 @@
# Make coding more python3-ish
from __future__ import absolute_import, division, print_function
+
__metaclass__ = type
from unittest.mock import MagicMock, patch
@@ -56,6 +56,11 @@ class TestVyosConfigModule(TestVyosModule):
)
self.get_connection = self.mock_get_connection.start()
+ self.mock_copy_file = patch(
+ "ansible_collections.vyos.vyos.plugins.modules.vyos_config.copy_file",
+ )
+ self.copy_file = self.mock_copy_file.start()
+
self.cliconf_obj = Cliconf(MagicMock())
self.running_config = load_fixture("vyos_config_config.cfg")
@@ -70,6 +75,7 @@ class TestVyosConfigModule(TestVyosModule):
self.mock_load_config.stop()
self.mock_run_commands.stop()
self.mock_get_connection.stop()
+ self.mock_copy_file.stop()
def load_fixtures(self, commands=None, filename=None):
config_file = "vyos_config_config.cfg"
@@ -177,3 +183,203 @@ class TestVyosConfigModule(TestVyosModule):
self.assertEqual(self.load_config.call_args[1]["confirm"], confirm_timeout)
self.run_commands.assert_not_called()
+
+ # -- replace=config (T6837, cisco.iosxr.iosxr_config replace=config analogue) --
+
+ def test_vyos_config_replace_config_requires_src(self):
+ """replace=config without src must fail argument validation, not run."""
+ set_module_args(dict(replace="config"))
+ result = self.execute_module(failed=True)
+ self.assertIn("src", result["msg"])
+
+ def test_vyos_config_replace_config_rejects_lines_only(self):
+ """replace=config with only lines (no src) must fail --
+ required_if demands src regardless of what else is set.
+ """
+ set_module_args(dict(replace="config", lines=["set system host-name foo"]))
+ self.execute_module(failed=True)
+
+ def test_vyos_config_replace_config_rejects_lines_and_src_together(self):
+ """lines/src remain mutually exclusive regardless of replace --
+ this is the pre-existing constraint, unaffected by replace=config."""
+ set_module_args(
+ dict(
+ replace="config",
+ src="system {\n host-name router\n}\n",
+ lines=["set system host-name foo"],
+ ),
+ )
+ self.execute_module(failed=True)
+
+ def test_vyos_config_replace_config_pushes_and_loads(self):
+ """replace=config with a real change: copies the candidate to a fixed
+ remote path, issues a single `load <path>` command, and reports the
+ device's own diff verbatim -- not an itemized set/delete list.
+ """
+ src = "interfaces {\n ethernet eth0 {\n address dhcp\n }\n}\n"
+ set_module_args(dict(replace="config", src=src))
+ self.load_config.side_effect = lambda *a, **kw: (
+ "[edit interfaces]\n+ethernet eth0 {\n+ address dhcp\n+}"
+ )
+
+ result = self.execute_module(changed=True)
+
+ self.assertEqual(result["commands"], ["load /tmp/ansible_vyos_replace.cfg"])
+ self.copy_file.assert_called_once()
+ # positional call: copy_file(module, local_path, remote_path, proto)
+ self.assertEqual(self.copy_file.call_args[0][2], "/tmp/ansible_vyos_replace.cfg")
+ self.assertEqual(self.copy_file.call_args[0][3], "scp")
+ self.assertEqual(
+ self.load_config.call_args[0][1],
+ ["load /tmp/ansible_vyos_replace.cfg"],
+ )
+
+ def test_vyos_config_replace_config_noop(self):
+ """replace=config with load_config() returning falsy (VyOS's own
+ `compare` reported no changes) must report changed=False, not
+ unconditionally True.
+ """
+ src = "system {\n host-name router\n}\n"
+ set_module_args(dict(replace="config", src=src))
+ self.load_config.return_value = None
+
+ result = self.execute_module(changed=False)
+ self.assertEqual(result["commands"], ["load /tmp/ansible_vyos_replace.cfg"])
+
+ def test_vyos_config_replace_config_check_mode(self):
+ """Under check_mode, commit=False must be passed through to
+ load_config() -- the candidate is still copied/loaded for an accurate
+ compare-based preview diff, but nothing is committed.
+ """
+ src = "system {\n host-name router\n}\n"
+ set_module_args(dict(replace="config", src=src, _ansible_check_mode=True))
+ self.load_config.side_effect = lambda *a, **kw: (
+ "[edit system]\n-host-name foo\n+host-name router"
+ )
+
+ self.execute_module(changed=True)
+
+ self.assertEqual(self.load_config.call_args[1]["commit"], False)
+
+ def test_vyos_config_replace_config_confirm_automatic(self):
+ src = "system {\n host-name router\n}\n"
+ confirm_timeout = 9
+ set_module_args(
+ dict(
+ replace="config",
+ src=src,
+ confirm="automatic",
+ confirm_timeout=confirm_timeout,
+ ),
+ )
+ self.load_config.side_effect = lambda *a, **kw: (
+ "[edit system]\n-host-name foo\n+host-name router"
+ )
+
+ self.execute_module(changed=True)
+
+ self.assertEqual(self.load_config.call_args[1]["confirm"], confirm_timeout)
+ self.run_commands.assert_called_once()
+ self.assertEqual(["configure", "confirm", "exit"], self.run_commands.call_args[0][1])
+
+ def test_vyos_config_replace_config_diff(self):
+ """With --diff, result['diff']['prepared'] must carry VyOS's own
+ compare() output verbatim -- not an itemized command list, since none
+ is computed in this mode.
+ """
+ src = "system {\n host-name router\n}\n"
+ set_module_args(dict(replace="config", src=src, _ansible_diff=True))
+ raw_compare = "[edit system]\n-host-name foo\n+host-name router"
+ self.load_config.side_effect = lambda *a, **kw: raw_compare
+
+ result = self.execute_module(changed=True)
+
+ self.assertEqual(result["diff"]["prepared"], raw_compare)
+
+ def test_vyos_config_replace_config_does_not_use_line_diff_path(self):
+ """replace=config must never call connection.get_diff() -- that path
+ (and match/allow_password_change) is specific to replace=line and is
+ documented as ignored under replace=config.
+ """
+ src = "system {\n host-name router\n}\n"
+ set_module_args(dict(replace="config", src=src, match="none"))
+ self.load_config.side_effect = lambda *a, **kw: (
+ "[edit system]\n-host-name foo\n+host-name router"
+ )
+
+ self.execute_module(changed=True)
+
+ self.conn.get_diff.assert_not_called()
+
+ def test_vyos_config_replace_config_confirm_automatic_check_mode_no_confirm_sent(self):
+ """Regression guard: confirm=automatic must not send the
+ configure/confirm/exit sequence under check_mode, even when a real
+ diff is present -- nothing was actually committed to confirm.
+ """
+ src = "system {\n host-name router\n}\n"
+ set_module_args(
+ dict(
+ replace="config",
+ src=src,
+ confirm="automatic",
+ _ansible_check_mode=True,
+ ),
+ )
+ self.load_config.side_effect = lambda *a, **kw: (
+ "[edit system]\n-host-name foo\n+host-name router"
+ )
+
+ self.execute_module(changed=True)
+
+ self.run_commands.assert_not_called()
+
+ def test_vyos_config_replace_config_confirm_automatic_noop_no_confirm_sent(self):
+ """Regression guard: confirm=automatic must not send the
+ configure/confirm/exit sequence when load_config() reports no diff
+ (VyOS's own compare() found nothing to commit) -- there is nothing
+ pending to confirm.
+ """
+ src = "system {\n host-name router\n}\n"
+ set_module_args(dict(replace="config", src=src, confirm="automatic"))
+ self.load_config.return_value = None
+
+ self.execute_module(changed=False)
+
+ self.run_commands.assert_not_called()
+
+ def test_vyos_config_replace_line_default_unaffected(self):
+ """Regression guard: default replace='line' must behave identically
+ to the pre-patch module -- copy_file() must never be invoked.
+ """
+ commands = ["set system host-name foo"]
+ set_module_args(dict(lines=commands))
+ candidate = "\n".join(commands)
+ self.conn.get_diff = MagicMock(
+ return_value=self.cliconf_obj.get_diff(candidate, self.running_config),
+ )
+ self.execute_module(changed=True, commands=commands)
+ self.copy_file.assert_not_called()
+
+ def test_sanitize_config_filters_password_delete_lines(self):
+ """
+ sanitize_config()/PASSWORD_NEEDLE must filter 'delete ... password'
+ lines the same way it filters 'set ... password' lines, since
+ replace=config can generate deletes for password config the
+ candidate omits.
+ """
+ result = {}
+ commands = [
+ "set system host-name foo",
+ "delete system login user admin authentication encrypted-password",
+ "set system login user admin authentication plaintext-password 'secret'",
+ ]
+ vyos_config.sanitize_config(commands, result, allow="none")
+ self.assertIn(
+ "delete system login user admin authentication encrypted-password",
+ result["filtered"],
+ )
+ self.assertIn(
+ "set system login user admin authentication plaintext-password 'secret'",
+ result["filtered"],
+ )
+ self.assertNotIn("set system host-name foo", result["filtered"])