diff --git a/hub/kojihub.py b/hub/kojihub.py index 4cfcf92c..fc67b66d 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -2052,7 +2052,7 @@ def grp_pkg_remove(taginfo, grpinfo, pkg_name): def _grp_pkg_remove(taginfo, grpinfo, pkg_name): - """grp_pkg_remove without permssion checks""" + """grp_pkg_remove without permission checks""" tag_id = get_tag_id(taginfo, strict=True) grp_id = get_group_id(grpinfo, strict=True) update = UpdateProcessor('group_package_listing', values=locals(), @@ -11851,24 +11851,25 @@ class RootExports(object): if remove: for (grpname, group) in dstgroups.items(): - if grpname not in srcgroups and group['tag_id'] == dst['id']: - _grplist_remove(dst['id'], group['id'], force=force) + if grpname not in srcgroups: + if group['tag_id'] == dst['id']: + # not inherited + _grplist_remove(dst['id'], group['id'], force=force) + else: + # block inherited groups + _grplist_add(dst['id'], group['name'], block=True, force=force) _delete_event_id() - grpchanges = OrderedDict() # dict of changes to make in shared groups for (grpname, group) in srcgroups.items(): if grpname in dstgroups: - dstgroup = dstgroups[grpname] - # Store whether group is inherited or not - grpchanges[grpname]['inherited'] = False - if dstgroup['tag_id'] != dst['id']: - grpchanges[grpname]['inherited'] = True + _grplist_add(dst['id'], grpname, block=group['blocked'], force=force, + opts=group) srcgrppkglist = [] dstgrppkglist = [] for pkg in group['packagelist']: - srcgrppkglist.append(pkg['package']) + srcgrppkglist.append(pkg) for pkg in dstgroups[grpname]['packagelist']: - dstgrppkglist.append(pkg['package']) + dstgrppkglist.append(pkg) for pkg in srcgrppkglist: if pkg not in dstgrppkglist: _grp_pkg_add(dst['name'], grpname, pkg['package'], @@ -11877,9 +11878,9 @@ class RootExports(object): srcgrpreqlist = [] dstgrpreqlist = [] for grp in group['grouplist']: - srcgrpreqlist.append(grp['name']) + srcgrpreqlist.append(grp) for grp in dstgroups[grpname]['grouplist']: - dstgrpreqlist.append(grp['name']) + dstgrpreqlist.append(grp) for grp in srcgrpreqlist: if grp not in dstgrpreqlist: _grp_req_add(dst['name'], grpname, grp['name'], @@ -11887,12 +11888,21 @@ class RootExports(object): _delete_event_id() if remove: for pkg in dstgrppkglist: - if pkg not in srcgrppkglist and pkg['tag_id'] == dst['id']: - _grp_pkg_remove(dst['name'], grpname, pkg['package'], force=force) + if pkg not in srcgrppkglist: + if pkg['tag_id'] == dst['id']: + _grp_pkg_remove(dst['name'], grpname, pkg['package'], + force=force) + else: + _grp_pkg_add(dst['id'], grpname, pkg['package'], + block=True, force=force) _delete_event_id() for grp in dstgrpreqlist: - if grp not in srcgrpreqlist and grp['group_id'] == dst['id']: - _grp_req_remove(dst['name'], grpname, grp['name'], force=force) + if grp not in srcgrpreqlist: + if grp['group_id'] == dst['id']: + _grp_req_remove(dst['name'], grpname, grp['name'], force=force) + else: + _grp_req_add(dst['name'], grpname, grp['name'], + block=True, force=force) _delete_event_id() def moveBuild(self, tag1, tag2, build, force=False): diff --git a/tests/test_hub/test_massTag.py b/tests/test_hub/test_massTag.py new file mode 100644 index 00000000..e5bbc40a --- /dev/null +++ b/tests/test_hub/test_massTag.py @@ -0,0 +1,53 @@ +import mock +import unittest +import koji +import kojihub + + +class TestDeleteEventId(unittest.TestCase): + @mock.patch('kojihub.context') + def test_delete_event_id(self, context): + kojihub.context.event_id = 123 + kojihub._delete_event_id() + self.assertFalse(hasattr(context, 'event_id')) + + @mock.patch('kojihub.context') + def test_delete_event_id_none(self, context): + kojihub._delete_event_id() + self.assertFalse(hasattr(context, 'event_id')) + + +class TestMassTag(unittest.TestCase): + def setUp(self): + self.get_tag = mock.patch('kojihub.get_tag').start() + self.get_build = mock.patch('kojihub.get_build').start() + self.get_user = mock.patch('kojihub.get_user').start() + self._direct_tag_build = mock.patch('kojihub._direct_tag_build').start() + self._delete_event_id = mock.patch('kojihub._delete_event_id').start() + self.context = mock.patch('kojihub.context').start() + self.context.session.assertPerm = mock.MagicMock() + self.hub = kojihub.RootExports() + + def tearDown(self): + mock.patch.stopall() + + def test_no_permission(self): + self.context.session.assertPerm.side_effect = koji.ActionNotAllowed + with self.assertRaises(koji.ActionNotAllowed): + self.hub.massTag('tag', ['n-v-r1']) + self.context.session.assertPerm.assert_called_once_with('tag') + + def test_non_existent_tag(self): + self.hub.massTag('non-existent-tag', ['n-v-r-1', 'n-v-r-2']) + + def test_non_existent_build(self): + self.hub.massTag('tag', ['non-existent-nvr']) + + def test_correct_tagging_mixed_build_id_nvr(self): + self.hub.massTag('tag', ['n-v-r1', 123]) + + def test_correct_tagging_tag_id(self): + self.hub.massTag(1234, ['n-v-r1', 123]) + + def test_correct_tagging_tag_dict(self): + self.hub.massTag({'id': 1234, 'name': 'tag'}, ['n-v-r1', 123]) diff --git a/tests/test_hub/test_snapshotTag.py b/tests/test_hub/test_snapshotTag.py new file mode 100644 index 00000000..c57144ca --- /dev/null +++ b/tests/test_hub/test_snapshotTag.py @@ -0,0 +1,120 @@ +import mock +import unittest +import koji +import kojihub + + +class TestSnapshotTag(unittest.TestCase): + def setUp(self): + self._create_tag = mock.patch('kojihub._create_tag').start() + self.get_tag = mock.patch('kojihub.get_tag').start() + self.get_build = mock.patch('kojihub.get_build').start() + self.get_user = mock.patch('kojihub.get_user').start() + self._direct_tag_build = mock.patch('kojihub._direct_tag_build').start() + self._direct_pkglist_add = mock.patch('kojihub._direct_pkglist_add').start() + self._delete_event_id = mock.patch('kojihub._delete_event_id').start() + self._grplist_add = mock.patch('kojihub._grplist_add').start() + self._grp_pkg_add = mock.patch('kojihub._grp_pkg_add').start() + self._grp_req_add = mock.patch('kojihub._grp_req_add').start() + self.readTagGroups = mock.patch('kojihub.readTagGroups').start() + self.readTaggedBuilds = mock.patch('kojihub.readTaggedBuilds').start() + self.context = mock.patch('kojihub.context').start() + self.context.session.assertPerm = mock.MagicMock() + self.hub = kojihub.RootExports() + self.hub.listPackages = mock.MagicMock() + self.hub.massTag = mock.MagicMock() + + def tearDown(self): + mock.patch.stopall() + + def test_no_permission(self): + self.context.session.assertPerm.side_effect = koji.ActionNotAllowed + with self.assertRaises(koji.ActionNotAllowed): + self.hub.snapshotTag('src', 'dst') + self.context.session.assertPerm.assert_called_once_with('tag') + + def test_builds_without_pkgs(self): + with self.assertRaises(koji.ParameterError): + self.hub.snapshotTag('src', 'dst', builds=True, pkgs=False) + + def test_existing_dst(self): + self.get_tag.side_effect = [{'id': 1}, {'id': 2}] + with self.assertRaises(koji.GenericError) as cm: + self.hub.snapshotTag('src', 'dst') + self.assertEqual("Target tag already exists", str(cm.exception)) + + def test_locked_without_force(self): + self.get_tag.side_effect = [None, {'id': 1, 'locked': True}] + with self.assertRaises(koji.GenericError) as cm: + self.hub.snapshotTag('src', 'dst') + self.assertEqual("Source tag is locked, use force to copy", str(cm.exception)) + + def test_correct_all(self): + src = { + 'id': 1, + 'name': 'src', + 'parent': 2, + 'locked': True, + 'arches': 'x86_64 s390x', + 'perm_id': 3, + 'maven_support': True, + 'maven_include_all': False, + 'extra': {'extra_field': 'text'}, + } + dst = src.copy() + dst['id'] = 11 + dst['name'] = 'dst' + pkg = { + 'package_name': 'pkg1', + 'owner_name': 'owner', + 'blocked': False, + 'extra_arches': None, + } + build = { + 'id': 21, + 'nvr': 'n-v-r', + } + + self.get_tag.side_effect = [ + None, # non-existing dst + src, # retrieve src + dst, # retrieve created dst + ] + self._create_tag.return_value = dst['id'] + self.hub.listPackages.return_value = [pkg] + self.readTaggedBuilds.return_value = [build] + self.readTagGroups.return_value = [ + { + 'id': 1, + 'name': 'group', + 'blocked': False, + 'packagelist': [{'package': 'pkg', 'blocked': False}], + 'grouplist': [{'name': 'group2', 'blocked': False}], + } + ] + + # call + self.hub.snapshotTag('src', 'dst', force=True) + + self._create_tag.assert_called_once_with('dst', parent=None, arches=src['arches'], + perm=src['perm_id'], locked=src['locked'], + maven_support=src['maven_support'], + maven_include_all=src['maven_include_all'], + extra=src['extra']) + self.get_tag.assert_has_calls([ + mock.call('dst'), + mock.call('src', event=None, strict=True), + mock.call(dst['id'], strict=True), + ]) + self.hub.listPackages.assert_called_once_with(tagID=src['id'], event=None, inherited=True) + self._direct_pkglist_add.assert_called_once_with( + taginfo=dst['id'], + pkginfo=pkg['package_name'], + owner=pkg['owner_name'], + block=pkg['blocked'], + extra_arches=pkg['extra_arches'], + force=True, + update=False, + ) + self.readTaggedBuilds.assert_called_once_with(tag=src['id'], inherit=True, event=None, latest=True) + self.hub.massTag.assert_called_once_with(dst['id'], [build]) diff --git a/tests/test_hub/test_snapshotTagModify.py b/tests/test_hub/test_snapshotTagModify.py new file mode 100644 index 00000000..386bf196 --- /dev/null +++ b/tests/test_hub/test_snapshotTagModify.py @@ -0,0 +1,209 @@ +import mock +import unittest +import koji +import kojihub + + +class TestSnapshotTagModify(unittest.TestCase): + def setUp(self): + self._create_tag = mock.patch('kojihub._create_tag').start() + self.get_tag = mock.patch('kojihub.get_tag').start() + self.get_build = mock.patch('kojihub.get_build').start() + self.get_user = mock.patch('kojihub.get_user').start() + self._direct_tag_build = mock.patch('kojihub._direct_tag_build').start() + self._direct_untag_build = mock.patch('kojihub._direct_untag_build').start() + self._tag_build = mock.patch('kojihub._tag_build').start() + self._untag_build = mock.patch('kojihub._untag_build').start() + self._direct_pkglist_add = mock.patch('kojihub._direct_pkglist_add').start() + self._delete_event_id = mock.patch('kojihub._delete_event_id').start() + self._grplist_add = mock.patch('kojihub._grplist_add').start() + self._grplist_remove = mock.patch('kojihub._grplist_remove').start() + self._grp_pkg_add = mock.patch('kojihub._grp_pkg_add').start() + self._grp_pkg_remove = mock.patch('kojihub._grp_pkg_remove').start() + self._grp_req_add = mock.patch('kojihub._grp_req_add').start() + self._grp_req_remove = mock.patch('kojihub._grp_req_remove').start() + self.readTagGroups = mock.patch('kojihub.readTagGroups').start() + self.readTaggedBuilds = mock.patch('kojihub.readTaggedBuilds').start() + self.context = mock.patch('kojihub.context').start() + self.context.session.assertPerm = mock.MagicMock() + self.edit_tag = mock.patch('kojihub.edit_tag').start() + self.hub = kojihub.RootExports() + self.hub.listPackages = mock.MagicMock() + self.hub.massTag = mock.MagicMock() + + def tearDown(self): + mock.patch.stopall() + + def test_no_permission(self): + self.context.session.assertPerm.side_effect = koji.ActionNotAllowed + with self.assertRaises(koji.ActionNotAllowed): + self.hub.snapshotTagModify('src', 'dst') + self.context.session.assertPerm.assert_called_once_with('tag') + + def test_builds_without_pkgs(self): + with self.assertRaises(koji.ParameterError): + self.hub.snapshotTagModify('src', 'dst', builds=True, pkgs=False) + + def test_nonexisting_dst(self): + self.get_tag.side_effect = [{'id': 1, 'locked': False}, koji.GenericError('xx')] + with self.assertRaises(koji.GenericError) as cm: + self.hub.snapshotTagModify('src', 'dst') + self.assertEqual("xx", str(cm.exception)) + + def test_locked_without_force_both(self): + self.get_tag.side_effect = [{'id': 1, 'locked': True}, {'id': 2, 'locked': True}] + with self.assertRaises(koji.GenericError) as cm: + self.hub.snapshotTagModify('src', 'dst') + self.assertEqual("Source or destination tag is locked, use force to copy", str(cm.exception)) + + def test_locked_without_force_src(self): + self.get_tag.side_effect = [{'id': 1, 'locked': True}, {'id': 2, 'locked': False}] + with self.assertRaises(koji.GenericError) as cm: + self.hub.snapshotTagModify('src', 'dst') + self.assertEqual("Source or destination tag is locked, use force to copy", str(cm.exception)) + + def test_locked_without_force_dst(self): + self.get_tag.side_effect = [{'id': 1, 'locked': False}, {'id': 2, 'locked': True}] + with self.assertRaises(koji.GenericError) as cm: + self.hub.snapshotTagModify('src', 'dst') + self.assertEqual("Source or destination tag is locked, use force to copy", str(cm.exception)) + + def test_correct_all(self): + src = { + 'id': 1, + 'name': 'src', + 'parent': 2, + 'locked': True, + 'arches': 'x86_64 s390x', + 'perm_id': 3, + 'maven_support': True, + 'maven_include_all': False, + 'extra': {'extra_field': 'text'}, + } + dst = src.copy() + dst['id'] = 11 + dst['name'] = 'dst' + pkg1 = { + 'tag_id': src['id'], + 'package_name': 'pkg1', + 'owner_name': 'owner', + 'blocked': False, + 'extra_arches': None, + } + pkg2 = { + 'tag_id': dst['id'], + 'package_name': 'pkg2', + 'owner_name': 'owner', + 'blocked': False, + 'extra_arches': None, + } + build = { + 'id': 21, + 'nvr': 'n-v-r', + 'package_name': pkg1['package_name'], + 'tag_name': 'src', + } + build2 = { + 'id': 22, + 'nvr': 'n-v-r2', + 'package_name': pkg1['package_name'], + 'tag_name': 'dst', + } + user = { + 'id': 321, + 'name': 'username', + } + src_group1 = { + 'id': 1, + 'name': 'group1', + 'blocked': False, + 'packagelist': [{'package': pkg1['package_name'], 'tag_id': src['id']}], + 'grouplist': [{'group_id': 5, 'name': 'group5', 'blocked': False}], + 'inherited': False, + } + src_group2 = { + 'id': 2, + 'name': 'group2', + 'blocked': False, + 'package_list': [], + 'grouplist': [], + 'inherited': False, + } + dst_group1 = { + 'id': 3, + 'name': 'group1', + 'blocked': False, + 'packagelist': [{'package': pkg2['package_name'], 'tag_id': dst['id']}], + 'grouplist': [{'group_id': 4, 'name': 'group4', 'blocked': False}], + 'inherited': False, + } + self.get_tag.side_effect = [ + src, # src + dst, # dst + dst, # edited dst + ] + self.get_user.return_value = user + self._create_tag.return_value = dst['id'] + self.hub.listPackages.side_effect = [[pkg1], [pkg2]] + self.readTaggedBuilds.side_effect = [[build], [build2]] + self.readTagGroups.side_effect = [[src_group1, src_group2], [dst_group1]] + self.context.session.user_id = user['id'] + + # call + self.hub.snapshotTagModify('src', 'dst', force=True, remove=True) + + # tests + self._create_tag.assert_not_called() + self.get_tag.assert_has_calls([ + mock.call('src', event=None, strict=True), + mock.call('dst', strict=True), + mock.call(dst['id'], strict=True), + ]) + + self.get_user.assert_called_once_with(user['id'], strict=True) + self.edit_tag.assert_called_once_with(dst['id'], parent=None, arches=src['arches'], + perm=src['perm_id'], locked=src['locked'], + maven_support=src['maven_support'], + maven_include_all=src['maven_include_all'], + extra=src['extra'], remove_extra=[]) + self.hub.listPackages.assert_has_calls([ + mock.call(tagID=src['id'], event=None, inherited=True), + mock.call(tagID=dst['id'], inherited=True) + ]) + self._direct_pkglist_add.assert_has_calls([ + # remove additional package + mock.call(dst, + pkg2['package_name'], + owner=pkg2['owner_name'], + block=True, + extra_arches=pkg2['extra_arches'], + force=True, + update=True), + # add missing package + mock.call(dst, + pkg1['package_name'], + owner=pkg1['owner_name'], + block=pkg1['blocked'], + extra_arches=pkg1['extra_arches'], + force=True, + update=False), + ]) + self.readTaggedBuilds.assert_has_calls([ + mock.call(src['id'], event=None, inherit=True, latest=True), + mock.call(dst['id'], inherit=False, latest=False), + ]) + self._direct_untag_build.assert_not_called() + self._untag_build.assert_called_once_with('dst', build2, force=True) + self._direct_tag_build.assert_called_once_with(dst, build, user, force=True) + self._grp_pkg_add.assert_called_once_with('dst', 'group1', pkg1['package_name'], + block=False, force=True) + self._grp_req_add.assert_has_calls([ + mock.call('dst', 'group1', 'group5', block=False, force=True), + mock.call('dst', 'group1', 'group4', block=True, force=True), + ]) + self._grplist_add.assert_has_calls([ + mock.call(dst['id'], 'group2', block=False, force=True), + mock.call(dst['id'], 'group1', block=False, force=True, opts=src_group1), + ]) + self._grplist_remove.assert_not_called() + self.hub.massTag.assert_not_called()