diff --git a/tests/unit/plugins/modules/fake_api.py b/tests/unit/plugins/modules/fake_api.py index f50c996c..37fae3b3 100644 --- a/tests/unit/plugins/modules/fake_api.py +++ b/tests/unit/plugins/modules/fake_api.py @@ -132,10 +132,12 @@ def _normalize_entry(entry, path_info): del entry[key] -def massage_expected_result_data(values, path, keep_all=False, remove_dynamic=False): +def massage_expected_result_data(values, path, keep_all=False, remove_dynamic=False, remove_builtin=False): path_info = PATHS[path] if remove_dynamic: values = [entry for entry in values if not entry.get('dynamic', False)] + if remove_builtin: + values = [entry for entry in values if not entry.get('builtin', False)] values = [entry.copy() for entry in values] for entry in values: _normalize_entry(entry, path_info) @@ -178,6 +180,8 @@ def add(self, **kwargs): raise Exception('Modifying read-only path: add %s' % repr(kwargs)) if '.id' in kwargs: raise Exception('Trying to create new entry with ".id" field: %s' % repr(kwargs)) + if 'dynamic' in kwargs or 'builtin' in kwargs: + raise Exception('Trying to add a dynamic or builtin entry') self._new_id_counter += 1 id = '*NEW%d' % self._new_id_counter entry = { @@ -195,16 +199,23 @@ def remove(self, *args): raise Exception('Modifying read-only path: remove %s' % repr(args)) for id in args: index = self._find_id(id, required=True) + entry = self._values[index] + if entry.get('dynamic', False) or entry.get('builtin', False): + raise Exception('Trying to remove a dynamic or builtin entry') del self._values[index] def update(self, **kwargs): if self._read_only: raise Exception('Modifying read-only path: update %s' % repr(kwargs)) + if 'dynamic' in kwargs or 'builtin' in kwargs: + raise Exception('Trying to update dynamic builtin fields') if self._path_info.single_value: index = 0 else: index = self._find_id(kwargs['.id'], required=True) entry = self._values[index] + if entry.get('dynamic', False) or entry.get('builtin', False): + raise Exception('Trying to update a dynamic or builtin entry') entry.update(kwargs) _normalize_entry(entry, self._path_info) diff --git a/tests/unit/plugins/modules/test_api_info.py b/tests/unit/plugins/modules/test_api_info.py index 8478daaf..ac374ddb 100644 --- a/tests/unit/plugins/modules/test_api_info.py +++ b/tests/unit/plugins/modules/test_api_info.py @@ -413,6 +413,128 @@ def test_dynamic(self, mock_compose_api_path): }, ]) + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_builtin_exclude(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + '.id': '*2000000', + 'name': 'all', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': True, + 'comment': 'contains all interfaces', + }, + { + '.id': '*2000001', + 'name': 'none', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': True, + 'comment': 'contains no interfaces', + }, + { + '.id': '*2000010', + 'name': 'WAN', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': False, + 'comment': 'defconf', + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'interface list', + 'handle_disabled': 'omit', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [ + { + '.id': '*2000010', + 'name': 'WAN', + 'include': '', + 'exclude': '', + 'comment': 'defconf', + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_builtin_include(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + '.id': '*2000000', + 'name': 'all', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': True, + 'comment': 'contains all interfaces', + }, + { + '.id': '*2000001', + 'name': 'none', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': True, + 'comment': 'contains no interfaces', + }, + { + '.id': '*2000010', + 'name': 'WAN', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': False, + 'comment': 'defconf', + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'interface list', + 'handle_disabled': 'omit', + 'include_builtin': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [ + { + '.id': '*2000000', + 'name': 'all', + 'include': '', + 'exclude': '', + 'builtin': True, + 'comment': 'contains all interfaces', + }, + { + '.id': '*2000001', + 'name': 'none', + 'include': '', + 'exclude': '', + 'builtin': True, + 'comment': 'contains no interfaces', + }, + { + '.id': '*2000010', + 'name': 'WAN', + 'include': '', + 'exclude': '', + 'builtin': False, + 'comment': 'defconf', + }, + ]) + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') def test_absent(self, mock_compose_api_path): mock_compose_api_path.return_value = [ @@ -501,3 +623,192 @@ def test_absent(self, mock_compose_api_path): 'server': 'all', }, ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_default_disable_1(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + '.id': '*10', + 'name': 'gre-tunnel3', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '0.0.0.0', + 'remote-address': '192.168.1.1', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + }, + { + '.id': '*11', + 'name': 'gre-tunnel4', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '0.0.0.0', + 'remote-address': '192.168.1.2', + 'keepalive': '10s,10', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + }, + { + '.id': '*12', + 'name': 'gre-tunnel5', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '192.168.0.1', + 'remote-address': '192.168.1.3', + 'keepalive': '20s,20', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + 'comment': 'foo', + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'interface gre', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [ + { + '.id': '*10', + 'name': 'gre-tunnel3', + 'remote-address': '192.168.1.1', + '!comment': None, + '!ipsec-secret': None, + '!keepalive': None, + }, + { + '.id': '*11', + 'name': 'gre-tunnel4', + 'remote-address': '192.168.1.2', + '!comment': None, + '!ipsec-secret': None, + }, + { + '.id': '*12', + 'name': 'gre-tunnel5', + 'local-address': '192.168.0.1', + 'remote-address': '192.168.1.3', + 'keepalive': '20s,20', + 'comment': 'foo', + '!ipsec-secret': None, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_default_disable_2(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + '.id': '*10', + 'name': 'gre-tunnel3', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '0.0.0.0', + 'remote-address': '192.168.1.1', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + }, + { + '.id': '*11', + 'name': 'gre-tunnel4', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '0.0.0.0', + 'remote-address': '192.168.1.2', + 'keepalive': '10s,10', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + }, + { + '.id': '*12', + 'name': 'gre-tunnel5', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '192.168.0.1', + 'remote-address': '192.168.1.3', + 'keepalive': '20s,20', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + 'comment': 'foo', + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'interface gre', + 'handle_disabled': 'omit', + 'hide_defaults': False, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [ + { + '.id': '*10', + 'name': 'gre-tunnel3', + 'mtu': 'auto', + 'local-address': '0.0.0.0', + 'remote-address': '192.168.1.1', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'disabled': False, + }, + { + '.id': '*11', + 'name': 'gre-tunnel4', + 'mtu': 'auto', + 'local-address': '0.0.0.0', + 'remote-address': '192.168.1.2', + 'keepalive': '10s,10', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'disabled': False, + }, + { + '.id': '*12', + 'name': 'gre-tunnel5', + 'mtu': 'auto', + 'local-address': '192.168.0.1', + 'remote-address': '192.168.1.3', + 'keepalive': '20s,20', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'disabled': False, + 'comment': 'foo', + }, + ]) diff --git a/tests/unit/plugins/modules/test_api_modify.py b/tests/unit/plugins/modules/test_api_modify.py index 2d70c430..2c6e0bd0 100644 --- a/tests/unit/plugins/modules/test_api_modify.py +++ b/tests/unit/plugins/modules/test_api_modify.py @@ -161,6 +161,97 @@ START_IP_DHCP_SEVER_LEASE_OLD_DATA = massage_expected_result_data(START_IP_DHCP_SEVER_LEASE, ('ip', 'dhcp-server', 'lease')) +START_INTERFACE_LIST = [ + { + '.id': '*2000000', + 'name': 'all', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': True, + 'comment': 'contains all interfaces', + }, + { + '.id': '*2000001', + 'name': 'none', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': True, + 'comment': 'contains no interfaces', + }, + { + '.id': '*2000010', + 'name': 'WAN', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': False, + 'comment': 'defconf', + }, + { + '.id': '*2000011', + 'name': 'Foo', + 'dynamic': False, + 'include': '', + 'exclude': '', + 'builtin': False, + 'comment': '', + }, +] + +START_INTERFACE_LIST_OLD_DATA = massage_expected_result_data(START_INTERFACE_LIST, ('interface', 'list'), remove_builtin=True) + +START_INTERFACE_GRE = [ + { + '.id': '*10', + 'name': 'gre-tunnel3', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '0.0.0.0', + 'remote-address': '192.168.1.1', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + }, + { + '.id': '*11', + 'name': 'gre-tunnel4', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '0.0.0.0', + 'remote-address': '192.168.1.2', + 'keepalive': '10s,10', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + }, + { + '.id': '*12', + 'name': 'gre-tunnel5', + 'mtu': 'auto', + 'actual-mtu': 65496, + 'local-address': '192.168.0.1', + 'remote-address': '192.168.1.3', + 'keepalive': '20s,20', + 'dscp': 'inherit', + 'clamp-tcp-mss': True, + 'dont-fragment': False, + 'allow-fast-path': True, + 'running': True, + 'disabled': False, + 'comment': 'foo', + }, +] + +START_INTERFACE_GRE_OLD_DATA = massage_expected_result_data(START_INTERFACE_GRE, ('interface', 'gre')) + class TestRouterosApiModifyModule(ModuleTestCase): @@ -1650,3 +1741,66 @@ def test_absent_value(self): self.assertEqual(result['changed'], False) self.assertEqual(result['old_data'], START_IP_DHCP_SEVER_LEASE_OLD_DATA) self.assertEqual(result['new_data'], START_IP_DHCP_SEVER_LEASE_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('interface', 'list'), START_INTERFACE_LIST, read_only=True)) + def test_absent_entries_builtin(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'interface list', + 'data': [ + { + 'name': 'WAN', + 'comment': 'defconf', + }, + { + 'name': 'Foo', + }, + ], + 'handle_absent_entries': 'remove', + 'ensure_order': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_INTERFACE_LIST_OLD_DATA) + self.assertEqual(result['new_data'], START_INTERFACE_LIST_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('interface', 'gre'), START_INTERFACE_GRE, read_only=True)) + def test_idempotent_default_disabled(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'interface gre', + 'data': [ + { + 'name': 'gre-tunnel3', + 'remote-address': '192.168.1.1', + '!keepalive': None, + }, + { + 'name': 'gre-tunnel4', + 'remote-address': '192.168.1.2', + }, + { + 'name': 'gre-tunnel5', + 'local-address': '192.168.0.1', + 'remote-address': '192.168.1.3', + 'keepalive': '20s,20', + 'comment': 'foo', + }, + ], + 'handle_absent_entries': 'remove', + 'ensure_order': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_INTERFACE_GRE_OLD_DATA) + self.assertEqual(result['new_data'], START_INTERFACE_GRE_OLD_DATA)