From d31750bf7086c96c67d07dc3d227ae850b42cd1e Mon Sep 17 00:00:00 2001
From: Jana Cupova <root@localhost>
Date: Feb 15 2024 07:25:19 +0000
Subject: CLI list-users with filters from listUsers


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

---

diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py
index 3981d25..15229b3 100644
--- a/cli/koji_cli/commands.py
+++ b/cli/koji_cli/commands.py
@@ -8130,3 +8130,34 @@ def handle_promote_build(goptions, session, args):
         error("Not a draft build: %s" % draft_build)
     rinfo = session.promoteBuild(binfo['id'], force=options.force)
     print("%s has been promoted to %s" % (binfo['nvr'], rinfo['nvr']))
+
+
+def anon_handle_list_users(goptions, session, args):
+    """[admin] List of users"""
+    usage = "usage: %prog list-users [options]"
+    parser = OptionParser(usage=get_usage_str(usage))
+    parser.add_option("--usertype", help="List users that have a given usertype "
+                                         "(e.g. NORMAL, HOST, GROUP)")
+    parser.add_option("--prefix", help="List users that have a given prefix")
+    (options, args) = parser.parse_args(args)
+
+    if len(args) > 0:
+        parser.error("This command takes no arguments")
+    activate_session(session, goptions)
+
+    if options.usertype:
+        if options.usertype.upper() in koji.USERTYPES.keys():
+            usertype = koji.USERTYPES[options.usertype.upper()]
+        else:
+            error("Usertype %s doesn't exist" % options.usertype)
+    else:
+        usertype = koji.USERTYPES['NORMAL']
+
+    if options.prefix:
+        prefix = options.prefix
+    else:
+        prefix = None
+
+    users_list = session.listUsers(userType=usertype, prefix=prefix)
+    for user in users_list:
+        print(user['name'])
diff --git a/tests/test_cli/data/list-commands-admin.txt b/tests/test_cli/data/list-commands-admin.txt
index 6e4422a..498bd68 100644
--- a/tests/test_cli/data/list-commands-admin.txt
+++ b/tests/test_cli/data/list-commands-admin.txt
@@ -42,6 +42,7 @@ admin commands:
         import-cg                 Import external builds with rich metadata
         import-sig                Import signatures into the database and write signed RPMs
         list-signed               List signed copies of rpms
+        list-users                List of users
         lock-tag                  Lock a tag
         make-task                 Create an arbitrary task
         prune-signed-copies       Prune signed copies
diff --git a/tests/test_cli/data/list-commands.txt b/tests/test_cli/data/list-commands.txt
index e0799eb..bcd720d 100644
--- a/tests/test_cli/data/list-commands.txt
+++ b/tests/test_cli/data/list-commands.txt
@@ -42,6 +42,7 @@ admin commands:
         import-cg                 Import external builds with rich metadata
         import-sig                Import signatures into the database and write signed RPMs
         list-signed               List signed copies of rpms
+        list-users                List of users
         lock-tag                  Lock a tag
         make-task                 Create an arbitrary task
         prune-signed-copies       Prune signed copies
