diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/routeros/tests | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/routeros/tests')
91 files changed, 6759 insertions, 0 deletions
diff --git a/ansible_collections/community/routeros/tests/config.yml b/ansible_collections/community/routeros/tests/config.yml new file mode 100644 index 000000000..38590f2e4 --- /dev/null +++ b/ansible_collections/community/routeros/tests/config.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# See template for more information: +# https://github.com/ansible/ansible/blob/devel/test/lib/ansible_test/config/config.yml +modules: + python_requires: default diff --git a/ansible_collections/community/routeros/tests/ee/all.yml b/ansible_collections/community/routeros/tests/ee/all.yml new file mode 100644 index 000000000..26f198b4f --- /dev/null +++ b/ansible_collections/community/routeros/tests/ee/all.yml @@ -0,0 +1,18 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- hosts: localhost + tasks: + - name: Find all roles + find: + paths: + - "{{ (playbook_dir | default('.')) ~ '/roles' }}" + file_type: directory + depth: 1 + register: result + - name: Include all roles + include_role: + name: "{{ item }}" + loop: "{{ result.files | map(attribute='path') | map('regex_replace', '.*/', '') | sort }}" diff --git a/ansible_collections/community/routeros/tests/ee/roles/filter_quoting/aliases b/ansible_collections/community/routeros/tests/ee/roles/filter_quoting/aliases new file mode 100644 index 000000000..ddba81818 --- /dev/null +++ b/ansible_collections/community/routeros/tests/ee/roles/filter_quoting/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +shippable/posix/group1 +skip/python2.6 diff --git a/ansible_collections/community/routeros/tests/ee/roles/filter_quoting/tasks/main.yml b/ansible_collections/community/routeros/tests/ee/roles/filter_quoting/tasks/main.yml new file mode 100644 index 000000000..e7a2d29a1 --- /dev/null +++ b/ansible_collections/community/routeros/tests/ee/roles/filter_quoting/tasks/main.yml @@ -0,0 +1,63 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Test split filter" + assert: + that: + - "'' | community.routeros.split == []" + - "'foo bar' | community.routeros.split == ['foo', 'bar']" + - > + 'foo bar="a b c"' | community.routeros.split == ['foo', 'bar=a b c'] + +- name: "Test split filter error handling" + set_fact: + test: >- + {{ 'a="' | community.routeros.split }} + ignore_errors: true + register: result + +- name: "Verify split filter error handling" + assert: + that: + - >- + result.msg == "Unexpected end of string during escaped parameter" + +- name: "Test quote_argument filter" + assert: + that: + - > + 'a=' | community.routeros.quote_argument == 'a=""' + - > + 'a=b' | community.routeros.quote_argument == 'a=b' + - > + 'a=b c' | community.routeros.quote_argument == 'a="b\\_c"' + - > + 'a=""' | community.routeros.quote_argument == 'a="\\"\\""' + +- name: "Test quote_argument_value filter" + assert: + that: + - > + '' | community.routeros.quote_argument_value == '""' + - > + 'foo' | community.routeros.quote_argument_value == 'foo' + - > + '"foo bar"' | community.routeros.quote_argument_value == '"\\"foo\\_bar\\""' + +- name: "Test join filter" + assert: + that: + - > + ['a=', 'b=c d'] | community.routeros.join == 'a="" b="c\\_d"' + +- name: "Test list_to_dict filter" + assert: + that: + - > + ['a=', 'b=c'] | community.routeros.list_to_dict == {'a': '', 'b': 'c'} + - > + ['a=', 'b=c'] | community.routeros.list_to_dict(skip_empty_values=True) == {'b': 'c'} + - > + ['a', 'b=c'] | community.routeros.list_to_dict(require_assignment=False) == {'a': none, 'b': 'c'} diff --git a/ansible_collections/community/routeros/tests/ee/roles/smoke/tasks/main.yml b/ansible_collections/community/routeros/tests/ee/roles/smoke/tasks/main.yml new file mode 100644 index 000000000..b992c8e18 --- /dev/null +++ b/ansible_collections/community/routeros/tests/ee/roles/smoke/tasks/main.yml @@ -0,0 +1,43 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Run api module + community.routeros.api: + username: foo + password: bar + hostname: localhost + path: ip address + ignore_errors: true + register: result + +- name: Validate result + assert: + that: + - result is failed + - result.msg in potential_errors + vars: + potential_errors: + - "Error while connecting: [Errno 111] Connection refused" + - "Error while connecting: [Errno 99] Cannot assign requested address" + +- name: Run command module + community.routeros.command: + commands: + - /ip address print + vars: + ansible_host: localhost + ansible_connection: ansible.netcommon.network_cli + ansible_network_os: community.routeros.routeros + ansible_user: foo + ansible_ssh_pass: bar + ansible_ssh_port: 12349 + ignore_errors: true + register: result + +- name: Validate result + assert: + that: + - result is failed + - "'Unable to connect to port 12349 ' in result.msg or 'ssh connect failed: Connection refused' in result.msg" diff --git a/ansible_collections/community/routeros/tests/integration/requirements.yml b/ansible_collections/community/routeros/tests/integration/requirements.yml new file mode 100644 index 000000000..6a22736b5 --- /dev/null +++ b/ansible_collections/community/routeros/tests/integration/requirements.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +collections: +- ansible.netcommon diff --git a/ansible_collections/community/routeros/tests/integration/targets/filter_quoting/aliases b/ansible_collections/community/routeros/tests/integration/targets/filter_quoting/aliases new file mode 100644 index 000000000..ddba81818 --- /dev/null +++ b/ansible_collections/community/routeros/tests/integration/targets/filter_quoting/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +shippable/posix/group1 +skip/python2.6 diff --git a/ansible_collections/community/routeros/tests/integration/targets/filter_quoting/tasks/main.yml b/ansible_collections/community/routeros/tests/integration/targets/filter_quoting/tasks/main.yml new file mode 100644 index 000000000..e7a2d29a1 --- /dev/null +++ b/ansible_collections/community/routeros/tests/integration/targets/filter_quoting/tasks/main.yml @@ -0,0 +1,63 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Test split filter" + assert: + that: + - "'' | community.routeros.split == []" + - "'foo bar' | community.routeros.split == ['foo', 'bar']" + - > + 'foo bar="a b c"' | community.routeros.split == ['foo', 'bar=a b c'] + +- name: "Test split filter error handling" + set_fact: + test: >- + {{ 'a="' | community.routeros.split }} + ignore_errors: true + register: result + +- name: "Verify split filter error handling" + assert: + that: + - >- + result.msg == "Unexpected end of string during escaped parameter" + +- name: "Test quote_argument filter" + assert: + that: + - > + 'a=' | community.routeros.quote_argument == 'a=""' + - > + 'a=b' | community.routeros.quote_argument == 'a=b' + - > + 'a=b c' | community.routeros.quote_argument == 'a="b\\_c"' + - > + 'a=""' | community.routeros.quote_argument == 'a="\\"\\""' + +- name: "Test quote_argument_value filter" + assert: + that: + - > + '' | community.routeros.quote_argument_value == '""' + - > + 'foo' | community.routeros.quote_argument_value == 'foo' + - > + '"foo bar"' | community.routeros.quote_argument_value == '"\\"foo\\_bar\\""' + +- name: "Test join filter" + assert: + that: + - > + ['a=', 'b=c d'] | community.routeros.join == 'a="" b="c\\_d"' + +- name: "Test list_to_dict filter" + assert: + that: + - > + ['a=', 'b=c'] | community.routeros.list_to_dict == {'a': '', 'b': 'c'} + - > + ['a=', 'b=c'] | community.routeros.list_to_dict(skip_empty_values=True) == {'b': 'c'} + - > + ['a', 'b=c'] | community.routeros.list_to_dict(require_assignment=False) == {'a': none, 'b': 'c'} diff --git a/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.json b/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.json new file mode 100644 index 000000000..9a28d174f --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.json @@ -0,0 +1,13 @@ +{ + "include_symlinks": false, + "prefixes": [ + "docs/docsite/", + "plugins/", + "roles/" + ], + "output": "path-line-column-message", + "requirements": [ + "ansible-core", + "antsibull-docs" + ] +} diff --git a/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.json.license b/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.json.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.json.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.py b/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.py new file mode 100755 index 000000000..c636beb08 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/extra-docs.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +"""Check extra collection docs with antsibull-docs.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import sys +import subprocess + + +def main(): + """Main entry point.""" + env = os.environ.copy() + suffix = ':{env}'.format(env=env["ANSIBLE_COLLECTIONS_PATH"]) if 'ANSIBLE_COLLECTIONS_PATH' in env else '' + env['ANSIBLE_COLLECTIONS_PATH'] = '{root}{suffix}'.format(root=os.path.dirname(os.path.dirname(os.path.dirname(os.getcwd()))), suffix=suffix) + p = subprocess.run( + ['antsibull-docs', 'lint-collection-docs', '--plugin-docs', '--disallow-semantic-markup', '--skip-rstcheck', '.'], + env=env, + check=False, + ) + if p.returncode not in (0, 3): + print('{0}:0:0: unexpected return code {1}'.format(sys.argv[0], p.returncode)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/routeros/tests/sanity/extra/licenses.json b/ansible_collections/community/routeros/tests/sanity/extra/licenses.json new file mode 100644 index 000000000..50e47ca88 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/licenses.json @@ -0,0 +1,4 @@ +{ + "include_symlinks": false, + "output": "path-message" +} diff --git a/ansible_collections/community/routeros/tests/sanity/extra/licenses.json.license b/ansible_collections/community/routeros/tests/sanity/extra/licenses.json.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/licenses.json.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/extra/licenses.py b/ansible_collections/community/routeros/tests/sanity/extra/licenses.py new file mode 100755 index 000000000..80eb795ef --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/licenses.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# Copyright (c) 2022, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +"""Prevent files without a correct license identifier from being added to the source tree.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import glob +import sys + + +def format_license_list(licenses): + if not licenses: + return '(empty)' + return ', '.join(['"%s"' % license for license in licenses]) + + +def find_licenses(filename, relax=False): + spdx_license_identifiers = [] + other_license_identifiers = [] + has_copyright = False + try: + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + line = line.rstrip() + if 'Copyright ' in line: + has_copyright = True + if 'Copyright: ' in line: + print('%s: found copyright line with "Copyright:". Please remove the colon.' % (filename, )) + if 'SPDX-FileCopyrightText: ' in line: + has_copyright = True + idx = line.find('SPDX-License-Identifier: ') + if idx >= 0: + lic_id = line[idx + len('SPDX-License-Identifier: '):] + spdx_license_identifiers.extend(lic_id.split(' OR ')) + if 'GNU General Public License' in line: + if 'v3.0+' in line: + other_license_identifiers.append('GPL-3.0-or-later') + if 'version 3 or later' in line: + other_license_identifiers.append('GPL-3.0-or-later') + if 'Simplified BSD License' in line: + other_license_identifiers.append('BSD-2-Clause') + if 'Apache License 2.0' in line: + other_license_identifiers.append('Apache-2.0') + if 'PSF License' in line or 'Python-2.0' in line: + other_license_identifiers.append('PSF-2.0') + if 'MIT License' in line: + other_license_identifiers.append('MIT') + except Exception as exc: + print('%s: error while processing file: %s' % (filename, exc)) + if len(set(spdx_license_identifiers)) < len(spdx_license_identifiers): + print('%s: found identical SPDX-License-Identifier values' % (filename, )) + if other_license_identifiers and set(other_license_identifiers) != set(spdx_license_identifiers): + print('%s: SPDX-License-Identifier yielded the license list %s, while manual guessing yielded the license list %s' % ( + filename, format_license_list(spdx_license_identifiers), format_license_list(other_license_identifiers))) + if not has_copyright and not relax: + print('%s: found no copyright notice' % (filename, )) + return sorted(spdx_license_identifiers) + + +def main(): + """Main entry point.""" + paths = sys.argv[1:] or sys.stdin.read().splitlines() + + # The following paths are allowed to have no license identifier + no_comments_allowed = [ + 'changelogs/fragments/*.yml', + 'changelogs/fragments/*.yaml', + ] + + # These files are completely ignored + ignore_paths = [ + '.ansible-test-timeout.json', + '.reuse/dep5', + 'LICENSES/*.txt', + 'COPYING', + ] + + no_comments_allowed = [fn for pattern in no_comments_allowed for fn in glob.glob(pattern)] + ignore_paths = [fn for pattern in ignore_paths for fn in glob.glob(pattern)] + + valid_licenses = [license_file[len('LICENSES/'):-len('.txt')] for license_file in glob.glob('LICENSES/*.txt')] + + for path in paths: + if path.startswith('./'): + path = path[2:] + if path in ignore_paths or path.startswith('tests/output/'): + continue + if os.stat(path).st_size == 0: + continue + if not path.endswith('.license') and os.path.exists(path + '.license'): + path = path + '.license' + valid_licenses_for_path = valid_licenses + if path.startswith('plugins/') and not path.startswith(('plugins/modules/', 'plugins/module_utils/')): + valid_licenses_for_path = [license for license in valid_licenses if license == 'GPL-3.0-or-later'] + licenses = find_licenses(path, relax=path in no_comments_allowed) + if not licenses: + if path not in no_comments_allowed: + print('%s: must have at least one license' % (path, )) + else: + for license in licenses: + if license not in valid_licenses_for_path: + print('%s: found not allowed license "%s", must be one of %s' % ( + path, license, format_license_list(valid_licenses_for_path))) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/routeros/tests/sanity/extra/licenses.py.license b/ansible_collections/community/routeros/tests/sanity/extra/licenses.py.license new file mode 100644 index 000000000..6c4958feb --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/licenses.py.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: 2022, Felix Fontein <felix@fontein.de> diff --git a/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.json b/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.json new file mode 100644 index 000000000..c789a7fd3 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.json @@ -0,0 +1,7 @@ +{ + "include_symlinks": true, + "prefixes": [ + "plugins/" + ], + "output": "path-message" +} diff --git a/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.json.license b/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.json.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.json.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.py b/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.py new file mode 100755 index 000000000..b39df83a1 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/no-unwanted-files.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +"""Prevent unwanted files from being added to the source tree.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import os.path +import sys + + +def main(): + """Main entry point.""" + paths = sys.argv[1:] or sys.stdin.read().splitlines() + + allowed_extensions = ( + '.cs', + '.ps1', + '.psm1', + '.py', + ) + + skip_paths = set([ + ]) + + skip_directories = ( + ) + + yaml_directories = ( + 'plugins/test/', + 'plugins/filter/', + ) + + for path in paths: + if path in skip_paths: + continue + + if any(path.startswith(skip_directory) for skip_directory in skip_directories): + continue + + if os.path.islink(path): + print('%s: is a symbolic link' % (path, )) + elif not os.path.isfile(path): + print('%s: is not a regular file' % (path, )) + + ext = os.path.splitext(path)[1] + + if ext in ('.yml', ) and any(path.startswith(yaml_directory) for yaml_directory in yaml_directories): + continue + + if ext not in allowed_extensions: + print('%s: extension must be one of: %s' % (path, ', '.join(allowed_extensions))) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/routeros/tests/sanity/extra/update-docs.json b/ansible_collections/community/routeros/tests/sanity/extra/update-docs.json new file mode 100644 index 000000000..029699f0f --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/update-docs.json @@ -0,0 +1,8 @@ +{ + "include_symlinks": false, + "prefixes": [ + "docs/docsite/rst/api-guide.rst", + "plugins/modules/" + ], + "output": "path-line-column-message" +} diff --git a/ansible_collections/community/routeros/tests/sanity/extra/update-docs.json.license b/ansible_collections/community/routeros/tests/sanity/extra/update-docs.json.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/update-docs.json.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/extra/update-docs.py b/ansible_collections/community/routeros/tests/sanity/extra/update-docs.py new file mode 100644 index 000000000..68e2edf87 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/extra/update-docs.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +"""Check whether update-docs.py modifies something.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import subprocess + + +def main(): + """Main entry point.""" + p = subprocess.run(['./update-docs.py'], check=False) + if p.returncode not in (0, 1): + print('{0}:0:0: unexpected return code {1}'.format(sys.argv[0], p.returncode)) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.10.txt b/ansible_collections/community/routeros/tests/sanity/ignore-2.10.txt new file mode 100644 index 000000000..876765a85 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.10.txt @@ -0,0 +1,6 @@ +update-docs.py compile-2.6 +update-docs.py compile-2.7 +update-docs.py compile-3.5 +update-docs.py future-import-boilerplate +update-docs.py metaclass-boilerplate +update-docs.py shebang diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.10.txt.license b/ansible_collections/community/routeros/tests/sanity/ignore-2.10.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.10.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.11.txt b/ansible_collections/community/routeros/tests/sanity/ignore-2.11.txt new file mode 100644 index 000000000..876765a85 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.11.txt @@ -0,0 +1,6 @@ +update-docs.py compile-2.6 +update-docs.py compile-2.7 +update-docs.py compile-3.5 +update-docs.py future-import-boilerplate +update-docs.py metaclass-boilerplate +update-docs.py shebang diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.11.txt.license b/ansible_collections/community/routeros/tests/sanity/ignore-2.11.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.11.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.12.txt b/ansible_collections/community/routeros/tests/sanity/ignore-2.12.txt new file mode 100644 index 000000000..ce635c32c --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.12.txt @@ -0,0 +1 @@ +update-docs.py shebang diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.12.txt.license b/ansible_collections/community/routeros/tests/sanity/ignore-2.12.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.12.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.13.txt b/ansible_collections/community/routeros/tests/sanity/ignore-2.13.txt new file mode 100644 index 000000000..ce635c32c --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.13.txt @@ -0,0 +1 @@ +update-docs.py shebang diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.13.txt.license b/ansible_collections/community/routeros/tests/sanity/ignore-2.13.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.13.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.14.txt b/ansible_collections/community/routeros/tests/sanity/ignore-2.14.txt new file mode 100644 index 000000000..ce635c32c --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.14.txt @@ -0,0 +1 @@ +update-docs.py shebang diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.14.txt.license b/ansible_collections/community/routeros/tests/sanity/ignore-2.14.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.14.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.15.txt b/ansible_collections/community/routeros/tests/sanity/ignore-2.15.txt new file mode 100644 index 000000000..ce635c32c --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.15.txt @@ -0,0 +1 @@ +update-docs.py shebang diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.15.txt.license b/ansible_collections/community/routeros/tests/sanity/ignore-2.15.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.15.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.16.txt b/ansible_collections/community/routeros/tests/sanity/ignore-2.16.txt new file mode 100644 index 000000000..ce635c32c --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.16.txt @@ -0,0 +1 @@ +update-docs.py shebang diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.16.txt.license b/ansible_collections/community/routeros/tests/sanity/ignore-2.16.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.16.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.9.txt b/ansible_collections/community/routeros/tests/sanity/ignore-2.9.txt new file mode 100644 index 000000000..876765a85 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.9.txt @@ -0,0 +1,6 @@ +update-docs.py compile-2.6 +update-docs.py compile-2.7 +update-docs.py compile-3.5 +update-docs.py future-import-boilerplate +update-docs.py metaclass-boilerplate +update-docs.py shebang diff --git a/ansible_collections/community/routeros/tests/sanity/ignore-2.9.txt.license b/ansible_collections/community/routeros/tests/sanity/ignore-2.9.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/sanity/ignore-2.9.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/compat/__init__.py b/ansible_collections/community/routeros/tests/unit/compat/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/compat/__init__.py diff --git a/ansible_collections/community/routeros/tests/unit/compat/builtins.py b/ansible_collections/community/routeros/tests/unit/compat/builtins.py new file mode 100644 index 000000000..d548601d4 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/compat/builtins.py @@ -0,0 +1,20 @@ +# Copyright (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# +# Compat for python2.7 +# + +# One unittest needs to import builtins via __import__() so we need to have +# the string that represents it +try: + import __builtin__ # noqa: F401, pylint: disable=unused-import +except ImportError: + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' diff --git a/ansible_collections/community/routeros/tests/unit/compat/mock.py b/ansible_collections/community/routeros/tests/unit/compat/mock.py new file mode 100644 index 000000000..bdbea945e --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/compat/mock.py @@ -0,0 +1,109 @@ +# Copyright (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python3.x's unittest.mock module +''' +import sys + +# Python 2.7 + +# Note: Could use the pypi mock library on python3.x as well as python2.x. It +# is the same as the python3 stdlib mock library + +try: + # Allow wildcard import because we really do want to import all of mock's + # symbols into this compat shim + # pylint: disable=wildcard-import,unused-wildcard-import + from unittest.mock import * # noqa: F401, pylint: disable=unused-import +except ImportError: + # Python 2 + # pylint: disable=wildcard-import,unused-wildcard-import + try: + from mock import * # noqa: F401, pylint: disable=unused-import + except ImportError: + print('You need the mock library installed on python2.x to run tests') + + +# Prior to 3.4.4, mock_open cannot handle binary read_data +if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): + file_spec = None + + def _iterate_read_data(read_data): + # Helper for mock_open: + # Retrieve lines from read_data via a generator so that separate calls to + # readline, read, and readlines are properly interleaved + sep = b'\n' if isinstance(read_data, bytes) else '\n' + data_as_list = [l + sep for l in read_data.split(sep)] + + if data_as_list[-1] == sep: + # If the last line ended in a newline, the list comprehension will have an + # extra entry that's just a newline. Remove this. + data_as_list = data_as_list[:-1] + else: + # If there wasn't an extra newline by itself, then the file being + # emulated doesn't have a newline to end the last line remove the + # newline that our naive format() added + data_as_list[-1] = data_as_list[-1][:-1] + + for line in data_as_list: + yield line + + def mock_open(mock=None, read_data=''): + """ + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read` methoddline`, and `readlines` of the + file handle to return. This is an empty string by default. + """ + def _readlines_side_effect(*args, **kwargs): + if handle.readlines.return_value is not None: + return handle.readlines.return_value + return list(_data) + + def _read_side_effect(*args, **kwargs): + if handle.read.return_value is not None: + return handle.read.return_value + return type(read_data)().join(_data) + + def _readline_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _data: + yield line + + global file_spec + if file_spec is None: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + + if mock is None: + mock = MagicMock(name='open', spec=open) + + handle = MagicMock(spec=file_spec) + handle.__enter__.return_value = handle + + _data = _iterate_read_data(read_data) + + handle.write.return_value = None + handle.read.return_value = None + handle.readline.return_value = None + handle.readlines.return_value = None + + handle.read.side_effect = _read_side_effect + handle.readline.side_effect = _readline_side_effect() + handle.readlines.side_effect = _readlines_side_effect + + mock.return_value = handle + return mock diff --git a/ansible_collections/community/routeros/tests/unit/compat/unittest.py b/ansible_collections/community/routeros/tests/unit/compat/unittest.py new file mode 100644 index 000000000..d50bab86f --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/compat/unittest.py @@ -0,0 +1,25 @@ +# Copyright (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python2.7's unittest module +''' + +import sys + +# Allow wildcard import because we really do want to import all of +# unittests's symbols into this compat shim +# pylint: disable=wildcard-import,unused-wildcard-import +if sys.version_info < (2, 7): + try: + # Need unittest2 on python2.6 + from unittest2 import * # noqa: F401, pylint: disable=unused-import + except ImportError: + print('You need unittest2 installed on python2.6.x to run tests') +else: + from unittest import * # noqa: F401, pylint: disable=unused-import diff --git a/ansible_collections/community/routeros/tests/unit/plugins/module_utils/test__api_data.py b/ansible_collections/community/routeros/tests/unit/plugins/module_utils/test__api_data.py new file mode 100644 index 000000000..1250fdaa5 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/module_utils/test__api_data.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Felix Fontein (@felixfontein) <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import pytest + +from ansible_collections.community.routeros.plugins.module_utils._api_data import ( + APIData, + KeyInfo, + split_path, + join_path, +) + + +def test_api_data_errors(): + with pytest.raises(ValueError) as exc: + APIData() + assert exc.value.args[0] == 'fields must be provided' + + values = [ + ('primary_keys', []), + ('stratify_keys', []), + ('has_identifier', True), + ('single_value', True), + ('unknown_mechanism', True), + ] + + for index, (param, param_value) in enumerate(values): + for param2, param2_value in values[index + 1:]: + with pytest.raises(ValueError) as exc: + APIData(**{param: param_value, param2: param2_value}) + assert exc.value.args[0] == 'primary_keys, stratify_keys, has_identifier, single_value, and unknown_mechanism are mutually exclusive' + + with pytest.raises(ValueError) as exc: + APIData(unknown_mechanism=True, fully_understood=True) + assert exc.value.args[0] == 'unknown_mechanism and fully_understood cannot be combined' + + with pytest.raises(ValueError) as exc: + APIData(unknown_mechanism=True, fixed_entries=True) + assert exc.value.args[0] == 'fixed_entries can only be used with primary_keys' + + with pytest.raises(ValueError) as exc: + APIData(primary_keys=['foo'], fields={}) + assert exc.value.args[0] == 'Primary key foo must be in fields!' + + with pytest.raises(ValueError) as exc: + APIData(stratify_keys=['foo'], fields={}) + assert exc.value.args[0] == 'Stratify key foo must be in fields!' + + with pytest.raises(ValueError) as exc: + APIData(required_one_of=['foo'], fields={}) + assert exc.value.args[0] == 'Require one of element at index #1 must be a list!' + + with pytest.raises(ValueError) as exc: + APIData(required_one_of=[['foo']], fields={}) + assert exc.value.args[0] == 'Require one of key foo must be in fields!' + + with pytest.raises(ValueError) as exc: + APIData(mutually_exclusive=['foo'], fields={}) + assert exc.value.args[0] == 'Mutually exclusive element at index #1 must be a list!' + + with pytest.raises(ValueError) as exc: + APIData(mutually_exclusive=[['foo']], fields={}) + assert exc.value.args[0] == 'Mutually exclusive key foo must be in fields!' + + +def test_key_info_errors(): + values = [ + ('required', True), + ('default', ''), + ('automatically_computed_from', ()), + ('can_disable', True), + ] + + params_allowed_together = [ + 'default', + 'can_disable', + ] + + emsg = 'required, default, automatically_computed_from, and can_disable are mutually exclusive besides default and can_disable which can be set together' + for index, (param, param_value) in enumerate(values): + for param2, param2_value in values[index + 1:]: + if param in params_allowed_together and param2 in params_allowed_together: + continue + with pytest.raises(ValueError) as exc: + KeyInfo(**{param: param_value, param2: param2_value}) + assert exc.value.args[0] == emsg + + with pytest.raises(ValueError) as exc: + KeyInfo('foo') + assert exc.value.args[0] == 'KeyInfo() does not have positional arguments' + + with pytest.raises(ValueError) as exc: + KeyInfo(remove_value='') + assert exc.value.args[0] == 'remove_value can only be specified if can_disable=True' + + +SPLITTED_PATHS = [ + ('', [], ''), + (' ip ', ['ip'], 'ip'), + ('ip', ['ip'], 'ip'), + (' ip \t\n\raddress ', ['ip', 'address'], 'ip address'), +] + + +@pytest.mark.parametrize("joined_input, splitted, joined_output", SPLITTED_PATHS) +def test_join_split_path(joined_input, splitted, joined_output): + assert split_path(joined_input) == splitted + assert join_path(splitted) == joined_output diff --git a/ansible_collections/community/routeros/tests/unit/plugins/module_utils/test_quoting.py b/ansible_collections/community/routeros/tests/unit/plugins/module_utils/test_quoting.py new file mode 100644 index 000000000..6d29d507c --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/module_utils/test_quoting.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Felix Fontein (@felixfontein) <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import pytest + +from ansible.module_utils.common.text.converters import to_native + +from ansible_collections.community.routeros.plugins.module_utils.quoting import ( + ParseError, + convert_list_to_dictionary, + join_routeros_command, + parse_argument_value, + quote_routeros_argument, + quote_routeros_argument_value, + split_routeros_command, +) + + +TEST_PARSE_ARGUMENT_VALUE = [ + ('a', {}, ('a', 1)), + ('a ', {'must_match_everything': False}, ('a', 1)), + (r'"a b"', {}, ('a b', 5)), + (r'"b\"f"', {}, ('b"f', 6)), + (r'"\01"', {}, ('\x01', 5)), + (r'"\1F"', {}, ('\x1f', 5)), + (r'"\FF"', {}, (to_native(b'\xff'), 5)), + (r'"\"e"', {}, ('"e', 5)), + (r'"\""', {}, ('"', 4)), + (r'"\\"', {}, ('\\', 4)), + (r'"\?"', {}, ('?', 4)), + (r'"\$"', {}, ('$', 4)), + (r'"\_"', {}, (' ', 4)), + (r'"\a"', {}, ('\a', 4)), + (r'"\b"', {}, ('\b', 4)), + (r'"\f"', {}, (to_native(b'\xff'), 4)), + (r'"\n"', {}, ('\n', 4)), + (r'"\r"', {}, ('\r', 4)), + (r'"\t"', {}, ('\t', 4)), + (r'"\v"', {}, ('\v', 4)), + (r'"b=c"', {}, ('b=c', 5)), + (r'""', {}, ('', 2)), + (r'"" ', {'must_match_everything': False}, ('', 2)), + ("'e", {'start_index': 1}, ('e', 2)), +] + + +@pytest.mark.parametrize("command, kwargs, result", TEST_PARSE_ARGUMENT_VALUE) +def test_parse_argument_value(command, kwargs, result): + result_ = parse_argument_value(command, **kwargs) + print(result_, result) + assert result_ == result + + +TEST_PARSE_ARGUMENT_VALUE_ERRORS = [ + (r'"e', {}, 'Unexpected end of string during escaped parameter'), + ("'e", {}, '"\'" can only be used inside double quotes'), + (r'\FF', {}, 'Escape sequences can only be used inside double quotes'), + (r'\"e', {}, 'Escape sequences can only be used inside double quotes'), + ('e=f', {}, '"=" can only be used inside double quotes'), + ('e$', {}, '"$" can only be used inside double quotes'), + ('e(', {}, '"(" can only be used inside double quotes'), + ('e)', {}, '")" can only be used inside double quotes'), + ('e[', {}, '"[" can only be used inside double quotes'), + ('e{', {}, '"{" can only be used inside double quotes'), + ('e`', {}, '"`" can only be used inside double quotes'), + ('?', {}, '"?" can only be used in escaped form'), + (r'b"', {}, '\'"\' must not appear in an unquoted value'), + (r'""a', {}, "Ending '\"' must be followed by space or end of string"), + (r'"" ', {}, "Unexpected data at end of value"), + ('"\\', {}, r"'\' must not be at the end of the line"), + (r'"\A', {}, r'Hex escape sequence cut off at end of line'), + (r'"\Z"', {}, r"Invalid escape sequence '\Z'"), + (r'"\Aa"', {}, r"Invalid hex escape sequence '\Aa'"), +] + + +@pytest.mark.parametrize("command, kwargs, message", TEST_PARSE_ARGUMENT_VALUE_ERRORS) +def test_parse_argument_value_errors(command, kwargs, message): + with pytest.raises(ParseError) as exc: + parse_argument_value(command, **kwargs) + print(exc.value.args[0], message) + assert exc.value.args[0] == message + + +TEST_SPLIT_ROUTEROS_COMMAND = [ + ('', []), + (' ', []), + (r'a b c', ['a', 'b', 'c']), + (r'a=b c d=e', ['a=b', 'c', 'd=e']), + (r'a="b f" c d=e', ['a=b f', 'c', 'd=e']), + (r'a="b\"f" c="\FF" d="\"e"', ['a=b"f', to_native(b'c=\xff'), 'd="e']), + (r'a="b=c"', ['a=b=c']), + (r'a=b ', ['a=b']), +] + + +@pytest.mark.parametrize("command, result", TEST_SPLIT_ROUTEROS_COMMAND) +def test_split_routeros_command(command, result): + result_ = split_routeros_command(command) + print(result_, result) + assert result_ == result + + +TEST_SPLIT_ROUTEROS_COMMAND_ERRORS = [ + (r'a=', 'Expected value, but found end of string'), + (r'a="b\"f" d="e', 'Unexpected end of string during escaped parameter'), + ('d=\'e', '"\'" can only be used inside double quotes'), + (r'c\FF', r'Found unexpected "\"'), + (r'd=\"e', 'Escape sequences can only be used inside double quotes'), + ('d=e=f', '"=" can only be used inside double quotes'), + ('d=e$', '"$" can only be used inside double quotes'), + ('d=e(', '"(" can only be used inside double quotes'), + ('d=e)', '")" can only be used inside double quotes'), + ('d=e[', '"[" can only be used inside double quotes'), + ('d=e{', '"{" can only be used inside double quotes'), + ('d=e`', '"`" can only be used inside double quotes'), + ('d=?', '"?" can only be used in escaped form'), + (r'a=b"', '\'"\' must not appear in an unquoted value'), + (r'a=""a', "Ending '\"' must be followed by space or end of string"), + ('a="\\', r"'\' must not be at the end of the line"), + (r'a="\Z', r"Invalid escape sequence '\Z'"), + (r'a="\Aa', r"Invalid hex escape sequence '\Aa'"), +] + + +@pytest.mark.parametrize("command, message", TEST_SPLIT_ROUTEROS_COMMAND_ERRORS) +def test_split_routeros_command_errors(command, message): + with pytest.raises(ParseError) as exc: + split_routeros_command(command) + print(exc.value.args[0], message) + assert exc.value.args[0] == message + + +TEST_CONVERT_LIST_TO_DICTIONARY = [ + (['a=b', 'c=d=e', 'e='], {}, {'a': 'b', 'c': 'd=e', 'e': ''}), + (['a=b', 'c=d=e', 'e='], {'skip_empty_values': False}, {'a': 'b', 'c': 'd=e', 'e': ''}), + (['a=b', 'c=d=e', 'e='], {'skip_empty_values': True}, {'a': 'b', 'c': 'd=e'}), + (['a=b', 'c=d=e', 'e=', 'f'], {'require_assignment': False}, {'a': 'b', 'c': 'd=e', 'e': '', 'f': None}), +] + + +@pytest.mark.parametrize("list, kwargs, expected_dict", TEST_CONVERT_LIST_TO_DICTIONARY) +def test_convert_list_to_dictionary(list, kwargs, expected_dict): + result = convert_list_to_dictionary(list, **kwargs) + print(result, expected_dict) + assert result == expected_dict + + +TEST_CONVERT_LIST_TO_DICTIONARY_ERRORS = [ + (['a=b', 'c=d=e', 'e=', 'f'], {}, "missing '=' after 'f'"), +] + + +@pytest.mark.parametrize("list, kwargs, message", TEST_CONVERT_LIST_TO_DICTIONARY_ERRORS) +def test_convert_list_to_dictionary_errors(list, kwargs, message): + with pytest.raises(ParseError) as exc: + result = convert_list_to_dictionary(list, **kwargs) + print(exc.value.args[0], message) + assert exc.value.args[0] == message + + +TEST_JOIN_ROUTEROS_COMMAND = [ + (['a=b', 'c=d=e', 'e=', 'f', 'g=h i j', 'h="h"'], r'a=b c="d=e" e="" f g="h\_i\_j" h="\"h\""'), +] + + +@pytest.mark.parametrize("list, expected", TEST_JOIN_ROUTEROS_COMMAND) +def test_join_routeros_command(list, expected): + result = join_routeros_command(list) + print(result, expected) + assert result == expected + + +TEST_QUOTE_ROUTEROS_ARGUMENT = [ + (r'', r''), + (r'a', r'a'), + (r'a=b', r'a=b'), + (r'a=b c', r'a="b\_c"'), + (r'a="b c"', r'a="\"b\_c\""'), + (r"a='b", "a=\"'b\""), + (r"a=b'", "a=\"b'\""), + (r'a=""', r'a="\"\""'), +] + + +@pytest.mark.parametrize("argument, expected", TEST_QUOTE_ROUTEROS_ARGUMENT) +def test_quote_routeros_argument(argument, expected): + result = quote_routeros_argument(argument) + print(result, expected) + assert result == expected + + +TEST_QUOTE_ROUTEROS_ARGUMENT_ERRORS = [ + ('a b', 'Attribute names must not contain spaces'), + ('a b=c', 'Attribute names must not contain spaces'), +] + + +@pytest.mark.parametrize("argument, message", TEST_QUOTE_ROUTEROS_ARGUMENT_ERRORS) +def test_quote_routeros_argument_errors(argument, message): + with pytest.raises(ParseError) as exc: + result = quote_routeros_argument(argument) + print(exc.value.args[0], message) + assert exc.value.args[0] == message + + +TEST_QUOTE_ROUTEROS_ARGUMENT_VALUE = [ + (r'', r'""'), + (r";", r'";"'), + (r" ", r'"\_"'), + (r"=", r'"="'), + (r'a', r'a'), + (r'a=b', r'"a=b"'), + (r'b c', r'"b\_c"'), + (r'"b c"', r'"\"b\_c\""'), + ("'b", "\"'b\""), + ("b'", "\"b'\""), + ('"', r'"\""'), + ('\\', r'"\\"'), + ('?', r'"\?"'), + ('$', r'"\$"'), + ('_', r'_'), + (' ', r'"\_"'), + ('\a', r'"\a"'), + ('\b', r'"\b"'), + # (to_native(b'\xff'), r'"\f"'), + ('\n', r'"\n"'), + ('\r', r'"\r"'), + ('\t', r'"\t"'), + ('\v', r'"\v"'), + ('\x01', r'"\01"'), + ('\x1f', r'"\1F"'), +] + + +@pytest.mark.parametrize("argument, expected", TEST_QUOTE_ROUTEROS_ARGUMENT_VALUE) +def test_quote_routeros_argument_value(argument, expected): + result = quote_routeros_argument_value(argument) + print(result, expected) + assert result == expected + + +TEST_ROUNDTRIP = [ + {'a': 'b', 'c': 'd'}, + {'script': ''':local host value=[/system identity get name]; +:local date value=[/system clock get date]; +:local day [ :pick $date 4 6 ]; +:local month [ :pick $date 0 3 ]; +:local year [ :pick $date 7 11 ]; +:local name value=($host."-".$day."-".$month."-".$year); +/system backup save name=$name; +/export file=$name; +/tool fetch address="192.168.1.1" user=ros password="PASSWORD" mode=ftp dst-path=("/mikrotik/rsc/".$name.".rsc") src-path=($name.".rsc") upload=yes; +/tool fetch address="192.168.1.1" user=ros password="PASSWORD" mode=ftp dst-path=("/mikrotik/backup/".$name.".backup") src-path=($name.".backup") upload=yes; +'''}, +] + + +@pytest.mark.parametrize("dictionary", TEST_ROUNDTRIP) +def test_roundtrip(dictionary): + argument_list = ['%s=%s' % (k, v) for k, v in dictionary.items()] + command = join_routeros_command(argument_list) + resplit_list = split_routeros_command(command) + print(resplit_list, argument_list) + assert resplit_list == argument_list + re_dictionary = convert_list_to_dictionary(resplit_list) + print(re_dictionary, dictionary) + assert re_dictionary == dictionary diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fake_api.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/fake_api.py new file mode 100644 index 000000000..a5ddb3180 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fake_api.py @@ -0,0 +1,243 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.routeros.plugins.module_utils._api_data import PATHS + + +class FakeLibRouterosError(Exception): + def __init__(self, message): + self.message = message + super(FakeLibRouterosError, self).__init__(self.message) + + +class TrapError(FakeLibRouterosError): + def __init__(self, message="failure: already have interface with such name"): + super(TrapError, self).__init__(message) + + +# fixtures +class fake_ros_api(object): + def __init__(self, api, path): + pass + + @classmethod + def path(cls, api, path): + fake_bridge = [{".id": "*DC", "name": "b2", "mtu": "auto", "actual-mtu": 1500, + "l2mtu": 65535, "arp": "enabled", "arp-timeout": "auto", + "mac-address": "3A:C1:90:D6:E8:44", "protocol-mode": "rstp", + "fast-forward": "true", "igmp-snooping": "false", + "auto-mac": "true", "ageing-time": "5m", "priority": + "0x8000", "max-message-age": "20s", "forward-delay": "15s", + "transmit-hold-count": 6, "vlan-filtering": "false", + "dhcp-snooping": "false", "running": "true", "disabled": "false"}] + return fake_bridge + + @classmethod + def arbitrary(cls, api, path): + def retr(self, *args, **kwargs): + if 'name' not in kwargs.keys(): + raise TrapError(message="no such command") + dummy_test_string = '/interface/bridge add name=unit_test_brige_arbitrary' + result = "/%s/%s add name=%s" % (path[0], path[1], kwargs['name']) + return [result] + return retr + + def add(self, name): + if name == "unit_test_brige_exist": + raise TrapError + return '*A1' + + def remove(self, id): + if id != "*A1": + raise TrapError(message="no such item (4)") + return '*A1' + + def update(self, **kwargs): + if kwargs['.id'] != "*A1" or 'name' not in kwargs.keys(): + raise TrapError(message="no such item (4)") + return ["updated: {'.id': '%s' % kwargs['.id'], 'name': '%s' % kwargs['name']}"] + + def select(self, *args): + dummy_bridge = [{".id": "*A1", "name": "dummy_bridge_A1"}, + {".id": "*A2", "name": "dummy_bridge_A2"}, + {".id": "*A3", "name": "dummy_bridge_A3"}] + + result = [] + for dummy in dummy_bridge: + found = {} + for search in args: + if search in dummy.keys(): + found[search] = dummy[search] + else: + continue + if len(found.keys()) == 2: + result.append(found) + + if result: + return result + else: + return [] + + @classmethod + def select_where(cls, api, path): + api_path = Where() + return api_path + + +class Where(object): + def __init__(self): + pass + + def select(self, *args): + return self + + def where(self, *args): + return [{".id": "*A1", "name": "dummy_bridge_A1"}] + + +class Key(object): + def __init__(self, name): + self.name = name + self.str_return() + + def str_return(self): + return str(self.name) + + +class Or(object): + def __init__(self, *args): + self.args = args + self.str_return() + + def str_return(self): + return repr(self.args) + + +def _normalize_entry(entry, path_info, on_create=False): + for key, data in path_info.fields.items(): + if key not in entry and data.default is not None and (not data.can_disable or on_create): + entry[key] = data.default + if data.can_disable: + if key in entry and entry[key] in (None, data.remove_value): + del entry[key] + if ('!%s' % key) in entry: + entry.pop(key, None) + del entry['!%s' % key] + if data.absent_value is not None and key in entry and entry[key] == data.absent_value: + del entry[key] + + +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) + if not keep_all: + for key in list(entry): + if key == '.id' or key in path_info.fields: + continue + del entry[key] + for key, data in path_info.fields.items(): + if data.absent_value is not None and key not in entry: + entry[key] = data.absent_value + return values + + +class Path(object): + def __init__(self, path, initial_values, read_only=False): + self._path = path + self._path_info = PATHS[path] + self._values = [entry.copy() for entry in initial_values] + for entry in self._values: + _normalize_entry(entry, self._path_info) + self._new_id_counter = 0 + self._read_only = read_only + + def __iter__(self): + return [entry.copy() for entry in self._values].__iter__() + + def _find_id(self, id, required=False): + for index, entry in enumerate(self._values): + if entry['.id'] == id: + return index + if required: + raise FakeLibRouterosError('Cannot find key "%s"' % id) + return None + + def add(self, **kwargs): + if self._path_info.fixed_entries or self._path_info.single_value: + raise Exception('Cannot add entries') + if self._read_only: + 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 = { + '.id': id, + } + entry.update(kwargs) + _normalize_entry(entry, self._path_info, on_create=True) + self._values.append(entry) + return id + + def remove(self, *args): + if self._path_info.fixed_entries or self._path_info.single_value: + raise Exception('Cannot remove entries') + if self._read_only: + 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) + + def __call__(self, command, *args, **kwargs): + if self._read_only: + raise Exception('Modifying read-only path: "%s" %s %s' % (command, repr(args), repr(kwargs))) + if command != 'move': + raise FakeLibRouterosError('Unsupported command "%s"' % command) + if self._path_info.fixed_entries or self._path_info.single_value: + raise Exception('Cannot move entries') + yield None # make sure that nothing happens if the result isn't consumed + source_index = self._find_id(kwargs.pop('numbers'), required=True) + entry = self._values.pop(source_index) + dest_index = self._find_id(kwargs.pop('destination'), required=True) + self._values.insert(dest_index, entry) + + +def create_fake_path(path, initial_values, read_only=False): + def create(api, called_path): + called_path = tuple(called_path) + if path != called_path: + raise AssertionError('Expected {path}, got {called_path}'.format(path=path, called_path=called_path)) + return Path(path, initial_values, read_only=read_only) + + return create diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export new file mode 100644 index 000000000..d1ac49140 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export @@ -0,0 +1,24 @@ +# sep/25/2018 10:10:52 by RouterOS 6.42.5 +# software id = 9EER-511K +# +# +# +/interface wireless security-profiles +set [ find default=yes ] supplicant-identity=MikroTik +/tool user-manager customer +set admin access=own-routers,own-users,own-profiles,own-limits,config-payment-gw +/ip address +add address=192.168.88.1/24 comment=defconf interface=ether1 network=192.168.88.0 +/ip dhcp-client +add dhcp-options=hostname,clientid disabled=no interface=ether1 +/system lcd page +set time disabled=yes display-time=5s +set resources disabled=yes display-time=5s +set uptime disabled=yes display-time=5s +set packets disabled=yes display-time=5s +set bits disabled=yes display-time=5s +set version disabled=yes display-time=5s +set identity disabled=yes display-time=5s +set ether1 disabled=yes display-time=5s +/tool user-manager database +set db-path=user-manager diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export_verbose b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export_verbose new file mode 100644 index 000000000..0f49fefe3 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export_verbose @@ -0,0 +1,26 @@ +# sep/25/2018 10:10:52 by RouterOS 6.42.5 +# software id = 9EER-511K +# +# +# +/interface wireless security-profiles +set [ find default=yes ] supplicant-identity=MikroTik +/tool user-manager customer +set admin access=own-routers,own-users,own-profiles,own-limits,config-payment-gw +/ip address +add address=192.168.88.1/24 comment=defconf interface=ether1 network=192.168.88.0 +/ip dhcp-client +add dhcp-options=hostname,clientid disabled=no interface=ether1 +/system lcd +set contrast=0 enabled=no port=parallel type=24x4 +/system lcd page +set time disabled=yes display-time=5s +set resources disabled=yes display-time=5s +set uptime disabled=yes display-time=5s +set packets disabled=yes display-time=5s +set bits disabled=yes display-time=5s +set version disabled=yes display-time=5s +set identity disabled=yes display-time=5s +set ether1 disabled=yes display-time=5s +/tool user-manager database +set db-path=user-manager diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export_verbose.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export_verbose.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/export_verbose.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/interface_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/interface_print_detail_without-paging new file mode 100644 index 000000000..9ccddb292 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/interface_print_detail_without-paging @@ -0,0 +1,34 @@ +Flags: D - dynamic, X - disabled, R - running, S - slave + 0 R name="ether1" default-name="ether1" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:90 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 1 R name="ether2" default-name="ether2" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:91 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 2 R name="ether3" default-name="ether3" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:92 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 3 R name="ether4" default-name="ether4" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:93 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 4 R name="ether5" default-name="ether5" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:94 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 5 R name="ether6" default-name="ether6" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:95 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 6 R name="ether7" default-name="ether7" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:96 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 7 R name="ether8" default-name="ether8" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:97 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 8 R name="ether9" default-name="ether9" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:98 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 + 9 R name="ether10" default-name="ether10" type="ether" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:99 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 +10 R name="pppoe" default-name="pppoe" type="ppp" mtu=1500 actual-mtu=1500 + mac-address=00:1C:42:36:52:00 last-link-up-time=sep/25/2018 06:30:04 + link-downs=0 diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/interface_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/interface_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/interface_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_address_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_address_print_detail_without-paging new file mode 100644 index 000000000..d4fd2bcd2 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_address_print_detail_without-paging @@ -0,0 +1,10 @@ +Flags: X - disabled, I - invalid, D - dynamic + 0 ;;; defconf + address=192.168.88.1/24 network=192.168.88.0 interface=ether1 + actual-interface=ether1 + + 1 D address=10.37.129.3/24 network=10.37.129.0 interface=ether1 + actual-interface=ether1 + + 2 D address=10.37.0.0/24 network=10.37.0.1 interface=pppoe + actual-interface=pppoe diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_address_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_address_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_address_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_neighbor_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_neighbor_print_detail_without-paging new file mode 100644 index 000000000..906dfb750 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_neighbor_print_detail_without-paging @@ -0,0 +1,15 @@ + 0 interface=ether2-master address=10.37.129.3 address4=10.37.129.3 mac-address=D4:CA:6D:C6:16:4C identity="router1" platform="MikroTik" version="6.42.2 (stable)" unpack=none age=59s + uptime=3w19h11m36s software-id="1234-1234" board="RBwAPG-5HacT2HnD" interface-name="bridge" system-description="MikroTik RouterOS 6.42.2 (stable) RBwAPG-5HacT2HnD" + system-caps="" system-caps-enabled="" + + 1 interface=ether3 address=10.37.129.4 address4=10.37.129.4 mac-address=D4:CA:6D:C6:18:2F identity="router2" platform="MikroTik" version="6.42.2 (stable)" unpack=none age=54s + uptime=3w19h11m30s software-id="1234-1234" board="RBwAPG-5HacT2HnD" ipv6=no interface-name="bridge" system-description="MikroTik RouterOS 6.42.2 (stable) RBwAPG-5HacT2HnD" + system-caps="" system-caps-enabled="" + + 2 interface=ether5 address=10.37.129.5 address4=10.37.129.5 mac-address=B8:69:F4:37:F0:C8 identity="router3" platform="MikroTik" version="6.40.8 (bugfix)" unpack=none age=43s + uptime=3d14h25m31s software-id="1234-1234" board="RB960PGS" interface-name="ether1" system-description="MikroTik RouterOS 6.40.8 (bugfix) RB960PGS" system-caps="" + system-caps-enabled="" + + 3 interface=ether10 address=10.37.129.6 address4=10.37.129.6 mac-address=6C:3B:6B:A1:0B:63 identity="router4" platform="MikroTik" version="6.42.2 (stable)" unpack=none age=54s + uptime=3w6d1h11m44s software-id="1234-1234" board="RBSXTLTE3-7" interface-name="bridge" system-description="MikroTik RouterOS 6.42.2 (stable) RBSXTLTE3-7" system-caps="" + system-caps-enabled="" diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_neighbor_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_neighbor_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_neighbor_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_route_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_route_print_detail_without-paging new file mode 100644 index 000000000..6c2e558ef --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_route_print_detail_without-paging @@ -0,0 +1,19 @@ +Flags: X - disabled, A - active, D - dynamic, +C - connect, S - static, r - rip, b - bgp, o - ospf, m - mme, +B - blackhole, U - unreachable, P - prohibit + 0 ADC dst-address=10.10.66.0/30 pref-src=10.10.66.1 gateway=bridge1 + gateway-status=bridge1 reachable distance=0 scope=10 + routing-mark=altegro + + 2 A S dst-address=0.0.0.0/0 gateway=85.15.75.109 + gateway-status=85.15.75.109 reachable via Internet-VTK distance=1 + scope=30 target-scope=10 + + 3 ADC dst-address=10.10.1.0/30 pref-src=10.10.1.1 gateway=GRE_TYRMA + gateway-status=GRE_TYRMA reachable distance=0 scope=10 + + 4 DC dst-address=10.10.1.4/30 pref-src=10.10.1.5 gateway=RB2011 + gateway-status=RB2011 unreachable distance=255 scope=10 + + 5 ADC dst-address=10.10.2.0/30 pref-src=10.10.2.1 gateway=VLAN_SAT.ROUTER + gateway-status=VLAN_SAT.ROUTER reachable distance=0 scope=10 diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_route_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_route_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ip_route_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging new file mode 100644 index 000000000..c18e9ea57 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging @@ -0,0 +1,3 @@ +Flags: X - disabled, I - invalid, D - dynamic, G - global, L - link-local + 0 DL address=fe80::21c:42ff:fe36:5290/64 from-pool="" interface=ether1 + actual-interface=ether1 eui-64=no advertise=no no-dad=no diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging_no-ipv6 b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging_no-ipv6 new file mode 100644 index 000000000..0ea34bc0d --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging_no-ipv6 @@ -0,0 +1 @@ +bad command name address (line 1 column 7) diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging_no-ipv6.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging_no-ipv6.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/ipv6_address_print_detail_without-paging_no-ipv6.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_instance_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_instance_print_detail_without-paging new file mode 100644 index 000000000..8c560e2a4 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_instance_print_detail_without-paging @@ -0,0 +1,10 @@ +Flags: * - default, X - disabled + 0 *X name="default" as=65530 router-id=0.0.0.0 redistribute-connected=no + redistribute-static=no redistribute-rip=no redistribute-ospf=no + redistribute-other-bgp=no out-filter="" client-to-client-reflection=yes + ignore-as-path-len=no routing-table="" + + 1 name="MAIN_AS_STARKDV" as=64520 router-id=10.10.50.1 + redistribute-connected=no redistribute-static=no redistribute-rip=no + redistribute-ospf=no redistribute-other-bgp=no out-filter="" + client-to-client-reflection=yes ignore-as-path-len=no routing-table="" diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_instance_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_instance_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_instance_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_peer_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_peer_print_detail_without-paging new file mode 100644 index 000000000..1ae4f5bcf --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_peer_print_detail_without-paging @@ -0,0 +1,13 @@ +Flags: X - disabled, E - established + 0 E name="iBGP_BRAS.TYRMA" instance=MAIN_AS_STARKDV remote-address=10.10.100.1 + remote-as=64520 tcp-md5-key="" nexthop-choice=default multihop=no + route-reflect=yes hold-time=3m ttl=default in-filter="" out-filter="" + address-families=ip,l2vpn,vpnv4 update-source=LAN_KHV + default-originate=never remove-private-as=no as-override=no passive=no + use-bfd=yes + + 1 E name="iBGP_BRAS_SAT" instance=MAIN_AS_STARKDV remote-address=10.10.50.230 + remote-as=64520 tcp-md5-key="" nexthop-choice=default multihop=no + route-reflect=yes hold-time=3m ttl=default in-filter="" out-filter="" + address-families=ip default-originate=never remove-private-as=no + as-override=no passive=no use-bfd=yes diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_peer_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_peer_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_peer_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_vpnv4-route_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_vpnv4-route_print_detail_without-paging new file mode 100644 index 000000000..f64fa6d00 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_vpnv4-route_print_detail_without-paging @@ -0,0 +1,7 @@ +Flags: L - label-present + 0 L route-distinguisher=64520:666 dst-address=10.10.66.8/30 gateway=10.10.100.1 + interface=GRE_TYRMA in-label=6136 out-label=6136 bgp-local-pref=100 + bgp-origin=incomplete bgp-ext-communities="RT:64520:666" + + 1 L route-distinguisher=64520:666 dst-address=10.10.66.0/30 interface=bridge1 + in-label=1790 bgp-ext-communities="RT:64520:666" diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_vpnv4-route_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_vpnv4-route_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_bgp_vpnv4-route_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_instance_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_instance_print_detail_without-paging new file mode 100644 index 000000000..964046633 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_instance_print_detail_without-paging @@ -0,0 +1,10 @@ +Flags: X - disabled, * - default + 0 * name="default" router-id=10.10.50.1 distribute-default=never redistribute-connected=no + redistribute-static=no redistribute-rip=no redistribute-bgp=no redistribute-other-ospf=no + metric-default=1 metric-connected=20 metric-static=20 metric-rip=20 metric-bgp=auto + metric-other-ospf=auto in-filter=ospf-in out-filter=ospf-out + + 1 name="OSPF_ALTEGRO" router-id=10.10.66.1 distribute-default=never redistribute-connected=no + redistribute-static=no redistribute-rip=no redistribute-bgp=no redistribute-other-ospf=no + metric-default=1 metric-connected=20 metric-static=20 metric-rip=20 metric-bgp=auto + metric-other-ospf=auto in-filter=ospf-in out-filter=ospf-out routing-table=altegro diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_instance_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_instance_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_instance_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_neighbor_print_detail_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_neighbor_print_detail_without-paging new file mode 100644 index 000000000..d683b252c --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_neighbor_print_detail_without-paging @@ -0,0 +1,3 @@ +0 instance=default router-id=10.10.100.1 address=10.10.1.2 interface=GRE_TYRMA priority=1 + dr-address=0.0.0.0 backup-dr-address=0.0.0.0 state="Full" state-changes=15 ls-retransmits=0 + ls-requests=0 db-summaries=0 adjacency=6h8m46s diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_neighbor_print_detail_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_neighbor_print_detail_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/routing_ospf_neighbor_print_detail_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_identity_print_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_identity_print_without-paging new file mode 100644 index 000000000..d7dc3ff3e --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_identity_print_without-paging @@ -0,0 +1 @@ + name: MikroTik diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_identity_print_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_identity_print_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_identity_print_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_resource_print_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_resource_print_without-paging new file mode 100644 index 000000000..79353f791 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_resource_print_without-paging @@ -0,0 +1,16 @@ + uptime: 3h28m52s + version: 6.42.5 (stable) + build-time: Jun/26/2018 12:12:08 + free-memory: 988.3MiB + total-memory: 1010.8MiB + cpu: Intel(R) + cpu-count: 2 + cpu-frequency: 2496MHz + cpu-load: 0% + free-hdd-space: 63.4GiB + total-hdd-space: 63.5GiB + write-sect-since-reboot: 4576 + write-sect-total: 4576 + architecture-name: x86 + board-name: x86 + platform: MikroTik diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_resource_print_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_resource_print_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_resource_print_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_routerboard_print_without-paging b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_routerboard_print_without-paging new file mode 100644 index 000000000..263c95909 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_routerboard_print_without-paging @@ -0,0 +1,7 @@ + routerboard: yes + model: RouterBOARD 3011UiAS + serial-number: 1234567890 + firmware-type: ipq8060 + factory-firmware: 3.41 + current-firmware: 3.41 + upgrade-firmware: 6.42.2 diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_routerboard_print_without-paging.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_routerboard_print_without-paging.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/facts/system_routerboard_print_without-paging.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_package_print b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_package_print new file mode 100644 index 000000000..3f806211e --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_package_print @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + MMM MMM KKK TTTTTTTTTTT KKK + + MMMM MMMM KKK TTTTTTTTTTT KKK + + MMM MMMM MMM III KKK KKK RRRRRR OOOOOO TTT III KKK KKK + + MMM MM MMM III KKKKK RRR RRR OOO OOO TTT III KKKKK + + MMM MMM III KKK KKK RRRRRR OOO OOO TTT III KKK KKK + + MMM MMM III KKK KKK RRR RRR OOOOOO TTT III KKK KKK + + + + MikroTik RouterOS 6.42.5 (c) 1999-2018 http://www.mikrotik.com/ + + +[?] Gives the list of available commands + +command [?] Gives help on the command and list of arguments + + + +[Tab] Completes the command/word. If the input is ambiguous, + + a second [Tab] gives possible options + + + +/ Move up to base level + +.. Move up one level + +/command Use command at the base level + +[9999B +[9999BZ [6n<[c[4l[20l[?47l[?7h[?5l[?25h[H[9999B[6n + + + +[admin@MainRouter] > +[admin@MainRouter] > /system routerboard print +[admin@MainRouter] > /system routerboard print + + routerboard: yes + model: 750GL + serial-number: 1234567890AB + firmware-type: ar7240 + factory-firmware: 3.09 + current-firmware: 6.41.2 + upgrade-firmware: 6.42.5 + + + + + +[admin@MainRouter] > +[admin@MainRouter] > /system identity print +[admin@MainRouter] > /system identity print + + name: MikroTik + + + + + +[admin@MainRouter] > +[admin@MainRouter] > /system package print +[admin@MainRouter] > /system package print + +Flags: [m[1mX[m - disabled +[m[1m # NAME VERSION SCHEDULED +[m 0 routeros-mipsbe 6.42.5 + 1 system 6.42.5 + 2 ipv6 6.42.5 + 3 wireless 6.42.5 + 4 hotspot 6.42.5 + 5 dhcp 6.42.5 + 6 mpls 6.42.5 + 7 routing 6.42.5 + 8 ppp 6.42.5 + 9 security 6.42.5 +10 advanced-tools 6.42.5 + + + + + +[admin@MainRouter] > +[admin@MainRouter] >
\ No newline at end of file diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_package_print.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_package_print.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_package_print.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_resource_print b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_resource_print new file mode 100644 index 000000000..63bc3beba --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_resource_print @@ -0,0 +1,17 @@ +[admin@RB1100test] /system resource> print + uptime: 2w1d23h34m57s + version: "5.0rc1" + free-memory: 385272KiB + total-memory: 516708KiB + cpu: "e500v2" + cpu-count: 1 + cpu-frequency: 799MHz + cpu-load: 9% + free-hdd-space: 466328KiB + total-hdd-space: 520192KiB + write-sect-since-reboot: 1411 + write-sect-total: 70625 + bad-blocks: 0.2% + architecture-name: "powerpc" + board-name: "RB1100" + platform: "MikroTik" diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_resource_print.license b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_resource_print.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/fixtures/system_resource_print.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/routeros_module.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/routeros_module.py new file mode 100644 index 000000000..0ec44f70b --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/routeros_module.py @@ -0,0 +1,75 @@ +# Copyright (c) 2016 Red Hat Inc. +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json + +from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase + + +fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') +fixture_data = {} + + +def load_fixture(name): + path = os.path.join(fixture_path, name) + + if path in fixture_data: + return fixture_data[path] + + with open(path) as f: + data = f.read() + + try: + data = json.loads(data) + except Exception: + pass + + fixture_data[path] = data + return data + + +class TestRouterosModule(ModuleTestCase): + + def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False): + + self.load_fixtures(commands) + + if failed: + result = self.failed() + self.assertTrue(result['failed'], result) + else: + result = self.changed(changed) + self.assertEqual(result['changed'], changed, result) + + if commands is not None: + if sort: + self.assertEqual(sorted(commands), sorted(result['commands']), result['commands']) + else: + self.assertEqual(commands, result['commands'], result['commands']) + + return result + + def failed(self): + with self.assertRaises(AnsibleFailJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertTrue(result['failed'], result) + return result + + def changed(self, changed=False): + with self.assertRaises(AnsibleExitJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], changed, result) + return result + + def load_fixtures(self, commands=None): + pass diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api.py new file mode 100644 index 000000000..4cfdeef1e --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api.py @@ -0,0 +1,308 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import FakeLibRouterosError, Key, Or, fake_ros_api +from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase +from ansible_collections.community.routeros.plugins.modules import api + + +class TestRouterosApiModule(ModuleTestCase): + + def setUp(self): + super(TestRouterosApiModule, self).setUp() + self.module = api + self.module.LibRouterosError = FakeLibRouterosError + self.module.connect = MagicMock(new=fake_ros_api) + self.module.check_has_library = MagicMock() + self.patch_create_api = patch('ansible_collections.community.routeros.plugins.modules.api.create_api', MagicMock(new=fake_ros_api)) + self.patch_create_api.start() + self.module.Key = MagicMock(new=Key) + self.module.Or = MagicMock(new=Or) + self.config_module_args = {"username": "admin", + "password": "pаss", + "hostname": "127.0.0.1", + "path": "interface bridge"} + + def tearDown(self): + self.patch_create_api.stop() + + def test_module_fail_when_required_args_missing(self): + with self.assertRaises(AnsibleFailJson) as exc: + set_module_args({}) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.path) + def test_api_path(self): + with self.assertRaises(AnsibleExitJson) as exc: + set_module_args(self.config_module_args.copy()) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_add(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['add'] = "name=unit_test_brige" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_add_already_exist(self): + with self.assertRaises(AnsibleFailJson) as exc: + module_args = self.config_module_args.copy() + module_args['add'] = "name=unit_test_brige_exist" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'][0], 'failure: already have interface with such name') + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_remove(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['remove'] = "*A1" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_remove_no_id(self): + with self.assertRaises(AnsibleFailJson) as exc: + module_args = self.config_module_args.copy() + module_args['remove'] = "*A2" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'][0], 'no such item (4)') + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.arbitrary) + def test_api_cmd(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['cmd'] = "add name=unit_test_brige_arbitrary" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.arbitrary) + def test_api_cmd_none_existing_cmd(self): + with self.assertRaises(AnsibleFailJson) as exc: + module_args = self.config_module_args.copy() + module_args['cmd'] = "add NONE_EXIST=unit_test_brige_arbitrary" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'][0], 'no such command') + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_update(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['update'] = ".id=*A1 name=unit_test_brige" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_update_none_existing_id(self): + with self.assertRaises(AnsibleFailJson) as exc: + module_args = self.config_module_args.copy() + module_args['update'] = ".id=*A2 name=unit_test_brige" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'][0], 'no such item (4)') + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_query(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['query'] = ".id name" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], [ + {'.id': '*A1', 'name': 'dummy_bridge_A1'}, + {'.id': '*A2', 'name': 'dummy_bridge_A2'}, + {'.id': '*A3', 'name': 'dummy_bridge_A3'}, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_query_missing_key(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['query'] = ".id other" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], ["no results for 'interface bridge 'query' .id other"]) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.select_where) + def test_api_query_and_WHERE(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['query'] = ".id name WHERE name == dummy_bridge_A2" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], [ + {'.id': '*A1', 'name': 'dummy_bridge_A1'}, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.select_where) + def test_api_query_and_WHERE_no_cond(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['query'] = ".id name WHERE name != dummy_bridge_A2" + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], [ + {'.id': '*A1', 'name': 'dummy_bridge_A1'}, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_extended_query(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['extended_query'] = { + 'attributes': ['.id', 'name'], + } + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], [ + {'.id': '*A1', 'name': 'dummy_bridge_A1'}, + {'.id': '*A2', 'name': 'dummy_bridge_A2'}, + {'.id': '*A3', 'name': 'dummy_bridge_A3'}, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api) + def test_api_extended_query_missing_key(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['extended_query'] = { + 'attributes': ['.id', 'other'], + } + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], []) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.select_where) + def test_api_extended_query_and_WHERE(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['extended_query'] = { + 'attributes': ['.id', 'name'], + 'where': [ + { + 'attribute': 'name', + 'is': '==', + 'value': 'dummy_bridge_A2', + }, + ], + } + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], [ + {'.id': '*A1', 'name': 'dummy_bridge_A1'}, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.select_where) + def test_api_extended_query_and_WHERE_no_cond(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['extended_query'] = { + 'attributes': ['.id', 'name'], + 'where': [ + { + 'attribute': 'name', + 'is': 'not', + 'value': 'dummy_bridge_A2', + }, + ], + } + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], [ + {'.id': '*A1', 'name': 'dummy_bridge_A1'}, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api.ROS_api_module.api_add_path', new=fake_ros_api.select_where) + def test_api_extended_query_and_WHERE_or(self): + with self.assertRaises(AnsibleExitJson) as exc: + module_args = self.config_module_args.copy() + module_args['extended_query'] = { + 'attributes': ['.id', 'name'], + 'where': [ + { + 'or': [ + { + 'attribute': 'name', + 'is': 'in', + 'value': [1, 2], + }, + { + 'attribute': 'name', + 'is': '!=', + 'value': 5, + }, + ], + }, + ], + } + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['msg'], [ + {'.id': '*A1', 'name': 'dummy_bridge_A1'}, + ]) diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_facts.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_facts.py new file mode 100644 index 000000000..64985f8b6 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_facts.py @@ -0,0 +1,752 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import FakeLibRouterosError, Key, fake_ros_api +from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase +from ansible_collections.community.routeros.plugins.modules import api_facts + + +API_RESPONSES = { + ('interface', ): [ + { + '.id': '*1', + 'name': 'first-ether', + 'default-name': 'ether1', + 'type': 'ether', + 'mtu': 1500, + 'actual-mtu': 1500, + 'l2mtu': 1598, + 'max-l2mtu': 4074, + 'mac-address': '00:11:22:33:44:55', + 'last-link-up-time': 'apr/22/2022 07:54:55', + 'link-downs': 0, + 'rx-byte': 1234, + 'tx-byte': 1234, + 'rx-packet': 1234, + 'tx-packet': 1234, + 'rx-drop': 1234, + 'tx-drop': 1234, + 'tx-queue-drop': 1234, + 'rx-error': 1234, + 'tx-error': 1234, + 'fp-rx-byte': 1234, + 'fp-tx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-packet': 1234, + 'running': True, + 'disabled': False, + }, + { + '.id': '*2', + 'name': 'second-ether', + 'default-name': 'ether2', + 'type': 'ether', + 'mtu': 1500, + 'actual-mtu': 1500, + 'l2mtu': 1598, + 'max-l2mtu': 4074, + 'mac-address': '00:11:22:33:44:66', + 'last-link-up-time': 'apr/22/2022 07:54:55', + 'link-downs': 0, + 'rx-byte': 1234, + 'tx-byte': 1234, + 'rx-packet': 1234, + 'tx-packet': 1234, + 'rx-drop': 1234, + 'tx-drop': 1234, + 'tx-queue-drop': 1234, + 'rx-error': 1234, + 'tx-error': 1234, + 'fp-rx-byte': 1234, + 'fp-tx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-packet': 1234, + 'running': True, + 'slave': True, + 'disabled': False, + }, + { + '.id': '*3', + 'name': 'third-ether', + 'default-name': 'ether3', + 'type': 'ether', + 'mtu': 1500, + 'actual-mtu': 1500, + 'l2mtu': 1598, + 'max-l2mtu': 4074, + 'mac-address': '00:11:22:33:44:77', + 'last-link-up-time': 'apr/22/2022 07:54:55', + 'link-downs': 0, + 'rx-byte': 1234, + 'tx-byte': 1234, + 'rx-packet': 1234, + 'tx-packet': 1234, + 'rx-drop': 1234, + 'tx-drop': 1234, + 'tx-queue-drop': 1234, + 'rx-error': 1234, + 'tx-error': 1234, + 'fp-rx-byte': 1234, + 'fp-tx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-packet': 1234, + 'running': True, + 'slave': True, + 'disabled': False, + }, + { + '.id': '*4', + 'name': 'fourth-ether', + 'default-name': 'ether4', + 'type': 'ether', + 'mtu': 1500, + 'actual-mtu': 1500, + 'l2mtu': 1598, + 'max-l2mtu': 4074, + 'mac-address': '00:11:22:33:44:88', + 'last-link-down-time': 'apr/23/2022 08:22:50', + 'last-link-up-time': 'apr/23/2022 08:22:52', + 'link-downs': 2, + 'rx-byte': 1234, + 'tx-byte': 1234, + 'rx-packet': 1234, + 'tx-packet': 1234, + 'rx-drop': 1234, + 'tx-drop': 1234, + 'tx-queue-drop': 1234, + 'rx-error': 1234, + 'tx-error': 1234, + 'fp-rx-byte': 1234, + 'fp-tx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-packet': 1234, + 'running': True, + 'disabled': False, + }, + { + '.id': '*5', + 'name': 'fifth-ether', + 'default-name': 'ether5', + 'type': 'ether', + 'mtu': 1500, + 'actual-mtu': 1500, + 'l2mtu': 1598, + 'max-l2mtu': 4074, + 'mac-address': '00:11:22:33:44:99', + 'last-link-down-time': 'may/02/2022 18:12:32', + 'last-link-up-time': 'may/02/2022 18:08:01', + 'link-downs': 14, + 'rx-byte': 1234, + 'tx-byte': 1234, + 'rx-packet': 1234, + 'tx-packet': 1234, + 'rx-drop': 1234, + 'tx-drop': 1234, + 'tx-queue-drop': 1234, + 'rx-error': 1234, + 'tx-error': 1234, + 'fp-rx-byte': 1234, + 'fp-tx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-packet': 1234, + 'running': False, + 'slave': True, + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'my-bridge', + 'type': 'bridge', + 'mtu': 'auto', + 'actual-mtu': 1500, + 'l2mtu': 1598, + 'mac-address': '00:11:22:33:44:66', + 'last-link-up-time': 'apr/22/2022 07:54:48', + 'link-downs': 0, + 'rx-byte': 1234, + 'tx-byte': 1234, + 'rx-packet': 1234, + 'tx-packet': 1234, + 'rx-drop': 1234, + 'tx-drop': 1234, + 'tx-queue-drop': 1234, + 'rx-error': 1234, + 'tx-error': 1234, + 'fp-rx-byte': 1234, + 'fp-tx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-packet': 1234, + 'running': True, + 'disabled': False, + }, + ], + ('ip', 'address', ): [ + { + '.id': '*1', + 'address': '192.168.1.1/24', + 'network': '192.168.1.0', + 'interface': 'my-bridge', + 'actual-interface': 'my-bridge', + 'invalid': False, + 'dynamic': False, + 'disabled': False, + 'comment': 'Wohnung', + }, + { + '.id': '*5', + 'address': '192.168.2.1/24', + 'network': '192.168.2.0', + 'interface': 'fourth-ether', + 'actual-interface': 'fourth-ether', + 'invalid': False, + 'dynamic': False, + 'disabled': False, + 'comment': 'VoIP', + }, + { + '.id': '*6', + 'address': '1.2.3.4/21', + 'network': '84.73.216.0', + 'interface': 'first-ether', + 'actual-interface': 'first-ether', + 'invalid': False, + 'dynamic': True, + 'disabled': False, + }, + ], + ('ipv6', 'address', ): [ + { + '.id': '*1', + 'address': 'fe80::1:2:3/64', + 'from-pool': '', + 'interface': 'my-bridge', + 'actual-interface': 'my-bridge', + 'eui-64': False, + 'advertise': False, + 'no-dad': False, + 'invalid': False, + 'dynamic': True, + 'link-local': True, + 'disabled': False, + }, + { + '.id': '*2', + 'address': 'fe80::1:2:4/64', + 'from-pool': '', + 'interface': 'fourth-ether', + 'actual-interface': 'fourth-ether', + 'eui-64': False, + 'advertise': False, + 'no-dad': False, + 'invalid': False, + 'dynamic': True, + 'link-local': True, + 'disabled': False, + }, + { + '.id': '*3', + 'address': 'fe80::1:2:5/64', + 'from-pool': '', + 'interface': 'first-ether', + 'actual-interface': 'first-ether', + 'eui-64': False, + 'advertise': False, + 'no-dad': False, + 'invalid': False, + 'dynamic': True, + 'link-local': True, + 'disabled': False, + }, + ], + ('ip', 'neighbor', ): [], + ('system', 'identity', ): [ + { + 'name': 'MikroTik', + }, + ], + ('system', 'resource', ): [ + { + 'uptime': '2w3d4h5m6s', + 'version': '6.49.6 (stable)', + 'build-time': 'Apr/07/2022 17:53:31', + 'free-memory': 12345678, + 'total-memory': 23456789, + 'cpu': 'MIPS 24Kc V7.4', + 'cpu-count': 1, + 'cpu-frequency': 400, + 'cpu-load': 48, + 'free-hdd-space': 123456789, + 'total-hdd-space': 234567890, + 'write-sect-since-reboot': 1234, + 'write-sect-total': 12345, + 'bad-blocks': 0, + 'architecture-name': 'mipsbe', + 'board-name': 'RB750GL', + 'platform': 'MikroTik', + }, + ], + ('system', 'routerboard', ): [ + { + 'routerboard': True, + 'model': '750GL', + 'serial-number': '0123456789AB', + 'firmware-type': 'ar7240', + 'factory-firmware': '3.09', + 'current-firmware': '6.49.6', + 'upgrade-firmware': '6.49.6', + }, + ], + ('routing', 'bgp', 'peer', ): [], + ('routing', 'bgp', 'vpnv4-route', ): [], + ('routing', 'bgp', 'instance', ): [ + { + '.id': '*0', + 'name': 'default', + 'as': 65530, + 'router-id': '0.0.0.0', + 'redistribute-connected': False, + 'redistribute-static': False, + 'redistribute-rip': False, + 'redistribute-ospf': False, + 'redistribute-other-bgp': False, + 'out-filter': '', + 'client-to-client-reflection': True, + 'ignore-as-path-len': False, + 'routing-table': '', + 'default': True, + 'disabled': False, + }, + ], + ('ip', 'route', ): [ + { + '.id': '*30000001', + 'dst-address': '0.0.0.0/0', + 'gateway': '1.2.3.0', + 'gateway-status': '1.2.3.0 reachable via first-ether', + 'distance': 1, + 'scope': 30, + 'target-scope': 10, + 'vrf-interface': 'first-ether', + 'active': True, + 'dynamic': True, + 'static': True, + 'disabled': False, + }, + { + '.id': '*40162F13', + 'dst-address': '84.73.216.0/21', + 'pref-src': '1.2.3.4', + 'gateway': 'first-ether', + 'gateway-status': 'first-ether reachable', + 'distance': 0, + 'scope': 10, + 'active': True, + 'dynamic': True, + 'connect': True, + 'disabled': False, + }, + { + '.id': '*4016AA23', + 'dst-address': '192.168.2.0/24', + 'pref-src': '192.168.2.1', + 'gateway': 'fourth-ether', + 'gateway-status': 'fourth-ether reachable', + 'distance': 0, + 'scope': 10, + 'active': True, + 'dynamic': True, + 'connect': True, + 'disabled': False, + }, + { + '.id': '*40168E05', + 'dst-address': '192.168.1.0/24', + 'pref-src': '192.168.1.1', + 'gateway': 'my-bridge', + 'gateway-status': 'my-bridge reachable', + 'distance': 0, + 'scope': 10, + 'active': True, + 'dynamic': True, + 'connect': True, + 'disabled': False, + }, + ], + ('routing', 'ospf', 'instance', ): [ + { + '.id': '*0', + 'name': 'default', + 'router-id': '0.0.0.0', + 'distribute-default': 'never', + 'redistribute-connected': False, + 'redistribute-static': False, + 'redistribute-rip': False, + 'redistribute-bgp': False, + 'redistribute-other-ospf': False, + 'metric-default': 1, + 'metric-connected': 20, + 'metric-static': 20, + 'metric-rip': 20, + 'metric-bgp': 'auto', + 'metric-other-ospf': 'auto', + 'in-filter': 'ospf-in', + 'out-filter': 'ospf-out', + 'state': 'down', + 'default': True, + 'disabled': False, + }, + ], + ('routing', 'ospf', 'neighbor', ): [], +} + + +class TestRouterosApiFactsModule(ModuleTestCase): + + def setUp(self): + super(TestRouterosApiFactsModule, self).setUp() + self.module = api_facts + self.module.LibRouterosError = FakeLibRouterosError + self.module.connect = MagicMock(new=fake_ros_api) + self.module.check_has_library = MagicMock() + self.patch_create_api = patch('ansible_collections.community.routeros.plugins.modules.api_facts.create_api', MagicMock(new=fake_ros_api)) + self.patch_create_api.start() + self.patch_query_path = patch('ansible_collections.community.routeros.plugins.modules.api_facts.FactsBase.query_path', self.query_path) + self.patch_query_path.start() + self.module.Key = MagicMock(new=Key) + self.config_module_args = { + 'username': 'admin', + 'password': 'pаss', + 'hostname': '127.0.0.1', + } + + def tearDown(self): + self.patch_query_path.stop() + self.patch_create_api.stop() + + def query_path(self, path): + response = API_RESPONSES.get(tuple(path)) + if response is None: + raise Exception('Unexpected command: %s' % repr(path)) + return response + + def test_module_fail_when_required_args_missing(self): + with self.assertRaises(AnsibleFailJson) as exc: + set_module_args({}) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + + def test_module_fail_when_invalid_gather_subset(self): + with self.assertRaises(AnsibleFailJson) as exc: + module_args = self.config_module_args.copy() + module_args['gather_subset'] = ['!foobar'] + set_module_args(module_args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Bad subset: foobar') + + def test_full_run(self): + with self.assertRaises(AnsibleExitJson) as exc: + set_module_args(self.config_module_args.copy()) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['ansible_facts']['ansible_net_all_ipv4_addresses'], [ + '192.168.1.1', + '192.168.2.1', + '1.2.3.4', + ]) + self.assertEqual(result['ansible_facts']['ansible_net_all_ipv6_addresses'], [ + 'fe80::1:2:3', + 'fe80::1:2:4', + 'fe80::1:2:5', + ]) + self.assertEqual(result['ansible_facts']['ansible_net_arch'], 'mipsbe') + self.assertEqual(result['ansible_facts']['ansible_net_bgp_instance'], { + 'default': { + 'as': 65530, + 'client-to-client-reflection': True, + 'default': True, + 'disabled': False, + 'ignore-as-path-len': False, + 'name': 'default', + 'out-filter': '', + 'redistribute-connected': False, + 'redistribute-ospf': False, + 'redistribute-other-bgp': False, + 'redistribute-rip': False, + 'redistribute-static': False, + 'router-id': '0.0.0.0', + 'routing-table': '' + }, + }) + self.assertEqual(result['ansible_facts']['ansible_net_bgp_peer'], {}) + self.assertEqual(result['ansible_facts']['ansible_net_bgp_vpnv4_route'], {}) + self.assertEqual(result['ansible_facts']['ansible_net_cpu_load'], 48) + self.assertEqual(result['ansible_facts']['ansible_net_gather_subset'], [ + 'default', + 'hardware', + 'interfaces', + 'routing', + ]) + self.assertEqual(result['ansible_facts']['ansible_net_hostname'], 'MikroTik') + self.assertEqual(result['ansible_facts']['ansible_net_interfaces'], { + 'my-bridge': { + 'actual-mtu': 1500, + 'disabled': False, + 'fp-rx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-byte': 1234, + 'fp-tx-packet': 1234, + 'ipv4': [ + { + 'address': '192.168.1.1', + 'subnet': 24 + } + ], + 'ipv6': [ + { + 'address': 'fe80::1:2:3', + 'subnet': 64 + } + ], + 'l2mtu': 1598, + 'last-link-up-time': 'apr/22/2022 07:54:48', + 'link-downs': 0, + 'mac-address': '00:11:22:33:44:66', + 'mtu': 'auto', + 'name': 'my-bridge', + 'running': True, + 'rx-byte': 1234, + 'rx-drop': 1234, + 'rx-error': 1234, + 'rx-packet': 1234, + 'tx-byte': 1234, + 'tx-drop': 1234, + 'tx-error': 1234, + 'tx-packet': 1234, + 'tx-queue-drop': 1234, + 'type': 'bridge' + }, + 'first-ether': { + 'actual-mtu': 1500, + 'default-name': 'ether1', + 'disabled': False, + 'fp-rx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-byte': 1234, + 'fp-tx-packet': 1234, + 'ipv4': [ + { + 'address': '1.2.3.4', + 'subnet': 21 + } + ], + 'ipv6': [ + { + 'address': 'fe80::1:2:5', + 'subnet': 64 + } + ], + 'l2mtu': 1598, + 'last-link-up-time': 'apr/22/2022 07:54:55', + 'link-downs': 0, + 'mac-address': '00:11:22:33:44:55', + 'max-l2mtu': 4074, + 'mtu': 1500, + 'name': 'first-ether', + 'running': True, + 'rx-byte': 1234, + 'rx-drop': 1234, + 'rx-error': 1234, + 'rx-packet': 1234, + 'tx-byte': 1234, + 'tx-drop': 1234, + 'tx-error': 1234, + 'tx-packet': 1234, + 'tx-queue-drop': 1234, + 'type': 'ether' + }, + 'second-ether': { + 'actual-mtu': 1500, + 'default-name': 'ether2', + 'disabled': False, + 'fp-rx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-byte': 1234, + 'fp-tx-packet': 1234, + 'l2mtu': 1598, + 'last-link-up-time': 'apr/22/2022 07:54:55', + 'link-downs': 0, + 'mac-address': '00:11:22:33:44:66', + 'max-l2mtu': 4074, + 'mtu': 1500, + 'name': 'second-ether', + 'running': True, + 'rx-byte': 1234, + 'rx-drop': 1234, + 'rx-error': 1234, + 'rx-packet': 1234, + 'slave': True, + 'tx-byte': 1234, + 'tx-drop': 1234, + 'tx-error': 1234, + 'tx-packet': 1234, + 'tx-queue-drop': 1234, + 'type': 'ether' + }, + 'third-ether': { + 'actual-mtu': 1500, + 'default-name': 'ether3', + 'disabled': False, + 'fp-rx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-byte': 1234, + 'fp-tx-packet': 1234, + 'l2mtu': 1598, + 'last-link-up-time': 'apr/22/2022 07:54:55', + 'link-downs': 0, + 'mac-address': '00:11:22:33:44:77', + 'max-l2mtu': 4074, + 'mtu': 1500, + 'name': 'third-ether', + 'running': True, + 'rx-byte': 1234, + 'rx-drop': 1234, + 'rx-error': 1234, + 'rx-packet': 1234, + 'slave': True, + 'tx-byte': 1234, + 'tx-drop': 1234, + 'tx-error': 1234, + 'tx-packet': 1234, + 'tx-queue-drop': 1234, + 'type': 'ether' + }, + 'fourth-ether': { + 'actual-mtu': 1500, + 'default-name': 'ether4', + 'disabled': False, + 'fp-rx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-byte': 1234, + 'fp-tx-packet': 1234, + 'ipv4': [ + { + 'address': '192.168.2.1', + 'subnet': 24 + } + ], + 'ipv6': [ + { + 'address': 'fe80::1:2:4', + 'subnet': 64 + } + ], + 'l2mtu': 1598, + 'last-link-down-time': 'apr/23/2022 08:22:50', + 'last-link-up-time': 'apr/23/2022 08:22:52', + 'link-downs': 2, + 'mac-address': '00:11:22:33:44:88', + 'max-l2mtu': 4074, + 'mtu': 1500, + 'name': 'fourth-ether', + 'running': True, + 'rx-byte': 1234, + 'rx-drop': 1234, + 'rx-error': 1234, + 'rx-packet': 1234, + 'tx-byte': 1234, + 'tx-drop': 1234, + 'tx-error': 1234, + 'tx-packet': 1234, + 'tx-queue-drop': 1234, + 'type': 'ether' + }, + 'fifth-ether': { + 'actual-mtu': 1500, + 'default-name': 'ether5', + 'disabled': False, + 'fp-rx-byte': 1234, + 'fp-rx-packet': 1234, + 'fp-tx-byte': 1234, + 'fp-tx-packet': 1234, + 'l2mtu': 1598, + 'last-link-down-time': 'may/02/2022 18:12:32', + 'last-link-up-time': 'may/02/2022 18:08:01', + 'link-downs': 14, + 'mac-address': '00:11:22:33:44:99', + 'max-l2mtu': 4074, + 'mtu': 1500, + 'name': 'fifth-ether', + 'running': False, + 'rx-byte': 1234, + 'rx-drop': 1234, + 'rx-error': 1234, + 'rx-packet': 1234, + 'slave': True, + 'tx-byte': 1234, + 'tx-drop': 1234, + 'tx-error': 1234, + 'tx-packet': 1234, + 'tx-queue-drop': 1234, + 'type': 'ether' + } + }) + self.assertEqual(result['ansible_facts']['ansible_net_memfree_mb'], 12345678 / 1048576.0) + self.assertEqual(result['ansible_facts']['ansible_net_memtotal_mb'], 23456789 / 1048576.0) + self.assertEqual(result['ansible_facts']['ansible_net_model'], '750GL') + self.assertEqual(result['ansible_facts']['ansible_net_neighbors'], []) + self.assertEqual(result['ansible_facts']['ansible_net_ospf_instance'], { + 'default': { + 'default': True, + 'disabled': False, + 'distribute-default': 'never', + 'in-filter': 'ospf-in', + 'metric-bgp': 'auto', + 'metric-connected': 20, + 'metric-default': 1, + 'metric-other-ospf': 'auto', + 'metric-rip': 20, + 'metric-static': 20, + 'name': 'default', + 'out-filter': 'ospf-out', + 'redistribute-bgp': False, + 'redistribute-connected': False, + 'redistribute-other-ospf': False, + 'redistribute-rip': False, + 'redistribute-static': False, + 'router-id': '0.0.0.0', + 'state': 'down' + } + }) + self.assertEqual(result['ansible_facts']['ansible_net_ospf_neighbor'], {}) + self.assertEqual(result['ansible_facts']['ansible_net_route'], { + 'main': { + 'active': True, + 'connect': True, + 'disabled': False, + 'distance': 0, + 'dst-address': '192.168.1.0/24', + 'dynamic': True, + 'gateway': 'my-bridge', + 'gateway-status': 'my-bridge reachable', + 'pref-src': '192.168.1.1', + 'scope': 10 + } + }) + self.assertEqual(result['ansible_facts']['ansible_net_serialnum'], '0123456789AB') + self.assertEqual(result['ansible_facts']['ansible_net_spacefree_mb'], 123456789 / 1048576.0) + self.assertEqual(result['ansible_facts']['ansible_net_spacetotal_mb'], 234567890 / 1048576.0) + self.assertEqual(result['ansible_facts']['ansible_net_uptime'], '2w3d4h5m6s') + self.assertEqual(result['ansible_facts']['ansible_net_version'], '6.49.6 (stable)') diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_find_and_modify.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_find_and_modify.py new file mode 100644 index 000000000..384bc8885 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_find_and_modify.py @@ -0,0 +1,651 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import ( + FakeLibRouterosError, fake_ros_api, massage_expected_result_data, create_fake_path, +) +from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase +from ansible_collections.community.routeros.plugins.modules import api_find_and_modify + + +START_IP_DNS_STATIC = [ + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'dynamic': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'dynamic': False, + }, + { + '.id': '*7', + 'comment': '', + 'name': 'foo', + 'address': '192.168.88.2', + 'dynamic': False, + }, +] + +START_IP_DNS_STATIC_OLD_DATA = massage_expected_result_data(START_IP_DNS_STATIC, ('ip', 'dns', 'static'), keep_all=True) + +START_IP_FIREWALL_FILTER = [ + { + '.id': '*2', + 'action': 'accept', + 'chain': 'input', + 'comment': 'defconf', + 'protocol': 'icmp', + }, + { + '.id': '*3', + 'action': 'accept', + 'chain': 'input', + 'comment': 'defconf', + 'connection-state': 'established', + }, + { + '.id': '*4', + 'action': 'accept', + 'chain': 'input', + 'comment': 'defconf', + 'connection-state': 'related', + }, + { + '.id': '*7', + 'action': 'drop', + 'chain': 'input', + 'comment': 'defconf', + 'in-interface': 'wan', + }, + { + '.id': '*8', + 'action': 'accept', + 'chain': 'forward', + 'comment': 'defconf', + 'connection-state': 'established', + }, + { + '.id': '*9', + 'action': 'accept', + 'chain': 'forward', + 'comment': 'defconf', + 'connection-state': 'related', + }, + { + '.id': '*A', + 'action': 'drop', + 'chain': 'forward', + 'comment': 'defconf', + 'connection-status': 'invalid', + }, +] + +START_IP_FIREWALL_FILTER_OLD_DATA = massage_expected_result_data(START_IP_FIREWALL_FILTER, ('ip', 'firewall', 'filter'), keep_all=True) + + +class TestRouterosApiFindAndModifyModule(ModuleTestCase): + + def setUp(self): + super(TestRouterosApiFindAndModifyModule, self).setUp() + self.module = api_find_and_modify + self.module.LibRouterosError = FakeLibRouterosError + self.module.connect = MagicMock(new=fake_ros_api) + self.module.check_has_library = MagicMock() + self.patch_create_api = patch( + 'ansible_collections.community.routeros.plugins.modules.api_find_and_modify.create_api', + MagicMock(new=fake_ros_api)) + self.patch_create_api.start() + self.config_module_args = { + 'username': 'admin', + 'password': 'pаss', + 'hostname': '127.0.0.1', + } + + def tearDown(self): + self.patch_create_api.stop() + + def test_module_fail_when_required_args_missing(self): + with self.assertRaises(AnsibleFailJson) as exc: + set_module_args({}) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + + def test_invalid_disabled_and_enabled_option_in_find(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + 'comment': 'foo', + '!comment': None, + }, + 'values': { + 'comment': 'bar', + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], '`find` must not contain both "comment" and "!comment"!') + + def test_invalid_disabled_option_invalid_value_in_find(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + '!comment': 'gone', + }, + 'values': { + 'comment': 'bar', + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'The value for "!comment" in `find` must not be non-trivial!') + + def test_invalid_disabled_and_enabled_option_in_values(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': {}, + 'values': { + 'comment': 'foo', + '!comment': None, + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], '`values` must not contain both "comment" and "!comment"!') + + def test_invalid_disabled_option_invalid_value_in_values(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': {}, + 'values': { + '!comment': 'gone', + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'The value for "!comment" in `values` must not be non-trivial!') + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_change_invalid_zero(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + 'name': 'bam', + }, + 'values': { + 'name': 'baz', + }, + 'require_matches_min': 10, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Found no entries, but allow_no_matches=false') + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_change_invalid_too_few(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + 'name': 'router', + }, + 'values': { + 'name': 'foobar', + }, + 'require_matches_min': 10, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Found 2 entries, but expected at least 10') + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_change_invalid_too_many(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + 'name': 'router', + }, + 'values': { + 'name': 'foobar', + }, + 'require_matches_max': 1, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Found 2 entries, but expected at most 1') + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_change_idempotent_zero_matches_1(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + 'name': 'baz', + }, + 'values': { + 'name': 'bam', + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['match_count'], 0) + self.assertEqual(result['modify_count'], 0) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_change_idempotent_zero_matches_2(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + 'name': 'baz', + }, + 'values': { + 'name': 'bam', + }, + 'require_matches_min': 2, + 'allow_no_matches': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['match_count'], 0) + self.assertEqual(result['modify_count'], 0) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_idempotent_1(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + }, + 'values': { + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['match_count'], 3) + self.assertEqual(result['modify_count'], 0) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_idempotent_2(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + 'name': 'foo', + }, + 'values': { + 'comment': None, + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['match_count'], 1) + self.assertEqual(result['modify_count'], 0) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_change(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + 'name': 'foo', + }, + 'values': { + 'comment': 'bar', + }, + '_ansible_diff': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + { + '.id': '*7', + 'comment': 'bar', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + ]) + self.assertEqual(result['diff']['before']['values'], [ + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + ]) + self.assertEqual(result['diff']['after']['values'], [ + { + '.id': '*7', + 'comment': 'bar', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + ]) + self.assertEqual(result['match_count'], 1) + self.assertEqual(result['modify_count'], 1) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_change_remove_comment_1(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + }, + 'values': { + 'comment': None, + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + ]) + self.assertEqual('diff' in result, False) + self.assertEqual(result['match_count'], 3) + self.assertEqual(result['modify_count'], 1) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_change_remove_comment_2(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + }, + 'values': { + 'comment': '', + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + ]) + self.assertEqual(result['match_count'], 3) + self.assertEqual(result['modify_count'], 1) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_change_remove_comment_3(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'find': { + }, + 'values': { + '!comment': None, + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + 'dynamic': False, + }, + ]) + self.assertEqual(result['match_count'], 3) + self.assertEqual(result['modify_count'], 1) + + @patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path', + new=create_fake_path(('ip', 'firewall', 'filter'), START_IP_FIREWALL_FILTER)) + def test_change_remove_generic(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip firewall filter', + 'find': { + 'chain': 'input', + '!protocol': '', + }, + 'values': { + '!connection-state': None, + }, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_FIREWALL_FILTER_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*2', + 'action': 'accept', + 'chain': 'input', + 'comment': 'defconf', + 'protocol': 'icmp', + }, + { + '.id': '*3', + 'action': 'accept', + 'chain': 'input', + 'comment': 'defconf', + }, + { + '.id': '*4', + 'action': 'accept', + 'chain': 'input', + 'comment': 'defconf', + }, + { + '.id': '*7', + 'action': 'drop', + 'chain': 'input', + 'comment': 'defconf', + 'in-interface': 'wan', + }, + { + '.id': '*8', + 'action': 'accept', + 'chain': 'forward', + 'comment': 'defconf', + 'connection-state': 'established', + }, + { + '.id': '*9', + 'action': 'accept', + 'chain': 'forward', + 'comment': 'defconf', + 'connection-state': 'related', + }, + { + '.id': '*A', + 'action': 'drop', + 'chain': 'forward', + 'comment': 'defconf', + 'connection-status': 'invalid', + }, + ]) + self.assertEqual(result['match_count'], 3) + self.assertEqual(result['modify_count'], 2) diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_info.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_info.py new file mode 100644 index 000000000..2dabc36ef --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_info.py @@ -0,0 +1,811 @@ +# Copyright (c) 2022, Felix Fontein (@felixfontein) <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import FakeLibRouterosError, Key, fake_ros_api +from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase +from ansible_collections.community.routeros.plugins.modules import api_info + + +class TestRouterosApiInfoModule(ModuleTestCase): + + def setUp(self): + super(TestRouterosApiInfoModule, self).setUp() + self.module = api_info + self.module.LibRouterosError = FakeLibRouterosError + self.module.connect = MagicMock(new=fake_ros_api) + self.module.check_has_library = MagicMock() + self.patch_create_api = patch('ansible_collections.community.routeros.plugins.modules.api_info.create_api', MagicMock(new=fake_ros_api)) + self.patch_create_api.start() + self.module.Key = MagicMock(new=Key) + self.config_module_args = { + 'username': 'admin', + 'password': 'pаss', + 'hostname': '127.0.0.1', + } + + def tearDown(self): + self.patch_create_api.stop() + + def test_module_fail_when_required_args_missing(self): + with self.assertRaises(AnsibleFailJson) as exc: + set_module_args({}) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + + def test_invalid_path(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'something invalid' + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'].startswith('value of path must be one of: '), True) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_empty_result(self, mock_compose_api_path): + mock_compose_api_path.return_value = [] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static' + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], []) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_regular_result(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + 'called-format': 'mac:ssid', + 'interim-update': 'enabled', + 'mac-caching': 'disabled', + 'mac-format': 'XX:XX:XX:XX:XX:XX', + 'mac-mode': 'as-username', + 'foo': 'bar', + '.id': '*1', + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'caps-man aaa', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [{ + 'interim-update': 'enabled', + '.id': '*1', + }]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_result_with_defaults(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + 'called-format': 'mac:ssid', + 'interim-update': 'enabled', + 'mac-caching': 'disabled', + 'mac-format': 'XX:XX:XX:XX:XX:XX', + 'mac-mode': 'as-username', + 'foo': 'bar', + '.id': '*1', + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'caps-man aaa', + 'hide_defaults': False, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [{ + 'called-format': 'mac:ssid', + 'interim-update': 'enabled', + 'mac-caching': 'disabled', + 'mac-format': 'XX:XX:XX:XX:XX:XX', + 'mac-mode': 'as-username', + '.id': '*1', + }]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_full_result(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + 'called-format': 'mac:ssid', + 'interim-update': 'enabled', + 'mac-caching': 'disabled', + 'mac-format': 'XX:XX:XX:XX:XX:XX', + 'mac-mode': 'as-username', + 'foo': 'bar', + '.id': '*1', + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'caps-man aaa', + 'unfiltered': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [{ + 'interim-update': 'enabled', + 'foo': 'bar', + '.id': '*1', + }]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_disabled_exclamation(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + 'chain': 'input', + 'in-interface-list': 'LAN', + '.id': '*1', + 'dynamic': False, + }, + { + 'chain': 'forward', + 'action': 'drop', + 'in-interface': 'sfp1', + '.id': '*2', + 'dynamic': True, + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip firewall filter', + 'handle_disabled': 'exclamation', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [{ + 'chain': 'input', + 'in-interface-list': 'LAN', + '!action': None, + '!comment': None, + '!connection-bytes': None, + '!connection-limit': None, + '!connection-mark': None, + '!connection-nat-state': None, + '!connection-rate': None, + '!connection-state': None, + '!connection-type': None, + '!content': None, + '!disabled': None, + '!dscp': None, + '!dst-address': None, + '!dst-address-list': None, + '!dst-address-type': None, + '!dst-limit': None, + '!dst-port': None, + '!fragment': None, + '!hotspot': None, + '!hw-offload': None, + '!icmp-options': None, + '!in-bridge-port': None, + '!in-bridge-port-list': None, + '!in-interface': None, + '!ingress-priority': None, + '!ipsec-policy': None, + '!ipv4-options': None, + '!jump-target': None, + '!layer7-protocol': None, + '!limit': None, + '!log': None, + '!log-prefix': None, + '!nth': None, + '!out-bridge-port': None, + '!out-bridge-port-list': None, + '!out-interface': None, + '!out-interface-list': None, + '!p2p': None, + '!packet-mark': None, + '!packet-size': None, + '!per-connection-classifier': None, + '!port': None, + '!priority': None, + '!protocol': None, + '!psd': None, + '!random': None, + '!reject-with': None, + '!routing-mark': None, + '!routing-table': None, + '!src-address': None, + '!src-address-list': None, + '!src-address-type': None, + '!src-mac-address': None, + '!src-port': None, + '!tcp-flags': None, + '!tcp-mss': None, + '!time': None, + '!tls-host': None, + '!ttl': None, + '.id': '*1', + }]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_disabled_null_value(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + 'chain': 'input', + 'in-interface-list': 'LAN', + '.id': '*1', + 'dynamic': False, + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip firewall filter', + 'handle_disabled': 'null-value', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [{ + 'chain': 'input', + 'in-interface-list': 'LAN', + 'action': None, + 'comment': None, + 'connection-bytes': None, + 'connection-limit': None, + 'connection-mark': None, + 'connection-nat-state': None, + 'connection-rate': None, + 'connection-state': None, + 'connection-type': None, + 'content': None, + 'disabled': None, + 'dscp': None, + 'dst-address': None, + 'dst-address-list': None, + 'dst-address-type': None, + 'dst-limit': None, + 'dst-port': None, + 'fragment': None, + 'hotspot': None, + 'hw-offload': None, + 'icmp-options': None, + 'in-bridge-port': None, + 'in-bridge-port-list': None, + 'in-interface': None, + 'ingress-priority': None, + 'ipsec-policy': None, + 'ipv4-options': None, + 'jump-target': None, + 'layer7-protocol': None, + 'limit': None, + 'log': None, + 'log-prefix': None, + 'nth': None, + 'out-bridge-port': None, + 'out-bridge-port-list': None, + 'out-interface': None, + 'out-interface-list': None, + 'p2p': None, + 'packet-mark': None, + 'packet-size': None, + 'per-connection-classifier': None, + 'port': None, + 'priority': None, + 'protocol': None, + 'psd': None, + 'random': None, + 'reject-with': None, + 'routing-mark': None, + 'routing-table': None, + 'src-address': None, + 'src-address-list': None, + 'src-address-type': None, + 'src-mac-address': None, + 'src-port': None, + 'tcp-flags': None, + 'tcp-mss': None, + 'time': None, + 'tls-host': None, + 'ttl': None, + '.id': '*1', + }]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_disabled_omit(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + 'chain': 'input', + 'in-interface-list': 'LAN', + '.id': '*1', + 'dynamic': False, + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip firewall filter', + 'handle_disabled': 'omit', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [{ + 'chain': 'input', + 'in-interface-list': 'LAN', + '.id': '*1', + }]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_info.compose_api_path') + def test_dynamic(self, mock_compose_api_path): + mock_compose_api_path.return_value = [ + { + 'chain': 'input', + 'in-interface-list': 'LAN', + 'dynamic': False, + '.id': '*1', + }, + { + 'chain': 'forward', + 'action': 'drop', + 'in-interface': 'sfp1', + '.id': '*2', + 'dynamic': True, + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip firewall filter', + 'handle_disabled': 'omit', + 'include_dynamic': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['result'], [ + { + 'chain': 'input', + 'in-interface-list': 'LAN', + '.id': '*1', + 'dynamic': False, + }, + { + 'chain': 'forward', + 'action': 'drop', + 'in-interface': 'sfp1', + '.id': '*2', + 'dynamic': True, + }, + ]) + + @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 = [ + { + '.id': '*1', + 'address': '192.168.88.2', + 'mac-address': '11:22:33:44:55:66', + 'client-id': 'ff:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f:0:1:2', + 'address-lists': '', + 'server': 'main', + 'dhcp-option': '', + 'status': 'waiting', + 'last-seen': 'never', + 'radius': False, + 'dynamic': False, + 'blocked': False, + 'disabled': False, + 'comment': 'foo', + }, + { + '.id': '*2', + 'address': '192.168.88.3', + 'mac-address': '11:22:33:44:55:77', + 'client-id': '1:2:3:4:5:6:7', + 'address-lists': '', + 'server': 'main', + 'dhcp-option': '', + 'status': 'bound', + 'expires-after': '3d7m8s', + 'last-seen': '1m52s', + 'active-address': '192.168.88.14', + 'active-mac-address': '11:22:33:44:55:76', + 'active-client-id': '1:2:3:4:5:6:7', + 'active-server': 'main', + 'host-name': 'bar', + 'radius': False, + 'dynamic': False, + 'blocked': False, + 'disabled': False, + }, + { + '.id': '*3', + 'address': '0.0.0.1', + 'mac-address': '00:00:00:00:00:01', + 'address-lists': '', + 'dhcp-option': '', + 'status': 'waiting', + 'last-seen': 'never', + 'radius': False, + 'dynamic': False, + 'blocked': False, + 'disabled': False, + }, + ] + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dhcp-server lease', + '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': '*1', + 'address': '192.168.88.2', + 'mac-address': '11:22:33:44:55:66', + 'client-id': 'ff:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f:0:1:2', + 'server': 'main', + 'comment': 'foo', + }, + { + '.id': '*2', + 'address': '192.168.88.3', + 'mac-address': '11:22:33:44:55:77', + 'client-id': '1:2:3:4:5:6:7', + 'server': 'main', + }, + { + '.id': '*3', + 'address': '0.0.0.1', + 'mac-address': '00:00:00:00:00:01', + '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/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_modify.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_modify.py new file mode 100644 index 000000000..78979733d --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_api_modify.py @@ -0,0 +1,1972 @@ +# Copyright (c) 2022, Felix Fontein (@felixfontein) <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock +from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import ( + FakeLibRouterosError, fake_ros_api, massage_expected_result_data, create_fake_path, +) +from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase +from ansible_collections.community.routeros.plugins.modules import api_modify + + +START_IP_DNS_STATIC = [ + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'dynamic': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'dynamic': False, + }, + { + '.id': '*7', + 'comment': '', + 'name': 'foo', + 'address': '192.168.88.2', + 'dynamic': False, + }, + { + '.id': '*8', + 'comment': '', + 'name': 'dynfoo', + 'address': '192.168.88.15', + 'dynamic': True, + }, +] + +START_IP_DNS_STATIC_OLD_DATA = massage_expected_result_data(START_IP_DNS_STATIC, ('ip', 'dns', 'static'), remove_dynamic=True) + +START_IP_SETTINGS = [ + { + 'accept-redirects': True, + 'accept-source-route': False, + 'allow-fast-path': True, + 'arp-timeout': '30s', + 'icmp-rate-limit': 20, + 'icmp-rate-mask': '0x1818', + 'ip-forward': True, + 'max-neighbor-entries': 8192, + 'route-cache': True, + 'rp-filter': False, + 'secure-redirects': True, + 'send-redirects': True, + 'tcp-syncookies': False, + }, +] + +START_IP_SETTINGS_OLD_DATA = massage_expected_result_data(START_IP_SETTINGS, ('ip', 'settings')) + +START_IP_ADDRESS = [ + { + '.id': '*1', + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'disabled': False, + }, + { + '.id': '*3', + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': False, + }, + { + '.id': '*F', + 'address': '10.0.0.0/16', + 'interface': 'WAN', + 'disabled': True, + }, +] + +START_IP_ADDRESS_OLD_DATA = massage_expected_result_data(START_IP_ADDRESS, ('ip', 'address')) + +START_IP_DHCP_CLIENT = [ + { + "!comment": None, + "!script": None, + ".id": "*1", + "add-default-route": True, + "default-route-distance": 1, + "dhcp-options": "hostname,clientid", + "disabled": False, + "interface": "ether1", + "use-peer-dns": True, + "use-peer-ntp": True, + }, + { + "!comment": None, + "!dhcp-options": None, + "!script": None, + ".id": "*2", + "add-default-route": True, + "default-route-distance": 1, + "disabled": False, + "interface": "ether2", + "use-peer-dns": True, + "use-peer-ntp": True, + }, + { + "!comment": None, + "!script": None, + ".id": "*3", + "add-default-route": True, + "default-route-distance": 1, + "dhcp-options": "hostname", + "disabled": False, + "interface": "ether3", + "use-peer-dns": True, + "use-peer-ntp": True, + }, +] + +START_IP_DHCP_CLIENT_OLD_DATA = massage_expected_result_data(START_IP_DHCP_CLIENT, ('ip', 'dhcp-client')) + +START_IP_DHCP_SERVER_LEASE = [ + { + '.id': '*1', + 'address': '192.168.88.2', + 'mac-address': '11:22:33:44:55:66', + 'client-id': 'ff:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f:0:1:2', + 'address-lists': '', + 'server': 'main', + 'dhcp-option': '', + 'status': 'waiting', + 'last-seen': 'never', + 'radius': False, + 'dynamic': False, + 'blocked': False, + 'disabled': False, + 'comment': 'foo', + }, + { + '.id': '*2', + 'address': '192.168.88.3', + 'mac-address': '11:22:33:44:55:77', + 'client-id': '1:2:3:4:5:6:7', + 'address-lists': '', + 'server': 'main', + 'dhcp-option': '', + 'status': 'bound', + 'expires-after': '3d7m8s', + 'last-seen': '1m52s', + 'active-address': '192.168.88.14', + 'active-mac-address': '11:22:33:44:55:76', + 'active-client-id': '1:2:3:4:5:6:7', + 'active-server': 'main', + 'host-name': 'bar', + 'radius': False, + 'dynamic': False, + 'blocked': False, + 'disabled': False, + }, + { + '.id': '*3', + 'address': '0.0.0.1', + 'mac-address': '00:00:00:00:00:01', + 'address-lists': '', + 'dhcp-option': '', + 'status': 'waiting', + 'last-seen': 'never', + 'radius': False, + 'dynamic': False, + 'blocked': False, + 'disabled': False, + }, + { + '.id': '*4', + 'address': '0.0.0.2', + 'mac-address': '00:00:00:00:00:02', + 'address-lists': '', + 'dhcp-option': '', + 'status': 'waiting', + 'last-seen': 'never', + 'radius': False, + 'dynamic': False, + 'blocked': False, + 'disabled': False, + }, +] + +START_IP_DHCP_SERVER_LEASE_OLD_DATA = massage_expected_result_data(START_IP_DHCP_SERVER_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): + + def setUp(self): + super(TestRouterosApiModifyModule, self).setUp() + self.module = api_modify + self.module.LibRouterosError = FakeLibRouterosError + self.module.connect = MagicMock(new=fake_ros_api) + self.module.check_has_library = MagicMock() + self.patch_create_api = patch( + 'ansible_collections.community.routeros.plugins.modules.api_modify.create_api', + MagicMock(new=fake_ros_api)) + self.patch_create_api.start() + self.config_module_args = { + 'username': 'admin', + 'password': 'pаss', + 'hostname': '127.0.0.1', + } + + def tearDown(self): + self.patch_create_api.stop() + + def test_module_fail_when_required_args_missing(self): + with self.assertRaises(AnsibleFailJson) as exc: + set_module_args({}) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + + def test_invalid_path(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'something invalid', + 'data': [], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'].startswith('value of path must be one of: '), True) + + def test_invalid_option(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [{ + 'name': 'baz', + 'foo': 'bar', + }], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Unknown key "foo" at index 1.') + + def test_invalid_disabled_and_enabled_option(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [{ + 'name': 'baz', + 'comment': 'foo', + '!comment': None, + }], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Not both "comment" and "!comment" must appear at index 1.') + + def test_invalid_disabled_option(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [{ + 'name': 'foo', + '!disabled': None, + }], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Key "!disabled" must not be disabled (leading "!") at index 1.') + + def test_invalid_disabled_option_value(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [{ + 'name': 'baz', + '!comment': 'foo', + }], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Disabled key "!comment" must not have a value at index 1.') + + def test_invalid_non_disabled_option_value(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [{ + 'name': None, + }], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Key "name" must not be disabled (value null/~/None) at index 1.') + + def test_invalid_required_missing(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dhcp-server', + 'data': [{ + 'interface': 'eth0', + }], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Every element in data must contain "name". For example, the element at index #1 does not provide it.') + + def test_invalid_required_one_of_missing(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [{ + 'address': '192.168.88.1', + }], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Every element in data must contain one of "name", "regexp". For example, the element at index 1 does not provide it.') + + def test_invalid_mutually_exclusive_both(self): + with self.assertRaises(AnsibleFailJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [{ + 'name': 'foo', + 'regexp': 'bar', + 'address': '192.168.88.1', + }], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['failed'], True) + self.assertEqual(result['msg'], 'Keys "name", "regexp" cannot be used at the same time at index 1.') + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_idempotent(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + '.id': 'bam', # this should be ignored + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + { + 'comment': None, + 'name': 'router', + 'text': 'Router Text Entry', + }, + ], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DNS_STATIC_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_idempotent_2(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'foo', + 'comment': '', + 'address': '192.168.88.2', + }, + { + 'name': 'router', + '!comment': None, + 'text': 'Router Text Entry', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DNS_STATIC_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_idempotent_3(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + ], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DNS_STATIC_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_sync_list_add(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'text': 'Router Text Entry', + }, + { + 'name': 'router', + 'text': 'Router Text Entry 2', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + ], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*NEW1', + 'name': 'router', + 'text': 'Router Text Entry 2', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_sync_list_modify_1(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'text': 'Router Text Entry 2', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + ], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*NEW1', + 'name': 'router', + 'text': 'Router Text Entry 2', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_modify_1_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'text': 'Router Text Entry 2', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + ], + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + { + 'name': 'router', + 'text': 'Router Text Entry 2', + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_sync_list_modify_2(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': '', + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'text': 'Router Text Entry 2', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry 2', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_modify_2_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': '', + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'text': 'Router Text Entry 2', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry 2', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_sync_list_modify_3(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + '!comment': None, + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'cname': 'router.com.', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*NEW1', + 'name': 'router', + 'cname': 'router.com.', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_modify_3_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + '!comment': None, + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'cname': 'router.com.', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + { + 'name': 'router', + 'cname': 'router.com.', + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_sync_list_modify_4(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'comment': 'defconf', + 'text': 'Router Text Entry 2', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*A', + 'comment': 'defconf', + 'name': 'router', + 'text': 'Router Text Entry 2', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_modify_4_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'name': 'router', + 'address': '192.168.88.1', + }, + { + 'name': 'router', + 'comment': 'defconf', + 'text': 'Router Text Entry 2', + }, + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*A', + 'comment': 'defconf', + 'name': 'router', + 'text': 'Router Text Entry 2', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_sync_list_delete(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_delete_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC)) + def test_sync_list_reorder(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + { + 'name': 'foo', + 'text': 'bar', + }, + { + 'name': 'router', + 'text': 'Router Text Entry', + }, + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + 'ensure_order': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*NEW1', + 'name': 'foo', + 'text': 'bar', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True)) + def test_sync_list_reorder_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dns static', + 'data': [ + { + 'name': 'foo', + 'address': '192.168.88.2', + }, + { + 'name': 'foo', + 'text': 'bar', + }, + { + 'name': 'router', + 'text': 'Router Text Entry', + }, + { + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + 'ensure_order': True, + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_DNS_STATIC_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*7', + 'name': 'foo', + 'address': '192.168.88.2', + 'ttl': '1d', + 'disabled': False, + }, + { + 'name': 'foo', + 'text': 'bar', + }, + { + '.id': '*A', + 'name': 'router', + 'text': 'Router Text Entry', + 'ttl': '1d', + 'disabled': False, + }, + { + '.id': '*1', + 'comment': 'defconf', + 'name': 'router', + 'address': '192.168.88.1', + 'ttl': '1d', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'settings'), START_IP_SETTINGS, read_only=True)) + def test_sync_value_idempotent(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip settings', + 'data': [ + { + 'arp-timeout': '30s', + 'icmp-rate-limit': 20, + 'icmp-rate-mask': '0x1818', + 'ip-forward': True, + 'max-neighbor-entries': 8192, + }, + ], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_SETTINGS_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_SETTINGS_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'settings'), START_IP_SETTINGS, read_only=True)) + def test_sync_value_idempotent_2(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip settings', + 'data': [ + { + 'accept-redirects': True, + 'icmp-rate-limit': 20, + }, + ], + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_SETTINGS_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_SETTINGS_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'settings'), START_IP_SETTINGS)) + def test_sync_value_modify(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip settings', + 'data': [ + { + 'accept-redirects': True, + 'accept-source-route': True, + 'max-neighbor-entries': 4096, + }, + ], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_SETTINGS_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + 'accept-redirects': True, + 'accept-source-route': True, + 'allow-fast-path': True, + 'arp-timeout': '30s', + 'icmp-rate-limit': 20, + 'icmp-rate-mask': '0x1818', + 'ip-forward': True, + 'max-neighbor-entries': 4096, + 'route-cache': True, + 'rp-filter': False, + 'secure-redirects': True, + 'send-redirects': True, + 'tcp-syncookies': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'settings'), START_IP_SETTINGS, read_only=True)) + def test_sync_value_modify_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip settings', + 'data': [ + { + 'accept-redirects': True, + 'accept-source-route': True, + 'max-neighbor-entries': 4096, + }, + ], + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_SETTINGS_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + 'accept-redirects': True, + 'accept-source-route': True, + 'allow-fast-path': True, + 'arp-timeout': '30s', + 'icmp-rate-limit': 20, + 'icmp-rate-mask': '0x1818', + 'ip-forward': True, + 'max-neighbor-entries': 4096, + 'route-cache': True, + 'rp-filter': False, + 'secure-redirects': True, + 'send-redirects': True, + 'tcp-syncookies': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'settings'), START_IP_SETTINGS)) + def test_sync_value_modify_2(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip settings', + 'data': [ + { + 'accept-redirects': True, + 'accept-source-route': True, + 'max-neighbor-entries': 4096, + }, + ], + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_SETTINGS_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + 'accept-redirects': True, + 'accept-source-route': True, + 'allow-fast-path': True, + 'arp-timeout': '30s', + 'icmp-rate-limit': 10, + 'icmp-rate-mask': '0x1818', + 'ip-forward': True, + 'max-neighbor-entries': 4096, + 'route-cache': True, + 'rp-filter': False, + 'secure-redirects': True, + 'send-redirects': True, + 'tcp-syncookies': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'settings'), START_IP_SETTINGS, read_only=True)) + def test_sync_value_modify_2_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip settings', + 'data': [ + { + 'accept-redirects': True, + 'accept-source-route': True, + 'max-neighbor-entries': 4096, + }, + ], + 'handle_entries_content': 'remove', + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_SETTINGS_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + 'accept-redirects': True, + 'accept-source-route': True, + 'allow-fast-path': True, + 'arp-timeout': '30s', + 'icmp-rate-limit': 10, + 'icmp-rate-mask': '0x1818', + 'ip-forward': True, + 'max-neighbor-entries': 4096, + 'route-cache': True, + 'rp-filter': False, + 'secure-redirects': True, + 'send-redirects': True, + 'tcp-syncookies': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'address'), START_IP_ADDRESS, read_only=True)) + def test_sync_primary_key_idempotent(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip address', + 'data': [ + { + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'comment': '', + }, + { + 'address': '192.168.88.0/24', + 'interface': 'bridge', + '!comment': None, + }, + ], + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_ADDRESS_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_ADDRESS_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'address'), START_IP_ADDRESS, read_only=True)) + def test_sync_primary_key_idempotent_2(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip address', + 'data': [ + { + 'address': '192.168.88.0/24', + 'interface': 'bridge', + }, + { + 'address': '10.0.0.0/16', + 'interface': 'WAN', + 'disabled': True, + '!comment': '', + }, + { + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': False, + 'comment': None, + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], False) + self.assertEqual(result['old_data'], START_IP_ADDRESS_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_ADDRESS_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'address'), START_IP_ADDRESS)) + def test_sync_primary_key_cru(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip address', + 'data': [ + { + 'address': '10.10.0.0/16', + 'interface': 'WIFI', + }, + { + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': True, + }, + { + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'comment': 'foo', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_ADDRESS_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'comment': 'foo', + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'disabled': False, + }, + { + '.id': '*3', + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': True, + }, + { + '.id': '*NEW1', + 'address': '10.10.0.0/16', + 'interface': 'WIFI', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'address'), START_IP_ADDRESS, read_only=True)) + def test_sync_primary_key_cru_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip address', + 'data': [ + { + 'address': '10.10.0.0/16', + 'interface': 'WIFI', + }, + { + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': True, + }, + { + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'comment': 'foo', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_ADDRESS_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*1', + 'comment': 'foo', + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'disabled': False, + }, + { + '.id': '*3', + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': True, + }, + { + 'address': '10.10.0.0/16', + 'interface': 'WIFI', + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'address'), START_IP_ADDRESS)) + def test_sync_primary_key_cru_reorder(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip address', + 'data': [ + { + 'address': '10.10.0.0/16', + 'interface': 'WIFI', + }, + { + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': True, + }, + { + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'comment': 'foo', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + 'ensure_order': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_ADDRESS_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + '.id': '*NEW1', + 'address': '10.10.0.0/16', + 'interface': 'WIFI', + 'disabled': False, + }, + { + '.id': '*3', + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': True, + }, + { + '.id': '*1', + 'comment': 'foo', + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'address'), START_IP_ADDRESS, read_only=True)) + def test_sync_primary_key_cru_reorder_check(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip address', + 'data': [ + { + 'address': '10.10.0.0/16', + 'interface': 'WIFI', + }, + { + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': True, + }, + { + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'comment': 'foo', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + 'ensure_order': True, + '_ansible_check_mode': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], START_IP_ADDRESS_OLD_DATA) + self.assertEqual(result['new_data'], [ + { + 'address': '10.10.0.0/16', + 'interface': 'WIFI', + }, + { + '.id': '*3', + 'address': '192.168.1.0/24', + 'interface': 'LAN', + 'disabled': True, + }, + { + '.id': '*1', + 'comment': 'foo', + 'address': '192.168.88.0/24', + 'interface': 'bridge', + 'disabled': False, + }, + ]) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dhcp-server', 'lease'), START_IP_DHCP_SERVER_LEASE, read_only=True)) + def test_absent_value(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dhcp-server lease', + 'data': [ + { + 'address': '192.168.88.2', + 'mac-address': '11:22:33:44:55:66', + 'client-id': 'ff:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f:0:1:2', + 'server': 'main', + 'comment': 'foo', + }, + { + 'address': '192.168.88.3', + 'mac-address': '11:22:33:44:55:77', + 'client-id': '1:2:3:4:5:6:7', + 'server': 'main', + }, + { + 'address': '0.0.0.1', + 'mac-address': '00:00:00:00:00:01', + 'server': 'all', + }, + { + 'address': '0.0.0.2', + 'mac-address': '00:00:00:00:00:02', + 'server': 'all', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': '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_IP_DHCP_SERVER_LEASE_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DHCP_SERVER_LEASE_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dhcp-client'), START_IP_DHCP_CLIENT, read_only=True)) + def test_default_remove_combination_idempotent(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dhcp-client', + 'data': [ + { + 'interface': 'ether1', + }, + { + 'interface': 'ether2', + 'dhcp-options': None, + }, + { + 'interface': 'ether3', + 'dhcp-options': 'hostname', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': '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_IP_DHCP_CLIENT_OLD_DATA) + self.assertEqual(result['new_data'], START_IP_DHCP_CLIENT_OLD_DATA) + + @patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path', + new=create_fake_path(('ip', 'dhcp-client'), [])) + def test_default_remove_combination_create(self): + with self.assertRaises(AnsibleExitJson) as exc: + args = self.config_module_args.copy() + args.update({ + 'path': 'ip dhcp-client', + 'data': [ + { + 'interface': 'ether1', + }, + { + 'interface': 'ether2', + 'dhcp-options': None, + }, + { + 'interface': 'ether3', + 'dhcp-options': 'hostname', + }, + ], + 'handle_absent_entries': 'remove', + 'handle_entries_content': 'remove', + 'ensure_order': True, + }) + set_module_args(args) + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], True) + self.assertEqual(result['old_data'], []) + self.assertEqual(result['new_data'], [ + { + ".id": "*NEW1", + "add-default-route": True, + "default-route-distance": 1, + "dhcp-options": "hostname,clientid", + "disabled": False, + "interface": "ether1", + "use-peer-dns": True, + "use-peer-ntp": True, + }, + { + # "!dhcp-options": None, + ".id": "*NEW2", + "add-default-route": True, + "default-route-distance": 1, + "disabled": False, + "interface": "ether2", + "use-peer-dns": True, + "use-peer-ntp": True, + }, + { + ".id": "*NEW3", + "add-default-route": True, + "default-route-distance": 1, + "dhcp-options": "hostname", + "disabled": False, + "interface": "ether3", + "use-peer-dns": True, + "use-peer-ntp": True, + }, + ]) + + @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) diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/test_command.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_command.py new file mode 100644 index 000000000..3fc586586 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_command.py @@ -0,0 +1,100 @@ +# Copyright (c) 2016 Red Hat Inc. +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible_collections.community.routeros.tests.unit.compat.mock import patch +from ansible_collections.community.routeros.plugins.modules import command +from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args +from .routeros_module import TestRouterosModule, load_fixture + + +class TestRouterosCommandModule(TestRouterosModule): + + module = command + + def setUp(self): + super(TestRouterosCommandModule, self).setUp() + + self.mock_run_commands = patch('ansible_collections.community.routeros.plugins.modules.command.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestRouterosCommandModule, self).tearDown() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None): + + def load_from_file(*args, **kwargs): + module, commands = args + output = list() + + for item in commands: + try: + obj = json.loads(item) + command = obj + except ValueError: + command = item + filename = str(command).replace(' ', '_').replace('/', '') + output.append(load_fixture(filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_command_simple(self): + set_module_args(dict(commands=['/system resource print'])) + result = self.execute_module(changed=True) + self.assertEqual(len(result['stdout']), 1) + self.assertTrue('platform: "MikroTik"' in result['stdout'][0]) + + def test_command_multiple(self): + set_module_args(dict(commands=['/system resource print', '/system resource print'])) + result = self.execute_module(changed=True) + self.assertEqual(len(result['stdout']), 2) + self.assertTrue('platform: "MikroTik"' in result['stdout'][0]) + + def test_command_wait_for(self): + wait_for = 'result[0] contains "MikroTik"' + set_module_args(dict(commands=['/system resource print'], wait_for=wait_for)) + self.execute_module(changed=True) + + def test_command_wait_for_fails(self): + wait_for = 'result[0] contains "test string"' + set_module_args(dict(commands=['/system resource print'], wait_for=wait_for)) + self.execute_module(failed=True) + self.assertEqual(self.run_commands.call_count, 10) + + def test_command_retries(self): + wait_for = 'result[0] contains "test string"' + set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, retries=2)) + self.execute_module(failed=True) + self.assertEqual(self.run_commands.call_count, 2) + + def test_command_match_any(self): + wait_for = ['result[0] contains "MikroTik"', + 'result[0] contains "test string"'] + set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, match='any')) + self.execute_module(changed=True) + + def test_command_match_all(self): + wait_for = ['result[0] contains "MikroTik"', + 'result[0] contains "RB1100"'] + set_module_args(dict(commands=['/system resource print'], wait_for=wait_for, match='all')) + self.execute_module(changed=True) + + def test_command_match_all_failure(self): + wait_for = ['result[0] contains "MikroTik"', + 'result[0] contains "test string"'] + commands = ['/system resource print', '/system resource print'] + set_module_args(dict(commands=commands, wait_for=wait_for, match='all')) + self.execute_module(failed=True) + + def test_command_wait_for_2(self): + wait_for = 'result[0] contains "wireless"' + set_module_args(dict(commands=['/system package print'], wait_for=wait_for)) + self.execute_module(changed=True) diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/test_facts.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_facts.py new file mode 100644 index 000000000..322fdda45 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/test_facts.py @@ -0,0 +1,335 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.community.routeros.tests.unit.compat.mock import patch +from ansible_collections.community.routeros.plugins.modules import facts +from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args +from .routeros_module import TestRouterosModule, load_fixture + + +class TestRouterosFactsModule(TestRouterosModule): + + module = facts + + def setUp(self): + super(TestRouterosFactsModule, self).setUp() + self.mock_run_commands = patch('ansible_collections.community.routeros.plugins.modules.facts.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestRouterosFactsModule, self).tearDown() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + module = args + commands = kwargs['commands'] + output = list() + + for command in commands: + filename = str(command).split(' | ', 1)[0].replace(' ', '_') + output.append(load_fixture('facts%s' % filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_facts_default(self): + set_module_args(dict(gather_subset='default')) + result = self.execute_module() + self.assertEqual( + result['ansible_facts']['ansible_net_hostname'], 'MikroTik' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_version'], '6.42.5 (stable)' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_model'], 'RouterBOARD 3011UiAS' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_serialnum'], '1234567890' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_arch'], 'x86' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_uptime'], '3h28m52s' + ) + + def test_facts_hardware(self): + set_module_args(dict(gather_subset='hardware')) + result = self.execute_module() + self.assertEqual( + result['ansible_facts']['ansible_net_spacefree_mb'], 64921.6 + ) + self.assertEqual( + result['ansible_facts']['ansible_net_spacetotal_mb'], 65024.0 + ) + self.assertEqual( + result['ansible_facts']['ansible_net_memfree_mb'], 988.3 + ) + self.assertEqual( + result['ansible_facts']['ansible_net_memtotal_mb'], 1010.8 + ) + + def test_facts_config(self): + set_module_args(dict(gather_subset='config')) + result = self.execute_module() + self.assertIsInstance( + result['ansible_facts']['ansible_net_config'], str + ) + + self.assertIsInstance( + result['ansible_facts']['ansible_net_config_nonverbose'], str + ) + + def test_facts_interfaces(self): + set_module_args(dict(gather_subset='interfaces')) + result = self.execute_module() + self.assertIn( + result['ansible_facts']['ansible_net_all_ipv4_addresses'][0], ['10.37.129.3', '10.37.0.0', '192.168.88.1'] + ) + self.assertEqual( + result['ansible_facts']['ansible_net_all_ipv6_addresses'], ['fe80::21c:42ff:fe36:5290'] + ) + self.assertEqual( + result['ansible_facts']['ansible_net_all_ipv6_addresses'][0], + result['ansible_facts']['ansible_net_interfaces']['ether1']['ipv6'][0]['address'] + ) + self.assertEqual( + len(result['ansible_facts']['ansible_net_interfaces'].keys()), 11 + ) + self.assertEqual( + len(result['ansible_facts']['ansible_net_neighbors']), 4 + ) + + def test_facts_interfaces_no_ipv6(self): + fixture = load_fixture( + 'facts/ipv6_address_print_detail_without-paging_no-ipv6' + ) + interfaces = self.module.Interfaces(module=self.module) + addresses = interfaces.parse_detail(data=fixture) + result = interfaces.populate_addresses(data=addresses, family='ipv6') + + self.assertEqual(result, None) + + def test_facts_routing(self): + set_module_args(dict(gather_subset='routing')) + result = self.execute_module() + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['name'], ['iBGP_BRAS.TYRMA'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['instance'], ['MAIN_AS_STARKDV'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['remote-address'], ['10.10.100.1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['remote-as'], ['64520'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['nexthop-choice'], ['default'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['multihop'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['route-reflect'], ['yes'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['hold-time'], ['3m'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['ttl'], ['default'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['address-families'], ['ip'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['update-source'], ['LAN_KHV'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['default-originate'], ['never'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['remove-private-as'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['as-override'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['passive'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_peer']['iBGP_BRAS.TYRMA']['use-bfd'], ['yes'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['route-distinguisher'], ['64520:666'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['dst-address'], ['10.10.66.8/30'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['gateway'], ['10.10.100.1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['interface'], ['GRE_TYRMA'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['in-label'], ['6136'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['out-label'], ['6136'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['bgp-local-pref'], ['100'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['bgp-origin'], ['incomplete'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_vpnv4_route']['GRE_TYRMA']['bgp-ext-communities'], ['RT:64520:666'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['name'], ['default'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['as'], ['65530'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['router-id'], ['0.0.0.0'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['redistribute-connected'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['redistribute-static'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['redistribute-rip'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['redistribute-ospf'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['redistribute-other-bgp'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['client-to-client-reflection'], ['yes'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_bgp_instance']['default']['ignore-as-path-len'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_route']['altegro']['dst-address'], ['10.10.66.0/30'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_route']['altegro']['pref-src'], ['10.10.66.1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_route']['altegro']['gateway'], ['bridge1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_route']['altegro']['gateway-status'], ['bridge1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_route']['altegro']['distance'], ['0'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_route']['altegro']['scope'], ['10'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_route']['altegro']['routing-mark'], ['altegro'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['name'], ['default'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['router-id'], ['10.10.50.1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['distribute-default'], ['never'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['redistribute-connected'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['redistribute-static'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['redistribute-rip'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['redistribute-bgp'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['redistribute-other-ospf'], ['no'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['metric-default'], ['1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['metric-connected'], ['20'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['metric-static'], ['20'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['metric-rip'], ['20'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['metric-bgp'], ['auto'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['metric-other-ospf'], ['auto'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['in-filter'], ['ospf-in'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_instance']['default']['out-filter'], ['ospf-out'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['instance'], ['default'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['router-id'], ['10.10.100.1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['address'], ['10.10.1.2'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['interface'], ['GRE_TYRMA'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['priority'], ['1'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['dr-address'], ['0.0.0.0'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['backup-dr-address'], ['0.0.0.0'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['state'], ['Full'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['state-changes'], ['15'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['ls-retransmits'], ['0'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['ls-requests'], ['0'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['db-summaries'], ['0'] + ) + self.assertIn( + result['ansible_facts']['ansible_net_ospf_neighbor']['default']['adjacency'], ['6h8m46s'] + ) diff --git a/ansible_collections/community/routeros/tests/unit/plugins/modules/utils.py b/ansible_collections/community/routeros/tests/unit/plugins/modules/utils.py new file mode 100644 index 000000000..419eef114 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/plugins/modules/utils.py @@ -0,0 +1,54 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json + +from ansible_collections.community.routeros.tests.unit.compat import unittest +from ansible_collections.community.routeros.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils.common.text.converters import to_bytes + + +def set_module_args(args): + if '_ansible_remote_tmp' not in args: + args['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in args: + args['_ansible_keep_remote_files'] = False + + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + pass + + +class AnsibleFailJson(Exception): + pass + + +def exit_json(*args, **kwargs): + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class ModuleTestCase(unittest.TestCase): + + def setUp(self): + self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) + self.mock_module.start() + self.mock_sleep = patch('time.sleep') + self.mock_sleep.start() + set_module_args({}) + self.addCleanup(self.mock_module.stop) + self.addCleanup(self.mock_sleep.stop) diff --git a/ansible_collections/community/routeros/tests/unit/requirements.txt b/ansible_collections/community/routeros/tests/unit/requirements.txt new file mode 100644 index 000000000..479f2fc6f --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/requirements.txt @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unittest2 ; python_version <= '2.6' +ordereddict ; python_version <= '2.6' diff --git a/ansible_collections/community/routeros/tests/unit/requirements.yml b/ansible_collections/community/routeros/tests/unit/requirements.yml new file mode 100644 index 000000000..6a22736b5 --- /dev/null +++ b/ansible_collections/community/routeros/tests/unit/requirements.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +collections: +- ansible.netcommon |