#2905 Add CLI related to channels + add comments to channels
Merged 11 months ago by tkopecek. Opened a year ago by jcupova.
jcupova/koji issue-1711  into  master

file modified
+28
@@ -298,6 +298,19 @@ 

      session.removeHostFromChannel(host, channel)

  

  

+ def handle_add_channel(goptions, session, args):

+     "[admin] Add a channel"

+     usage = _("usage: %prog add-channel [options] <channel_name>")

+     parser = OptionParser(usage=get_usage_str(usage))

+     parser.add_option("--description", help=_("Description of channel"))

+     (options, args) = parser.parse_args(args)

+     if len(args) != 1:

+         parser.error(_("Please specify one channel name"))

+     activate_session(session, goptions)

+     channel_id = session.addChannel(args[0], description=options.description)

+     print("%s added: id %d" % (args[0], channel_id))

+ 

+ 

  def handle_remove_channel(goptions, session, args):

      "[admin] Remove a channel entirely"

      usage = _("usage: %prog remove-channel [options] <channel>")
@@ -318,6 +331,8 @@ 

      usage = _("usage: %prog rename-channel [options] <old-name> <new-name>")

      parser = OptionParser(usage=get_usage_str(usage))

      (options, args) = parser.parse_args(args)

+     print("rename-channel is deprecated and will be removed in 1.28, this call is replaced by "

+           "edit-channel")

      if len(args) != 2:

          parser.error(_("Incorrect number of arguments"))

      activate_session(session, goptions)
@@ -327,6 +342,19 @@ 

      session.renameChannel(args[0], args[1])

  

  

+ def handle_edit_channel(goptions, session, args):

+     "[admin] Edit a channel"

+     usage = _("usage: %prog edit-channel [options] <old-name>")

+     parser = OptionParser(usage=get_usage_str(usage))

+     parser.add_option("--name", help=_("New channel name"))

+     parser.add_option("--description", help=_("Description of channel"))

+     (options, args) = parser.parse_args(args)

+     if len(args) != 1:

+         parser.error(_("Incorrect number of arguments"))

+     activate_session(session, goptions)

+     session.editChannel(args[0], name=options.name, description=options.description)

+ 

+ 

  def handle_add_pkg(goptions, session, args):

      "[admin] Add a package to the listing for tag"

      usage = _("usage: %prog add-pkg [options] --owner <owner> <tag> <package> [<package> ...]")

@@ -0,0 +1,9 @@ 

+ -- upgrade script to migrate the Koji database schema

+ -- from version 1.25 to 1.26

+ 

+ 

+ BEGIN;

+ 

+ ALTER TABLE channels ADD COLUMN description TEXT;

+ 

+ COMMIT;

file modified
+2 -1
@@ -135,7 +135,8 @@ 

  -- listening to.

  CREATE TABLE channels (

  	id SERIAL NOT NULL PRIMARY KEY,

- 	name VARCHAR(128) UNIQUE NOT NULL

+ 	name VARCHAR(128) UNIQUE NOT NULL,

+ 	description TEXT

  ) WITHOUT OIDS;

  

  -- create default channel

file modified
+60 -4
@@ -2293,6 +2293,7 @@ 

  

  def rename_channel(old, new):

      """Rename a channel"""

+     logger.warning("renameChannel call is deprecated and will be removed in 1.28")

      context.session.assertPerm('admin')

      if not isinstance(new, str):

          raise koji.GenericError("new channel name must be a string")
@@ -2305,8 +2306,40 @@ 

      update.execute()

  

  

+ def edit_channel(channelInfo, name=None, description=None):

+     """Edit information for an existing channel.

+ 

+     :param str/int channelInfo: channel name or ID

+     :param str name: new channel name

+     :param str description: description of channel

+     """

+     context.session.assertPerm('admin')

+     channel = get_channel(channelInfo, strict=True)

+ 

+     if name:

+         if not isinstance(name, str):

+             raise koji.GenericError("new channel name must be a string")

+         dup_check = get_channel(name, strict=False)

+         if dup_check:

+             raise koji.GenericError("channel %(name)s already exists (id=%(id)i)" % dup_check)

+ 

+     update = UpdateProcessor('channels',

+                              values={'channelID': channel['id']},

+                              clauses=['id = %(channelID)i'])

+     if name:

+         update.set(name=name)

+     if description:

+         update.set(description=description)

+     update.execute()

+ 

+     return None

+ 

+ 

  def remove_channel(channel_name, force=False):

-     """Remove a channel

+     """Remove a channel.

+ 

+     :param str channel_name: channel name

+     :param bool force: remove channel which has hosts

  

      Channel must have no hosts, unless force is set to True

      If a channel has associated tasks, it cannot be removed
@@ -2334,6 +2367,26 @@ 

      _dml(delete, locals())

  

  

+ def add_channel(channel_name, description=None):

+     """Add a channel.

+ 

+     :param str channel_name: channel name

+     :param str description: description of channel

+     """

+     context.session.assertPerm('admin')

+     if not isinstance(channel_name, str):

+         raise koji.GenericError("Channel name must be a string")

+     dup_check = get_channel(channel_name, strict=False)

+     if dup_check:

+         raise koji.GenericError("channel %(name)s already exists (id=%(id)i)" % dup_check)

+     table = 'channels'

+     channel_id = _singleValue("SELECT nextval('%s_id_seq')" % table, strict=True)

+     insert = InsertProcessor(table)

+     insert.set(id=channel_id, name=channel_name, description=description)

+     insert.execute()

+     return channel_id

+ 

+ 

  def get_ready_hosts():

      """Return information about hosts that are ready to build.

  
@@ -5318,7 +5371,7 @@ 

      :returns: dict of the channel ID and name, or None.

                For example, {'id': 20, 'name': 'container'}

      """

-     fields = ('id', 'name')

+     fields = ('id', 'name', 'description')

      query = """SELECT %s FROM channels

      WHERE """ % ', '.join(fields)

      if isinstance(channelInfo, int):
@@ -5473,9 +5526,10 @@ 

                        settings. You must specify a hostID parameter with this

                        option.

      :returns: list of dicts, one per channel. For example,

-               [{'id': 20, 'name': 'container'}]

+               [{'id': 20, 'name': 'container', 'description': 'container channel'}]

      """

-     fields = {'channels.id': 'id', 'channels.name': 'name'}

+     fields = {'channels.id': 'id', 'channels.name': 'name',

+               'channels.description': 'description'}

      columns, aliases = zip(*fields.items())

      if hostID:

          tables = ['host_channels']
@@ -12589,7 +12643,9 @@ 

      addHostToChannel = staticmethod(add_host_to_channel)

      removeHostFromChannel = staticmethod(remove_host_from_channel)

      renameChannel = staticmethod(rename_channel)

+     editChannel = staticmethod(edit_channel)

      removeChannel = staticmethod(remove_channel)

+     addChannel = staticmethod(add_channel)

  

      def listHosts(self, arches=None, channelID=None, ready=None, enabled=None, userID=None,

                    queryOpts=None):

@@ -1,6 +1,7 @@ 

  Available commands:

  

  admin commands:

+         add-channel               Add a channel

          add-external-repo         Create an external repo and/or add one to a tag

          add-group                 Add a group to a tag

          add-group-pkg             Add a package to a group's package listing
@@ -21,6 +22,7 @@ 

          clone-tag                 Duplicate the contents of one tag onto another tag

          disable-host              Mark one or more hosts as disabled

          disable-user              Disable logins by a user

+         edit-channel              Edit a channel

          edit-external-repo        Edit data for an external repo

          edit-host                 Edit a host

          edit-tag                  Alter tag information

@@ -1,6 +1,7 @@ 

  Available commands:

  

  admin commands:

+         add-channel               Add a channel

          add-external-repo         Create an external repo and/or add one to a tag

          add-group                 Add a group to a tag

          add-group-pkg             Add a package to a group's package listing
@@ -21,6 +22,7 @@ 

          clone-tag                 Duplicate the contents of one tag onto another tag

          disable-host              Mark one or more hosts as disabled

          disable-user              Disable logins by a user

+         edit-channel              Edit a channel

          edit-external-repo        Edit data for an external repo

          edit-host                 Edit a host

          edit-tag                  Alter tag information

@@ -0,0 +1,96 @@ 

+ from __future__ import absolute_import

+ 

+ import unittest

+ 

+ import mock

+ import six

+ 

+ import koji

+ from koji_cli.commands import handle_add_channel

+ from . import utils

+ 

+ 

+ class TestAddChannel(utils.CliTestCase):

+ 

+     def setUp(self):

+         self.maxDiff = None

+         self.channel_name = 'test-channel'

+         self.description = 'test-description'

+         self.channel_id = 1

+         self.options = mock.MagicMock()

+         self.session = mock.MagicMock()

+ 

+     @mock.patch('sys.stdout', new_callable=six.StringIO)

+     @mock.patch('koji_cli.commands.activate_session')

+     def test_handle_add_channel(self, activate_session_mock, stdout):

+         self.session.addChannel.return_value = self.channel_id

+         rv = handle_add_channel(self.options, self.session,

+                                 ['--description', self.description, self.channel_name])

+         actual = stdout.getvalue()

+         expected = '%s added: id %s\n' % (self.channel_name, self.channel_id)

+         self.assertMultiLineEqual(actual, expected)

+         activate_session_mock.assert_called_once_with(self.session, self.options)

+         self.session.addChannel.assert_called_once_with(self.channel_name,

+                                                         description=self.description)

+         self.assertNotEqual(rv, 1)

+ 

+     @mock.patch('sys.stderr', new_callable=six.StringIO)

+     @mock.patch('koji_cli.commands.activate_session')

+     def test_handle_add_channel_exist(self, activate_session_mock, stderr):

+         expected = 'channel %(name)s already exists (id=%(id)i)'

+ 

+         self.session.addChannel.side_effect = koji.GenericError(expected)

+         with self.assertRaises(koji.GenericError) as ex:

+             handle_add_channel(self.options, self.session,

+                                ['--description', self.description, self.channel_name])

+         self.assertEqual(str(ex.exception), expected)

+         activate_session_mock.assert_called_once_with(self.session, self.options)

+         self.session.addChannel.assert_called_once_with(self.channel_name,

+                                                         description=self.description)

+ 

+     @mock.patch('sys.stderr', new_callable=six.StringIO)

+     @mock.patch('koji_cli.commands.activate_session')

+     def test_handle_add_channel_without_args(self, activate_session_mock, stderr):

+         with self.assertRaises(SystemExit) as ex:

+             handle_add_channel(self.options, self.session, [])

+         self.assertExitCode(ex, 2)

+         actual = stderr.getvalue()

+         expected_stderr = """Usage: %s add-channel [options] <channel_name>