diff --git a/tests/test_cli/test_list_users.py b/tests/test_cli/test_list_users.py
new file mode 100644
index 0000000..174b87a
--- /dev/null
+++ b/tests/test_cli/test_list_users.py
@@ -0,0 +1,133 @@
+from __future__ import absolute_import
+
+import mock
+from six.moves import StringIO
+
+import koji
+from koji_cli.commands import anon_handle_list_users
+from . import utils
+
+
+class TestListUsers(utils.CliTestCase):
+    def setUp(self):
+        self.maxDiff = None
+        self.options = mock.MagicMock()
+        self.options.debug = False
+        self.session = mock.MagicMock()
+        self.session.getAPIVersion.return_value = koji.API_VERSION
+        self.activate_session = mock.patch('koji_cli.commands.activate_session').start()
+        self.error_format = """Usage: %s list-users [options]
+(Specify the --help global option for a list of other help options)
+
+%s: error: {message}
+""" % (self.progname, self.progname)
+
+    def tearDown(self):
+        mock.patch.stopall()
+
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_list_users_default_valid(self, stdout):
+        arguments = []
+        self.session.listUsers.return_value = [{
+            'id': 1, 'krb_principals': [],
+            'name': 'kojiadmin',
+            'status': 0,
+            'usertype': 0},
+            {'id': 2,
+             'krb_principals': [],
+             'name': 'testuser',
+             'status': 0,
+             'usertype': 0},
+        ]
+        rv = anon_handle_list_users(self.options, self.session, arguments)
+        actual = stdout.getvalue()
+        expected = """kojiadmin
+testuser
+"""
+        self.assertMultiLineEqual(actual, expected)
+        self.assertEqual(rv, None)
+        self.session.listUsers.assert_called_once_with(
+            userType=koji.USERTYPES['NORMAL'], prefix=None)
+
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_list_users_with_prefix(self, stdout):
+        arguments = ['--prefix', 'koji']
+        self.session.listUsers.return_value = [{
+            'id': 1, 'krb_principals': [],
+            'name': 'kojiadmin',
+            'status': 0,
+            'usertype': 0},
+        ]
+        rv = anon_handle_list_users(self.options, self.session, arguments)
+        actual = stdout.getvalue()
+        expected = """kojiadmin
+"""
+        self.assertMultiLineEqual(actual, expected)
+        self.assertEqual(rv, None)
+        self.session.listUsers.assert_called_once_with(
+            userType=koji.USERTYPES['NORMAL'], prefix='koji')
+
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_list_users_with_usertype(self, stdout):
+        arguments = ['--usertype', 'host']
+        self.session.listUsers.return_value = [{
+            'id': 3, 'krb_principals': [],
+            'name': 'kojihost',
+            'status': 0,
+            'usertype': 1},
+            {'id': 5, 'krb_principals': [],
+             'name': 'testhost',
+             'status': 0,
+             'usertype': 1},
+        ]
+        rv = anon_handle_list_users(self.options, self.session, arguments)
+        actual = stdout.getvalue()
+        expected = """kojihost
+testhost
+"""
+        self.assertMultiLineEqual(actual, expected)
+        self.assertEqual(rv, None)
+        self.session.listUsers.assert_called_once_with(
+            userType=koji.USERTYPES['HOST'], prefix=None)
+
+    def test_list_users_with_usertype_non_existing(self):
+        arguments = ['--usertype', 'test']
+        self.assert_system_exit(
+            anon_handle_list_users,
+            self.options, self.session, arguments,
+            stdout='',
+            stderr="Usertype test doesn't exist\n",
+            activate_session=None,
+            exit_code=1)
+        self.session.listUsers.assert_not_called()
+
+    @mock.patch('sys.stdout', new_callable=StringIO)
+    def test_list_users_with_usertype_and_prefix(self, stdout):
+        arguments = ['--usertype', 'host', '--prefix', 'test']
+        self.session.listUsers.return_value = [{
+            'id': 5, 'krb_principals': [],
+            'name': 'testhost',
+            'status': 0,
+            'usertype': 1},
+        ]
+        rv = anon_handle_list_users(self.options, self.session, arguments)
+        actual = stdout.getvalue()
+        expected = """testhost
+"""
+        self.assertMultiLineEqual(actual, expected)
+        self.assertEqual(rv, None)
+        self.session.listUsers.assert_called_once_with(
+            userType=koji.USERTYPES['HOST'], prefix='test')
+
+    def test_anon_handle_list_users_help(self):
+        self.assert_help(
+            anon_handle_list_users,
+            """Usage: %s list-users [options]
+(Specify the --help global option for a list of other help options)
+
+Options:
+  -h, --help           show this help message and exit
+  --usertype=USERTYPE  List users that have a given usertype (e.g. NORMAL,
+                       HOST, GROUP)
+  --prefix=PREFIX      List users that have a given prefix
+""" % self.progname)

