diff options
Diffstat (limited to 'src/tests')
| -rw-r--r-- | src/tests/helper.py | 2 | ||||
| -rw-r--r-- | src/tests/test_config_diff.py | 13 | ||||
| -rw-r--r-- | src/tests/test_config_merge.py | 50 | ||||
| -rw-r--r-- | src/tests/test_config_parser.py | 6 | ||||
| -rw-r--r-- | src/tests/test_config_tree.py | 48 | ||||
| -rw-r--r-- | src/tests/test_configd_inspect.py | 210 | ||||
| -rw-r--r-- | src/tests/test_configverify.py | 2 | ||||
| -rw-r--r-- | src/tests/test_dependency_graph.py | 2 | ||||
| -rw-r--r-- | src/tests/test_dict_search.py | 10 | ||||
| -rw-r--r-- | src/tests/test_find_device_file.py | 2 | ||||
| -rw-r--r-- | src/tests/test_initial_setup.py | 6 | ||||
| -rw-r--r-- | src/tests/test_op_mode.py | 2 | ||||
| -rw-r--r-- | src/tests/test_task_scheduler.py | 2 | ||||
| -rw-r--r-- | src/tests/test_template.py | 20 | ||||
| -rw-r--r-- | src/tests/test_utils.py | 66 | ||||
| -rw-r--r-- | src/tests/test_utils_auth.py | 75 | ||||
| -rw-r--r-- | src/tests/test_utils_file.py | 86 | ||||
| -rw-r--r-- | src/tests/test_utils_network.py | 11 |
18 files changed, 517 insertions, 96 deletions
diff --git a/src/tests/helper.py b/src/tests/helper.py index cc0710494..2c4421d0a 100644 --- a/src/tests/helper.py +++ b/src/tests/helper.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 diff --git a/src/tests/test_config_diff.py b/src/tests/test_config_diff.py index 39e17613a..edbc0804e 100644 --- a/src/tests/test_config_diff.py +++ b/src/tests/test_config_diff.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 @@ -31,11 +31,11 @@ class TestConfigDiff(TestCase): 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()) + self.assertEqual(sub, self.config_left) diff = vyos.configtree.DiffTree(self.config_null, self.config_left) add = diff.add - self.assertEqual(add.to_string(), self.config_left.to_string()) + self.assertEqual(add, self.config_left) def test_symmetry(self): lr_diff = vyos.configtree.DiffTree(self.config_left, @@ -45,10 +45,10 @@ class TestConfigDiff(TestCase): sub = lr_diff.sub add = rl_diff.add - self.assertEqual(sub.to_string(), add.to_string()) + self.assertEqual(sub, add) add = lr_diff.add sub = rl_diff.sub - self.assertEqual(add.to_string(), sub.to_string()) + self.assertEqual(add, sub) def test_identity(self): lr_diff = vyos.configtree.DiffTree(self.config_left, @@ -61,6 +61,9 @@ class TestConfigDiff(TestCase): r_union = vyos.configtree.union(add, inter) l_union = vyos.configtree.union(sub, inter) + # here we must compare string representations instead of using + # dunder equal, as we assert equivalence of the values list, which + # is optionally ordered at render self.assertEqual(r_union.to_string(), self.config_right.to_string(ordered_values=True)) self.assertEqual(l_union.to_string(), diff --git a/src/tests/test_config_merge.py b/src/tests/test_config_merge.py new file mode 100644 index 000000000..c9b4ad5c8 --- /dev/null +++ b/src/tests/test_config_merge.py @@ -0,0 +1,50 @@ +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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) + + def test_merge_destructive(self): + res = vyos.configtree.merge(self.config_left, self.config_right, + destructive=True) + right_value = self.config_right.return_value(['node1', 'tag_node', 'foo', 'single']) + merge_value = res.return_value(['node1', 'tag_node', 'foo', 'single']) + + # Check includes new value + self.assertEqual(right_value, merge_value) + + # Check preserves non-confliciting paths + self.assertTrue(res.exists(['node3'])) + + def test_merge_non_destructive(self): + res = vyos.configtree.merge(self.config_left, self.config_right) + left_value = self.config_left.return_value(['node1', 'tag_node', 'foo', 'single']) + merge_value = res.return_value(['node1', 'tag_node', 'foo', 'single']) + + # Check includes original value + self.assertEqual(left_value, merge_value) + + # Check preserves non-confliciting paths + self.assertTrue(res.exists(['node3'])) diff --git a/src/tests/test_config_parser.py b/src/tests/test_config_parser.py index 9a4f02859..823d5a7c1 100644 --- a/src/tests/test_config_parser.py +++ b/src/tests/test_config_parser.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 @@ -51,3 +51,7 @@ class TestConfigParser(TestCase): def test_rename_duplicate(self): with self.assertRaises(vyos.configtree.ConfigTreeError): self.config.rename(["top-level-tag-node", "foo"], "bar") + + def test_leading_slashes(self): + self.assertTrue(self.config.exists(["normal-node", "value-with-leading-slashes"])) + self.assertEqual(self.config.return_value(["normal-node", "value-with-leading-slashes"]), "//other-value") diff --git a/src/tests/test_config_tree.py b/src/tests/test_config_tree.py new file mode 100644 index 000000000..d6339c570 --- /dev/null +++ b/src/tests/test_config_tree.py @@ -0,0 +1,48 @@ +# Copyright (C) VyOS Inc. +# +# 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 json +import unittest +from unittest import TestCase + +from vyos.configtree import ConfigTree +from vyos.referencetree import ReferenceTree +from vyos.derivedtree import subtree_from_list_of_partial_paths + + +class TestInitialSetup(TestCase): + def setUp(self): + with open('data/config.boot.default') as f: + config_str = f.read() + self.ct = ConfigTree(config_str) + + def test_subtree_from_partial(self): + reftree = ReferenceTree(cache_file='data/reftree.cache') + + # workaround since configtree.list_nodes does not take an empty path + d = json.loads(self.ct.to_json()) + top_nodes = list(d) + paths = [s.split() for s in top_nodes] + + reassemble = subtree_from_list_of_partial_paths( + self.ct, paths, reference_tree=reftree + ) + + self.assertEqual(self.ct, reassemble) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/tests/test_configd_inspect.py b/src/tests/test_configd_inspect.py index ccd631893..3363ab653 100644 --- a/src/tests/test_configd_inspect.py +++ b/src/tests/test_configd_inspect.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 @@ -12,93 +12,151 @@ # 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 re +import ast import json -import warnings -import importlib.util -from inspect import signature -from inspect import getsource -from functools import wraps from unittest import TestCase INC_FILE = 'data/configd-include.json' CONF_DIR = 'src/conf_mode' -f_list = ['get_config', 'verify', 'generate', 'apply'] - -def import_script(s): - path = os.path.join(CONF_DIR, s) - name = os.path.splitext(s)[0].replace('-', '_') - spec = importlib.util.spec_from_file_location(name, path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - -# importing conf_mode scripts imports jinja2 with deprecation warning -def ignore_deprecation_warning(f): - @wraps(f) - def decorated_function(*args, **kwargs): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - f(*args, **kwargs) - return decorated_function +funcs = ['get_config', 'verify', 'generate', 'apply'] + + +class FunctionSig(ast.NodeVisitor): + def __init__(self): + self.func_sig_len = dict.fromkeys(funcs, None) + self.get_config_default_values = [] + + def visit_FunctionDef(self, node): + func_name = node.name + if func_name in funcs: + self.func_sig_len[func_name] = len(node.args.args) + + if func_name == 'get_config': + for default in node.args.defaults: + if isinstance(default, ast.Constant): + self.get_config_default_values.append(default.value) + + self.generic_visit(node) + + def get_sig_lengths(self): + return self.func_sig_len + + def get_config_default(self): + return self.get_config_default_values[0] + + +class LegacyCall(ast.NodeVisitor): + def __init__(self): + self.legacy_func_count = 0 + + def visit_Constant(self, node): + value = node.value + if isinstance(value, str): + if 'my_set' in value or 'my_delete' in value: + self.legacy_func_count += 1 + + self.generic_visit(node) + + def get_legacy_func_count(self): + return self.legacy_func_count + + +class ConfigInstance(ast.NodeVisitor): + def __init__(self): + self.count = 0 + + def visit_Call(self, node): + if isinstance(node.func, ast.Name): + name = node.func.id + if name == 'Config': + self.count += 1 + self.generic_visit(node) + + def get_count(self): + return self.count + + +class FunctionConfigInstance(ast.NodeVisitor): + def __init__(self): + self.func_config_instance = dict.fromkeys(funcs, 0) + + def visit_FunctionDef(self, node): + func_name = node.name + if func_name in funcs: + config_instance = ConfigInstance() + config_instance.visit(node) + self.func_config_instance[func_name] = config_instance.get_count() + self.generic_visit(node) + + def get_func_config_instance(self): + return self.func_config_instance + class TestConfigdInspect(TestCase): def setUp(self): + self.ast_list = [] + with open(INC_FILE) as f: self.inc_list = json.load(f) - @ignore_deprecation_warning - def test_signatures(self): for s in self.inc_list: - m = import_script(s) - for i in f_list: - f = getattr(m, i, None) - self.assertIsNotNone(f, f"'{s}': missing function '{i}'") - sig = signature(f) - par = sig.parameters - l = len(par) - self.assertEqual(l, 1, - f"'{s}': '{i}' incorrect signature") - if i == 'get_config': - for p in par.values(): - self.assertTrue(p.default is None, - f"'{s}': '{i}' incorrect signature") - - @ignore_deprecation_warning - def test_function_instance(self): - for s in self.inc_list: - m = import_script(s) - for i in f_list: - f = getattr(m, i, None) - if not f: - continue - str_f = getsource(f) - # Regex not XXXConfig() T3108 - n = len(re.findall(r'[^a-zA-Z]Config\(\)', str_f)) - if i == 'get_config': - self.assertEqual(n, 1, - f"'{s}': '{i}' no instance of Config") - if i != 'get_config': - self.assertEqual(n, 0, - f"'{s}': '{i}' instance of Config") - - @ignore_deprecation_warning - def test_file_instance(self): - for s in self.inc_list: - m = import_script(s) - str_m = getsource(m) - # Regex not XXXConfig T3108 - n = len(re.findall(r'[^a-zA-Z]Config\(\)', str_m)) - self.assertEqual(n, 1, - f"'{s}' more than one instance of Config") - - @ignore_deprecation_warning + s_path = f'{CONF_DIR}/{s}' + with open(s_path) as f: + s_str = f.read() + s_tree = ast.parse(s_str) + self.ast_list.append((s, s_tree)) + + def test_signatures(self): + for s, t in self.ast_list: + visitor = FunctionSig() + visitor.visit(t) + sig_lens = visitor.get_sig_lengths() + + for f in funcs: + self.assertIsNotNone(sig_lens[f], f"'{s}': '{f}' missing") + self.assertEqual(sig_lens[f], 1, f"'{s}': '{f}' incorrect signature") + + self.assertEqual( + visitor.get_config_default(), + None, + f"'{s}': 'get_config' incorrect signature", + ) + + def test_file_config_instance(self): + for s, t in self.ast_list: + visitor = ConfigInstance() + visitor.visit(t) + count = visitor.get_count() + + self.assertEqual(count, 1, f"'{s}' more than one instance of Config") + + def test_function_config_instance(self): + for s, t in self.ast_list: + visitor = FunctionConfigInstance() + visitor.visit(t) + func_config_instance = visitor.get_func_config_instance() + + for f in funcs: + if f == 'get_config': + self.assertTrue( + func_config_instance[f] > 0, + f"'{s}': '{f}' no instance of Config", + ) + self.assertTrue( + func_config_instance[f] < 2, + f"'{s}': '{f}' more than one instance of Config", + ) + else: + self.assertEqual( + func_config_instance[f], 0, f"'{s}': '{f}' instance of Config" + ) + def test_config_modification(self): - for s in self.inc_list: - m = import_script(s) - str_m = getsource(m) - n = str_m.count('my_set') - self.assertEqual(n, 0, f"'{s}' modifies config") + for s, t in self.ast_list: + visitor = LegacyCall() + visitor.visit(t) + legacy_func_count = visitor.get_legacy_func_count() + + self.assertEqual(legacy_func_count, 0, f"'{s}' modifies config") diff --git a/src/tests/test_configverify.py b/src/tests/test_configverify.py index f1ec65cd2..8f80365d7 100644 --- a/src/tests/test_configverify.py +++ b/src/tests/test_configverify.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 diff --git a/src/tests/test_dependency_graph.py b/src/tests/test_dependency_graph.py index f3f1db376..12f50a1c4 100644 --- a/src/tests/test_dependency_graph.py +++ b/src/tests/test_dependency_graph.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 diff --git a/src/tests/test_dict_search.py b/src/tests/test_dict_search.py index 6b4bc933a..cbc170ecf 100644 --- a/src/tests/test_dict_search.py +++ b/src/tests/test_dict_search.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 @@ -44,6 +44,14 @@ class TestDictSearch(TestCase): self.assertEqual(dict_search('non_existing', data), None) self.assertEqual(dict_search('non.existing.fancy.key', data), None) + def test_non_existing_keys_with_default_positional(self): + # TestDictSearch: Return a default value when querying for non-existent key (positional arg) + self.assertEqual(dict_search('non.existing.fancy.key', data, 'test'), 'test') + + def test_non_existing_keys_with_default_named(self): + # TestDictSearch: Return a default value when querying for non-existent key (named arg) + self.assertEqual(dict_search('non.existing.fancy.key', data, default='test'), 'test') + def test_string(self): # TestDictSearch: Return value when querying string self.assertEqual(dict_search('string', data), data['string']) diff --git a/src/tests/test_find_device_file.py b/src/tests/test_find_device_file.py index 5b90f2034..02a33c224 100644 --- a/src/tests/test_find_device_file.py +++ b/src/tests/test_find_device_file.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index 4cd5fb169..09feea7ba 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 @@ -92,8 +92,8 @@ class TestInitialSetup(TestCase): 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'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute'])) if __name__ == "__main__": unittest.main() diff --git a/src/tests/test_op_mode.py b/src/tests/test_op_mode.py index 23f709653..848a9666d 100644 --- a/src/tests/test_op_mode.py +++ b/src/tests/test_op_mode.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 diff --git a/src/tests/test_task_scheduler.py b/src/tests/test_task_scheduler.py index 795ffeb9d..0d0319495 100644 --- a/src/tests/test_task_scheduler.py +++ b/src/tests/test_task_scheduler.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 diff --git a/src/tests/test_template.py b/src/tests/test_template.py index 6377f6da5..e2548602d 100644 --- a/src/tests/test_template.py +++ b/src/tests/test_template.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 @@ -190,3 +190,21 @@ class TestVyOSTemplate(TestCase): 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)) + + def test_get_default_port(self): + from vyos.defaults import config_files + from vyos.defaults import internal_ports + + with self.assertRaises(RuntimeError): + vyos.template.get_default_config_file('UNKNOWN') + with self.assertRaises(RuntimeError): + vyos.template.get_default_port('UNKNOWN') + with self.assertRaises(RuntimeError): + vyos.template.nft_accept_invalid('UNKNOWN') + + self.assertEqual(vyos.template.get_default_config_file('sshd_user_ca'), + config_files['sshd_user_ca']) + self.assertEqual(vyos.template.get_default_port('certbot_haproxy'), + internal_ports['certbot_haproxy']) + self.assertEqual(vyos.template.nft_accept_invalid('arp'), + 'ct state invalid ether type arp counter accept') diff --git a/src/tests/test_utils.py b/src/tests/test_utils.py index 7bfd2618e..5022b24f6 100644 --- a/src/tests/test_utils.py +++ b/src/tests/test_utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 @@ -13,6 +13,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from unittest import TestCase +from unittest.mock import patch + class TestVyOSUtils(TestCase): def test_key_mangling(self): from vyos.utils.dict import mangle_dict_keys @@ -23,4 +25,64 @@ class TestVyOSUtils(TestCase): def test_sysctl_read(self): from vyos.utils.system import sysctl_read - self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1') + self.assertEqual(sysctl_read(['net', 'ipv4', 'conf', 'lo', 'forwarding']), '1') + + def test_sysctl_key_normalization(self): + from vyos.utils.system import sysctl_read + with patch('vyos.utils.system.run') as mock_run: + mock_run.return_value.stdout = b'1\n' + sysctl_read(['net', 'ipv4', 'conf', 'eth0.10', 'forwarding']) + mock_run.assert_called_with( + ['sysctl', '-nb', 'net.ipv4.conf.eth0/10.forwarding'], + capture_output=True, + ) + + def test_list_strip(self): + from vyos.utils.list import list_strip + + lst = ['a', 'b', 'c', 'd', 'e'] + sub = ['a', 'b'] + rsb = ['d', 'e'] + non = ['a', 'e'] + self.assertEqual(list_strip(lst, sub), ['c', 'd', 'e']) + self.assertEqual(list_strip(lst, rsb, right=True), ['a', 'b', 'c']) + self.assertEqual(list_strip(lst, non), []) + self.assertEqual(list_strip(sub, lst), []) + + def test_range_str_to_list(self): + from vyos.utils.convert import range_str_to_list + + # basic cases + self.assertEqual(range_str_to_list('1-3'), [1, 2, 3]) + self.assertEqual(range_str_to_list('1-3,5,7-8'), [1, 2, 3, 5, 7, 8]) + self.assertEqual(range_str_to_list('3'), [3]) + # empty string + self.assertEqual(range_str_to_list(''), []) + # unordered input + self.assertEqual(range_str_to_list('5,1-3,4'), [1, 2, 3, 4, 5]) + self.assertEqual(range_str_to_list('7-9,1-3'), [1, 2, 3, 7, 8, 9]) + # overlapping ranges + self.assertEqual(range_str_to_list('1-5,3-7'), [1, 2, 3, 4, 5, 6, 7]) + self.assertEqual(range_str_to_list('1-3,2-4,3-5'), [1, 2, 3, 4, 5]) + # duplicated values + self.assertEqual(range_str_to_list('1,1,2,2,3'), [1, 2, 3]) + self.assertEqual(range_str_to_list('5,1-3,2,3'), [1, 2, 3, 5]) + # adjacent ranges + self.assertEqual(range_str_to_list('1-3,4-6'), [1, 2, 3, 4, 5, 6]) + + def test_list_to_range_str(self): + from vyos.utils.convert import list_to_range_str + + # basic cases + self.assertEqual(list_to_range_str([1, 2, 3]), '1-3') + self.assertEqual(list_to_range_str([1, 2, 3, 5, 7, 8]), '1-3,5,7-8') + self.assertEqual(list_to_range_str([1, 3]), '1,3') + self.assertEqual(list_to_range_str([3]), '3') + # empty list + self.assertEqual(list_to_range_str([]), '') + # unordered input + self.assertEqual(list_to_range_str([5, 1, 2, 3, 4]), '1-5') + self.assertEqual(list_to_range_str([7, 8, 9, 1, 2, 3]), '1-3,7-9') + # duplicated values + self.assertEqual(list_to_range_str([1, 1, 2, 2, 3, 3]), '1-3') + self.assertEqual(list_to_range_str([5, 1, 2, 2, 3, 5]), '1-3,5') diff --git a/src/tests/test_utils_auth.py b/src/tests/test_utils_auth.py new file mode 100644 index 000000000..f3b52ab29 --- /dev/null +++ b/src/tests/test_utils_auth.py @@ -0,0 +1,75 @@ +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 pwd +import unittest + +from vyos.utils import auth + +class TestVyOSUtilsAuth(unittest.TestCase): + def test_uid_root(self): + self.assertEqual(auth.get_local_passwd_entries(0).pw_name, 'root') + self.assertEqual(auth.get_local_passwd_entries(0).pw_uid, 0) + + def test_uid_daemon(self): + uid = None + for user in auth.get_local_passwd_entries(): + if user.pw_name == 'daemon': + uid = user.pw_uid + break + + self.assertEqual(auth.get_local_passwd_entries(uid).pw_name, 'daemon') + self.assertEqual(auth.get_local_passwd_entries(uid).pw_uid, uid) + + def test_uid_not_found(self): + self.assertEqual(auth.get_local_passwd_entries(5465487635), None) + + def test_get_local_users_returns_existing_usernames(self): + # Returned users exist, skip list is excluded, and UIDs are in range + + all_users = set(s_user.pw_name for s_user in pwd.getpwall()) + local_users = auth.get_local_users() + + # All returned users must really exist + for user in local_users: + self.assertIn(user, all_users) + + # Nobody in the skip list + for skipped in auth.SYSTEM_USER_SKIP_LIST: + self.assertNotIn(skipped, local_users) + + # All are within UID range + for s_user in pwd.getpwall(): + if s_user.pw_name in local_users: + self.assertGreaterEqual(s_user.pw_uid, auth.MIN_USER_UID) + self.assertLessEqual(s_user.pw_uid, auth.MAX_USER_UID) + + def test_get_user_home_dir_for_real_user(self): + # User's homedir is a non-empty string for a valid user + + local_users = auth.get_local_users() + if local_users: + for user in local_users: + home_dir = auth.get_user_home_dir(user) + self.assertIsInstance(home_dir, str) + self.assertTrue(bool(home_dir)) # Should not be empty + else: + self.skipTest("No suitable non-system users found on this system") + + def test_get_user_home_dir_invalid_user(self): + # Raises KeyError for nonexistent username + + user = "__this_user_does_not_exist__" # Test using unlikely username + with self.assertRaises(KeyError): + auth.get_user_home_dir(user) diff --git a/src/tests/test_utils_file.py b/src/tests/test_utils_file.py new file mode 100644 index 000000000..4519cf03c --- /dev/null +++ b/src/tests/test_utils_file.py @@ -0,0 +1,86 @@ +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 tempfile +import unittest + +from pathlib import Path + +from vyos.utils.file import copy_recursive +from vyos.utils.file import move_recursive + + +class TestVyOSUtilsFile(unittest.TestCase): + def setUp(self): + """Create temporary directories for source and destination.""" + self.tmpdir = tempfile.TemporaryDirectory() + self.src = Path(self.tmpdir.name) / 'src' + self.dst = Path(self.tmpdir.name) / 'dst' + + # Create test directory structure in `src` + (self.src / 'subdir').mkdir(parents=True) + (self.dst).mkdir(parents=True) + + # Create files + (self.src / 'file1.txt').write_text('hello world') + (self.src / 'subdir' / 'file2.txt').write_text('subdir file') + + def tearDown(self): + """Cleanup temp directory.""" + self.tmpdir.cleanup() + + def test_copy_recursive_no_overwrite(self): + copy_recursive(str(self.src), str(self.dst), overwrite=False) + + self.assertTrue((self.dst / 'file1.txt').exists()) + self.assertTrue((self.dst / 'subdir' / 'file2.txt').exists()) + + def test_copy_recursive_skip_existing(self): + # Create conflicting file in destination + (self.dst / 'file1.txt').write_text('different content') + + copy_recursive(str(self.src), str(self.dst), overwrite=False) + + # Destination should remain the same (not overwritten) + content = (self.dst / 'file1.txt').read_text() + self.assertEqual(content, 'different content') + + def test_copy_recursive_overwrite(self): + (self.dst / 'file1.txt').write_text('different content') + + copy_recursive(str(self.src), str(self.dst), overwrite=True) + + # Destination should be overwritten with source content + content = (self.dst / 'file1.txt').read_text() + self.assertEqual(content, 'hello world') + + def test_move_recursive(self): + move_recursive(str(self.src), str(self.dst), overwrite=False) + + # Files should appear in destination + self.assertTrue((self.dst / 'file1.txt').exists()) + self.assertTrue((self.dst / 'subdir' / 'file2.txt').exists()) + + # Source should be removed + self.assertFalse(self.src.exists()) + + def test_move_recursive_overwrite(self): + # Prepare conflicting file in destination + (self.dst / 'file1.txt').write_text('conflicting') + + move_recursive(str(self.src), str(self.dst), overwrite=True) + + content = (self.dst / 'file1.txt').read_text() + self.assertEqual(content, 'hello world') # overwritten + self.assertFalse(self.src.exists()) # source cleaned up diff --git a/src/tests/test_utils_network.py b/src/tests/test_utils_network.py index d68dec16f..6d9a358c1 100644 --- a/src/tests/test_utils_network.py +++ b/src/tests/test_utils_network.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> # # 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 @@ -43,3 +43,12 @@ class TestVyOSUtilsNetwork(TestCase): self.assertFalse(vyos.utils.network.is_loopback_addr('::2')) self.assertFalse(vyos.utils.network.is_loopback_addr('192.0.2.1')) + + def test_check_port_availability(self): + self.assertTrue(vyos.utils.network.check_port_availability('::1', 8080)) + self.assertTrue(vyos.utils.network.check_port_availability('127.0.0.1', 8080)) + self.assertTrue(vyos.utils.network.check_port_availability(None, 8080, protocol='udp')) + # We do not have 192.0.2.1 configured on this system + self.assertFalse(vyos.utils.network.check_port_availability('192.0.2.1', 443)) + # We do not have 2001:db8::1 configured on this system + self.assertFalse(vyos.utils.network.check_port_availability('2001:db8::1', 80, protocol='udp')) |
