diff --git a/tests/test_cli/test_assign_task.py b/tests/test_cli/test_assign_task.py index 45c0966d..4a039c3b 100644 --- a/tests/test_cli/test_assign_task.py +++ b/tests/test_cli/test_assign_task.py @@ -22,7 +22,6 @@ class TestAssignTask(unittest.TestCase): task_id = "1" arguments = [task_id, hostname] options = mock.MagicMock() - progname = os.path.basename(sys.argv[0]) or 'koji' # Mock out the xmlrpc server session = mock.MagicMock() diff --git a/tests/test_cli/test_call.py b/tests/test_cli/test_call.py new file mode 100644 index 00000000..79e2b265 --- /dev/null +++ b/tests/test_cli/test_call.py @@ -0,0 +1,176 @@ +from __future__ import absolute_import +import mock +import os +import six +import sys +import unittest +import json + +from koji_cli.commands import handle_call + + +class TestResubmit(unittest.TestCase): + + # Show long diffs in error output... + maxDiff = None + + def setUp(self): + self.progname = os.path.basename(sys.argv[0]) or 'koji' + + def format_error_message(self, message): + return """Usage: %s call [options] name [arg...] +(Specify the --help global option for a list of other help options) + +%s: error: %s +""" % (self.progname, self.progname, message) + + def assert_console_output(self, device, expected, wipe=True, regex=False): + if not isinstance(device, six.StringIO): + raise TypeError('Not a StringIO object') + + output = device.getvalue() + if not regex: + self.assertMultiLineEqual(output, expected) + else: + six.assertRegex(self, output, expected) + if wipe: + device.truncate(0) + device.seek(0) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_call(self, activate_session_mock, stdout): + """Test handle_call function""" + arguments = ['ssl_login', 'cert=/etc/pki/cert', 'debug'] + response = "SUCCESS" + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + session.ssl_login.return_value = response + + handle_call(options, session, arguments) + activate_session_mock.assert_called_with(session, options) + session.ssl_login.assert_called_with('debug', cert='/etc/pki/cert') + self.assert_console_output(stdout, "'%s'\n" % response) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_call_python_syntax(self, activate_session_mock, stdout): + """Test handle_call with python syntax""" + arguments = [] + response = ["SUCCESS", "FAKE-RESPONSE"] + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + session.ssl_login.return_value = response[1] + + # Invalid python syntax + arguments = ['ssl_login', 'cert=/etc/pki/cert', '--python'] + with self.assertRaises(SyntaxError, msg='invalid syntax'): + handle_call(options, session, arguments) + + arguments = ['ssl_login', '--kwargs', '{"cert":"/etc/pki/cert"}'] + handle_call(options, session, arguments) + activate_session_mock.assert_called_with(session, options) + session.ssl_login.assert_called_with(cert='/etc/pki/cert') + self.assert_console_output(stdout, "'%s'\n" % response[1]) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_call_json_output(self, activate_session_mock, stdout): + """Test handle_call with json output""" + arguments = ['ssl_login', 'cert=/etc/pki/cert', '--json-output'] + options = mock.MagicMock() + + response = { + 'method': 'ssl_login', + 'parameters': { + 'cert': '/etc/pki/cert', + 'ca': ['/etc/pki/clientca', '/etc/pki/serverca'], + }, + 'result': 'success' + } + + # Mock out the xmlrpc server + session = mock.MagicMock() + session.ssl_login.return_value = response + + handle_call(options, session, arguments) + activate_session_mock.assert_called_with(session, options) + session.ssl_login.assert_called_with(cert='/etc/pki/cert') + + expect = json.dumps(response, indent=2, separators=(',', ': ')) + self.assert_console_output(stdout, '%s\n' % expect) + + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_call_errors(self, activate_session_mock, stderr): + """Test handle_call error messages""" + arguments = [] + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + + # Run it and check immediate output + # argument is empty + with self.assertRaises(SystemExit) as cm: + handle_call(options, session, arguments) + expected = self.format_error_message( + "Please specify the name of the XML-RPC method") + self.assert_console_output(stderr, expected) + activate_session_mock.assert_not_called() + self.assertEqual(cm.exception.code, 2) + + arguments = ['ssl_login', '--python', '--json-output'] + + module = { + 'ast': "The ast module is required to read python syntax", + 'json': "The json module is required to output JSON syntax", + } + + for mod, msg in module.items(): + with mock.patch('koji_cli.commands.%s' % mod, new=None), \ + self.assertRaises(SystemExit) as cm: + handle_call(options, session, arguments) + expected = self.format_error_message(msg) + self.assert_console_output(stderr, expected) + activate_session_mock.assert_not_called() + self.assertEqual(cm.exception.code, 2) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_call_help( + self, activate_session_mock, stderr, stdout): + """Test handle_call help message full output""" + arguments = ['--help'] + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + + # Run it and check immediate output + with self.assertRaises(SystemExit) as cm: + handle_call(options, session, arguments) + expected = """Usage: %s call [options] name [arg...] +(Specify the --help global option for a list of other help options) + +Options: + -h, --help show this help message and exit + --python Use python syntax for values + --kwargs=KWARGS Specify keyword arguments as a dictionary (implies + --python) + --json-output Use JSON syntax for output +""" % (self.progname) + self.assert_console_output(stdout, expected) + self.assert_console_output(stderr, '') + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_not_called() + self.assertEqual(cm.exception.code, 0) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cli/test_hello.py b/tests/test_cli/test_hello.py new file mode 100644 index 00000000..ea7d9b4b --- /dev/null +++ b/tests/test_cli/test_hello.py @@ -0,0 +1,119 @@ +from __future__ import absolute_import +import mock +import os +import six +import sys +import unittest +import koji + +from koji_cli.commands import handle_moshimoshi + + +class TestResubmit(unittest.TestCase): + + # Show long diffs in error output... + maxDiff = None + + def setUp(self): + self.progname = os.path.basename(sys.argv[0]) or 'koji' + self.huburl = "https://%s.local/%shub" % (self.progname, self.progname) + + def assert_console_output(self, device, expected, wipe=True, regex=False): + if not isinstance(device, six.StringIO): + raise TypeError('Not a StringIO object') + + output = device.getvalue() + if not regex: + self.assertMultiLineEqual(output, expected) + else: + six.assertRegex(self, output, expected) + if wipe: + device.truncate(0) + device.seek(0) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands._printable_unicode') + @mock.patch('koji_cli.commands.activate_session') + def test_handle_moshimoshi( + self, + activate_session_mock, + print_unicode_mock, + stderr, + stdout): + """Test handle_moshimoshi function""" + user = {'name': self.progname, + 'krb_principal': '%s@localhost' % self.progname} + cert = '/etc/pki/user.cert' + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock(baseurl=self.huburl, authtype=None) + session.getLoggedInUser.return_value = None + print_unicode_mock.return_value = "Hello" + + with self.assertRaises(SystemExit) as cm: + handle_moshimoshi(options, session, ['argument']) + expect = """Usage: %s moshimoshi [options] + +%s: error: This command takes no arguments +""" % (self.progname, self.progname) + self.assert_console_output(stderr, expect) + self.assertEqual(cm.exception.code, 2) + + auth_tests = { + koji.AUTHTYPE_NORMAL: 'Authenticated via password', + koji.AUTHTYPE_GSSAPI: 'Authenticated via GSSAPI', + koji.AUTHTYPE_KERB: 'Authenticated via Kerberos principal %s' % + user['krb_principal'], + koji.AUTHTYPE_SSL: 'Authenticated via client certificate %s' % + cert + } + + message = "Not authenticated\n" + "Hello, anonymous user!" + hubinfo = "You are using the hub at %s" % self.huburl + handle_moshimoshi(options, session, []) + self.assert_console_output( + stdout, "{0}\n\n{1}\n".format(message, hubinfo)) + + session.getLoggedInUser.return_value = user + message = "Hello, %s!" % self.progname + options.cert = cert + for authtype, authinfo in auth_tests.items(): + session.authtype = authtype + print_unicode_mock.reset_mock() + print_unicode_mock.return_value = "Hello" + handle_moshimoshi(options, session, []) + print_unicode_mock.assert_called_once() + self.assert_console_output( + stdout, "{0}\n\n{1}\n{2}\n".format(message, hubinfo, authinfo)) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_moshimoshi_help( + self, activate_session_mock, stderr, stdout): + """Test handle_moshimoshi help message full output""" + arguments = ['--help'] + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + + # Run it and check immediate output + with self.assertRaises(SystemExit) as cm: + handle_moshimoshi(options, session, arguments) + expected_stdout = """Usage: %s moshimoshi [options] + +Options: + -h, --help show this help message and exit +""" % (self.progname) + self.assert_console_output(stdout, expected_stdout) + self.assert_console_output(stderr, '') + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_not_called() + self.assertEqual(cm.exception.code, 0) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cli/test_maven_chain.py b/tests/test_cli/test_maven_chain.py new file mode 100644 index 00000000..adeb6899 --- /dev/null +++ b/tests/test_cli/test_maven_chain.py @@ -0,0 +1,211 @@ +from __future__ import absolute_import +import mock +import os +import six +import sys +import unittest + +from koji_cli.commands import handle_maven_chain + + +class TestMavenChain(unittest.TestCase): + + # Show long diffs in error output... + maxDiff = None + + def setUp(self): + self.progname = os.path.basename(sys.argv[0]) or 'koji' + self.target = 'target' + self.config = 'config' + self.task_id = 101 + + def format_error_message(self, message): + return """Usage: %s maven-chain [options] target config... +(Specify the --help global option for a list of other help options) + +%s: error: %s +""" % (self.progname, self.progname, message) + + def assert_console_output(self, device, expected, wipe=True, regex=False): + if not isinstance(device, six.StringIO): + raise TypeError('Not a StringIO object') + + output = device.getvalue() + if not regex: + self.assertMultiLineEqual(output, expected) + else: + six.assertRegex(self, output, expected) + if wipe: + device.truncate(0) + device.seek(0) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji.util.parse_maven_chain') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks') + @mock.patch('koji_cli.commands.activate_session') + def test_handle_maven_chain( + self, + activate_session_mock, + watch_tasks_mock, + running_in_bg_mock, + parse_maven_chain_mock, + stderr, + stdout): + """Test handle_maven_chain function""" + arguments = [self.target, self.config] + options = mock.MagicMock(weburl='weburl') + + # Mock out the xmlrpc server + session = mock.MagicMock() + session.logout.return_value = None + session.getBuildTarget.return_value = None + session.getTag.return_value = None + session.chainMaven.return_value = self.task_id + + target_info = { + 'dest_tag_name': 'dest_tag_name', + 'dest_tag': 'dest_tag' + } + + tag_info = { + 'name': 'dest_tag', + 'locked': True + } + + # Unknonw target test + with self.assertRaises(SystemExit) as cm: + handle_maven_chain(options, session, arguments) + expected = self.format_error_message("Unknown build target: %s" % + self.target) + self.assert_console_output(stderr, expected) + self.assertEqual(cm.exception.code, 2) + + # Unknow destination tag test + session.getBuildTarget.return_value = target_info + with self.assertRaises(SystemExit) as cm: + handle_maven_chain(options, session, arguments) + expected = self.format_error_message("Unknown destination tag: %s" % + target_info['dest_tag_name']) + self.assert_console_output(stderr, expected) + self.assertEqual(cm.exception.code, 2) + + # Distination is locked and --scratch is not specified + session.getTag.return_value = tag_info + with self.assertRaises(SystemExit) as cm: + handle_maven_chain(options, session, arguments) + expected = self.format_error_message("Destination tag %s is locked" % + tag_info['name']) + self.assert_console_output(stderr, expected) + self.assertEqual(cm.exception.code, 2) + + # Test ValueError exception asserted in parse_maven_chain + arguments.extend(['--skip-tag', '--scratch', + '--force', '--background']) + parse_maven_chain_mock.side_effect = ValueError('fake-value-error') + with self.assertRaises(SystemExit) as cm: + handle_maven_chain(options, session, arguments) + expected = self.format_error_message("fake-value-error") + self.assert_console_output(stderr, expected) + self.assertEqual(cm.exception.code, 2) + + # Background or --nowait is true + parse_maven_chain_mock.side_effect = None + parse_maven_chain_mock.return_value = 'build' + handle_maven_chain(options, session, arguments + ['--nowait']) + expected = "Created task: %d\n" % self.task_id + expected += "Task info: %s/taskinfo?taskID=%s\n" % \ + (options.weburl, self.task_id) + self.assert_console_output(stdout, expected) + + # reset mocks to run full test + activate_session_mock.reset_mock() + parse_maven_chain_mock.reset_mock() + running_in_bg_mock.reset_mock() + watch_tasks_mock.reset_mock() + + # Foreground/wait for task test + watch_tasks_mock.return_value = True + self.assertTrue(handle_maven_chain(options, session, arguments)) + expected = "Created task: %d\n" % self.task_id + expected += "Task info: %s/taskinfo?taskID=%s\n" % \ + (options.weburl, self.task_id) + self.assert_console_output(stdout, expected) + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_called_with(session, options) + parse_maven_chain_mock.assert_called_with([self.config], scratch=True) + session.chainMaven.assert_called_with('build', self.target, + {'skip_tag': True, + 'scratch': True, + 'force': True}, + priority=5) + running_in_bg_mock.assert_called() + watch_tasks_mock.assert_called_with( + session, [self.task_id], quiet=options.quiet, + poll_interval=options.poll_interval) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_maven_chain_help_compat( + self, + activate_session_mock, + stderr, + stdout): + """Test handle_maven_chain help message compact output""" + arguments = [] + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + + # Run it and check immediate output + with self.assertRaises(SystemExit) as cm: + handle_maven_chain(options, session, arguments) + expected_stderr = self.format_error_message( + "Two arguments (a build target and a config file) are required") + self.assert_console_output(stdout, '') + self.assert_console_output(stderr, expected_stderr) + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_not_called() + self.assertEqual(cm.exception.code, 2) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_maven_chain_help_full( + self, activate_session_mock, stderr, stdout): + """Test handle_maven_chain help message full output""" + arguments = ['--help'] + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + + # Run it and check immediate output + with self.assertRaises(SystemExit) as cm: + handle_maven_chain(options, session, arguments) + expected_stdout = """Usage: %s maven-chain [options] target config... +(Specify the --help global option for a list of other help options) + +Options: + -h, --help show this help message and exit + --skip-tag Do not attempt to tag builds + --scratch Perform scratch builds + --debug Run Maven build in debug mode + --force Force rebuilds of all packages + --nowait Don't wait on build + --background Run the build at a lower priority +""" % (self.progname) + self.assert_console_output(stdout, expected_stdout) + self.assert_console_output(stderr, '') + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_not_called() + self.assertEqual(cm.exception.code, 0) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cli/test_resubmit.py b/tests/test_cli/test_resubmit.py new file mode 100644 index 00000000..fd464bb1 --- /dev/null +++ b/tests/test_cli/test_resubmit.py @@ -0,0 +1,163 @@ +from __future__ import absolute_import +import mock +import os +import six +import sys +import unittest + +from koji_cli.commands import handle_resubmit + + +class TestResubmit(unittest.TestCase): + + # Show long diffs in error output... + maxDiff = None + + def setUp(self): + self.progname = os.path.basename(sys.argv[0]) or 'koji' + self.task_id = 101 + self.taskinfo = """Task: 101 +Type: createrepo +Owner: kojiadmin +State: closed +Created: Thu Oct 12 20:29:29 2017 +Started: Thu Oct 12 20:29:44 2017 +Finished: Thu Oct 12 20:33:11 2017 +Host: kojibuilder +Log Files: + /mnt/koji/work/tasks/2/2/createrepo.log + /mnt/koji/work/tasks/2/2/mergerepos.log +""" + + def format_error_message(self, message): + return """Usage: %s resubmit [options] taskID +(Specify the --help global option for a list of other help options) + +%s: error: %s +""" % (self.progname, self.progname, message) + + def assert_console_output(self, device, expected, wipe=True, regex=False): + if not isinstance(device, six.StringIO): + raise TypeError('Not a StringIO object') + + output = device.getvalue() + if not regex: + self.assertMultiLineEqual(output, expected) + else: + six.assertRegex(self, output, expected) + if wipe: + device.truncate(0) + device.seek(0) + + def print_taskinfo(self, *args, **kwargs): + print(self.taskinfo) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.watch_tasks') + @mock.patch('koji_cli.commands.activate_session') + def test_handle_resubmit( + self, + activate_session_mock, + watch_tasks_mock, + stderr, + stdout): + """Test handle_maven_chain function""" + arguments = [str(self.task_id)] + options = mock.MagicMock(quiet=False) + new_task_id = self.task_id + 100 + + # Mock out the xmlrpc server + session = mock.MagicMock() + session.logout.return_value = None + session.resubmitTask.return_value = new_task_id + session.getTaskInfo.return_value = None + + # Generate task info and nowait tests + with mock.patch('koji_cli.commands._printTaskInfo') as p_mock: + p_mock.side_effect = self.print_taskinfo + handle_resubmit(options, session, arguments + ['--nowait']) + activate_session_mock.assert_called_with(session, options) + expected = "Resubmitting the following task:" + "\n" + expected += self.taskinfo + "\n" + expected += "Resubmitted task %s as new task %s" % \ + (self.task_id, new_task_id) + "\n" + self.assert_console_output(stdout, expected) + + session.logout.reset_mock() + session.resubmitTask.reset_mock() + session.resubmitTask.return_value = new_task_id + + # Quiet and watch tasks tests + arguments.append('--quiet') + with mock.patch('koji_cli.commands._running_in_bg') as run_bg_mock: + run_bg_mock.return_value = False + self.assertTrue(handle_resubmit(options, session, arguments)) + run_bg_mock.assert_called_once() + watch_tasks_mock.assert_called_with( + session, + [new_task_id], + quiet=True, + poll_interval=options.poll_interval) + session.logout.assert_called_once() + session.resubmitTask.assert_called_with(self.task_id) + session.resubmitTask.assert_called_once() + self.assert_console_output(stdout, '') + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_resubmit_help_compat( + self, activate_session_mock, stderr, stdout): + """Test handle_resubmit help message compact output""" + arguments = [] + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + + # Run it and check immediate output + with self.assertRaises(SystemExit) as cm: + handle_resubmit(options, session, arguments) + expected_stderr = self.format_error_message( + "Please specify a single task ID") + self.assert_console_output(stdout, '') + self.assert_console_output(stderr, expected_stderr) + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_not_called() + self.assertEqual(cm.exception.code, 2) + + @mock.patch('sys.stdout', new_callable=six.StringIO) + @mock.patch('sys.stderr', new_callable=six.StringIO) + @mock.patch('koji_cli.commands.activate_session') + def test_handle_resubmit_help_full( + self, activate_session_mock, stderr, stdout): + """Test handle_resubmit help message full output""" + arguments = ['--help'] + options = mock.MagicMock() + + # Mock out the xmlrpc server + session = mock.MagicMock() + + # Run it and check immediate output + with self.assertRaises(SystemExit) as cm: + handle_resubmit(options, session, arguments) + expected_stdout = """Usage: %s resubmit [options] taskID +(Specify the --help global option for a list of other help options) + +Options: + -h, --help show this help message and exit + --nowait Don't wait on task + --nowatch An alias for --nowait + --quiet Do not print the task information +""" % (self.progname) + self.assert_console_output(stdout, expected_stdout) + self.assert_console_output(stderr, '') + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_not_called() + self.assertEqual(cm.exception.code, 0) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cli/test_wrapper_rpm.py b/tests/test_cli/test_wrapper_rpm.py index 5aca281e..6a5ad6dc 100644 --- a/tests/test_cli/test_wrapper_rpm.py +++ b/tests/test_cli/test_wrapper_rpm.py @@ -4,7 +4,6 @@ import os import six import sys import unittest -import koji from koji_cli.commands import handle_wrapper_rpm