From 09d4f3d93fe272b12b5fae5de36e2c5c47206133 Mon Sep 17 00:00:00 2001
From: Yuya Kusakabe <yuya.kusakabe@gmail.com>
Date: Thu, 17 May 2018 00:22:02 +0900
Subject: Add tests for hostname

---
 Makefile                              |   2 +-
 sonar-project.properties              |   2 +-
 src/conf_mode/host_name.py            |  27 +++++--
 src/tests/test_host_name.py           | 136 ++++++++++++++++++++++++++++++++++
 src/tests/test_task_scheduler.py      | 130 ++++++++++++++++++++++++++++++++
 src/tests/test_vyos_update_crontab.py | 130 --------------------------------
 6 files changed, 289 insertions(+), 138 deletions(-)
 create mode 100644 src/tests/test_host_name.py
 create mode 100644 src/tests/test_task_scheduler.py
 delete mode 100644 src/tests/test_vyos_update_crontab.py

diff --git a/Makefile b/Makefile
index e90e3b640..d1ac73c2f 100644
--- a/Makefile
+++ b/Makefile
@@ -38,7 +38,7 @@ clean:
 
 .PHONY: test
 test:
-	python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode --verbose
+	python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators --verbose
 
 .PHONY: sonar
 sonar:
diff --git a/sonar-project.properties b/sonar-project.properties
index 476285275..1258da817 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -3,7 +3,7 @@ sonar.projectName=vyos-1x
 sonar.projectVersion=1.2.0
 sonar.organization=vyos
 
-sonar.sources=src/conf_mode,src/op_mode
+sonar.sources=src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators
 sonar.language=py
 sonar.sourceEncoding=UTF-8
 
diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py
index 2a245b211..f6d091ac2 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -16,17 +16,26 @@
 #
 #
 
+"""
+conf-mode script for 'system host-name' and 'system domain-name'.
+"""
+
 import os
 import re
 import sys
 import subprocess
 
 from vyos.config import Config
-from vyos.util import ConfigError
+from vyos import ConfigError
+
 
+hosts_file = '/etc/hosts'
 hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$")
+local_addr = '127.0.1.1' # NOSONAR
+
 
 def get_config():
+    """Get configuration"""
     conf = Config()
 
     hostname = conf.return_value("system host-name")
@@ -44,9 +53,11 @@ def get_config():
 
     return {"hostname": hostname, "domain": domain, "fqdn": fqdn}
 
+
 def verify(config):
+    """Verify configuration"""
     # check for invalid host
-	
+
     # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)"
     if not hostname_regex.match(config["hostname"]):
         raise ConfigError('Invalid host name ' + config["hostname"])
@@ -54,29 +65,33 @@ def verify(config):
     # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length"
     length = len(config["hostname"])
     if length < 1 or length > 63:
-        raise ConfigError('Invalid host-name length, must be less than 63 characters')
+        raise ConfigError(
+            'Invalid host-name length, must be less than 63 characters')
 
     return None
 
 
 def generate(config):
+    """Generate configuration files"""
     # read the hosts file
-    with open('/etc/hosts', 'r') as f:
+    with open(hosts_file, 'r') as f:
         hosts = f.read()
 
     # get the current hostname
     old_hostname = subprocess.check_output(['hostname']).decode().strip()
 
     # replace the local host line
-    hosts = re.sub(r"(127.0.1.1\s+{0}.*)".format(old_hostname), r"127.0.1.1\t{0} # VyOS entry\n".format(config["fqdn"]), hosts)
+    hosts = re.sub(r"({}\s+{}.*)".format(local_addr, old_hostname),
+                   r"{}\t{} # VyOS entry\n".format(local_addr, config["fqdn"]), hosts)
 
-    with open('/etc/hosts', 'w') as f:
+    with open(hosts_file, 'w') as f:
         f.write(hosts)
 
     return None
 
 
 def apply(config):
+    """Apply configuration"""
     os.system("hostnamectl set-hostname {0}".format(config["fqdn"]))
 
     # restart services that use the hostname