+ (Specify the --help global option for a list of other help options)

+ 

+ %s: error: Please specify one channel name

+ """ % (self.progname, self.progname)

+         self.assertMultiLineEqual(actual, expected_stderr)

+         activate_session_mock.assert_not_called()

+ 

+     @mock.patch('sys.stderr', new_callable=six.StringIO)

+     @mock.patch('koji_cli.commands.activate_session')

+     def test_handle_add_channel_more_args(self, activate_session_mock, stderr):

+         channel_2 = 'channel-2'

+         with self.assertRaises(SystemExit) as ex:

+             handle_add_channel(self.options, self.session, [self.channel_name, channel_2])

+         self.assertExitCode(ex, 2)

+         actual = stderr.getvalue()

+         expected_stderr = """Usage: %s add-channel [options] <channel_name>

+ (Specify the --help global option for a list of other help options)

+ 

+ %s: error: Please specify one channel name

+ """ % (self.progname, self.progname)

+         self.assertMultiLineEqual(actual, expected_stderr)

+         activate_session_mock.assert_not_called()

+ 

+     def test_handle_add_host_help(self):

+         self.assert_help(

+             handle_add_channel,

+             """Usage: %s add-channel [options] <channel_name>

+ (Specify the --help global option for a list of other help options)

+ 

+ Options:

+   -h, --help            show this help message and exit

+   --description=DESCRIPTION

+                         Description of channel

+ """ % self.progname)

+ 

+ 

+ if __name__ == '__main__':

+     unittest.main()

@@ -1,13 +1,16 @@ 

  from __future__ import absolute_import

- import mock

+ 

  import os

- import six

  import sys

  

+ import mock

+ import six

+ 

  import koji

  from koji_cli.commands import handle_add_host

  from . import utils

  

+ 

  class TestAddHost(utils.CliTestCase):

  

      # Show long diffs in error output...
@@ -101,7 +104,7 @@ 

      @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_add_host_help(self, activate_session_mock, stderr, stdout):

+     def test_handle_add_host_without_args(self, activate_session_mock, stderr, stdout):

          arguments = []

          options = mock.MagicMock()

          progname = os.path.basename(sys.argv[0]) or 'koji'
@@ -129,6 +132,20 @@ 

          session.hasHost.assert_not_called()

          session.addHost.assert_not_called()

  

+     def test_handle_add_host_help(self):

+         self.assert_help(

+             handle_add_host,

+             """Usage: %s add-host [options] <hostname> <arch> [<arch> ...]

+ (Specify the --help global option for a list of other help options)

+ 

+ Options:

+   -h, --help            show this help message and exit

+   --krb-principal=KRB_PRINCIPAL

+                         set a non-default kerberos principal for the host

+   --force               if existing used is a regular user, convert it to a

+                         host

+ """ % self.progname)

+ 

      @mock.patch('sys.stderr', new_callable=six.StringIO)

      @mock.patch('koji_cli.commands.activate_session')

      def test_handle_add_host_failed(self, activate_session_mock, stderr):
@@ -148,7 +165,7 @@ 

          # Run it and check immediate output

          # args: host, arch1, arch2, --krb-principal=krb

          # expected: failed

-         with self.assertRaises(koji.GenericError) as ex:

+         with self.assertRaises(koji.GenericError):

              handle_add_host(options, session, arguments)

          actual = stderr.getvalue()

          expected = ''

@@ -0,0 +1,65 @@ 

+ # coding=utf-8

+ from __future__ import absolute_import

+ 

+ import unittest

+ 

+ import mock

+ import six

+ 

+ from koji_cli.commands import handle_edit_channel

+ from . import utils

+ 

+ 

+ class TestEditChannel(utils.CliTestCase):

+ 

+     def setUp(self):

+         self.options = mock.MagicMock()

+         self.session = mock.MagicMock()

+         self.channel_old = 'test-channel'

+         self.channel_new = 'test-channel-new'

+         self.description = 'description'

+         self.maxDiff = None

+ 

+     def tearDown(self):

+         mock.patch.stopall()

+ 

+     def test_handle_edit_channel_help(self):

+         self.assert_help(

+             handle_edit_channel,

+             """Usage: %s edit-channel [options] <old-name>

+ (Specify the --help global option for a list of other help options)

+ 

+ Options:

+   -h, --help            show this help message and exit

+   --name=NAME           New channel name

+   --description=DESCRIPTION

+                         Description of channel

