From 404d828815b698c9975b0a3c29c5ef9f13f5ad83 Mon Sep 17 00:00:00 2001
From: Jana Cupova <root@localhost>
Date: Mar 03 2023 08:58:47 +0000
Subject: CLI: cancel error msg


cancel returns error msg when build is not existing
cancel returns error mgs for all not existing tasks/builds

Fixes: https://pagure.io/koji/issue/3715

---

diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py
index 3469417..6372acf 100644
--- a/cli/koji_cli/commands.py
+++ b/cli/koji_cli/commands.py
@@ -6166,12 +6166,12 @@ def handle_cancel(goptions, session, args):
             for task_id in tlist:
                 results.append(remote_fn(task_id, **opts))
         for build in blist:
-            results.append(m.cancelBuild(build))
+            results.append(m.cancelBuild(build, strict=True))
 
     err = False
     for r in results:
-        if isinstance(r.result, dict):
-            warn(r.result['faultString'])
+        if isinstance(r._result, dict):
+            warn(r._result['faultString'])
             err = True
     if err:
         return 1
diff --git a/kojihub/kojihub.py b/kojihub/kojihub.py
index d76817d..55703a2 100644
--- a/kojihub/kojihub.py
+++ b/kojihub/kojihub.py
@@ -11621,13 +11621,18 @@ class RootExports(object):
         results = _applyQueryOpts(results, queryOpts)
         return koji.fixEncodingRecurse(results, remove_nonprintable=True)
 
-    def cancelBuild(self, buildID):
+    def cancelBuild(self, buildID, strict=False):
         """Cancel the build with the given buildID
 
+        :param int|str|dict buildID: int ID, a string NVR, or
+                                     a map containing 'name', 'version' and 'release'.
+        :param bool strict: if strict is True and build is not existing, an exception is raised,
+                            if strict is False and build is not existing, returns False
+
         If the build is associated with a task, cancel the task as well.
         Return True if the build was successfully canceled, False if not."""
         context.session.assertLogin()
-        build = get_build(buildID)
+        build = get_build(buildID, strict)
         if build is None:
             return False
         if build['owner_id'] != context.session.user_id:
diff --git a/tests/test_cli/test_cancel.py b/tests/test_cli/test_cancel.py
index 161c8df..c8f5056 100644
--- a/tests/test_cli/test_cancel.py
+++ b/tests/test_cli/test_cancel.py
@@ -3,12 +3,21 @@ from __future__ import absolute_import
 import mock
 
 import koji
+import six
 from koji_cli.commands import handle_cancel
 from . import utils
 
 
 class TestCancel(utils.CliTestCase):
 
+    def __vm(self, result):
+        m = koji.VirtualCall('mcall_method', [], {})
+        if isinstance(result, dict) and result.get('faultCode'):
+            m._result = result
+        else:
+            m._result = (result,)
+        return m
+
     def setUp(self):
         self.maxDiff = None
         self.options = mock.MagicMock()
@@ -16,7 +25,8 @@ class TestCancel(utils.CliTestCase):
         self.session = mock.MagicMock()
         self.session.multicall.return_value.__enter__.return_value = self.session
         self.activate_session_mock = mock.patch('koji_cli.commands.activate_session').start()
-
+        self.stderr = mock.patch('sys.stderr', new_callable=six.StringIO).start()
+        self.stdout = mock.patch('sys.stdout', new_callable=six.StringIO).start()
         self.error_format = """Usage: %s cancel [options] <task_id|build> [<task_id|build> ...]
 (Specify the --help global option for a list of other help options)
 
@@ -68,7 +78,7 @@ class TestCancel(utils.CliTestCase):
         self.activate_session_mock.assert_called_once_with(self.session, self.options)
         self.session.cancelTask.assert_not_called()
         self.session.cancelTaskFull.assert_not_called()
-        self.session.cancelBuild.assert_called_once_with(args[0])
+        self.session.cancelBuild.assert_called_once_with(args[0], strict=True)
 
     def test_cancel_builds_unused_options(self):
         # it is good for nothing here
@@ -78,7 +88,7 @@ class TestCancel(utils.CliTestCase):
         self.activate_session_mock.assert_called_once_with(self.session, self.options)
         self.session.cancelTask.assert_not_called()
         self.session.cancelTaskFull.assert_not_called()
-        self.session.cancelBuild.assert_called_once_with(args[0])
+        self.session.cancelBuild.assert_called_once_with(args[0], strict=True)
 
     def test_cancel_tasks_full(self):
         args = ['123', '--full']
@@ -124,6 +134,24 @@ class TestCancel(utils.CliTestCase):
         self.session.cancelTaskFull.assert_not_called()
         self.session.cancelBuild.assert_not_called()
 
+    def test_non_exist_build_and_task(self):
+        args = ['11111', 'nvr-1-30.1']
+        expected_warn = """No such task: %s
+No such build: '%s'
+""" % (args[0], args[1])
+        mcall = self.session.multicall.return_value.__enter__.return_value
+        mcall.cancelTask.return_value = self.__vm(
+            {'faultCode': 1000, 'faultString': 'No such task: %s' % args[0]})
+        mcall.cancelBuild.return_value = self.__vm(
+            {'faultCode': 1000, 'faultString': "No such build: '%s'" % args[1]})
+        rv = handle_cancel(self.options, self.session, args)
+        self.assertEqual(rv, 1)
+        self.assert_console_message(self.stderr, expected_warn)
+        self.activate_session_mock.assert_called_once_with(self.session, self.options)
+        self.session.cancelTask.assert_called_once_with(int(args[0]))
+        self.session.cancelTaskFull.assert_not_called()
+        self.session.cancelBuild.assert_called_once_with(args[1], strict=True)
+
     def test_cancel_help(self):
         self.assert_help(
             handle_cancel,