diff --git a/src/tests/test_host_name.py b/src/tests/test_host_name.py
new file mode 100644
index 000000000..3df1f975c
--- /dev/null
+++ b/src/tests/test_host_name.py
@@ -0,0 +1,136 @@
+#!/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 os
+import tempfile
+import unittest
+from unittest import TestCase, mock
+
+from vyos import ConfigError
+try:
+    from src.conf_mode import host_name
+except ModuleNotFoundError:  # for unittest.main()
+    import sys
+    sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
+    from src.conf_mode import host_name
+
+
+class TestHostName(TestCase):
+
+    def test_get_config(self):
+        tests = [
+            {'name': 'empty_hostname_and_domain',
+             'host-name': '',
+             'domain-name': '',
+             'expected': {"hostname": 'vyos', "domain": '', "fqdn": 'vyos'}},
+            {'name': 'empty_hostname',
+             'host-name': '',
+             'domain-name': 'localdomain',
+             'expected': {"hostname": 'vyos', "domain": 'localdomain', "fqdn": 'vyos.localdomain'}},
+            {'name': 'has_hostname',
+             'host-name': 'router',
+             'domain-name': '',
+             'expected': {"hostname": 'router', "domain": '', "fqdn": 'router'}},
+            {'name': 'has_hostname_and_domain',
+             'host-name': 'router',
+             'domain-name': 'localdomain',
+             'expected': {"hostname": 'router', "domain": 'localdomain', "fqdn": 'router.localdomain'}},
+        ]
+        for t in tests:
+            def mocked_return_value(path, default=None):
+                return t[path.split()[1]]
+
+            with self.subTest(msg=t['name'], hostname=t['host-name'], domain=t['domain-name'], expected=t['expected']):
+                with mock.patch('vyos.config.Config.return_value', side_effect=mocked_return_value):
+                    actual = host_name.get_config()
+                    self.assertEqual(actual, t['expected'])
+
+
+    def test_verify(self):
+        tests = [
+            {'name': 'valid_hostname',
+             'config': {"hostname": 'vyos', "domain": 'localdomain', "fqdn": 'vyos.localdomain'},
+             'expected': None},
+            {'name': 'invalid_hostname',
+             'config': {"hostname": 'vyos..', "domain": '', "fqdn": ''},
+             'expected': ConfigError},
+            {'name': 'invalid_hostname_length',
+             'config': {"hostname": 'a'*64, "domain": '', "fqdn": ''},
+             'expected': ConfigError}
+        ]
+        for t in tests:
+            with self.subTest(msg=t['name'], config=t['config'], expected=t['expected']):
+                if t['expected'] is not None:
+                    with self.assertRaises(t['expected']):
+                        host_name.verify(t['config'])
+                else:
+                    host_name.verify(t['config'])
+
+    def test_generate(self):
+        tests = [
+            {'name': 'valid_hostname',
+             'config': {"hostname": 'router', "domain": 'localdomain', "fqdn": 'vyos.localdomain'},
+             'expected': ['127.0.0.1       localhost debian',
+                          '::1             localhost ip6-localhost ip6-loopback',
+                          'fe00::0         ip6-localnet',
+                          'ff00::0         ip6-mcastprefix',
+                          'ff02::1         ip6-allnodes',
+                          'ff02::2         ip6-allrouters',
+                          '127.0.1.1	  vyos	 #vyatta entry']}
+        ]
+        for t in tests:
+            with self.subTest(msg=t['name'], tasks=t['config'], expected=t['expected']):
+                m = mock.MagicMock(return_value=b'localhost')
+                with mock.patch('subprocess.check_output', m):
+                    host_name.hosts_file = tempfile.mkstemp()[1]
+                    with open(host_name.hosts_file, 'w') as f:
+                        f.writelines(['127.0.0.1       localhost debian\n',
+                                      '::1             localhost ip6-localhost ip6-loopback\n',
+                                      'fe00::0         ip6-localnet\n',
+                                      'ff00::0         ip6-mcastprefix\n',
+                                      'ff02::1         ip6-allnodes\n',
+                                      'ff02::2         ip6-allrouters\n',
+                                      '127.0.1.1	  vyos	 #vyatta entry'])
+                    host_name.generate(t['config'])
+                    if len(t['expected']) > 0:
+                        self.assertTrue(os.path.isfile(host_name.hosts_file))
+                        with open(host_name.hosts_file) as f:
+                            actual = f.read()
+                            self.assertEqual(
+                                t['expected'], actual.splitlines())
+                        os.remove(host_name.hosts_file)
+                    else:
+                        self.assertFalse(os.path.isfile(host_name.hosts_file))
+
+
+    def test_apply(self):
+        tests = [
+            {'name': 'valid_hostname',
+             'config': {"hostname": 'router', "domain": 'localdomain', "fqdn": 'vyos.localdomain'},
+             'expected': [mock.call('hostnamectl set-hostname vyos.localdomain'),
+                          mock.call('systemctl restart rsyslog.service')]}
+        ]
+        for t in tests:
+            with self.subTest(msg=t['name'], c=t['config'], expected=t['expected']):
+                with mock.patch('os.system') as os_system:
+                    host_name.apply(t['config'])
+                    os_system.assert_has_calls(t['expected'])
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/src/tests/test_task_scheduler.py b/src/tests/test_task_scheduler.py
new file mode 100644
index 000000000..7acbbddc5
--- /dev/null
+++ b/src/tests/test_task_scheduler.py
@@ -0,0 +1,130 @@
+#!/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 os
+import tempfile
+import unittest
+
+from vyos import ConfigError
+try:
+    from src.conf_mode import task_scheduler
+except ModuleNotFoundError:  # for unittest.main()
+    import sys
+    sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
+    from src.conf_mode import 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 /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 /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 /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 /bin/ls -l',
+                 '0 0 * * * root /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_vyos_update_crontab.py b/src/tests/test_vyos_update_crontab.py
deleted file mode 100644
index 7acbbddc5..000000000
--- a/src/tests/test_vyos_update_crontab.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/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 os
-import tempfile
-import unittest
-
-from vyos import ConfigError
-try:
-    from src.conf_mode import task_scheduler
-except ModuleNotFoundError:  # for unittest.main()
-    import sys
-    sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
-    from src.conf_mode import 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 /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 /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 /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 /bin/ls -l',
-                 '0 0 * * * root /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()
-- 
cgit v1.2.3