+ """ % self.progname)

+ 

+     @mock.patch('sys.stderr', new_callable=six.StringIO)

+     @mock.patch('koji_cli.commands.activate_session')

+     def test_handle_edit_channel_without_args(self, activate_session_mock, stderr):

+         with self.assertRaises(SystemExit) as ex:

+             handle_edit_channel(self.options, self.session, [])

+         self.assertExitCode(ex, 2)

+         actual = stderr.getvalue()

+         expected_stderr = """Usage: %s edit-channel [options] <old-name>

+ (Specify the --help global option for a list of other help options)

+ 

+ %s: error: Incorrect number of arguments

+ """ % (self.progname, self.progname)

+         self.assertMultiLineEqual(actual, expected_stderr)

+         activate_session_mock.assert_not_called()

+ 

+     @mock.patch('koji_cli.commands.activate_session')

+     def test_handle_edit_channel(self, activate_session_mock):

+         handle_edit_channel(self.options, self.session,

+                             [self.channel_old, '--name', self.channel_new,

+                              '--description', self.description])

+         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()

@@ -1,112 +1,78 @@ 

  from __future__ import absolute_import

- import mock

- import os

- import six

- import sys

+ 

  import unittest

  

+ import six

+ import mock

+ 

  from koji_cli.commands import handle_remove_channel

  from . import utils

  

  

  class TestRemoveChannel(utils.CliTestCase):

  

-     # Show long diffs in error output...

-     maxDiff = None

+     def setUp(self):

+         self.options = mock.MagicMock()

+         self.session = mock.MagicMock()

+         self.channel_name = 'test-channel'

+         self.description = 'description'

+         self.channel_info = {

+             'id': 123,

+             'name': self.channel_name,

+             'description': self.description,

+         }

+         self.maxDiff = None

  

      @mock.patch('sys.stdout', new_callable=six.StringIO)

      @mock.patch('koji_cli.commands.activate_session')

      def test_handle_remove_channel(self, activate_session_mock, stdout):

-         channel = 'channel'

-         channel_info = mock.ANY

-         args = [channel]

-         options = mock.MagicMock()

- 

-         # Mock out the xmlrpc server

-         session = mock.MagicMock()

- 

-         session.getChannel.return_value = channel_info

-         # Run it and check immediate output

-         # args: channel

-         # expected: success

-         rv = handle_remove_channel(options, session, args)

+         self.session.getChannel.return_value = self.channel_info

+         rv = handle_remove_channel(self.options, self.session, [self.channel_name])

          actual = stdout.getvalue()

          expected = ''

          self.assertMultiLineEqual(actual, expected)

-         # Finally, assert that things were called as we expected.

-         activate_session_mock.assert_called_once_with(session, options)

-         session.getChannel.assert_called_once_with(channel)

-         session.removeChannel.assert_called_once_with(channel, force=None)

+         activate_session_mock.assert_called_once_with(self.session, self.options)

+         self.session.getChannel.assert_called_once_with(self.channel_name)

+         self.session.removeChannel.assert_called_once_with(self.channel_name, force=None)

          self.assertNotEqual(rv, 1)

  

      @mock.patch('sys.stdout', new_callable=six.StringIO)

      @mock.patch('koji_cli.commands.activate_session')

      def test_handle_remove_channel_force(self, activate_session_mock, stdout):

-         channel = 'channel'

-         channel_info = mock.ANY

-         force_arg = '--force'

-         args = [force_arg, channel]

-         options = mock.MagicMock()

- 

-         # Mock out the xmlrpc server

-         session = mock.MagicMock()

- 

-         session.getChannel.return_value = channel_info

-         # Run it and check immediate output

-         # args: --force, channel

-         # expected: success

-         rv = handle_remove_channel(options, session, args)

+         self.session.getChannel.return_value = self.channel_info

+         rv = handle_remove_channel(self.options, self.session, ['--force', self.channel_name])

          actual = stdout.getvalue()

          expected = ''

          self.assertMultiLineEqual(actual, expected)

-         # Finally, assert that things were called as we expected.

-         activate_session_mock.assert_called_once_with(session, options)

-         session.getChannel.assert_called_once_with(channel)

-         session.removeChannel.assert_called_once_with(channel, force=True)

+         activate_session_mock.assert_called_once_with(self.session, self.options)

+         self.session.getChannel.assert_called_once_with(self.channel_name)

+         self.session.removeChannel.assert_called_once_with(self.channel_name, force=True)

          self.assertNotEqual(rv, 1)

  

      @mock.patch('sys.stderr', new_callable=six.StringIO)

      @mock.patch('koji_cli.commands.activate_session')

      def test_handle_remove_channel_no_channel(

              self, activate_session_mock, stderr):

-         channel = 'channel'

          channel_info = None

-         args = [channel]

-         options = mock.MagicMock()

- 

-         # Mock out the xmlrpc server

-         session = mock.MagicMock()

  

-         session.getChannel.return_value = channel_info

-         # Run it and check immediate output

-         # args: channel

-         # expected: failed: no such channel

+         self.session.getChannel.return_value = channel_info

          with self.assertRaises(SystemExit) as ex:

-             handle_remove_channel(options, session, args)

+             handle_remove_channel(self.options, self.session, [self.channel_name])

          self.assertExitCode(ex, 1)

          actual = stderr.getvalue()

-         expected = 'No such channel: channel\n'

+         expected = 'No such channel: %s\n' % self.channel_name

          self.assertMultiLineEqual(actual, expected)

-         # Finally, assert that things were called as we expected.

-         activate_session_mock.assert_called_once_with(session, options)

-         session.getChannel.assert_called_once_with(channel)

-         session.removeChannel.assert_not_called()

+         activate_session_mock.assert_called_once_with(self.session, self.options)

+         self.session.getChannel.assert_called_once_with(self.channel_name)

+         self.session.removeChannel.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_remove_channel_help(

              self, activate_session_mock, stderr, stdout):

-         args = []

-         options = mock.MagicMock()

-         progname = os.path.basename(sys.argv[0]) or 'koji'

- 

-         # Mock out the xmlrpc server

-         session = mock.MagicMock()

- 

-         # Run it and check immediate output

          with self.assertRaises(SystemExit) as ex:

-             handle_remove_channel(options, session, args)

+             handle_remove_channel(self.options, self.session, [])

          self.assertExitCode(ex, 2)

          actual_stdout = stdout.getvalue()

          actual_stderr = stderr.getvalue()
@@ -115,14 +81,12 @@ 

  (Specify the --help global option for a list of other help options)

  

  %s: error: Incorrect number of arguments

- """ % (progname, progname)

