From 8179378a8905194090ddf07e7fb40269737736a1 Mon Sep 17 00:00:00 2001
From: Jana Cupova <root@localhost>
Date: Oct 18 2021 07:39:30 +0000
Subject: Add CLI unit tests


---

diff --git a/tests/test_cli/test_add_host_to_channel.py b/tests/test_cli/test_add_host_to_channel.py
index e3298bc..0ca6681 100644
--- a/tests/test_cli/test_add_host_to_channel.py
+++ b/tests/test_cli/test_add_host_to_channel.py
@@ -74,13 +74,14 @@ class TestAddHostToChannel(utils.CliTestCase):
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
     @mock.patch('koji_cli.commands.activate_session')
-    def test_handle_add_host_to_channel_new(
+    def test_handle_add_host_to_channel_new_and_force(
             self, activate_session_mock, stdout):
         host = 'host'
         host_info = mock.ANY
         channel = 'channel'
         new_arg = '--new'
-        args = [host, channel, new_arg]
+        force_arg = '--force'
+        args = [host, channel, new_arg, force_arg]
         options = mock.MagicMock()
 
         # Mock out the xmlrpc server
@@ -98,7 +99,7 @@ class TestAddHostToChannel(utils.CliTestCase):
         activate_session_mock.assert_called_once_with(session, options)
         session.getChannel.assert_not_called()
         session.getHost.assert_called_once_with(host)
-        session.addHostToChannel.assert_called_once_with(host, channel, create=True)
+        session.addHostToChannel.assert_called_once_with(host, channel, create=True, force=True)
         self.assertNotEqual(rv, 1)
 
     @mock.patch('sys.stderr', new_callable=six.StringIO)
diff --git a/tests/test_cli/test_add_target.py b/tests/test_cli/test_add_target.py
index cc0ff17..18a2858 100644
--- a/tests/test_cli/test_add_target.py
+++ b/tests/test_cli/test_add_target.py
@@ -127,6 +127,25 @@ class TestAddTarget(utils.CliTestCase):
         self.session.createBuildTarget.assert_called_once_with(target, tag, target)
         self.session.getTag.assert_called_with(target)
 
+    @mock.patch('sys.stderr', new_callable=StringIO)
+    def test_add_target_without_perms(self, stderr):
+        side_effect_result = [False, False]
+
+        target = 'test-target'
+        tag = 'test-tag'
+        self.session.hasPerm.side_effect = side_effect_result
+        with self.assertRaises(SystemExit) as ex:
+            handle_add_target(self.options, self.session, [target, tag])
+        self.assertExitCode(ex, 2)
+        expected_msg = """Usage: %s add-target <name> <build tag> <dest tag>
+(Specify the --help global option for a list of other help options)
+
+%s: error: This action requires target or admin privileges
+""" % (self.progname, self.progname)
+        self.assert_console_message(stderr, expected_msg)
+        self.session.createBuildTarget.assert_not_called()
+        self.session.getTag.assert_not_called()
+
     def test_add_target_help(self):
         self.assert_help(
             handle_add_target,
diff --git a/tests/test_cli/test_build.py b/tests/test_cli/test_build.py
index fb0b911..076d2c0 100644
--- a/tests/test_cli/test_build.py
+++ b/tests/test_cli/test_build.py
@@ -1,12 +1,11 @@
 from __future__ import absolute_import
 import mock
-import os
 import six
-import sys
 
 from koji_cli.commands import handle_build, _progress_callback
 from . import utils
 
+
 class TestBuild(utils.CliTestCase):
     # Show long diffs in error output...
     maxDiff = None
@@ -17,8 +16,24 @@ class TestBuild(utils.CliTestCase):
         self.options.quiet = None
         self.options.weburl = 'weburl'
         self.options.poll_interval = 0
+        self.options.debug = False
         # Mock out the xmlrpc server
         self.session = mock.MagicMock()
+        self.target = 'target'
+        self.dest_tag = 'dest_tag'
+        self.target_info = {'dest_tag': self.dest_tag}
+        self.dest_tag_info = {'locked': False}
+        self.source_srpm = 'srpm'
+        self.source_scm = 'http://scm'
+        self.task_id = 1
+        self.priority = None
+        self.activate_session_mock = mock.patch('koji_cli.commands.activate_session').start()
+        self.unique_path_mock = mock.patch('koji_cli.commands.unique_path').start()
+        self.unique_path_mock.return_value = 'random_path'
+        self.running_in_bg_mock = mock.patch('koji_cli.commands._running_in_bg').start()
+        self.running_in_bg_mock.return_value = False
+        self.watch_tasks_mock = mock.patch('koji_cli.commands.watch_tasks').start()
+        self.watch_tasks_mock.return_value = 0
         self.error_format = """Usage: %s build [options] <target> <srpm path or scm url>
 
 The first option is the build target, not to be confused with the destination
@@ -34,30 +49,13 @@ https://docs.pagure.org/koji/HOWTO/#package-organization
 """ % (self.progname, self.progname, self.progname)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_from_srpm(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'srpm'
-        task_id = 1
-        args = [target, source]
+    def test_handle_build_from_srpm(self, stdout):
+        args = [self.target, self.source_srpm]
         opts = {'custom_user_metadata': {}, 'wait_builds': []}
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: target, srpm
         # expected: success
@@ -70,47 +68,30 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_called_once_with('cli-build')
-        self.assertEqual(running_in_bg_mock.call_count, 2)
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_called_once_with('cli-build')
+        self.assertEqual(self.running_in_bg_mock.call_count, 2)
         self.session.uploadWrapper.assert_called_once_with(
-            source, 'random_path', callback=_progress_callback)
+            self.source_srpm, 'random_path', callback=_progress_callback)
         self.session.build.assert_called_once_with(
-            'random_path/' + source, target, opts, priority=priority)
+            'random_path/' + self.source_srpm, self.target, opts, priority=self.priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=self.options.quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=self.options.quiet,
             poll_interval=self.options.poll_interval,
             topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_from_scm(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'http://scm'
-        task_id = 1
-        args = [target, source]
+    def test_handle_build_from_scm(self, stdout):
+        args = [self.target, self.source_scm]
         opts = {'custom_user_metadata': {}, 'wait_builds': []}
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: target, http://scm
         # expected: success
@@ -121,36 +102,24 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_called_once()
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_called_once_with(
-            source, target, opts, priority=priority)
+            self.source_scm, self.target, opts, priority=self.priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=self.options.quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=self.options.quiet,
             poll_interval=self.options.poll_interval, topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_no_arg(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stderr,
-            stdout):
+    def test_handle_build_no_arg(self, stderr, stdout):
         args = []
-        progname = os.path.basename(sys.argv[0]) or 'koji'
 
         # Run it and check immediate output
         with self.assertRaises(SystemExit) as ex:
@@ -159,37 +128,26 @@ Task info: weburl/taskinfo?taskID=1
         actual_stdout = stdout.getvalue()
         actual_stderr = stderr.getvalue()
         expected_stdout = ''
-        expected_stderr = self.format_error_message("Exactly two arguments (a build target and a SCM URL or srpm file) are required")
+        expected_stderr = self.format_error_message("Exactly two arguments (a build target and "
+                                                    "a SCM URL or srpm file) are required")
         self.assertMultiLineEqual(actual_stdout, expected_stdout)
         self.assertMultiLineEqual(actual_stderr, expected_stderr)
 
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_not_called()
+        self.activate_session_mock.assert_not_called()
         self.session.getBuildTarget.assert_not_called()
         self.session.getTag.assert_not_called()
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_not_called()
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_not_called()
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_help(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stderr,
-            stdout):
+    def test_handle_build_help(self, stderr, stdout):
         args = ['--help']
-        progname = os.path.basename(sys.argv[0]) or 'koji'
 
         # Run it and check immediate output
         with self.assertRaises(SystemExit) as ex:
@@ -230,47 +188,31 @@ Options:
                         Provide a JSON string of custom metadata to be
                         deserialized and stored under the build's
                         extra.custom_user_metadata field
-""" % (progname, progname)
+""" % (self.progname, self.progname)
         expected_stderr = ''
         self.assertMultiLineEqual(actual_stdout, expected_stdout)
         self.assertMultiLineEqual(actual_stderr, expected_stderr)
 
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_not_called()
+        self.activate_session_mock.assert_not_called()
         self.session.getBuildTarget.assert_not_called()
         self.session.getTag.assert_not_called()
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_not_called()
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_not_called()
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_custom_user_metadata(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'http://scm'
-        task_id = 1
-        args = ['--custom-user-metadata={"automation-triggered-by": "yoda"}', target, source]
+    def test_handle_build_custom_user_metadata(self, stdout):
+        args = ['--custom-user-metadata={"automation-triggered-by": "yoda"}', self.target,
+                self.source_scm]
         opts = {'custom_user_metadata': {'automation-triggered-by': 'yoda'}, 'wait_builds': []}
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: target, http://scm
         # expected: success
@@ -281,37 +223,25 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_called_once()
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_called_once_with(
-            source, target, opts, priority=priority)
+            self.source_scm, self.target, opts, priority=self.priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=self.options.quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=self.options.quiet,
             poll_interval=self.options.poll_interval, topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_custom_user_metadata_invalid_json(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stderr,
-            stdout):
-        target = 'target'
-        source = 'http://scm'
-        args = [target, source, '--custom-user-metadata={Do or do not. There is no try.}']
+    def test_handle_build_custom_user_metadata_invalid_json(self, stderr, stdout):
+        args = [self.target, self.source_scm,
+                '--custom-user-metadata={Do or do not. There is no try.}']
 
         # Run it and check immediate output
         with self.assertRaises(SystemExit) as ex:
@@ -325,33 +255,21 @@ Task info: weburl/taskinfo?taskID=1
         self.assertMultiLineEqual(actual_stderr, expected_stderr)
 
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_not_called()
+        self.activate_session_mock.assert_not_called()
         self.session.getBuildTarget.assert_not_called()
         self.session.getTag.assert_not_called()
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_not_called()
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_not_called()
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_custom_user_metadata_not_json_object(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stderr,
-            stdout):
-        target = 'target'
-        source = 'http://scm'
-        args = [target, source, '--custom-user-metadata="Do or do not. There is no try."']
+    def test_handle_build_custom_user_metadata_not_json_object(self, stderr, stdout):
+        args = [self.target, self.source_scm,
+                '--custom-user-metadata="Do or do not. There is no try."']
 
         # Run it and check immediate output
         with self.assertRaises(SystemExit) as ex:
@@ -365,35 +283,21 @@ Task info: weburl/taskinfo?taskID=1
         self.assertMultiLineEqual(actual_stderr, expected_stderr)
 
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_not_called()
+        self.activate_session_mock.assert_not_called()
         self.session.getBuildTarget.assert_not_called()
         self.session.getTag.assert_not_called()
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_not_called()
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_not_called()
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_arch_override_denied(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stderr,
-            stdout):
-        target = 'target'
-        source = 'http://scm'
+    def test_handle_build_arch_override_denied(self, stderr, stdout):
         arch_override = 'somearch'
-        args = [target, source, '--arch-override=' + arch_override]
-        progname = os.path.basename(sys.argv[0]) or 'koji'
+        args = [self.target, self.source_scm, '--arch-override=' + arch_override]
 
         # Run it and check immediate output
         with self.assertRaises(SystemExit) as ex:
@@ -402,44 +306,32 @@ Task info: weburl/taskinfo?taskID=1
         actual_stdout = stdout.getvalue()
         actual_stderr = stderr.getvalue()
         expected_stdout = ''
-        expected_stderr = self.format_error_message("--arch_override is only allowed for --scratch builds")
+        expected_stderr = self.format_error_message(
+            "--arch_override is only allowed for --scratch builds")
         self.assertMultiLineEqual(actual_stdout, expected_stdout)
         self.assertMultiLineEqual(actual_stderr, expected_stderr)
 
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_not_called()
+        self.activate_session_mock.assert_not_called()
         self.session.getBuildTarget.assert_not_called()
         self.session.getTag.assert_not_called()
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_not_called()
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_not_called()
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_none_tag(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
+    def test_handle_build_none_tag(self, stdout):
         target = 'nOne'
-        source = 'http://scm'
-        task_id = 1
         repo_id = 2
-        args = ['--repo-id=' + str(repo_id), target, source]
+        args = ['--repo-id=' + str(repo_id), target, self.source_scm]
         opts = {
             'repo_id': repo_id, 'skip_tag': True, 'wait_builds': [], 'custom_user_metadata': {}
         }
-        priority = None
 
-        self.session.build.return_value = task_id
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: --repo-id=2, nOne, http://scm
         # expected: success
@@ -450,39 +342,25 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
         self.session.getBuildTarget.assert_not_called()
         self.session.getTag.assert_not_called()
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_called_once()
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
         # target==None, repo_id==2, skip_tag==True
         self.session.build.assert_called_once_with(
-            source, None, opts, priority=priority)
+            self.source_scm, None, opts, priority=self.priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=self.options.quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=self.options.quiet,
             poll_interval=self.options.poll_interval, topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_target_not_found(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stderr):
-        target = 'target'
+    def test_handle_build_target_not_found(self, stderr):
         target_info = None
-        source = 'http://scm'
-        args = [target, source]
-
-        progname = os.path.basename(sys.argv[0]) or 'koji'
+        args = [self.target, self.source_scm]
 
         self.session.getBuildTarget.return_value = target_info
         # Run it and check immediate output
@@ -492,40 +370,25 @@ Task info: weburl/taskinfo?taskID=1
             handle_build(self.options, self.session, args)
         self.assertExitCode(ex, 2)
         actual = stderr.getvalue()
-        expected = self.format_error_message( "No such build target: target")
+        expected = self.format_error_message("No such build target: target")
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
         self.session.getTag.assert_not_called()
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_not_called()
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_not_called()
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_dest_tag_not_found(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stderr):
-        target = 'target'
-        dest_tag = 'dest_tag'
+    def test_handle_build_dest_tag_not_found(self, stderr):
         dest_tag_name = 'dest_tag_name'
-        target_info = {'dest_tag': dest_tag, 'dest_tag_name': dest_tag_name}
+        target_info = {'dest_tag': self.dest_tag, 'dest_tag_name': dest_tag_name}
         dest_tag_info = None
-        source = 'http://scm'
-        args = [target, source]
-
-        progname = os.path.basename(sys.argv[0]) or 'koji'
+        args = [self.target, self.source_scm]
 
         self.session.getBuildTarget.return_value = target_info
         self.session.getTag.return_value = dest_tag_info
@@ -539,37 +402,22 @@ Task info: weburl/taskinfo?taskID=1
         expected = self.format_error_message("No such destination tag: dest_tag_name")
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_not_called()
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_not_called()
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_dest_tag_locked(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stderr):
-        target = 'target'
-        dest_tag = 'dest_tag'
+    def test_handle_build_dest_tag_locked(self, stderr):
         dest_tag_name = 'dest_tag_name'
-        target_info = {'dest_tag': dest_tag, 'dest_tag_name': dest_tag_name}
+        target_info = {'dest_tag': self.dest_tag, 'dest_tag_name': dest_tag_name}
         dest_tag_info = {'name': 'dest_tag_name', 'locked': True}
-        source = 'http://scm'
-        args = [target, source]
-
-        progname = os.path.basename(sys.argv[0]) or 'koji'
+        args = [self.target, self.source_scm]
 
         self.session.getBuildTarget.return_value = target_info
         self.session.getTag.return_value = dest_tag_info
@@ -583,52 +431,30 @@ Task info: weburl/taskinfo?taskID=1
         expected = self.format_error_message("Destination tag dest_tag_name is locked")
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_not_called()
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_not_called()
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_arch_override(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'http://scm'
-        task_id = 1
+    def test_handle_build_arch_override(self, stdout):
         arch_override = 'somearch'
-        args = [
-            '--arch-override=' +
-            arch_override,
-            '--scratch',
-            target,
-            source]
+        args = ['--arch-override=' + arch_override, '--scratch', self.target, self.source_scm]
         opts = {
             'arch_override': arch_override,
             'custom_user_metadata': {},
             'scratch': True,
             'wait_builds': [],
         }
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: --arch-override=somearch, --scratch, target, http://scm
         # expected: success
@@ -639,46 +465,30 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_called_once()
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
         # arch-override=='somearch', scratch==True
         self.session.build.assert_called_once_with(
-            source, target, opts, priority=priority)
+            self.source_scm, self.target, opts, priority=self.priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=self.options.quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=self.options.quiet,
             poll_interval=self.options.poll_interval, topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_background(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'http://scm'
-        task_id = 1
-        args = ['--background', target, source]
+    def test_handle_build_background(self, stdout):
+        args = ['--background', self.target, self.source_scm]
         priority = 5
         opts = {'custom_user_metadata': {}, 'wait_builds': []}
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: --background, target, http://scm
         # expected: success
@@ -689,45 +499,29 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_not_called()
-        running_in_bg_mock.assert_called_once()
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_called_once()
         self.session.uploadWrapper.assert_not_called()
         self.session.build.assert_called_once_with(
-            source, target, opts, priority=priority)
+            self.source_scm, self.target, opts, priority=priority)
         self.session.logout.assert_called()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=self.options.quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=self.options.quiet,
             poll_interval=self.options.poll_interval, topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
     @mock.patch('koji_cli.commands._running_in_bg', return_value=True)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_running_in_bg(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'srpm'
-        task_id = 1
-        args = [target, source]
+    def test_handle_build_running_in_bg(self, running_in_bg_mock, stdout):
+        args = [self.target, self.source_srpm]
         opts = {'custom_user_metadata': {}, 'wait_builds': []}
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: target, srpm
         # expected: success
@@ -740,45 +534,28 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_called_once_with('cli-build')
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_called_once_with('cli-build')
         self.assertEqual(running_in_bg_mock.call_count, 2)
         # callback==None
         self.session.uploadWrapper.assert_called_once_with(
-            source, 'random_path', callback=None)
+            self.source_srpm, 'random_path', callback=None)
         self.session.build.assert_called_once_with(
-            'random_path/' + source, target, opts, priority=priority)
+            'random_path/' + self.source_srpm, self.target, opts, priority=self.priority)
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
         self.assertIsNone(rv)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_noprogress(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'srpm'
-        task_id = 1
-        args = ['--noprogress', target, source]
+    def test_handle_build_noprogress(self, stdout):
+        args = ['--noprogress', self.target, self.source_srpm]
         opts = {'custom_user_metadata': {}, 'wait_builds': []}
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: --noprogress, target, srpm
         # expected: success
@@ -791,48 +568,31 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_called_once_with('cli-build')
-        self.assertEqual(running_in_bg_mock.call_count, 2)
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_called_once_with('cli-build')
+        self.assertEqual(self.running_in_bg_mock.call_count, 2)
         # callback==None
         self.session.uploadWrapper.assert_called_once_with(
-            source, 'random_path', callback=None)
+            self.source_srpm, 'random_path', callback=None)
         self.session.build.assert_called_once_with(
-            'random_path/' + source, target, opts, priority=priority)
+            'random_path/' + self.source_srpm, self.target, opts, priority=self.priority)
         self.session.logout.assert_called_once()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=self.options.quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=self.options.quiet,
             poll_interval=self.options.poll_interval, topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_quiet(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'srpm'
-        task_id = 1
+    def test_handle_build_quiet(self, stdout):
         quiet = True
-        args = ['--quiet', target, source]
+        args = ['--quiet', self.target, self.source_srpm]
         opts = {'custom_user_metadata': {}, 'wait_builds': []}
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: --quiet, target, srpm
         # expected: success
@@ -841,48 +601,30 @@ Task info: weburl/taskinfo?taskID=1
         expected = '\n'
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_called_once_with('cli-build')
-        self.assertEqual(running_in_bg_mock.call_count, 2)
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_called_once_with('cli-build')
+        self.assertEqual(self.running_in_bg_mock.call_count, 2)
         # callback==None
         self.session.uploadWrapper.assert_called_once_with(
-            source, 'random_path', callback=None)
+            self.source_srpm, 'random_path', callback=None)
         self.session.build.assert_called_once_with(
-            'random_path/' + source, target, opts, priority=priority)
+            'random_path/' + self.source_srpm, self.target, opts, priority=self.priority)
         self.session.logout.assert_called_once()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=quiet,
             poll_interval=self.options.poll_interval, topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_wait(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'srpm'
-        task_id = 1
-        quiet = None
-        args = ['--wait', target, source]
+    def test_handle_build_wait(self, stdout):
+        args = ['--wait', self.target, self.source_srpm]
         opts = {'custom_user_metadata': {}, 'wait_builds': []}
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: --wait, target, srpm
         # expected: success
@@ -895,47 +637,30 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_called_once_with('cli-build')
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_called_once_with('cli-build')
         # the second one won't be executed when wait==False
-        self.assertEqual(running_in_bg_mock.call_count, 1)
+        self.assertEqual(self.running_in_bg_mock.call_count, 1)
         self.session.uploadWrapper.assert_called_once_with(
-            source, 'random_path', callback=_progress_callback)
+            self.source_srpm, 'random_path', callback=_progress_callback)
         self.session.build.assert_called_once_with(
-            'random_path/' + source, target, opts, priority=priority)
+            'random_path/' + self.source_srpm, self.target, opts, priority=self.priority)
         self.session.logout.assert_called_once()
-        watch_tasks_mock.assert_called_once_with(
-            self.session, [task_id], quiet=self.options.quiet,
+        self.watch_tasks_mock.assert_called_once_with(
+            self.session, [self.task_id], quiet=self.options.quiet,
             poll_interval=self.options.poll_interval, topurl=self.options.topurl)
         self.assertEqual(rv, 0)
 
     @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.activate_session')
-    @mock.patch('koji_cli.commands.unique_path', return_value='random_path')
-    @mock.patch('koji_cli.commands._running_in_bg', return_value=False)
-    @mock.patch('koji_cli.commands.watch_tasks', return_value=0)
-    def test_handle_build_nowait(
-            self,
-            watch_tasks_mock,
-            running_in_bg_mock,
-            unique_path_mock,
-            activate_session_mock,
-            stdout):
-        target = 'target'
-        dest_tag = 'dest_tag'
-        target_info = {'dest_tag': dest_tag}
-        dest_tag_info = {'locked': False}
-        source = 'srpm'
-        task_id = 1
-        args = ['--nowait', target, source]
+    def test_handle_build_nowait(self, stdout):
+        args = ['--nowait', self.target, self.source_srpm]
         opts = {'custom_user_metadata': {}, 'wait_builds': []}
-        priority = None
 
-        self.session.getBuildTarget.return_value = target_info
-        self.session.getTag.return_value = dest_tag_info
-        self.session.build.return_value = task_id
+        self.session.getBuildTarget.return_value = self.target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        self.session.build.return_value = self.task_id
         # Run it and check immediate output
         # args: --nowait, target, srpm
         # expected: success
@@ -948,16 +673,44 @@ Task info: weburl/taskinfo?taskID=1
 """
         self.assertMultiLineEqual(actual, expected)
         # Finally, assert that things were called as we expected.
-        activate_session_mock.assert_called_once_with(self.session, self.options)
-        self.session.getBuildTarget.assert_called_once_with(target)
-        self.session.getTag.assert_called_once_with(dest_tag)
-        unique_path_mock.assert_called_once_with('cli-build')
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.getBuildTarget.assert_called_once_with(self.target)
+        self.session.getTag.assert_called_once_with(self.dest_tag)
+        self.unique_path_mock.assert_called_once_with('cli-build')
         # the second one won't be executed when wait==False
-        self.assertEqual(running_in_bg_mock.call_count, 1)
+        self.assertEqual(self.running_in_bg_mock.call_count, 1)
         self.session.uploadWrapper.assert_called_once_with(
-            source, 'random_path', callback=_progress_callback)
+            self.source_srpm, 'random_path', callback=_progress_callback)
         self.session.build.assert_called_once_with(
-            'random_path/' + source, target, opts, priority=priority)
+            'random_path/' + self.source_srpm, self.target, opts, priority=self.priority)
         self.session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
         self.assertIsNone(rv)
+
+    @mock.patch('sys.stdout', new_callable=six.StringIO)
+    @mock.patch('sys.stderr', new_callable=six.StringIO)
+    def test_handle_build_rebuild_srpm_without_scratch(self, stderr, stdout):
+        args = ['--rebuild-srpm', self.target, self.source_srpm]
+
+        # Run it and check immediate output
+        with self.assertRaises(SystemExit) as ex:
+            handle_build(self.options, self.session, args)
+        self.assertExitCode(ex, 2)
+        actual_stdout = stdout.getvalue()
+        actual_stderr = stderr.getvalue()
+        expected_stdout = ''
+        expected_stderr = self.format_error_message(
+            "--no-/rebuild-srpm is only allowed for --scratch builds")
+        self.assertMultiLineEqual(actual_stdout, expected_stdout)
+        self.assertMultiLineEqual(actual_stderr, expected_stderr)
+
+        # Finally, assert that things were called as we expected.
+        self.activate_session_mock.assert_not_called()
+        self.session.getBuildTarget.assert_not_called()
+        self.session.getTag.assert_not_called()
+        self.unique_path_mock.assert_not_called()
+        self.running_in_bg_mock.assert_not_called()
+        self.session.uploadWrapper.assert_not_called()
+        self.session.build.assert_not_called()
+        self.session.logout.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
diff --git a/tests/test_cli/test_edit_channel.py b/tests/test_cli/test_edit_channel.py
index 58f37cb..017440f 100644
--- a/tests/test_cli/test_edit_channel.py
+++ b/tests/test_cli/test_edit_channel.py
@@ -19,6 +19,11 @@ class TestEditChannel(utils.CliTestCase):
         self.channel_old = 'test-channel'
         self.channel_new = 'test-channel-new'
         self.description = 'description'
+        self.channel_info = {
+            'id': 123,
+            'name': self.channel_old,
+            'description': self.description,
+        }
         self.maxDiff = None
 
     def tearDown(self):
@@ -82,6 +87,39 @@ Options:
         self.session.editChannel.assert_called_once_with(self.channel_old, name=self.channel_new,
                                                          description=self.description)
 
+    @mock.patch('sys.stderr', new_callable=six.StringIO)
+    @mock.patch('koji_cli.commands.activate_session')
+    def test_handle_edit_channel_non_exist_channel(self, activate_session_mock, stderr):
+        expected = 'No such channel: %s\n' % self.channel_old
+        channel_info = None
+        self.session.getChannel.return_value = channel_info
+        with self.assertRaises(SystemExit) as ex:
+            handle_edit_channel(self.options, self.session,
+                                [self.channel_old, '--name', self.channel_new,
+                                 '--description', self.description])
+        self.assertExitCode(ex, 1)
+        actual = stderr.getvalue()
+        self.assertMultiLineEqual(actual, expected)
+        activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.editChannel.assert_not_called()
+
+    @mock.patch('sys.stderr', new_callable=six.StringIO)
+    @mock.patch('koji_cli.commands.activate_session')
+    def test_handle_edit_channel_non_result(self, activate_session_mock, stderr):
+        expected = 'No changes made, please correct the command line\n'
+        self.session.getChannel.return_value = self.channel_info
+        self.session.editChannel.return_value = None
+        with self.assertRaises(SystemExit) as ex:
+            handle_edit_channel(self.options, self.session,
+                                [self.channel_old, '--name', self.channel_new,
+                                 '--description', self.description])
+        self.assertExitCode(ex, 1)
+        actual = stderr.getvalue()
+        self.assertMultiLineEqual(actual, expected)
+        activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.editChannel.assert_called_once_with(self.channel_old, name=self.channel_new,
+                                                         description=self.description)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_cli/test_edit_external_repo.py b/tests/test_cli/test_edit_external_repo.py
index c1b17d3..c0d2b32 100644
--- a/tests/test_cli/test_edit_external_repo.py
+++ b/tests/test_cli/test_edit_external_repo.py
@@ -56,7 +56,7 @@ class TestEditExternalRepo(utils.CliTestCase):
 
         # edit ext-repo only
         handle_edit_external_repo(self.options, self.session,
-                                  ['ext_repo','--name', 'newname', '--url', 'https://newurl'])
+                                  ['ext_repo', '--name', 'newname', '--url', 'https://newurl'])
         self.assert_console_message(stdout, "")
         self.assert_console_message(stderr, "")
         self.session.editExternalRepo.assert_called_once_with('ext_repo',
@@ -66,14 +66,15 @@ class TestEditExternalRepo(utils.CliTestCase):
         # edit tag-repo only
         self.session.reset_mock()
         handle_edit_external_repo(self.options, self.session,
-                                  ['ext_repo','-t', 'tag', '-p', '0', '-m', 'koji'])
+                                  ['ext_repo', '-t', 'tag', '-p', '0', '-m', 'koji', '-a', 'i386'])
         self.assert_console_message(stdout, "")
         self.assert_console_message(stderr, "")
         self.session.editExternalRepo.assert_not_called()
         self.session.editTagExternalRepo.assert_called_once_with(repo_info='ext_repo',
                                                                  tag_info='tag',
                                                                  priority=0,
-                                                                 merge_mode='koji')
+                                                                 merge_mode='koji',
+                                                                 arches='i386')
 
     def test_handle_edit_external_repo_help(self):
         self.assert_help(
diff --git a/tests/test_cli/test_edit_target.py b/tests/test_cli/test_edit_target.py
index fdda38c..d8760c6 100644
--- a/tests/test_cli/test_edit_target.py
+++ b/tests/test_cli/test_edit_target.py
@@ -1,5 +1,7 @@
 from __future__ import absolute_import
 
+import copy
+
 import mock
 from six.moves import StringIO
 
@@ -14,6 +16,20 @@ class TestEditTarget(utils.CliTestCase):
         self.options.debug = False
         self.session = mock.MagicMock()
         self.session.getAPIVersion.return_value = koji.API_VERSION
+        self.build_target_info = {'build_tag': 444,
+                                  'build_tag_name': 'test-tag',
+                                  'dest_tag': 445,
+                                  'dest_tag_name': 'dest-test-tag',
+                                  'id': 1,
+                                  'name': 'test-target'}
+        self.dest_tag_info = {'arches': 'x86_64',
+                              'extra': {},
+                              'id': 1,
+                              'name': 'new-dest-tag'}
+        self.build_tag_info = {'arches': 'x86_64',
+                               'extra': {},
+                               'id': 1,
+                               'name': 'new-build-tag'}
 
     @mock.patch('sys.stderr', new_callable=StringIO)
     def test_edit_target_without_option(self, stderr):
@@ -46,3 +62,100 @@ class TestEditTarget(utils.CliTestCase):
         self.assertExitCode(ex, 1)
         self.assert_console_message(stderr, expected)
         self.session.editBuildTarget.assert_not_called()
+
+    @mock.patch('sys.stderr', new_callable=StringIO)
+    def test_edit_target_without_perms(self, stderr):
+        side_effect_result = [False, False]
+
+        target = 'test-target'
+        self.session.hasPerm.side_effect = side_effect_result
+        with self.assertRaises(SystemExit) as ex:
+            handle_edit_target(self.options, self.session, [target])
+        self.assertExitCode(ex, 2)
+        expected_msg = """Usage: %s edit-target [options] <name>
+(Specify the --help global option for a list of other help options)
+
+%s: error: This action requires target or admin privileges
+""" % (self.progname, self.progname)
+        self.assert_console_message(stderr, expected_msg)
+        self.session.editBuildTarget.assert_not_called()
+        self.session.getBuildTarget.assert_not_called()
+
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_edit_target_new_name(self, stdout):
+        target = 'test-target'
+        new_target_name = 'new-test-target'
+        self.session.getBuildTarget.return_value = self.build_target_info
+        rv = handle_edit_target(self.options, self.session, ['--rename', new_target_name, target])
+        self.assertEqual(rv, None)
+        expected_msg = ''
+        self.assert_console_message(stdout, expected_msg)
+        self.session.getTag.assert_not_called()
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.editBuildTarget.assert_called_once_with(
+            self.build_target_info['orig_name'], new_target_name,
+            self.build_target_info['build_tag_name'], self.build_target_info['dest_tag_name'])
+
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_edit_target_dest_tag(self, stdout):
+        target = 'test-target'
+        new_dest_tag = 'new-dest-tag'
+        self.session.getBuildTarget.return_value = self.build_target_info
+        self.session.getTag.return_value = self.dest_tag_info
+        rv = handle_edit_target(self.options, self.session, ['--dest-tag', new_dest_tag, target])
+        self.assertEqual(rv, None)
+        expected_msg = ''
+        self.assert_console_message(stdout, expected_msg)
+        self.session.getTag.assert_called_once_with(new_dest_tag)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.editBuildTarget.assert_called_once_with(
+            self.build_target_info['orig_name'], self.build_target_info['name'],
+            self.build_target_info['build_tag_name'], self.build_target_info['dest_tag_name'])
+
+    @mock.patch('sys.stderr', new_callable=StringIO)
+    def test_edit_target_non_exist_build_tag(self, stderr):
+        target = 'test-target'
+        new_build_tag = 'new-build-tag'
+        self.session.getBuildTarget.return_value = self.build_target_info
+        self.session.getTag.return_value = None
+        with self.assertRaises(SystemExit) as ex:
+            handle_edit_target(self.options, self.session, ['--build-tag', new_build_tag, target])
+        self.assertExitCode(ex, 1)
+        expected_msg = "No such tag: %s\n" % new_build_tag
+        self.assert_console_message(stderr, expected_msg)
+        self.session.getTag.assert_called_once_with(new_build_tag)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.editBuildTarget.assert_not_called()
+
+    @mock.patch('sys.stderr', new_callable=StringIO)
+    def test_edit_target_tag_arch_none(self, stderr):
+        target = 'test-target'
+        new_build_tag = 'new-build-tag'
+        build_tag_info = copy.deepcopy(self.build_tag_info)
+        build_tag_info['arches'] = ''
+        self.session.getBuildTarget.return_value = self.build_target_info
+        self.session.getTag.return_value = build_tag_info
+        with self.assertRaises(SystemExit) as ex:
+            handle_edit_target(self.options, self.session, ['--build-tag', new_build_tag, target])
+        self.assertExitCode(ex, 1)
+        expected_msg = "Build tag has no arches: %s\n" % new_build_tag
+        self.assert_console_message(stderr, expected_msg)
+        self.session.getTag.assert_called_once_with(new_build_tag)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.editBuildTarget.assert_not_called()
+
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_edit_target_build_tag_valid(self, stdout):
+        target = 'test-target'
+        new_build_tag = 'new-build-tag'
+        self.session.getBuildTarget.return_value = self.build_target_info
+        self.session.getTag.return_value = self.build_tag_info
+        rv = handle_edit_target(self.options, self.session, ['--build-tag', new_build_tag, target])
+        self.assertEqual(rv, None)
+        expected_msg = ''
+        self.assert_console_message(stdout, expected_msg)
+        self.session.getTag.assert_called_once_with(new_build_tag)
+        self.session.getBuildTarget.assert_called_once_with(target)
+        self.session.editBuildTarget.assert_called_once_with(
+            self.build_target_info['orig_name'], self.build_target_info['name'],
+            self.build_target_info['build_tag_name'], self.build_target_info['dest_tag_name'])
diff --git a/tests/test_cli/test_remove_tag.py b/tests/test_cli/test_remove_tag.py
index 0f045d4..67441d6 100644
--- a/tests/test_cli/test_remove_tag.py
+++ b/tests/test_cli/test_remove_tag.py
@@ -52,6 +52,24 @@ class TestRemoveTag(utils.CliTestCase):
         self.session.deleteTag.assert_called_once_with(tag_info['id'])
         self.session.getTag.assert_called_with(tag_info['name'])
 
+    @mock.patch('sys.stderr', new_callable=StringIO)
+    def test_remove_tag_without_perms(self, stderr):
+        side_effect_result = [False, False]
+
+        tag = 'test-tag'
+        self.session.hasPerm.side_effect = side_effect_result
+        with self.assertRaises(SystemExit) as ex:
+            handle_remove_tag(self.options, self.session, [tag])
+        self.assertExitCode(ex, 2)
+        expected_msg = """Usage: %s remove-tag [options] <name>
+(Specify the --help global option for a list of other help options)
+
+%s: error: This action requires tag or admin privileges
+""" % (self.progname, self.progname)
+        self.assert_console_message(stderr, expected_msg)
+        self.session.deleteTag.assert_not_called()
+        self.session.getTag.assert_not_called()
+
     def test_remove_tag_help(self):
         self.assert_help(
             handle_remove_tag,
diff --git a/tests/test_cli/test_remove_target.py b/tests/test_cli/test_remove_target.py
index 8bb6c94..45b0e0e 100644
--- a/tests/test_cli/test_remove_target.py
+++ b/tests/test_cli/test_remove_target.py
@@ -51,6 +51,24 @@ class TestRemoveTarget(utils.CliTestCase):
         self.session.deleteBuildTarget.assert_called_once_with(build_target['id'])
         self.session.getBuildTarget.assert_called_with(build_target['name'])
 
+    @mock.patch('sys.stderr', new_callable=StringIO)
+    def test_remove_target_without_perms(self, stderr):
+        side_effect_result = [False, False]
+
+        target = 'test-target'
+        self.session.hasPerm.side_effect = side_effect_result
+        with self.assertRaises(SystemExit) as ex:
+            handle_remove_target(self.options, self.session, [target])
+        self.assertExitCode(ex, 2)
+        expected_msg = """Usage: %s remove-target [options] <name>
+(Specify the --help global option for a list of other help options)
+
+%s: error: This action requires target or admin privileges
+""" % (self.progname, self.progname)
+        self.assert_console_message(stderr, expected_msg)
+        self.session.deleteBuildTarget.assert_not_called()
+        self.session.getBuildTarget.assert_not_called()
+
     def test_remove_target_help(self):
         self.assert_help(
             handle_remove_target,
diff --git a/tests/test_cli/test_rename_channel.py b/tests/test_cli/test_rename_channel.py
index b485c5c..dd6cd4f 100644
--- a/tests/test_cli/test_rename_channel.py
+++ b/tests/test_cli/test_rename_channel.py
@@ -64,6 +64,23 @@ class TestRenameChannel(utils.CliTestCase):
         self.session.getChannel.assert_called_once_with(self.channel_name_old)
         self.session.renameChannel.assert_not_called()
 
+    @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_rename_channel_more_args(self, activate_session_mock, stderr, stdout):
+        args = [self.channel_name_old, self.channel_name_new, 'extra-arg']
+        with self.assertRaises(SystemExit) as ex:
+            handle_rename_channel(self.options, self.session, args)
+        self.assertExitCode(ex, 2)
+        expected = 'Incorrect number of arguments'
+        depr_warn = 'rename-channel is deprecated and will be removed in 1.28'
+        self.assert_console_message(stderr, expected, wipe=False, regex=True)
+        self.assert_console_message(stdout, depr_warn, wipe=False, regex=True)
+        # Finally, assert that things were called as we expected.
+        activate_session_mock.assert_not_called()
+        self.session.getChannel.assert_not_called()
+        self.session.renameChannel.assert_not_called()
+
     def test_handle_rename_channel_help(self):
         self.assert_help(
             handle_rename_channel,
diff --git a/tests/test_cli/test_restart_host.py b/tests/test_cli/test_restart_host.py
index 750bb3d..7801148 100644
--- a/tests/test_cli/test_restart_host.py
+++ b/tests/test_cli/test_restart_host.py
@@ -14,59 +14,44 @@ class TestRestartHosts(utils.CliTestCase):
     maxDiff = None
 
     def setUp(self):
+        self.options = mock.MagicMock()
+        self.options.quiet = None
+        self.options.poll_interval = 3
+        self.session = mock.MagicMock()
+        self.activate_session_mock = mock.patch('koji_cli.commands.activate_session').start()
+        self.running_in_bg_mock = mock.patch('koji_cli.commands._running_in_bg').start()
+        self.running_in_bg_mock.return_value = False
+        self.watch_tasks_mock = mock.patch('koji_cli.commands.watch_tasks').start()
         self.task_id = 101
 
-    @mock.patch('koji_cli.commands.watch_tasks')
-    @mock.patch('koji_cli.commands._running_in_bg')
-    @mock.patch('koji_cli.commands.activate_session')
-    def test_handle_restart_hosts_force_options(
-            self, activate_session_mock, running_in_bg_mock, watch_tasks_mock):
+    def test_handle_restart_hosts_force_options(self):
         """Test %s function with --force option""" % handle_restart_hosts.__name__
         arguments = ['--force']
-        options = mock.MagicMock(quiet=None, poll_interval=3)
-        session = mock.MagicMock()
-
-        # set running in foreground
-        running_in_bg_mock.return_value = False
 
-        session.getHost.return_value = None
-        session.restartHosts.return_value = self.task_id
-        session.logout.return_value = None
+        self.session.getHost.return_value = None
+        self.session.restartHosts.return_value = self.task_id
+        self.session.logout.return_value = None
 
         # has other restart tasks are running case
-        session.listTasks.return_value = [{'id': 1}, {'id': 2}, {'id': 3}]
+        self.session.listTasks.return_value = [{'id': 1}, {'id': 2}, {'id': 3}]
 
-        handle_restart_hosts(options, session, arguments)
-        activate_session_mock.assert_called_once()
-        session.listTasks.assert_not_called()
+        handle_restart_hosts(self.options, self.session, arguments)
+        self.activate_session_mock.assert_called_once()
+        self.session.listTasks.assert_not_called()
 
-        session.restartHosts.assert_called_with()
-        session.logout.assert_called_once()
-        watch_tasks_mock.assert_called_with(
-            session, [self.task_id], quiet=None, poll_interval=3, topurl=options.topurl)
+        self.session.restartHosts.assert_called_with()
+        self.session.logout.assert_called_once()
+        self.watch_tasks_mock.assert_called_with(
+            self.session, [self.task_id], quiet=None, poll_interval=3, topurl=self.options.topurl)
 
     @mock.patch('sys.stderr', new_callable=six.StringIO)
-    @mock.patch('sys.stdout', new_callable=six.StringIO)
-    @mock.patch('koji_cli.commands.watch_tasks')
-    @mock.patch('koji_cli.commands._running_in_bg')
-    @mock.patch('koji_cli.commands.activate_session')
-    def test_handle_restart_hosts_has_other_tasks(
-            self,
-            activate_session_mock,
-            running_in_bg_mock,
-            watch_tasks_mock,
-            stdout,
-            stderr):
-        """Test %s function when there has other restart tasks exist""" % handle_restart_hosts.__name__
-        options = mock.MagicMock()
-        session = mock.MagicMock()
-
-        # set running in foreground
-        running_in_bg_mock.return_value = False
-
-        session.getHost.return_value = None
-        session.restartHosts.return_value = True
-        session.logout.return_value = None
+    def test_handle_restart_hosts_has_other_tasks(self, stderr):
+        """Test %s function when there has other restart tasks exist
+        """ % handle_restart_hosts.__name__
+
+        self.session.getHost.return_value = None
+        self.session.restartHosts.return_value = True
+        self.session.logout.return_value = None
 
         #
         # session.listTasks returns:
@@ -90,94 +75,110 @@ class TestRestartHosts(utils.CliTestCase):
         #   'start_ts': 1509647428.02884,
         #   'create_ts': 1509647408.93375,
         #   'host_id': 1, 'arch': 'noarch',
-        #   'request': "<?xml version='1.0'?>\n<methodCall>\n<methodName>restartHosts</methodName>\n<params>\n</params>\n</methodCall>\n",
+        #   'request': "<?xml version='1.0'?>\n<methodCall>\n<methodName>restartHosts</methodName>
+        #   \n<params>\n</params>\n</methodCall>\n",
         #   'channel_id': 1,
         #   'owner_type': 0}]
         #
 
         # has other restart tasks are running case
-        session.listTasks.return_value = [{'id': 1}, {'id': 2}, {'id': 3}]
+        self.session.listTasks.return_value = [{'id': 1}, {'id': 2}, {'id': 3}]
 
         with self.assertRaises(SystemExit) as ex:
-            handle_restart_hosts(options, session, [])
+            handle_restart_hosts(self.options, self.session, [])
         self.assertExitCode(ex, 1)
-        activate_session_mock.assert_called_once()
+        self.activate_session_mock.assert_called_once()
 
         query_opt = {
             'method': 'restartHosts',
             'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')]
         }
 
-        session.listTasks.assert_called_with(query_opt)
-        session.restartHosts.assert_not_called()
-        session.logout.assert_not_called()
+        self.session.listTasks.assert_called_with(query_opt)
+        self.session.restartHosts.assert_not_called()
+        self.session.logout.assert_not_called()
 
         expect = "Found other restartHosts tasks running.\n"
         expect += "Task ids: %r\n" % \
-            [t['id'] for t in session.listTasks.return_value]
+            [t['id'] for t in self.session.listTasks.return_value]
         expect += "Use --force to run anyway\n"
         self.assert_console_message(stderr, expect)
 
-    @mock.patch('koji_cli.commands.watch_tasks')
     @mock.patch('koji_cli.commands._running_in_bg')
-    @mock.patch('koji_cli.commands.activate_session')
-    def test_handle_restart_hosts_wait_option(
-            self, activate_session_mock, running_in_bg_mock, watch_tasks_mock):
+    def test_handle_restart_hosts_wait_option(self, running_in_bg_mock):
         """Test %s function with --force option""" % handle_restart_hosts.__name__
         arguments = ['--wait']
-        options = mock.MagicMock(quiet=None, poll_interval=3)
-        session = mock.MagicMock()
 
         # --wait is specified, running_in_bg() should not matter.
         running_in_bg_mock.return_value = True
 
-        session.getHost.return_value = None
-        session.restartHosts.return_value = self.task_id
-        session.logout.return_value = None
+        self.session.getHost.return_value = None
+        self.session.restartHosts.return_value = self.task_id
+        self.session.logout.return_value = None
 
         # has other restart tasks are running case
-        session.listTasks.return_value = []
+        self.session.listTasks.return_value = []
 
-        handle_restart_hosts(options, session, arguments)
-        activate_session_mock.assert_called_once()
-        session.listTasks.assert_called_once()
+        handle_restart_hosts(self.options, self.session, arguments)
+        self.activate_session_mock.assert_called_once()
+        self.session.listTasks.assert_called_once()
 
-        session.restartHosts.assert_called_with()
-        session.logout.assert_called_once()
-        watch_tasks_mock.assert_called_with(
-            session, [self.task_id], quiet=None, poll_interval=3, topurl=options.topurl)
+        self.session.restartHosts.assert_called_with()
+        self.session.logout.assert_called_once()
+        self.watch_tasks_mock.assert_called_with(
+            self.session, [self.task_id], quiet=None, poll_interval=3, topurl=self.options.topurl)
 
-    @mock.patch('koji_cli.commands.watch_tasks')
     @mock.patch('koji_cli.commands._running_in_bg')
-    @mock.patch('koji_cli.commands.activate_session')
-    def test_handle_restart_hosts_other_options(
-            self, activate_session_mock, running_in_bg_mock, watch_tasks_mock):
+    def test_handle_restart_hosts_other_options(self, running_in_bg_mock):
         """Test %s function with --force option""" % handle_restart_hosts.__name__
         arguments = ['--nowait',
                      '--channel', 'createrepo',
                      '--arch', 'x86_64',
                      '--timeout', '10']
-        options = mock.MagicMock(quiet=None, poll_interval=3)
-        session = mock.MagicMock()
 
         # --no-wait is specified, running_in_bg() should not matter.
         running_in_bg_mock.return_value = True
 
-        session.getHost.return_value = None
-        session.restartHosts.return_value = 101
-        session.logout.return_value = None
+        self.session.getHost.return_value = None
+        self.session.restartHosts.return_value = 101
+        self.session.logout.return_value = None
 
         # has other restart tasks are running case
-        session.listTasks.return_value = []
+        self.session.listTasks.return_value = []
 
-        handle_restart_hosts(options, session, arguments)
-        activate_session_mock.assert_called_once()
-        session.listTasks.assert_called_once()
+        handle_restart_hosts(self.options, self.session, arguments)
+        self.activate_session_mock.assert_called_once()
+        self.session.listTasks.assert_called_once()
 
-        session.restartHosts.assert_called_with(
+        self.session.restartHosts.assert_called_with(
             options={'arches': ['x86_64'], 'timeout': 10, 'channel': 'createrepo'})
-        session.logout.assert_not_called()
-        watch_tasks_mock.assert_not_called()
+        self.session.logout.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
+
+    @mock.patch('sys.stderr', new_callable=six.StringIO)
+    @mock.patch('koji_cli.commands._running_in_bg')
+    def test_handle_restart_hosts_arguments(self, running_in_bg_mock, stderr):
+        """Test %s function with --force option""" % handle_restart_hosts.__name__
+
+        # --no-wait is specified, running_in_bg() should not matter.
+        running_in_bg_mock.return_value = True
+
+        with self.assertRaises(SystemExit) as ex:
+            handle_restart_hosts(self.options, self.session, ['10'])
+
+        self.assertExitCode(ex, 2)
+        expected_msg = """Usage: %s restart-hosts [options]
+(Specify the --help global option for a list of other help options)
+
+%s: error: restart-hosts does not accept arguments
+""" % (self.progname, self.progname)
+        self.assert_console_message(stderr, expected_msg)
+
+        self.session.listTasks.assert_not_called()
+        self.activate_session_mock.assert_not_called()
+        self.session.restartHosts.assert_not_called()
+        self.session.logout.assert_not_called()
+        self.watch_tasks_mock.assert_not_called()
 
     def test_handle_restart_hosts_help(self):
         self.assert_help(
diff --git a/tests/test_cli/test_spin_commands.py b/tests/test_cli/test_spin_commands.py
index 5fb86b1..9de23e2 100644
--- a/tests/test_cli/test_spin_commands.py
+++ b/tests/test_cli/test_spin_commands.py
@@ -501,6 +501,16 @@ class TestSpinLiveCD(utils.CliTestCase):
                 activate_session=None)
         build_image_mock.assert_not_called()
 
+    @mock.patch('koji_cli.commands._build_image')
+    def test_handle_spin_livecd_longer_volid(self, build_image_mock):
+        """Test handle_spin_livecd volid options error"""
+        expected = self.format_error_message("Volume ID has a maximum length of 32 characters")
+        volid = '12345678901234567890123456789012345'
+        args = ['--volid', volid, 'name', 'version', 'target', 'arch', 'file.ks']
+        self.assert_system_exit(handle_spin_livecd, self.options, self.session, args,
+                                stderr=expected, activate_session=None)
+        build_image_mock.assert_not_called()
+
     def test_handle_spin_livecd_help(self):
         """Test handle_spin_livecd help message"""
         self.assert_help(

