diff options
Diffstat (limited to 'src/tests')
-rw-r--r-- | src/tests/helper.py | 24 | ||||
-rw-r--r-- | src/tests/test_config_diff.py | 69 | ||||
-rw-r--r-- | src/tests/test_config_parser.py | 55 | ||||
-rw-r--r-- | src/tests/test_configverify.py | 33 | ||||
-rw-r--r-- | src/tests/test_dependency_graph.py | 31 | ||||
-rw-r--r-- | src/tests/test_dict_search.py | 84 | ||||
-rw-r--r-- | src/tests/test_find_device_file.py | 35 | ||||
-rw-r--r-- | src/tests/test_initial_setup.py | 101 | ||||
-rw-r--r-- | src/tests/test_op_mode.py | 65 | ||||
-rw-r--r-- | src/tests/test_task_scheduler.py | 129 | ||||
-rw-r--r-- | src/tests/test_template.py | 194 | ||||
-rw-r--r-- | src/tests/test_utils.py | 28 | ||||
-rw-r--r-- | src/tests/test_utils_network.py | 50 |
13 files changed, 898 insertions, 0 deletions
diff --git a/src/tests/helper.py b/src/tests/helper.py new file mode 100644 index 0000000..f703314 --- /dev/null +++ b/src/tests/helper.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +import importlib.util + +def prepare_module(file_path='', module_name=''): + spec = importlib.util.spec_from_file_location(module_name, file_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + sys.modules[module_name] = module diff --git a/src/tests/test_config_diff.py b/src/tests/test_config_diff.py new file mode 100644 index 0000000..61a2f34 --- /dev/null +++ b/src/tests/test_config_diff.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import vyos.configtree + +from unittest import TestCase + +class TestConfigDiff(TestCase): + def setUp(self): + with open('tests/data/config.left', 'r') as f: + config_string = f.read() + self.config_left = vyos.configtree.ConfigTree(config_string) + + with open('tests/data/config.right', 'r') as f: + config_string = f.read() + self.config_right = vyos.configtree.ConfigTree(config_string) + + self.config_null = vyos.configtree.ConfigTree('') + + def test_unit(self): + diff = vyos.configtree.DiffTree(self.config_left, self.config_null) + sub = diff.sub + self.assertEqual(sub.to_string(), self.config_left.to_string()) + + diff = vyos.configtree.DiffTree(self.config_null, self.config_left) + add = diff.add + self.assertEqual(add.to_string(), self.config_left.to_string()) + + def test_symmetry(self): + lr_diff = vyos.configtree.DiffTree(self.config_left, + self.config_right) + rl_diff = vyos.configtree.DiffTree(self.config_right, + self.config_left) + + sub = lr_diff.sub + add = rl_diff.add + self.assertEqual(sub.to_string(), add.to_string()) + add = lr_diff.add + sub = rl_diff.sub + self.assertEqual(add.to_string(), sub.to_string()) + + def test_identity(self): + lr_diff = vyos.configtree.DiffTree(self.config_left, + self.config_right) + + sub = lr_diff.sub + inter = lr_diff.inter + add = lr_diff.add + + r_union = vyos.configtree.union(add, inter) + l_union = vyos.configtree.union(sub, inter) + + self.assertEqual(r_union.to_string(), + self.config_right.to_string(ordered_values=True)) + self.assertEqual(l_union.to_string(), + self.config_left.to_string(ordered_values=True)) diff --git a/src/tests/test_config_parser.py b/src/tests/test_config_parser.py new file mode 100644 index 0000000..c69732d --- /dev/null +++ b/src/tests/test_config_parser.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import vyos.configtree + +from unittest import TestCase + +class TestConfigParser(TestCase): + def setUp(self): + with open('tests/data/config.valid', 'r') as f: + config_string = f.read() + self.config = vyos.configtree.ConfigTree(config_string) + + def test_top_level_valueless(self): + self.assertTrue(self.config.exists(["top-level-valueless-node"])) + + def test_top_level_leaf(self): + self.assertTrue(self.config.exists(["top-level-leaf-node"])) + self.assertEqual(self.config.return_value(["top-level-leaf-node"]), "foo") + + def test_top_level_tag(self): + self.assertTrue(self.config.exists(["top-level-tag-node"])) + # Sorting is now intentional, during parsing of config + self.assertEqual(self.config.list_nodes(["top-level-tag-node"]), ["bar", "foo"]) + + def test_copy(self): + self.config.copy(["top-level-tag-node", "bar"], ["top-level-tag-node", "baz"]) + print(self.config.to_string()) + self.assertTrue(self.config.exists(["top-level-tag-node", "baz"])) + + def test_copy_duplicate(self): + with self.assertRaises(vyos.configtree.ConfigTreeError): + self.config.copy(["top-level-tag-node", "foo"], ["top-level-tag-node", "bar"]) + + def test_rename(self): + self.config.rename(["top-level-tag-node", "bar"], "quux") + print(self.config.to_string()) + self.assertTrue(self.config.exists(["top-level-tag-node", "quux"])) + + def test_rename_duplicate(self): + with self.assertRaises(vyos.configtree.ConfigTreeError): + self.config.rename(["top-level-tag-node", "foo"], "bar") diff --git a/src/tests/test_configverify.py b/src/tests/test_configverify.py new file mode 100644 index 0000000..15ccdf1 --- /dev/null +++ b/src/tests/test_configverify.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from unittest import TestCase +from vyos.configverify import verify_diffie_hellman_length +from vyos.utils.process import cmd + +dh_file = '/tmp/dh.pem' + +class TestDictSearch(TestCase): + def setUp(self): + pass + + def test_dh_key_none(self): + self.assertFalse(verify_diffie_hellman_length('/tmp/non_existing_file', '1024')) + + def test_dh_key_512(self): + key_len = '512' + cmd(f'openssl dhparam -out {dh_file} {key_len}') + self.assertTrue(verify_diffie_hellman_length(dh_file, key_len)) diff --git a/src/tests/test_dependency_graph.py b/src/tests/test_dependency_graph.py new file mode 100644 index 0000000..f682e87 --- /dev/null +++ b/src/tests/test_dependency_graph.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from vyos.configdep import check_dependency_graph + +_here = os.path.dirname(__file__) +ddir = os.path.join(_here, '../../data/config-mode-dependencies') + +from unittest import TestCase + +class TestDependencyGraph(TestCase): + def setUp(self): + pass + + def test_acyclic(self): + res = check_dependency_graph(dependency_dir=ddir) + self.assertTrue(res) diff --git a/src/tests/test_dict_search.py b/src/tests/test_dict_search.py new file mode 100644 index 0000000..2435d89 --- /dev/null +++ b/src/tests/test_dict_search.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from unittest import TestCase +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_recursive + +data = { + 'string': 'fooo', + 'nested': {'string': 'bar', 'empty': '', 'list': ['foo', 'bar']}, + 'non': {}, + 'list': ['bar', 'baz'], + 'dict': {'key_1': {}, 'key_2': 'vyos'}, + 'interfaces': {'dummy': {'dum0': {'address': ['192.0.2.17/29']}}, + 'ethernet': {'eth0': {'address': ['2001:db8::1/64', '192.0.2.1/29'], + 'description': 'Test123', + 'duplex': 'auto', + 'hw_id': '00:00:00:00:00:01', + 'speed': 'auto'}, + 'eth1': {'address': ['192.0.2.9/29'], + 'description': 'Test456', + 'duplex': 'auto', + 'hw_id': '00:00:00:00:00:02', + 'speed': 'auto'}}} +} + +class TestDictSearch(TestCase): + def setUp(self): + pass + + def test_non_existing_keys(self): + # TestDictSearch: Return False when querying for non-existent key + self.assertEqual(dict_search('non_existing', data), None) + self.assertEqual(dict_search('non.existing.fancy.key', data), None) + + def test_string(self): + # TestDictSearch: Return value when querying string + self.assertEqual(dict_search('string', data), data['string']) + + def test_list(self): + # TestDictSearch: Return list items when querying list + self.assertEqual(dict_search('list', data), data['list']) + + def test_dict_key_value(self): + # TestDictSearch: Return dictionary keys value when value is present + self.assertEqual(dict_search('dict.key_2', data), data['dict']['key_2']) + + def test_nested_dict_key_value(self): + # TestDictSearch: Return string value of last key when querying for a nested string + self.assertEqual(dict_search('nested.string', data), data['nested']['string']) + + def test_nested_dict_key_empty(self): + # TestDictSearch: Return False when querying for a nested string whose last key is empty + self.assertEqual(dict_search('nested.empty', data), '') + self.assertFalse(dict_search('nested.empty', data)) + + def test_nested_list(self): + # TestDictSearch: Return list items when querying nested list + self.assertEqual(dict_search('nested.list', data), data['nested']['list']) + + def test_invalid_input(self): + # TestDictSearch: Return list items when querying nested list + self.assertEqual(dict_search('nested.list', None), None) + self.assertEqual(dict_search(None, data), None) + + def test_dict_search_recursive(self): + # Test nested search in dictionary + tmp = list(dict_search_recursive(data, 'hw_id')) + self.assertEqual(len(tmp), 2) + tmp = list(dict_search_recursive(data, 'address')) + self.assertEqual(len(tmp), 3) diff --git a/src/tests/test_find_device_file.py b/src/tests/test_find_device_file.py new file mode 100644 index 0000000..f18043d --- /dev/null +++ b/src/tests/test_find_device_file.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from unittest import TestCase +from vyos.utils.system import find_device_file + +class TestDeviceFile(TestCase): + """ used to find USB devices on target """ + def setUp(self): + pass + + def test_null(self): + self.assertEqual(find_device_file('null'), '/dev/null') + + def test_zero(self): + self.assertEqual(find_device_file('zero'), '/dev/zero') + + def test_input_event(self): + self.assertEqual(find_device_file('event0'), '/dev/input/event0') + + def test_non_existing(self): + self.assertFalse(find_device_file('vyos')) diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py new file mode 100644 index 0000000..f85bf12 --- /dev/null +++ b/src/tests/test_initial_setup.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +import vyos.configtree +import vyos.initialsetup as vis + +from unittest import TestCase +from vyos.xml_ref import definition +from vyos.xml_ref.pkg_cache.vyos_1x_cache import reference + +class TestInitialSetup(TestCase): + def setUp(self): + with open('tests/data/config.boot.default', 'r') as f: + config_string = f.read() + self.config = vyos.configtree.ConfigTree(config_string) + self.xml = definition.Xml() + self.xml.define(reference) + + def test_set_user_password(self): + vis.set_user_password(self.config, 'vyos', 'vyosvyos') + + # Old password hash from the default config + old_pw = '$6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/' + new_pw = self.config.return_value(["system", "login", "user", "vyos", "authentication", "encrypted-password"]) + + # Just check it changed the hash, don't try to check if hash is good + self.assertNotEqual(old_pw, new_pw) + + def test_disable_user_password(self): + vis.disable_user_password(self.config, 'vyos') + new_pw = self.config.return_value(["system", "login", "user", "vyos", "authentication", "encrypted-password"]) + + self.assertEqual(new_pw, '!') + + def test_set_ssh_key_with_name(self): + test_ssh_key = " ssh-rsa fakedata vyos@vyos " + vis.set_user_ssh_key(self.config, 'vyos', test_ssh_key) + + key_type = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos@vyos", "type"]) + key_data = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos@vyos", "key"]) + + self.assertEqual(key_type, 'ssh-rsa') + self.assertEqual(key_data, 'fakedata') + self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + + def test_set_ssh_key_without_name(self): + # If key file doesn't include a name, the function will use user name for the key name + + test_ssh_key = " ssh-rsa fakedata " + vis.set_user_ssh_key(self.config, 'vyos', test_ssh_key) + + key_type = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos", "type"]) + key_data = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos", "key"]) + + self.assertEqual(key_type, 'ssh-rsa') + self.assertEqual(key_data, 'fakedata') + self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + + def test_create_user(self): + vis.create_user(self.config, 'jrandomhacker', password='qwerty', key=" ssh-rsa fakedata jrandomhacker@foovax ") + + self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker"])) + self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker", "authentication", "public-keys", "jrandomhacker@foovax"])) + self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker", "authentication", "encrypted-password"])) + self.assertEqual(self.config.return_value(["system", "login", "user", "jrandomhacker", "level"]), "admin") + + def test_set_hostname(self): + vis.set_host_name(self.config, "vyos-test") + + self.assertEqual(self.config.return_value(["system", "host-name"]), "vyos-test") + + def test_set_name_servers(self): + vis.set_name_servers(self.config, ["192.0.2.10", "203.0.113.20"]) + servers = self.config.return_values(["system", "name-server"]) + + self.assertIn("192.0.2.10", servers) + self.assertIn("203.0.113.20", servers) + + def test_set_gateway(self): + vis.set_default_gateway(self.config, '192.0.2.1') + + self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route'])) + +if __name__ == "__main__": + unittest.main() diff --git a/src/tests/test_op_mode.py b/src/tests/test_op_mode.py new file mode 100644 index 0000000..90963b3 --- /dev/null +++ b/src/tests/test_op_mode.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from unittest import TestCase + +import vyos.opmode + +class TestVyOSOpMode(TestCase): + def test_field_name_normalization(self): + from vyos.opmode import _normalize_field_name + + self.assertEqual(_normalize_field_name(" foo bar "), "foo_bar") + self.assertEqual(_normalize_field_name("foo-bar"), "foo_bar") + self.assertEqual(_normalize_field_name("foo (bar) baz"), "foo_bar_baz") + self.assertEqual(_normalize_field_name("load%"), "load_percentage") + + def test_dict_fields_normalization_non_unique(self): + from vyos.opmode import _normalize_field_names + + # Space and dot are both replaced by an underscore, + # so dicts like this cannor be normalized uniquely + data = {"foo bar": True, "foo.bar": False} + + with self.assertRaises(vyos.opmode.InternalError): + _normalize_field_names(data) + + def test_dict_fields_normalization_simple_dict(self): + from vyos.opmode import _normalize_field_names + + data = {"foo bar": True, "Bar-Baz": False} + self.assertEqual(_normalize_field_names(data), {"foo_bar": True, "bar_baz": False}) + + def test_dict_fields_normalization_nested_dict(self): + from vyos.opmode import _normalize_field_names + + data = {"foo bar": True, "bar-baz": {"baz-quux": {"quux-xyzzy": False}}} + self.assertEqual(_normalize_field_names(data), + {"foo_bar": True, "bar_baz": {"baz_quux": {"quux_xyzzy": False}}}) + + def test_dict_fields_normalization_mixed(self): + from vyos.opmode import _normalize_field_names + + data = [{"foo bar": True, "bar-baz": [{"baz-quux": {"quux-xyzzy": [False]}}]}] + self.assertEqual(_normalize_field_names(data), + [{"foo_bar": True, "bar_baz": [{"baz_quux": {"quux_xyzzy": [False]}}]}]) + + def test_dict_fields_normalization_primitive(self): + from vyos.opmode import _normalize_field_names + + data = [1, False, "foo"] + self.assertEqual(_normalize_field_names(data), [1, False, "foo"]) + diff --git a/src/tests/test_task_scheduler.py b/src/tests/test_task_scheduler.py new file mode 100644 index 0000000..130f825 --- /dev/null +++ b/src/tests/test_task_scheduler.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import tempfile +import unittest +import importlib + +from vyos import ConfigError + +try: + task_scheduler = importlib.import_module("src.conf_mode.system_task-scheduler") +except ModuleNotFoundError: # for unittest.main() + import sys + sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) + task_scheduler = importlib.import_module("src.conf_mode.system_task-scheduler") + +class TestUpdateCrontab(unittest.TestCase): + + def test_verify(self): + tests = [ + {'name': 'one_task', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': None + }, + {'name': 'has_interval_and_spec', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '0 * * * *', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'has_no_interval_and_spec', + 'tasks': [{'name': 'aaa', 'interval': '', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'invalid_interval', + 'tasks': [{'name': 'aaa', 'interval': '1y', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'invalid_interval_min', + 'tasks': [{'name': 'aaa', 'interval': '61m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'invalid_interval_hour', + 'tasks': [{'name': 'aaa', 'interval': '25h', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'invalid_interval_day', + 'tasks': [{'name': 'aaa', 'interval': '32d', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'no_executable', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '', 'args': ''}], + 'expected': ConfigError + }, + {'name': 'invalid_executable', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/aaa', 'args': ''}], + 'expected': ConfigError + } + ] + for t in tests: + with self.subTest(msg=t['name'], tasks=t['tasks'], expected=t['expected']): + if t['expected'] is not None: + with self.assertRaises(t['expected']): + task_scheduler.verify(t['tasks']) + else: + task_scheduler.verify(t['tasks']) + + def test_generate(self): + tests = [ + {'name': 'zero_task', + 'tasks': [], + 'expected': [] + }, + {'name': 'one_task', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': [ + '### Generated by vyos-update-crontab.py ###', + '*/60 * * * * root sg vyattacfg \"/bin/ls -l\"'] + }, + {'name': 'one_task_with_hour', + 'tasks': [{'name': 'aaa', 'interval': '10h', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': [ + '### Generated by vyos-update-crontab.py ###', + '0 */10 * * * root sg vyattacfg \"/bin/ls -l\"'] + }, + {'name': 'one_task_with_day', + 'tasks': [{'name': 'aaa', 'interval': '10d', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': [ + '### Generated by vyos-update-crontab.py ###', + '0 0 */10 * * root sg vyattacfg \"/bin/ls -l\"'] + }, + {'name': 'multiple_tasks', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}, + {'name': 'bbb', 'interval': '', 'spec': '0 0 * * *', 'executable': '/bin/ls', 'args': '-ltr'} + ], + 'expected': [ + '### Generated by vyos-update-crontab.py ###', + '*/60 * * * * root sg vyattacfg \"/bin/ls -l\"', + '0 0 * * * root sg vyattacfg \"/bin/ls -ltr\"'] + } + ] + for t in tests: + with self.subTest(msg=t['name'], tasks=t['tasks'], expected=t['expected']): + task_scheduler.crontab_file = tempfile.mkstemp()[1] + task_scheduler.generate(t['tasks']) + if len(t['expected']) > 0: + self.assertTrue(os.path.isfile(task_scheduler.crontab_file)) + with open(task_scheduler.crontab_file) as f: + actual = f.read() + self.assertEqual(t['expected'], actual.splitlines()) + os.remove(task_scheduler.crontab_file) + else: + self.assertFalse(os.path.isfile(task_scheduler.crontab_file)) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/tests/test_template.py b/src/tests/test_template.py new file mode 100644 index 0000000..dbb86b4 --- /dev/null +++ b/src/tests/test_template.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import vyos.template + +from vyos.utils.network import interface_exists +from ipaddress import ip_network +from unittest import TestCase + +class TestVyOSTemplate(TestCase): + def setUp(self): + pass + + def test_is_interface(self): + for interface in ['lo', 'eth0']: + if interface_exists(interface): + self.assertTrue(vyos.template.is_interface(interface)) + else: + self.assertFalse(vyos.template.is_interface(interface)) + self.assertFalse(vyos.template.is_interface('non-existent')) + + def test_is_ip(self): + self.assertTrue(vyos.template.is_ip('192.0.2.1')) + self.assertTrue(vyos.template.is_ip('2001:db8::1')) + self.assertFalse(vyos.template.is_ip('VyOS')) + + def test_is_ipv4(self): + self.assertTrue(vyos.template.is_ipv4('192.0.2.1')) + self.assertTrue(vyos.template.is_ipv4('192.0.2.0/24')) + self.assertTrue(vyos.template.is_ipv4('192.0.2.1/32')) + + self.assertFalse(vyos.template.is_ipv4('2001:db8::1')) + self.assertFalse(vyos.template.is_ipv4('2001:db8::/64')) + self.assertFalse(vyos.template.is_ipv4('VyOS')) + + def test_is_ipv6(self): + self.assertTrue(vyos.template.is_ipv6('2001:db8::1')) + self.assertTrue(vyos.template.is_ipv6('2001:db8::/64')) + self.assertTrue(vyos.template.is_ipv6('2001:db8::1/64')) + + self.assertFalse(vyos.template.is_ipv6('192.0.2.1')) + self.assertFalse(vyos.template.is_ipv6('192.0.2.0/24')) + self.assertFalse(vyos.template.is_ipv6('192.0.2.1/32')) + self.assertFalse(vyos.template.is_ipv6('VyOS')) + + def test_address_from_cidr(self): + self.assertEqual(vyos.template.address_from_cidr('192.0.2.0/24'), '192.0.2.0') + self.assertEqual(vyos.template.address_from_cidr('2001:db8::/48'), '2001:db8::') + + with self.assertRaises(ValueError): + # ValueError: 192.0.2.1/24 has host bits set + self.assertEqual(vyos.template.address_from_cidr('192.0.2.1/24'), '192.0.2.1') + + with self.assertRaises(ValueError): + # ValueError: 2001:db8::1/48 has host bits set + self.assertEqual(vyos.template.address_from_cidr('2001:db8::1/48'), '2001:db8::1') + + network_v4 = '192.0.2.0/26' + self.assertEqual(vyos.template.address_from_cidr(network_v4), str(ip_network(network_v4).network_address)) + + def test_netmask_from_cidr(self): + self.assertEqual(vyos.template.netmask_from_cidr('192.0.2.0/24'), '255.255.255.0') + self.assertEqual(vyos.template.netmask_from_cidr('192.0.2.128/25'), '255.255.255.128') + self.assertEqual(vyos.template.netmask_from_cidr('2001:db8::/48'), 'ffff:ffff:ffff::') + + with self.assertRaises(ValueError): + # ValueError: 192.0.2.1/24 has host bits set + self.assertEqual(vyos.template.netmask_from_cidr('192.0.2.1/24'), '255.255.255.0') + + with self.assertRaises(ValueError): + # ValueError: 2001:db8:1:/64 has host bits set + self.assertEqual(vyos.template.netmask_from_cidr('2001:db8:1:/64'), 'ffff:ffff:ffff:ffff::') + + network_v4 = '192.0.2.0/26' + self.assertEqual(vyos.template.netmask_from_cidr(network_v4), str(ip_network(network_v4).netmask)) + + def test_first_host_address(self): + self.assertEqual(vyos.template.first_host_address('10.0.0.0/24'), '10.0.0.1') + self.assertEqual(vyos.template.first_host_address('10.0.0.10/24'), '10.0.0.1') + self.assertEqual(vyos.template.first_host_address('10.0.0.255/24'), '10.0.0.1') + self.assertEqual(vyos.template.first_host_address('10.0.0.128/25'), '10.0.0.129') + self.assertEqual(vyos.template.first_host_address('2001:db8::/64'), '2001:db8::1') + self.assertEqual(vyos.template.first_host_address('2001:db8::1000/64'), '2001:db8::1') + self.assertEqual(vyos.template.first_host_address('2001:db8::ffff:ffff:ffff:ffff/64'), '2001:db8::1') + + def test_last_host_address(self): + self.assertEqual(vyos.template.last_host_address('10.0.0.0/24'), '10.0.0.254') + self.assertEqual(vyos.template.last_host_address('10.0.0.128/25'), '10.0.0.254') + self.assertEqual(vyos.template.last_host_address('2001:db8::/64'), '2001:db8::ffff:ffff:ffff:ffff') + + def test_increment_ip(self): + self.assertEqual(vyos.template.inc_ip('10.0.0.0/24', '2'), '10.0.0.2') + self.assertEqual(vyos.template.inc_ip('10.0.0.0', '2'), '10.0.0.2') + self.assertEqual(vyos.template.inc_ip('10.0.0.0', '10'), '10.0.0.10') + self.assertEqual(vyos.template.inc_ip('2001:db8::/64', '2'), '2001:db8::2') + self.assertEqual(vyos.template.inc_ip('2001:db8::', '10'), '2001:db8::a') + + def test_decrement_ip(self): + self.assertEqual(vyos.template.dec_ip('10.0.0.100/24', '1'), '10.0.0.99') + self.assertEqual(vyos.template.dec_ip('10.0.0.90', '10'), '10.0.0.80') + self.assertEqual(vyos.template.dec_ip('2001:db8::b/64', '10'), '2001:db8::1') + self.assertEqual(vyos.template.dec_ip('2001:db8::f', '5'), '2001:db8::a') + + def test_is_network(self): + self.assertFalse(vyos.template.is_ip_network('192.0.2.0')) + self.assertFalse(vyos.template.is_ip_network('192.0.2.1/24')) + self.assertTrue(vyos.template.is_ip_network('192.0.2.0/24')) + + self.assertFalse(vyos.template.is_ip_network('2001:db8::')) + self.assertFalse(vyos.template.is_ip_network('2001:db8::ffff')) + self.assertTrue(vyos.template.is_ip_network('2001:db8::/48')) + self.assertTrue(vyos.template.is_ip_network('2001:db8:1000::/64')) + + def test_is_network(self): + self.assertTrue(vyos.template.compare_netmask('10.0.0.0/8', '20.0.0.0/8')) + self.assertTrue(vyos.template.compare_netmask('10.0.0.0/16', '20.0.0.0/16')) + self.assertFalse(vyos.template.compare_netmask('10.0.0.0/8', '20.0.0.0/16')) + self.assertFalse(vyos.template.compare_netmask('10.0.0.1', '20.0.0.0/16')) + + self.assertTrue(vyos.template.compare_netmask('2001:db8:1000::/48', '2001:db8:2000::/48')) + self.assertTrue(vyos.template.compare_netmask('2001:db8:1000::/64', '2001:db8:2000::/64')) + self.assertFalse(vyos.template.compare_netmask('2001:db8:1000::/48', '2001:db8:2000::/64')) + + def test_cipher_to_string(self): + ESP_DEFAULT = 'aes256gcm128-sha256-ecp256,aes128ccm64-sha256-ecp256' + IKEv2_DEFAULT = 'aes256gcm128-sha256-ecp256,aes128ccm128-md5_128-modp1024' + + data = { + 'esp_group': { + 'ESP_DEFAULT': { + 'compression': 'disable', + 'lifetime': '3600', + 'mode': 'tunnel', + 'pfs': 'dh-group19', + 'proposal': { + '10': { + 'encryption': 'aes256gcm128', + 'hash': 'sha256', + }, + '20': { + 'encryption': 'aes128ccm64', + 'hash': 'sha256', + } + } + } + }, + 'ike_group': { + 'IKEv2_DEFAULT': { + 'close_action': 'none', + 'dead_peer_detection': { + 'action': 'hold', + 'interval': '30', + 'timeout': '120' + }, + 'ikev2_reauth': 'no', + 'key_exchange': 'ikev2', + 'lifetime': '10800', + 'mobike': 'disable', + 'proposal': { + '10': { + 'dh_group': '19', + 'encryption': 'aes256gcm128', + 'hash': 'sha256' + }, + '20': { + 'dh_group': '2', + 'encryption': 'aes128ccm128', + 'hash': 'md5_128' + }, + } + } + }, + } + + for group_name, group_config in data['esp_group'].items(): + ciphers = vyos.template.get_esp_ike_cipher(group_config) + self.assertIn(ESP_DEFAULT, ','.join(ciphers)) + + for group_name, group_config in data['ike_group'].items(): + ciphers = vyos.template.get_esp_ike_cipher(group_config) + self.assertIn(IKEv2_DEFAULT, ','.join(ciphers)) diff --git a/src/tests/test_utils.py b/src/tests/test_utils.py new file mode 100644 index 0000000..9ae329c --- /dev/null +++ b/src/tests/test_utils.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from unittest import TestCase +class TestVyOSUtils(TestCase): + def test_key_mangling(self): + from vyos.utils.dict import mangle_dict_keys + data = {"foo-bar": {"baz-quux": None}} + expected_data = {"foo_bar": {"baz_quux": None}} + new_data = mangle_dict_keys(data, '-', '_') + self.assertEqual(new_data, expected_data) + + def test_sysctl_read(self): + from vyos.utils.system import sysctl_read + self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1') diff --git a/src/tests/test_utils_network.py b/src/tests/test_utils_network.py new file mode 100644 index 0000000..5a6dc25 --- /dev/null +++ b/src/tests/test_utils_network.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import vyos.utils.network +from unittest import TestCase + +class TestVyOSUtilsNetwork(TestCase): + def setUp(self): + pass + + def test_is_addr_assigned(self): + self.assertTrue(vyos.utils.network.is_addr_assigned('127.0.0.1')) + self.assertTrue(vyos.utils.network.is_addr_assigned('::1')) + self.assertFalse(vyos.utils.network.is_addr_assigned('127.251.255.123')) + + def test_is_ipv6_link_local(self): + self.assertFalse(vyos.utils.network.is_ipv6_link_local('169.254.0.1')) + self.assertTrue(vyos.utils.network.is_ipv6_link_local('fe80::')) + self.assertTrue(vyos.utils.network.is_ipv6_link_local('fe80::affe:1')) + self.assertTrue(vyos.utils.network.is_ipv6_link_local('fe80::affe:1%eth0')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('2001:db8::')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('2001:db8::%eth0')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('VyOS')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('::1')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('::1%lo')) + + def test_is_ipv6_link_local(self): + self.assertTrue(vyos.utils.network.is_loopback_addr('127.0.0.1')) + self.assertTrue(vyos.utils.network.is_loopback_addr('127.0.1.1')) + self.assertTrue(vyos.utils.network.is_loopback_addr('127.1.1.1')) + self.assertTrue(vyos.utils.network.is_loopback_addr('::1')) + + self.assertFalse(vyos.utils.network.is_loopback_addr('::2')) + self.assertFalse(vyos.utils.network.is_loopback_addr('192.0.2.1')) + + + |