+ """ % (self.progname, self.progname)

          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()

-         session.getChannel.assert_not_called()

-         session.removeChannel.assert_not_called()

+         self.session.getChannel.assert_not_called()

+         self.session.removeChannel.assert_not_called()

  

  

  if __name__ == '__main__':

@@ -1,9 +1,9 @@ 

  from __future__ import absolute_import

+ 

+ import unittest

+ 

  import mock

- import os

  import six

- import sys

- import unittest

  

  from koji_cli.commands import handle_rename_channel

  from . import utils
@@ -11,94 +11,68 @@ 

  

  class TestRenameChannel(utils.CliTestCase):

  

-     # Show long diffs in error output...

-     maxDiff = None

+     def setUp(self):

+         self.options = mock.MagicMock()

+         self.session = mock.MagicMock()

+         self.channel_name_old = 'old-channel'

+         self.channel_name_new = 'new-channel'

+         self.description = 'description'

+         self.channel_info = {

+             'id': 123,

+             'name': self.channel_name_old,

+             'description': self.description,

+         }

+         self.maxDiff = None

  

      @mock.patch('sys.stdout', new_callable=six.StringIO)

      @mock.patch('koji_cli.commands.activate_session')

      def test_handle_rename_channel(self, activate_session_mock, stdout):

-         old_name = 'old_name'

-         new_name = 'new_name'

-         channel_info = mock.ANY

-         args = [old_name, new_name]

-         options = mock.MagicMock()

- 

-         # Mock out the xmlrpc server

-         session = mock.MagicMock()

- 

-         session.getChannel.return_value = channel_info

+         args = [self.channel_name_old, self.channel_name_new]

+         self.session.getChannel.return_value = self.channel_info

          # Run it and check immediate output

          # args: old_name, new_name

          # expected: success

-         rv = handle_rename_channel(options, session, args)

-         actual = stdout.getvalue()

-         expected = ''

-         self.assertMultiLineEqual(actual, expected)

+         rv = handle_rename_channel(self.options, self.session, args)

+         depr_warn = 'rename-channel is deprecated and will be removed in 1.28'

+         self.assert_console_message(stdout, depr_warn, regex=True)

          # Finally, assert that things were called as we expected.

-         activate_session_mock.assert_called_once_with(session, options)

-         session.getChannel.assert_called_once_with(old_name)

-         session.renameChannel.assert_called_once_with(old_name, new_name)

+         activate_session_mock.assert_called_once_with(self.session, self.options)

+         self.session.getChannel.assert_called_once_with(self.channel_name_old)

+         self.session.renameChannel.assert_called_once_with(self.channel_name_old,

+                                                            self.channel_name_new)

          self.assertNotEqual(rv, 1)

  

+     @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_no_channel(

-             self, activate_session_mock, stderr):

-         old_name = 'old_name'

-         new_name = 'new_name'

+     def test_handle_rename_channel_no_channel(self, activate_session_mock, stderr, stdout):

          channel_info = None

-         args = [old_name, new_name]

-         options = mock.MagicMock()

- 

-         # Mock out the xmlrpc server

-         session = mock.MagicMock()

- 

-         session.getChannel.return_value = channel_info

+         args = [self.channel_name_old, self.channel_name_new]

+         self.session.getChannel.return_value = channel_info

          # Run it and check immediate output

          # args: old_name, new_name

          # expected: failed: no such channel

          with self.assertRaises(SystemExit) as ex:

-             handle_rename_channel(options, session, args)

+             handle_rename_channel(self.options, self.session, args)

          self.assertExitCode(ex, 1)

-         actual = stderr.getvalue()

-         expected = 'No such channel: old_name\n'

-         self.assertMultiLineEqual(actual, expected)

+         expected = 'No such channel: %s' % self.channel_name_old

+         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_called_once_with(session, options)

-         session.getChannel.assert_called_once_with(old_name)

-         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_help(

-             self, activate_session_mock, stderr, stdout):

-         args = []

-         options = mock.MagicMock()

-         progname = os.path.basename(sys.argv[0]) or 'koji'

- 

-         # Mock out the xmlrpc server

-         session = mock.MagicMock()

- 

-         # Run it and check immediate output

-         with self.assertRaises(SystemExit) as ex:

-             handle_rename_channel(options, session, args)

-         self.assertExitCode(ex, 2)

-         actual_stdout = stdout.getvalue()

-         actual_stderr = stderr.getvalue()

-         expected_stdout = ''

-         expected_stderr = """Usage: %s rename-channel [options] <old-name> <new-name>

+         activate_session_mock.assert_called_once_with(self.session, self.options)

+         self.session.getChannel.assert_called_once_with(self.channel_name_old)

+         self.session.renameChannel.assert_not_called()

+ 

+     def test_handle_rename_channel_help(self):

+         self.assert_help(

+             handle_rename_channel,

+             """Usage: %s rename-channel [options] <old-name> <new-name>

  (Specify the --help global option for a list of other help options)

  

- %s: error: Incorrect number of arguments

- """ % (progname, progname)

-         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()

-         session.getChannel.assert_not_called()

-         session.renameChannel.assert_not_called()

+ Options:

+   -h, --help  show this help message and exit

+ """ % self.progname)

  

  

  if __name__ == '__main__':

@@ -0,0 +1,65 @@ 

+ import unittest

+ 

+ import mock

+ 

+ import koji

+ import kojihub

+ 

+ UP = kojihub.UpdateProcessor

+ IP = kojihub.InsertProcessor

+ 

+ 

+ class TestAddChannel(unittest.TestCase):

+ 

+     def setUp(self):

+ 

+         self.context = mock.patch('kojihub.context').start()

+         self.context.session.assertPerm = mock.MagicMock()

+         self.exports = kojihub.RootExports()

+         self.channel_name = 'test-channel'

+         self.description = 'test-description'

+         self.InsertProcessor = mock.patch('kojihub.InsertProcessor',

+                                           side_effect=self.getInsert).start()

+         self.inserts = []

+         self.insert_execute = mock.MagicMock()

+ 

+     def tearDown(self):

+         mock.patch.stopall()

+ 

+     def getInsert(self, *args, **kwargs):

+         insert = IP(*args, **kwargs)

+         insert.execute = self.insert_execute

+         self.inserts.append(insert)

+         return insert

+ 

+     @mock.patch('kojihub.get_channel')

+     @mock.patch('kojihub._singleValue')

+     def test_add_channel_exists(self, _singleValue, get_channel):

+         get_channel.return_value = {'id': 123, 'name': self.channel_name}

+         with self.assertRaises(koji.GenericError):

+             self.exports.addChannel(self.channel_name)

+         get_channel.assert_called_once_with(self.channel_name, strict=False)

+         _singleValue.assert_not_called()

+         self.assertEqual(len(self.inserts), 0)

+ 

+     @mock.patch('kojihub.get_channel')

+     @mock.patch('kojihub._singleValue')

+     def test_add_channel_valid(self, _singleValue, get_channel):

+         get_channel.return_value = {}

+         _singleValue.side_effect = [12]

+ 

+         r = self.exports.addChannel(self.channel_name, description=self.description)

+         self.assertEqual(r, 12)

+         self.assertEqual(len(self.inserts), 1)

+         insert = self.inserts[0]

+         self.assertEqual(insert.data['name'], self.channel_name)

+         self.assertEqual(insert.data['id'], 12)

+         self.assertEqual(insert.data['description'], self.description)

+         self.assertEqual(insert.table, 'channels')

+ 

+         self.context.session.assertPerm.assert_called_once_with('admin')

+         get_channel.assert_called_once_with(self.channel_name, strict=False)

+         self.assertEqual(_singleValue.call_count, 1)

+         _singleValue.assert_has_calls([

+             mock.call("SELECT nextval('channels_id_seq')", strict=True)

+         ])

@@ -0,0 +1,99 @@ 

+ import unittest

+ 

+ import mock

+ 

+ import koji

+ import kojihub

+ 

+ UP = kojihub.UpdateProcessor

+ IP = kojihub.InsertProcessor

+ 

+ 

+ class TestEditChannel(unittest.TestCase):

+     def getInsert(self, *args, **kwargs):

+         insert = IP(*args, **kwargs)

+         insert.execute = mock.MagicMock()

+         self.inserts.append(insert)

+         return insert

+ 

+     def getUpdate(self, *args, **kwargs):

+         update = UP(*args, **kwargs)

+         update.execute = mock.MagicMock()

+         self.updates.append(update)

+         return update

+ 

+     def setUp(self):

+         self.InsertProcessor = mock.patch('kojihub.InsertProcessor',

+                                           side_effect=self.getInsert).start()

+         self.inserts = []

+         self.UpdateProcessor = mock.patch('kojihub.UpdateProcessor',

+                                           side_effect=self.getUpdate).start()

+         self.updates = []

+         self.context = mock.patch('kojihub.context').start()

+         self.context.session.assertPerm = mock.MagicMock()

+         self.exports = kojihub.RootExports()

+         self.channel_name = 'test-channel'

+         self.channel_name_new = 'test-channel-2'

+ 

+     def tearDown(self):

+         mock.patch.stopall()

+ 

+     @mock.patch('kojihub.get_channel')

+     def test_edit_channel_missing(self, get_channel):

+         expected = 'Invalid type for channelInfo: %s' % self.channel_name

+         get_channel.side_effect = koji.GenericError(expected)

+         with self.assertRaises(koji.GenericError) as ex:

+             self.exports.editChannel(self.channel_name, name=self.channel_name_new)

+         get_channel.assert_called_once_with(self.channel_name, strict=True)

+         self.assertEqual(self.inserts, [])

+         self.assertEqual(self.updates, [])

+         self.assertEqual(expected, str(ex.exception))

+ 

+     @mock.patch('kojihub.get_channel')

+     def test_edit_channel_already_exists(self, get_channel):

+         get_channel.side_effect = [

+             {

+                 'id': 123,

+                 'name': self.channel_name,

+                 'description': 'description',

+             },

+             {

+                 'id': 124,

+                 'name': self.channel_name_new,

+                 'description': 'description',

+             }

+         ]

+         with self.assertRaises(koji.GenericError) as ex:

+             self.exports.editChannel(self.channel_name, name=self.channel_name_new)

+         expected_calls = [mock.call(self.channel_name, strict=True),

+                           mock.call(self.channel_name_new, strict=False)]

+         get_channel.assert_has_calls(expected_calls)

+         self.assertEqual(self.inserts, [])

+         self.assertEqual(self.updates, [])

+         self.assertEqual('channel %s already exists (id=124)' % self.channel_name_new,

+                          str(ex.exception))

+ 

+     @mock.patch('kojihub.get_channel')

+     def test_edit_channel_valid(self, get_channel):

+         kojihub.get_channel.side_effect = [{

+             'id': 123,

+             'name': self.channel_name,

+             'description': 'description',

+         },

+             {}]

+ 

+         r = self.exports.editChannel(self.channel_name, name=self.channel_name_new,

+                                      description='description_new')

+         self.assertIsNone(r)

+         expected_calls = [mock.call(self.channel_name, strict=True),

+                           mock.call(self.channel_name_new, strict=False)]

+         get_channel.assert_has_calls(expected_calls)

+ 

+         self.assertEqual(len(self.updates), 1)

+         values = {'channelID': 123}

+         clauses = ['id = %(channelID)i']

+ 

+         update = self.updates[0]

+         self.assertEqual(update.table, 'channels')

+         self.assertEqual(update.values, values)

+         self.assertEqual(update.clauses, clauses)

@@ -1,22 +1,31 @@ 

  import unittest

  

+ import mock

+ 

  import koji

  import kojihub

  

  

  class TestGetChannel(unittest.TestCase):

  

+     def setUp(self):

+         self.context = mock.patch('kojihub.context').start()

+         self.exports = kojihub.RootExports()

+ 

+     def tearDown(self):

+         mock.patch.stopall()

+ 

      def test_wrong_type_channelInfo(self):

          # dict

          channel_info = {'channel': 'val'}

          with self.assertRaises(koji.GenericError) as cm:

-             kojihub.get_channel(channel_info)

+             self.exports.getChannel(channel_info)

          self.assertEqual('Invalid type for channelInfo: %s' % type(channel_info),

                           str(cm.exception))

  

-         #list

+         # list

          channel_info = ['channel']

          with self.assertRaises(koji.GenericError) as cm:

-             kojihub.get_channel(channel_info)

+             self.exports.getChannel(channel_info)

          self.assertEqual('Invalid type for channelInfo: %s' % type(channel_info),

                           str(cm.exception))

@@ -1,6 +1,7 @@ 

- import mock

  import unittest

  

+ import mock

+ 

  import koji

  import kojihub

  
@@ -17,7 +18,7 @@ 

  

      def setUp(self):

          self.QueryProcessor = mock.patch('kojihub.QueryProcessor',

-                                           side_effect=self.getQuery).start()

+                                          side_effect=self.getQuery).start()

          self.queries = []

          self.context = mock.patch('kojihub.context').start()

          # It seems MagicMock will not automatically handle attributes that
@@ -27,16 +28,15 @@ 

      def tearDown(self):

          mock.patch.stopall()

  

- 

      def test_all(self):

          kojihub.list_channels()

          self.assertEqual(len(self.queries), 1)

          query = self.queries[0]

          self.assertEqual(query.tables, ['channels'])

-         self.assertEqual(query.aliases, ['id', 'name'])

+         self.assertEqual(query.aliases, ['description', 'id', 'name'])

          self.assertEqual(query.joins, None)

          self.assertEqual(query.values, {})

-         self.assertEqual(query.columns, ['channels.id', 'channels.name'])

+         self.assertEqual(query.columns, ['channels.description', 'channels.id', 'channels.name'])

          self.assertEqual(query.clauses, None)

  

      def test_host(self):
@@ -50,10 +50,10 @@ 

              'host_channels.host_id = %(host_id)s'

          ]

          self.assertEqual(query.tables, ['host_channels'])

-         self.assertEqual(query.aliases, ['id', 'name'])

+         self.assertEqual(query.aliases, ['description', 'id', 'name'])

          self.assertEqual(query.joins, joins)

          self.assertEqual(query.values, {'host_id': 1234})

-         self.assertEqual(query.columns, ['channels.id', 'channels.name'])

+         self.assertEqual(query.columns, ['channels.description', 'channels.id', 'channels.name'])

          self.assertEqual(query.clauses, clauses)

  

      def test_host_and_event(self):
@@ -63,14 +63,15 @@ 

          query = self.queries[0]

          joins = ['channels ON channels.id = host_channels.channel_id']

          clauses = [

-             '(host_channels.create_event <= 2345 AND ( host_channels.revoke_event IS NULL OR 2345 < host_channels.revoke_event ))',

+             '(host_channels.create_event <= 2345 AND ( host_channels.revoke_event '

+             'IS NULL OR 2345 < host_channels.revoke_event ))',

              'host_channels.host_id = %(host_id)s',

          ]

          self.assertEqual(query.tables, ['host_channels'])

-         self.assertEqual(query.aliases, ['id', 'name'])

+         self.assertEqual(query.aliases, ['description', 'id', 'name'])

          self.assertEqual(query.joins, joins)

          self.assertEqual(query.values, {'host_id': 1234})

-         self.assertEqual(query.columns, ['channels.id', 'channels.name'])

+         self.assertEqual(query.columns, ['channels.description', 'channels.id', 'channels.name'])

          self.assertEqual(query.clauses, clauses)

  

      def test_event_only(self):

@@ -0,0 +1,38 @@ 

+ import unittest

+ 

+ import mock

+ 

+ import koji

+ import kojihub

+ 

+ UP = kojihub.UpdateProcessor

+ 

+ 

+ class TestRemoveChannel(unittest.TestCase):

+     def getUpdate(self, *args, **kwargs):

+         update = UP(*args, **kwargs)

+         update.execute = mock.MagicMock()

+         self.updates.append(update)

+         return update

+ 

+     def setUp(self):

+         self.UpdateProcessor = mock.patch('kojihub.UpdateProcessor',

+                                           side_effect=self.getUpdate).start()

+         self.updates = []

+         self.context = mock.patch('kojihub.context').start()

+         self.context.session.assertPerm = mock.MagicMock()

+         self.exports = kojihub.RootExports()

+         self.channel_name = 'test-channel'

+ 

+     def tearDown(self):

+         mock.patch.stopall()

+ 

+     @mock.patch('kojihub.get_channel_id')

+     def test_non_exist_channel(self, get_channel_id):

+         get_channel_id.side_effect = koji.GenericError('No such channel: %s' % self.channel_name)

+ 

+         with self.assertRaises(koji.GenericError):

+             kojihub.remove_channel(self.channel_name)

+ 

+         get_channel_id.assert_called_once_with(self.channel_name, strict=True)

+         self.assertEqual(len(self.updates), 0)

@@ -1,6 +1,7 @@ 

- import mock

  import unittest

  

+ import mock

+ 

  import koji

  import kojihub

  

@@ -12,6 +12,9 @@ 

        <th>ID</th><td>$channel.id</td>

      </tr>

      <tr>

+       <th>Description</th><td>$util.escapeHTML($channel.description)</td>

+     </tr>

+     <tr>

        <th>Active Tasks</th><td><a href="tasks?view=flat&channelID=$channel.id">$taskCount</a></td>

      </tr>

      <tr>

rebased onto f7858844b089e96272ef8fe1434b67c96e8694dd

a year ago

add info about command being replaced by edit-channel

Don't use direct inserts if InsertProcessor can be used.

It is not necessarry if addChannel raises an exception (there could be one less call)

We usually return None from edit methods.

It should already internally use editChannel, so we don't have two codepaths meanwhile.

Generally, I would replace "comment" with "description" to be aligned with what we've in hostinfo.

With strict=True it could be less code.

Also not needed, editChannel would raise an exception.

rebased onto d85ad2dd7e91da51da5f5b4da5f4580de3ce5fe5

a year ago

rebased onto 7e7982ce22b5b244f18e480b5cf648e0168855c8

a year ago

rebased onto 85825b63f08a7194859b2612e1403e0cbc578cd2

11 months ago

rebased onto 8a6394c0e551179f64221b771ae26148b36d9dd1

11 months ago

pretty please pagure-ci rebuild

11 months ago

Metadata Update from @tkopecek:
- Pull-request tagged with: testing-ready

11 months ago

"comma" here will cause a syntax error (in the last "column" line)

rebased onto 8f16c8f784466737cf1029475085a57c26e33efe

11 months ago

rebased onto 38a041019ea6df5722d670fb682bf22d8082bb89

11 months ago

rebased onto 6e29ea3dc57baa01a1e799bf75cc96dc1678f833

11 months ago

rebased onto 0e953ec

11 months ago

Metadata Update from @jobrauer:
- Pull-request tagged with: testing-done

11 months ago

Commit 1206011 fixes this pull-request

Pull-Request has been merged by tkopecek

11 months ago