From 9bfefe782ef0b3cb7a0f8383f9387fb5ca6c92d8 Mon Sep 17 00:00:00 2001 From: Jana Cupova Date: Mon, 4 Apr 2022 08:38:29 +0200 Subject: [PATCH] Hub, plugins and tools inputs validation Fixes: https://pagure.io/koji/issue/3319 --- hub/kojihub.py | 173 +++- plugins/hub/kiwi.py | 13 +- plugins/hub/runroot_hub.py | 7 +- plugins/hub/save_failed_tree.py | 2 +- plugins/hub/sidetag_hub.py | 5 +- tests/test_hub/test_add_archivetype.py | 92 +- tests/test_hub/test_add_channel.py | 40 +- .../test_hub/test_add_external_repo_to_tag.py | 23 +- tests/test_hub/test_add_external_rpm.py | 54 +- tests/test_hub/test_add_host.py | 136 ++- tests/test_hub/test_add_user_krb_principal.py | 42 + tests/test_hub/test_build.py | 40 + tests/test_hub/test_build_image.py | 69 ++ .../test_hub/test_build_image_indirection.py | 33 + tests/test_hub/test_build_image_oz.py | 59 ++ tests/test_hub/test_cg_importer.py | 45 +- tests/test_hub/test_chain_build.py | 40 + tests/test_hub/test_chain_maven.py | 48 + tests/test_hub/test_create_notification.py | 193 +++- .../test_create_notification_block.py | 161 ++++ tests/test_hub/test_create_tag.py | 26 +- tests/test_hub/test_create_user.py | 14 +- tests/test_hub/test_delete_notification.py | 103 +++ .../test_delete_notification_block.py | 104 +++ tests/test_hub/test_disable_channel.py | 8 +- tests/test_hub/test_dist_repo.py | 31 +- tests/test_hub/test_download_task_output.py | 36 + tests/test_hub/test_edit_channel.py | 98 ++- tests/test_hub/test_edit_host.py | 77 +- tests/test_hub/test_edit_permission.py | 9 + tests/test_hub/test_edit_tag.py | 35 +- tests/test_hub/test_edit_tag_external_repo.py | 3 +- tests/test_hub/test_enable_channel.py | 8 +- tests/test_hub/test_getRPM.py | 4 +- tests/test_hub/test_get_archive.py | 28 + tests/test_hub/test_get_archive_type.py | 20 + tests/test_hub/test_get_build_notification.py | 22 + .../test_get_build_notification_block.py | 22 + .../test_get_build_notification_blocks.py | 29 + .../test_hub/test_get_build_notifications.py | 25 +- tests/test_hub/test_get_changelog_entries.py | 10 +- tests/test_hub/test_get_external_repo.py | 2 +- tests/test_hub/test_get_external_repos.py | 7 - tests/test_hub/test_get_next_release.py | 10 +- .../test_get_notification_recipients.py | 258 ++++++ tests/test_hub/test_grant_permissions.py | 10 + tests/test_hub/test_import_archive.py | 40 + .../test_hub/test_import_archive_internal.py | 23 + tests/test_hub/test_import_build.py | 270 +----- tests/test_hub/test_import_rpm.py | 187 ++++ tests/test_hub/test_list_archives.py | 361 ++++---- tests/test_hub/test_list_btypes.py | 98 +-- tests/test_hub/test_list_builds.py | 17 +- tests/test_hub/test_list_packages_simple.py | 40 + tests/test_hub/test_list_rpms.py | 77 +- .../{test_listing.py => test_list_tasks.py} | 0 tests/test_hub/test_lookup_name.py | 11 +- tests/test_hub/test_maven_build.py | 49 ++ tests/test_hub/test_new_build.py | 9 +- tests/test_hub/test_notifications.py | 821 ------------------ tests/test_hub/test_repo_delete.py | 13 + tests/test_hub/test_repo_init.py | 13 + tests/test_hub/test_repo_set_state.py | 13 + tests/test_hub/test_repos.py | 22 +- tests/test_hub/test_restart_hosts.py | 11 +- tests/test_hub/test_search.py | 2 +- tests/test_hub/test_tag_operations.py | 14 +- tests/test_hub/test_update_notification.py | 148 ++++ tests/test_hub/test_win_build.py | 68 ++ tests/test_hub/test_wrapper_rpm.py | 56 ++ tests/test_hub/test_write_signed_rpm.py | 23 + tests/test_plugins/test_runroot_hub.py | 36 +- util/koji-gc | 23 + util/koji-shadow | 2 + util/koji-sidetag-cleanup | 5 + 75 files changed, 3031 insertions(+), 1695 deletions(-) create mode 100644 tests/test_hub/test_add_user_krb_principal.py create mode 100644 tests/test_hub/test_build.py create mode 100644 tests/test_hub/test_build_image.py create mode 100644 tests/test_hub/test_build_image_indirection.py create mode 100644 tests/test_hub/test_build_image_oz.py create mode 100644 tests/test_hub/test_chain_build.py create mode 100644 tests/test_hub/test_chain_maven.py create mode 100644 tests/test_hub/test_create_notification_block.py create mode 100644 tests/test_hub/test_delete_notification.py create mode 100644 tests/test_hub/test_delete_notification_block.py create mode 100644 tests/test_hub/test_download_task_output.py create mode 100644 tests/test_hub/test_get_archive.py create mode 100644 tests/test_hub/test_get_archive_type.py create mode 100644 tests/test_hub/test_get_build_notification.py create mode 100644 tests/test_hub/test_get_build_notification_block.py create mode 100644 tests/test_hub/test_get_build_notification_blocks.py create mode 100644 tests/test_hub/test_get_notification_recipients.py create mode 100644 tests/test_hub/test_import_archive.py create mode 100644 tests/test_hub/test_import_archive_internal.py create mode 100644 tests/test_hub/test_import_rpm.py create mode 100644 tests/test_hub/test_list_packages_simple.py rename tests/test_hub/{test_listing.py => test_list_tasks.py} (100%) create mode 100644 tests/test_hub/test_maven_build.py delete mode 100644 tests/test_hub/test_notifications.py create mode 100644 tests/test_hub/test_repo_delete.py create mode 100644 tests/test_hub/test_repo_init.py create mode 100644 tests/test_hub/test_repo_set_state.py create mode 100644 tests/test_hub/test_update_notification.py create mode 100644 tests/test_hub/test_win_build.py create mode 100644 tests/test_hub/test_wrapper_rpm.py create mode 100644 tests/test_hub/test_write_signed_rpm.py diff --git a/hub/kojihub.py b/hub/kojihub.py index b2e50ccf..1da5f846 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -91,6 +91,30 @@ def xform_user_krb(entry): return entry +def convert_value(value, cast=None, message=None, exc_type=None, none_allowed=False): + if exc_type is None: + exc_type = koji.ParameterError + if value is None: + if not none_allowed: + raise(exc_type(message or "Invalid type")) + else: + return value + try: + return cast(value) + except (ValueError, TypeError): + raise(exc_type(message or f"Invalid type for value '{value}': {type(value)}")) + + +def check_value_type(value, cast=None, message=None, exc_type=None, none_allowed=False): + if none_allowed: + if value is None: + return value + if exc_type is None: + exc_type = koji.ParameterError + if not isinstance(value, cast): + raise (exc_type(message or f"Invalid type for value '{value}': {type(value)}")) + + class Task(object): """A task for the build hosts""" @@ -116,7 +140,7 @@ class Task(object): ('task.weight', 'weight')) def __init__(self, id): - self.id = int(id) + self.id = convert_value(id, cast=int) self.logger = logging.getLogger("koji.hub.Task") def _split_fields(self, fields=None): @@ -288,7 +312,7 @@ class Task(object): def setWeight(self, weight): """Set weight for task""" task_id = self.id - weight = float(weight) + weight = convert_value(weight, cast=float) info = self.getInfo(request=True) self.runCallbacks('preTaskStateChange', info, 'weight', weight) # access checks should be performed by calling function @@ -299,7 +323,7 @@ class Task(object): def setPriority(self, priority, recurse=False): """Set priority for task""" task_id = self.id - priority = int(priority) + priority = convert_value(priority, cast=int) info = self.getInfo(request=True) self.runCallbacks('preTaskStateChange', info, 'priority', priority) # access checks should be performed by calling function @@ -540,6 +564,22 @@ def make_task(method, arglist, **opts): priority: the priority of the task assign: a host_id to assign the task to """ + check_value_type(method, cast=str) + if 'parent' in opts: + opts['parent'] = convert_value(opts['parent'], cast=int) + if 'label' in opts: + check_value_type(opts['label'], cast=str) + if 'owner' in opts: + if not isinstance(opts['owner'], int): + opts['owner'] = get_user(opts['owner'], strict=True)['id'] + if 'arch' in opts: + opts['arch'] = koji.parse_arches(opts['arch'], strict=True, allow_none=True) + if 'priority' in opts: + opts['priority'] = \ + convert_value(opts['priority'], cast=int) + if 'assign' in opts: + if not isinstance(opts['assign'], int): + opts['assign'] = get_host(opts['assign'], strict=True)['id'] if 'parent' in opts: # for subtasks, we use some of the parent's options as defaults fields = ('state', 'owner', 'channel_id', 'priority', 'arch') @@ -1389,6 +1429,8 @@ def readTaggedBuilds(tag, event=None, inherit=False, latest=False, package=None, 'build.state = %(st_complete)i' ] if package: + if not isinstance(package, str): + package = lookup_package(package, strict=True)['name'] clauses.append('package.name = %(package)s') if owner: clauses.append('users.name = %(owner)s') @@ -1636,8 +1678,10 @@ def check_tag_access(tag_id, user_id=None): """ if user_id is None: user_id = context.session.user_id - if user_id is None: - raise koji.GenericError("a user_id is required") + if not user_id: + raise koji.GenericError("a user_id is required") + else: + user_id = convert_value(user_id, cast=int) perms = koji.auth.get_user_perms(user_id) override = False if 'admin' in perms: @@ -2376,12 +2420,14 @@ def edit_channel(channelInfo, **kw): return False if kw.get('name'): - if not isinstance(kw['name'], str): - raise koji.GenericError("new channel name must be a string") verify_name_internal(kw['name']) dup_check = get_channel(kw['name'], strict=False) if dup_check: raise koji.GenericError("channel %(name)s already exists (id=%(id)i)" % dup_check) + if kw.get('description'): + check_value_type(kw['description'], cast=str) + if kw.get('comment'): + check_value_type(kw['comment'], cast=str) update = UpdateProcessor('channels', values={'channelID': channel['id']}, @@ -2433,8 +2479,7 @@ def add_channel(channel_name, description=None): :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") + check_value_type(description, cast=str, none_allowed=True) verify_name_internal(channel_name) dup_check = get_channel(channel_name, strict=False) if dup_check: @@ -2449,6 +2494,7 @@ def add_channel(channel_name, description=None): def set_channel_enabled(channelname, enabled=True, comment=None): context.session.assertPerm('host') + check_value_type(comment, cast=str, none_allowed=True) channel = get_channel(channelname) if not channel: raise koji.GenericError('No such channel: %s' % channelname) @@ -2640,6 +2686,7 @@ def repo_init(tag, task_id=None, with_src=False, with_debuginfo=False, event=Non Returns a dictionary containing repo_id, event_id """ + task_id = convert_value(task_id, cast=int, none_allowed=True) logger = logging.getLogger("koji.hub.repo_init") state = koji.REPO_INIT tinfo = get_tag(tag, strict=True, event=event) @@ -2694,9 +2741,9 @@ def repo_init(tag, task_id=None, with_src=False, with_debuginfo=False, event=Non 'tag_id': tinfo['id'], 'task_id': task_id, 'event_id': event_id, - 'with_src': with_src, - 'with_separate_src': with_separate_src, - 'with_debuginfo': with_debuginfo, + 'with_src': bool(with_src), + 'with_separate_src': bool(with_separate_src), + 'with_debuginfo': bool(with_debuginfo), } with open('%s/repo.json' % repodir, 'wt', encoding='utf-8') as fp: json.dump(repo_info, fp, indent=2) @@ -2823,9 +2870,12 @@ def _write_maven_repo_metadata(destdir, artifacts): def dist_repo_init(tag, keys, task_opts): """Create a new repo entry in the INIT state, return full repo data""" state = koji.REPO_INIT + check_value_type(keys, cast=list) tinfo = get_tag(tag, strict=True) tag_id = tinfo['id'] + check_value_type(task_opts, cast=dict) event = task_opts.get('event') + event = convert_value(event, cast=int, none_allowed=True) volume = task_opts.get('volume') if volume is not None: volume = lookup_name('volume', volume, strict=True)['name'] @@ -2853,6 +2903,7 @@ def dist_repo_init(tag, keys, task_opts): os.symlink(relpath, basedir) # handle comps if task_opts.get('comps'): + check_value_type(task_opts['comps'], cast=str) groupsdir = joinpath(repodir, 'groups') koji.ensuredir(groupsdir) shutil.copyfile(joinpath(koji.pathinfo.work(), @@ -2877,6 +2928,7 @@ def dist_repo_init(tag, keys, task_opts): def repo_set_state(repo_id, state, check=True): """Set repo state""" + repo_id = convert_value(repo_id, cast=int) if check: # The repo states are sequential, going backwards makes no sense q = """SELECT state FROM repo WHERE id = %(repo_id)s FOR UPDATE""" @@ -2934,6 +2986,7 @@ def repo_delete(repo_id): """Attempt to mark repo deleted, return number of references If the number of references is nonzero, no change is made""" + repo_id = convert_value(repo_id, cast=int) # get a row lock on the repo q = """SELECT state FROM repo WHERE id = %(repo_id)s FOR UPDATE""" _singleValue(q, locals()) @@ -3235,14 +3288,12 @@ def name_or_id_clause(table, info): """ if isinstance(info, dict): if 'id' in info: - try: - info = int(info['id']) - except (ValueError, TypeError): - raise koji.ParameterError('Invalid name or id value: %r' % info) + info = convert_value(info['id'], cast=int, + message=fr"Invalid name or id value: {info}") elif 'name' in info: info = info['name'] else: - raise koji.ParameterError('Invalid name or id value: %r' % info) + raise koji.ParameterError(fr'Invalid name or id value: {info}') if isinstance(info, int): clause = f"({table}.id = %({table}_id)s)" values = {f"{table}_id": info} @@ -3250,7 +3301,7 @@ def name_or_id_clause(table, info): clause = f"({table}.name = %({table}_name)s)" values = {f"{table}_name": info} else: - raise koji.ParameterError('Invalid name or id value: %r' % info) + raise koji.ParameterError(fr"Invalid name or id value: {info}") return clause, values @@ -3557,6 +3608,10 @@ def _edit_tag(tagInfo, **kwargs): if not context.opts.get('EnableMaven') \ and dslice(kwargs, ['maven_support', 'maven_include_all'], strict=False): raise koji.GenericError("Maven support not enabled") + if kwargs.get('remove_extra'): + check_value_type(kwargs['remove_extra'], cast=list, none_allowed=True) + if kwargs.get('block_extra'): + check_value_type(kwargs['block_extra'], cast=list, none_allowed=True) tag = get_tag(tagInfo, strict=True) if 'perm' in kwargs and 'perm_id' not in kwargs: @@ -3882,6 +3937,8 @@ def add_external_repo_to_tag(tag_info, repo_info, priority, merge_mode='koji', a if arches is not None: arches = koji.parse_arches(arches, strict=True) + priority = convert_value(priority, cast=int) + tag_repos = get_tag_external_repos(tag_info=tag_id) if [tr for tr in tag_repos if tr['external_repo_id'] == repo_id]: raise koji.GenericError('tag %s already associated with external repo %s' % @@ -4442,8 +4499,7 @@ def get_next_release(build_info, incr=1): :raises: BuildError if the latest build uses a release value that Koji does not know how to increment. """ - if not isinstance(incr, int): - raise koji.ParameterError("incr parameter must be an integer") + incr = convert_value(incr, cast=int, message='incr parameter must be an integer') values = { 'name': build_info['name'], 'version': build_info['version'], @@ -5522,6 +5578,10 @@ def edit_host(hostInfo, **kw): if not changes: return False + for change in changes: + if change in ['description', 'comment', 'arches']: + check_value_type(kw[change], cast=str) + update = UpdateProcessor('host_config', values=host, clauses=['host_id = %(id)i']) update.make_revoke() update.execute() @@ -5675,7 +5735,7 @@ def query_buildroots(hostID=None, tagID=None, state=None, rpmID=None, archiveID= def get_buildroot(buildrootID, strict=False): """Return information about a buildroot. buildrootID must be an int ID.""" - + buildrootID = convert_value(buildrootID, cast=int) result = query_buildroots(buildrootID=buildrootID) if len(result) == 0: if strict: @@ -5741,6 +5801,7 @@ def list_channels(hostID=None, event=None, enabled=None): def new_package(name, strict=True): + verify_name_internal(name) c = context.cnx.cursor() # TODO - table lock? # check for existing @@ -6171,6 +6232,10 @@ def import_build(srpm, rpms, brmap=None, task_id=None, build_id=None, logs=None) """ if brmap is None: brmap = {} + else: + check_value_type(brmap, cast=dict) + check_value_type(srpm, cast=str) + check_value_type(rpms, cast=list) koji.plugin.run_callbacks('preImport', type='build', srpm=srpm, rpms=rpms, brmap=brmap, task_id=task_id, build_id=build_id, build=None, logs=logs) uploadpath = koji.pathinfo.work() @@ -6536,7 +6601,6 @@ class CG_Importer(object): def get_metadata(self, metadata, directory): """Get the metadata from the args""" - if isinstance(metadata, dict): self.metadata = metadata try: @@ -6548,8 +6612,7 @@ class CG_Importer(object): if metadata is None: # default to looking for uploaded file metadata = 'metadata.json' - if not isinstance(metadata, str): - raise koji.GenericError("Invalid type for metadata value: %r" % type(metadata)) + check_value_type(metadata, cast=str) if metadata.endswith('.json'): # handle uploaded metadata workdir = koji.pathinfo.work() @@ -7324,8 +7387,7 @@ def get_archive_type(filename=None, type_name=None, type_id=None, strict=False): elif type_name: return _get_archive_type_by_name(type_name, strict) elif filename: - # we handle that below - pass + check_value_type(filename, cast=str) else: raise koji.GenericError('one of filename, type_name, or type_id must be specified') @@ -7365,6 +7427,8 @@ def add_archive_type(name, description, extensions): """ context.session.assertPerm('admin') verify_name_internal(name) + check_value_type(description, cast=str) + check_value_type(extensions, cast=str) data = {'name': name, 'description': description, 'extensions': extensions, @@ -8774,6 +8838,9 @@ def tag_notification(is_successful, tag_id, from_id, build_id, user_id, ignore_s failure_msg=''): if context.opts.get('DisableNotifications'): return + if user_id: + if not isinstance(user_id, int): + user_id = get_user(user_id, strict=True) if is_successful: state = koji.BUILD_STATES['COMPLETE'] else: @@ -10320,6 +10387,7 @@ def policy_data_from_task_args(method, arglist): # newRepo has a 'src' parameter that means something else break if k in params: + check_value_type(params[k], cast=str) policy_data['source'] = params.get(k) break # parameters that indicate build target @@ -10482,8 +10550,10 @@ class RootExports(object): context.session.assertPerm('host') if options is None: args = [] - else: + elif isinstance(options, dict): args = [options] + else: + raise koji.ParameterError('Invalid type of options: %s' % type(options)) return make_task('restartHosts', args, priority=priority) def build(self, src, target, opts=None, priority=None, channel=None): @@ -10499,6 +10569,7 @@ class RootExports(object): if not opts: opts = {} taskOpts = {} + check_value_type(src, cast=str) if priority: if priority < 0: if not context.session.hasPerm('admin'): @@ -10522,6 +10593,7 @@ class RootExports(object): Returns a list of all the dependent task ids """ context.session.assertLogin() + check_value_type(srcs, cast=list) if not opts: opts = {} taskOpts = {} @@ -10550,6 +10622,7 @@ class RootExports(object): context.session.assertLogin() if not context.opts.get('EnableMaven'): raise koji.GenericError("Maven support not enabled") + check_value_type(url, cast=str) if not opts: opts = {} taskOpts = {} @@ -10586,13 +10659,13 @@ class RootExports(object): if not opts: opts = {} + check_value_type(url, cast=str) + build = self.getBuild(build, strict=True) if list_rpms(build['id']) and not (opts.get('scratch') or opts.get('create_build')): raise koji.PreBuildError('wrapper rpms for %s have already been built' % koji.buildLabel(build)) - build_target = self.getBuildTarget(target) - if not build_target: - raise koji.PreBuildError('no such build target: %s' % target) + build_target = self.getBuildTarget(target, strict=True) build_tag = self.getTag(build_target['build_tag'], strict=True) repo_info = self.getRepo(build_tag['id']) if not repo_info: @@ -10602,7 +10675,8 @@ class RootExports(object): taskOpts = {} if priority: taskOpts['priority'] = koji.PRIO_DEFAULT + priority - taskOpts['channel'] = channel + if channel: + taskOpts['channel'] = channel return make_task('wrapperRPM', [url, build_target, build, None, opts], **taskOpts) @@ -10621,6 +10695,7 @@ class RootExports(object): context.session.assertLogin() if not context.opts.get('EnableMaven'): raise koji.GenericError("Maven support not enabled") + check_value_type(builds, cast=list) taskOpts = {} if priority: if priority < 0: @@ -10650,7 +10725,9 @@ class RootExports(object): context.session.assertLogin() if not context.opts.get('EnableWin'): raise koji.GenericError("Windows support not enabled") - targ_info = self.getBuildTarget(target) + check_value_type(vm, cast=str) + check_value_type(url, cast=str) + targ_info = get_build_target(target, strict=True) policy_data = {'vm_name': vm, 'tag': targ_info['dest_tag']} assert_policy('vm', policy_data) @@ -10676,6 +10753,8 @@ class RootExports(object): if img_type not in ('livecd', 'appliance', 'livemedia'): raise koji.GenericError('Unrecognized image type: %s' % img_type) + for i in [name, ksfile, version]: + check_value_type(i, cast=str) context.session.assertPerm(img_type) @@ -10723,6 +10802,8 @@ class RootExports(object): """ Create an image using a kickstart file and group package list. """ + for i in [name, inst_tree, version]: + check_value_type(i, cast=str) context.session.assertPerm('image') taskOpts = {'channel': 'image'} if priority: @@ -10986,6 +11067,9 @@ class RootExports(object): def downloadTaskOutput(self, taskID, fileName, offset=0, size=-1, volume=None): """Download the file with the given name, generated by the task with the given ID.""" + size = convert_value(size, cast=int) + if volume: + volume = self.getVolume(volume, strict=True)['name'] if '..' in fileName: raise koji.GenericError('Invalid file name: %s' % fileName) filePath = '%s/%s/%s' % (koji.pathinfo.work(volume), @@ -12493,6 +12577,7 @@ class RootExports(object): verify_name_internal(permission) if description is not None and not create: raise koji.GenericError('Description should be specified only with create.') + check_value_type(description, cast=str, none_allowed=True) user_id = get_user(userinfo, strict=True)['id'] perm = lookup_perm(permission, strict=(not create), create=create) perm_id = perm['id'] @@ -12526,6 +12611,7 @@ class RootExports(object): def editPermission(self, permission, description): """Edit a permission description""" context.session.assertPerm('admin') + check_value_type(description, cast=str) perm = lookup_perm(permission, strict=True) perm_id = perm['id'] update = UpdateProcessor('permissions', clauses=['id=%(perm_id)i'], @@ -12550,22 +12636,18 @@ class RootExports(object): raise koji.GenericError('user already exists: %s' % username) if krb_principal and get_user_by_krb_principal(krb_principal): raise koji.GenericError( - 'user with this Kerberos principal already exists: %s' - % krb_principal) - - return context.session.createUser(username, status=status, - krb_principal=krb_principal) + 'user with this Kerberos principal already exists: %s' % krb_principal) + status = convert_value(status, cast=int, none_allowed=True) + return context.session.createUser(username, status=status, krb_principal=krb_principal) def addUserKrbPrincipal(self, user, krb_principal): """Add a Kerberos principal for user""" context.session.assertPerm('admin') userinfo = get_user(user, strict=True) - if not krb_principal: - raise koji.GenericError('krb_principal must be specified') + verify_name_user(krb=krb_principal) if get_user_by_krb_principal(krb_principal): raise koji.GenericError( - 'user with this Kerberos principal already exists: %s' - % krb_principal) + 'user with this Kerberos principal already exists: %s' % krb_principal) return context.session.setKrbPrincipal(userinfo['name'], krb_principal) def removeUserKrbPrincipal(self, user, krb_principal): @@ -13036,6 +13118,7 @@ class RootExports(object): # builder user can already exist, if host tried to log in before adding into db userinfo = {'name': hostname} if krb_principal: + verify_name_user(krb=krb_principal) userinfo['krb_principal'] = krb_principal user = get_user(userInfo=userinfo) if user: @@ -13757,6 +13840,7 @@ class BuildRoot(object): self.is_standard = True def new(self, host, repo, arch, task_id=None, ctype='chroot'): + arch = koji.parse_arches(arch, strict=True, allow_none=True) state = koji.BR_STATES['INIT'] br_id = _singleValue("SELECT nextval('buildroot_id_seq')", strict=True) insert = InsertProcessor('buildroot', data={'id': br_id}) @@ -14016,7 +14100,7 @@ class Host(object): def taskSetWait(self, parent, tasks): """Mark task waiting and subtasks awaited""" - + check_value_type(tasks, cast=list, none_allowed=True) # mark parent as waiting update = UpdateProcessor('task', clauses=['id=%(parent)s'], values=locals()) update.set(waiting=True) @@ -14134,6 +14218,7 @@ class Host(object): def updateHost(self, task_load, ready): host_data = get_host(self.id) + task_load = convert_value(task_load, cast=float) if task_load != host_data['task_load'] or ready != host_data['ready']: c = context.cnx.cursor() id = self.id @@ -14416,6 +14501,7 @@ class HostExports(object): def moveImageBuildToScratch(self, task_id, results): """move a completed image scratch build into place""" + check_value_type(results, cast=dict) host = Host() host.verify() task = Task(task_id) @@ -14861,6 +14947,8 @@ class HostExports(object): pkg_id = build['package_id'] tag_id = get_tag(tag, strict=True)['id'] user_id = task.getOwner() + if not isinstance(fromtag, str): + tag = get_tag(tag, strict=True)['name'] policy_data = {'tag': tag, 'build': build, 'fromtag': fromtag} policy_data['user_id'] = user_id if fromtag is None: @@ -15156,6 +15244,7 @@ class HostExports(object): host = Host() host.verify() rinfo = repo_info(repo_id, strict=True) + check_value_type(data, cast=dict) koji.plugin.run_callbacks('preRepoDone', repo=rinfo, data=data, expire=expire) if rinfo['state'] != koji.REPO_INIT: raise koji.GenericError("Repo %(id)s not in INIT state (got %(state)s)" % rinfo) diff --git a/plugins/hub/kiwi.py b/plugins/hub/kiwi.py index fd4877c5..57c0c960 100644 --- a/plugins/hub/kiwi.py +++ b/plugins/hub/kiwi.py @@ -18,10 +18,19 @@ koji.tasks.LEGACY_SIGNATURES['createKiwiImage'] = [ def kiwiBuild(target, arches, desc_url, desc_path, optional_arches=None, profile=None, scratch=False, priority=None, make_prep=False, repos=None, release=None): context.session.assertPerm('image') + for i in [desc_url, desc_path, profile, release]: + if i is not None: + kojihub.check_value_type(i, cast=str) + if repos: + kojihub.check_value_type(repos, cast=list) + kojihub.get_build_targets(target, strict=True) + arches = koji.parse_arches(arches, strict=True, allow_none=False) + optional_arches = koji.parse_arches(optional_arches, strict=True, allow_none=True) taskOpts = { 'channel': 'image', } if priority: + priority = kojihub.convert_value(priority, cast=int) if priority < 0: if not context.session.hasPerm('admin'): raise koji.ActionNotAllowed( @@ -31,10 +40,10 @@ def kiwiBuild(target, arches, desc_url, desc_path, optional_arches=None, profile opts = { 'optional_arches': optional_arches, 'profile': profile, - 'scratch': scratch, + 'scratch': bool(scratch), 'release': release, 'repos': repos or [], - 'make_prep': make_prep, + 'make_prep': bool(make_prep), } return kojihub.make_task('kiwiBuild', [target, arches, desc_url, desc_path, opts], diff --git a/plugins/hub/runroot_hub.py b/plugins/hub/runroot_hub.py index 847cf447..c6f3639a 100644 --- a/plugins/hub/runroot_hub.py +++ b/plugins/hub/runroot_hub.py @@ -30,12 +30,17 @@ def get_channel_arches(channel): def runroot(tagInfo, arch, command, channel=None, **opts): """ Create a runroot task """ context.session.assertPerm('runroot') + arch = koji.parse_arches(arch, strict=True, allow_none=False) + kojihub.check_value_type(command, cast=str) taskopts = { 'priority': 15, 'arch': arch, } - taskopts['channel'] = channel or 'runroot' + if channel is None: + taskopts['channel'] = 'runroot' + else: + taskopts['channel'] = kojihub.get_channel(channel, strict=True)['name'] tag = kojihub.get_tag(tagInfo, strict=True) if arch == 'noarch': diff --git a/plugins/hub/save_failed_tree.py b/plugins/hub/save_failed_tree.py index 855eaa81..45c58366 100644 --- a/plugins/hub/save_failed_tree.py +++ b/plugins/hub/save_failed_tree.py @@ -22,7 +22,7 @@ def saveFailedTree(buildrootID, full=False, **opts): global config, allowed_methods # let it raise errors - buildrootID = int(buildrootID) + buildrootID = kojihub.convert_value(buildrootID, cast=int) full = bool(full) # read configuration only once diff --git a/plugins/hub/sidetag_hub.py b/plugins/hub/sidetag_hub.py index 5ce0e4d9..2daf98a6 100644 --- a/plugins/hub/sidetag_hub.py +++ b/plugins/hub/sidetag_hub.py @@ -21,7 +21,8 @@ from kojihub import ( # noqa: E402 get_tag, get_user, nextval, - policy_get_user + policy_get_user, + check_value_type ) CONFIG_FILE = "/etc/koji-hub/plugins/sidetag.conf" @@ -270,9 +271,11 @@ def editSideTag(sidetag, debuginfo=None, rpm_macros=None, remove_rpm_macros=None if debuginfo is not None: kwargs['extra']['with_debuginfo'] = bool(debuginfo) if rpm_macros is not None: + check_value_type(rpm_macros, cast=dict) for macro, value in rpm_macros.items(): kwargs['extra']['rpm.macro.%s' % macro] = value if remove_rpm_macros is not None: + check_value_type(remove_rpm_macros, cast=list) kwargs['remove_extra'] = ['rpm.macro.%s' % m for m in remove_rpm_macros] _edit_tag(sidetag['id'], **kwargs) diff --git a/tests/test_hub/test_add_archivetype.py b/tests/test_hub/test_add_archivetype.py index 49cd35aa..fabeab38 100644 --- a/tests/test_hub/test_add_archivetype.py +++ b/tests/test_hub/test_add_archivetype.py @@ -10,54 +10,74 @@ IP = kojihub.InsertProcessor class TestAddArchiveType(unittest.TestCase): - @mock.patch('kojihub.verify_name_internal') - @mock.patch('kojihub._multiRow') - @mock.patch('kojihub.get_archive_type') - @mock.patch('kojihub.InsertProcessor') - def test_add_archive_type(self, InsertProcessor, get_archive_type, _multiRow, - verify_name_internal): - # Not sure why mock can't patch kojihub.context, so we do this - session = kojihub.context.session = mock.MagicMock() - mocks = [InsertProcessor, get_archive_type, session] - # It seems MagicMock will not automatically handle attributes that - # start with "assert" - session.assertPerm = mock.MagicMock() - verify_name_internal.return_value = None + def setUp(self): - # expected case - get_archive_type.return_value = None - insert = InsertProcessor.return_value + 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() + self.verify_name_internal = mock.patch('kojihub.verify_name_internal').start() + self.get_archive_type = mock.patch('kojihub.get_archive_type').start() + self._multiRow = mock.patch('kojihub._multiRow').start() + + 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 + + def test_add_archive_type_valid(self): + self.verify_name_internal.return_value = None + self.get_archive_type.return_value = None kojihub.add_archive_type('deb', 'Debian package', 'deb') - InsertProcessor.assert_called_once() - insert.execute.assert_called_once() - args, kwargs = InsertProcessor.call_args - ip = IP(*args, **kwargs) - self.assertEqual(ip.table, 'archivetypes') - self.assertEqual(ip.data, {'name': 'deb', - 'description': 'Debian package', - 'extensions': 'deb'}) - self.assertEqual(ip.rawdata, {}) - session.assertPerm.assert_called_with('admin') + self.assertEqual(len(self.inserts), 1) + insert = self.inserts[0] + self.assertEqual(insert.table, 'archivetypes') + self.assertEqual(insert.data, {'name': 'deb', + 'description': 'Debian package', + 'extensions': 'deb'}) + self.assertEqual(insert.rawdata, {}) + self.context.session.assertPerm.assert_called_with('admin') - for m in mocks: - m.reset_mock() - session.assertPerm = mock.MagicMock() - - # already exists - get_archive_type.return_value = True + def test_add_archive_type_already_exists(self): + self.get_archive_type.return_value = True with self.assertRaises(koji.GenericError): kojihub.add_archive_type('deb', 'Debian package', 'deb') - InsertProcessor.assert_not_called() - session.assertPerm.assert_called_with('admin') + self.assertEqual(len(self.inserts), 0) + self.context.session.assertPerm.assert_called_with('admin') + def test_add_archive_type_invalid_value_type(self): + self.verify_name_internal.return_value = None + description = ['Debian package'] + with self.assertRaises(koji.ParameterError) as ex: + kojihub.add_archive_type('deb', description, 'deb') + self.assertEqual(f"Invalid type for value '{description}': {type(description)}", + str(ex.exception)) + + def test_add_archive_type_invalid_value_extensions(self): + extensions = ['deb'] + with self.assertRaises(koji.ParameterError) as ex: + kojihub.add_archive_type('deb', 'Debian package', extensions) + self.assertEqual(f"Invalid type for value '{extensions}': {type(extensions)}", + str(ex.exception)) + + def test_add_archive_type_wrong_name_verify(self): # name is longer as expected new_archive_type = 'new-archive-type+' - verify_name_internal.side_effect = koji.GenericError + self.verify_name_internal.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): kojihub.add_archive_type(new_archive_type, 'Debian package', 'deb') # not except regex rules - verify_name_internal.side_effect = koji.GenericError + self.verify_name_internal.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): kojihub.add_archive_type(new_archive_type, 'Debian package', 'deb') diff --git a/tests/test_hub/test_add_channel.py b/tests/test_hub/test_add_channel.py index 5588c7b9..a2506c65 100644 --- a/tests/test_hub/test_add_channel.py +++ b/tests/test_hub/test_add_channel.py @@ -22,6 +22,9 @@ class TestAddChannel(unittest.TestCase): side_effect=self.getInsert).start() self.inserts = [] self.insert_execute = mock.MagicMock() + self.verify_name_internal = mock.patch('kojihub.verify_name_internal').start() + self.get_channel = mock.patch('kojihub.get_channel').start() + self._singleValue = mock.patch('kojihub._singleValue').start() def tearDown(self): mock.patch.stopall() @@ -32,25 +35,19 @@ class TestAddChannel(unittest.TestCase): self.inserts.append(insert) return insert - @mock.patch('kojihub.verify_name_internal') - @mock.patch('kojihub.get_channel') - @mock.patch('kojihub._singleValue') - def test_add_channel_exists(self, _singleValue, get_channel, verify_name_internal): - verify_name_internal.return_value = None - get_channel.return_value = {'id': 123, 'name': self.channel_name} + def test_add_channel_exists(self): + self.verify_name_internal.return_value = None + self.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.get_channel.assert_called_once_with(self.channel_name, strict=False) + self._singleValue.assert_not_called() self.assertEqual(len(self.inserts), 0) - @mock.patch('kojihub.verify_name_internal') - @mock.patch('kojihub.get_channel') - @mock.patch('kojihub._singleValue') - def test_add_channel_valid(self, _singleValue, get_channel, verify_name_internal): - get_channel.return_value = {} - _singleValue.side_effect = [12] - verify_name_internal.return_value = None + def test_add_channel_valid(self): + self.get_channel.return_value = {} + self._singleValue.side_effect = [12] + self.verify_name_internal.return_value = None r = self.exports.addChannel(self.channel_name, description=self.description) self.assertEqual(r, 12) @@ -62,21 +59,20 @@ class TestAddChannel(unittest.TestCase): 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([ + self.get_channel.assert_called_once_with(self.channel_name, strict=False) + self.assertEqual(self._singleValue.call_count, 1) + self._singleValue.assert_has_calls([ mock.call("SELECT nextval('channels_id_seq')", strict=True) ]) - @mock.patch('kojihub.verify_name_internal') - def test_add_channel_wrong_format(self, verify_name_internal): + def test_add_channel_wrong_name(self): # name is longer as expected channel_name = 'test-channel+' - verify_name_internal.side_effect = koji.GenericError + self.verify_name_internal.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): self.exports.addChannel(channel_name) # not except regex rules - verify_name_internal.side_effect = koji.GenericError + self.verify_name_internal.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): self.exports.addChannel(channel_name) diff --git a/tests/test_hub/test_add_external_repo_to_tag.py b/tests/test_hub/test_add_external_repo_to_tag.py index 0a47b366..189516e4 100644 --- a/tests/test_hub/test_add_external_repo_to_tag.py +++ b/tests/test_hub/test_add_external_repo_to_tag.py @@ -1,4 +1,5 @@ import unittest +import mock import koji import kojihub @@ -6,8 +7,26 @@ import kojihub class TestAddExternalRepoToTag(unittest.TestCase): + def setUp(self): + self.tag_name = 'test-tag' + self.get_tag = mock.patch('kojihub.get_tag').start() + self.get_external_repo = mock.patch('kojihub.get_external_repo').start() + self.tag_info = {'id': 1, 'name': self.tag_name} + + def tearDown(self): + mock.patch.stopall() + def test_with_wrong_merge_mode(self): merge_mode = 'test-mode' with self.assertRaises(koji.GenericError) as cm: - kojihub.add_external_repo_to_tag('tag', 'repo', 1, merge_mode=merge_mode) - self.assertEqual('No such merge mode: %s' % merge_mode, str(cm.exception)) + kojihub.add_external_repo_to_tag(self.tag_name, 'repo', 1, merge_mode=merge_mode) + self.assertEqual(f"No such merge mode: {merge_mode}", str(cm.exception)) + + def test_priority_not_int(self): + priority = 'test-priority' + self.get_tag.return_value = self.tag_info + self.get_external_repo.return_value = {'id': 123} + with self.assertRaises(koji.GenericError) as cm: + kojihub.add_external_repo_to_tag(self.tag_name, 'repo', priority, merge_mode=None) + self.assertEqual(f"Invalid type for value '{priority}': {type(priority)}", + str(cm.exception)) diff --git a/tests/test_hub/test_add_external_rpm.py b/tests/test_hub/test_add_external_rpm.py index 7aa928b5..e36f808b 100644 --- a/tests/test_hub/test_add_external_rpm.py +++ b/tests/test_hub/test_add_external_rpm.py @@ -20,20 +20,20 @@ class TestAddExternalRPM(unittest.TestCase): self.nextval = mock.patch('kojihub.nextval').start() self.Savepoint = mock.patch('kojihub.Savepoint').start() self.InsertProcessor = mock.patch('kojihub.InsertProcessor', - side_effect=self.getInsert).start() + side_effect=self.getInsert).start() self.inserts = [] self.insert_execute = mock.MagicMock() self.rpminfo = { - 'name': 'NAME', - 'version': 'VERSION', - 'release': 'RELEASE', - 'epoch': None, - 'arch': 'noarch', - 'payloadhash': 'fakehash', - 'size': 42, - 'buildtime': 0, - } + 'name': 'NAME', + 'version': 'VERSION', + 'release': 'RELEASE', + 'epoch': None, + 'arch': 'noarch', + 'payloadhash': 'fakehash', + 'size': 42, + 'buildtime': 0, + } self.repo = 'myrepo' def tearDown(self): @@ -63,8 +63,9 @@ class TestAddExternalRPM(unittest.TestCase): rpminfo = self.rpminfo.copy() del rpminfo['size'] - with self.assertRaises(koji.GenericError): + with self.assertRaises(koji.GenericError) as ex: kojihub.add_external_rpm(rpminfo, self.repo) + self.assertEqual(f"size field missing: {rpminfo}", str(ex.exception)) self.get_rpm.assert_not_called() self.nextval.assert_not_called() @@ -73,8 +74,10 @@ class TestAddExternalRPM(unittest.TestCase): rpminfo = self.rpminfo.copy() rpminfo['size'] = ['invalid type'] - with self.assertRaises(koji.GenericError): + with self.assertRaises(koji.GenericError) as ex: kojihub.add_external_rpm(rpminfo, self.repo) + self.assertEqual(f"Invalid value for size: {rpminfo['size']}", + str(ex.exception)) self.get_rpm.assert_not_called() self.nextval.assert_not_called() @@ -88,8 +91,11 @@ class TestAddExternalRPM(unittest.TestCase): self.get_external_repo_id.return_value = mock.sentinel.repo_id # call it (default is strict) - with self.assertRaises(koji.GenericError): + nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % self.rpminfo + disp = f"{nvra}@{self.repo}" + with self.assertRaises(koji.GenericError) as ex: kojihub.add_external_rpm(self.rpminfo, self.repo) + self.assertEqual(f"external rpm already exists: {disp}", str(ex.exception)) self.assertEqual(len(self.inserts), 0) self.nextval.assert_not_called() @@ -103,8 +109,12 @@ class TestAddExternalRPM(unittest.TestCase): # different hash prev['payloadhash'] = 'different hash' - with self.assertRaises(koji.GenericError): + nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % self.rpminfo + disp = f"{nvra}@{self.repo}" + with self.assertRaises(koji.GenericError) as ex: kojihub.add_external_rpm(self.rpminfo, self.repo, strict=False) + self.assertEqual(f"hash changed for external rpm: {disp} (different hash -> fakehash)", + str(ex.exception)) self.assertEqual(len(self.inserts), 0) self.nextval.assert_not_called() @@ -118,8 +128,11 @@ class TestAddExternalRPM(unittest.TestCase): self.insert_execute.side_effect = FakeException('insert failed') # call it (default is strict) - with self.assertRaises(koji.GenericError): + nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % self.rpminfo + disp = f"{nvra}@{self.repo}" + with self.assertRaises(koji.GenericError) as ex: kojihub.add_external_rpm(self.rpminfo, self.repo) + self.assertEqual(f"external rpm already exists: {disp}", str(ex.exception)) self.assertEqual(len(self.inserts), 1) self.nextval.assert_called_once() @@ -139,8 +152,12 @@ class TestAddExternalRPM(unittest.TestCase): self.nextval.reset_mock() self.get_rpm.side_effect = [None, prev] prev['payloadhash'] = 'different hash' - with self.assertRaises(koji.GenericError): + nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % self.rpminfo + disp = f"{nvra}@{self.repo}" + with self.assertRaises(koji.GenericError) as ex: kojihub.add_external_rpm(self.rpminfo, self.repo, strict=False) + self.assertEqual(f"hash changed for external rpm: {disp} (different hash -> fakehash)", + str(ex.exception)) self.assertEqual(len(self.inserts), 1) self.nextval.assert_called_once() @@ -149,10 +166,9 @@ class TestAddExternalRPM(unittest.TestCase): self.inserts[:] = [] self.nextval.reset_mock() self.get_rpm.side_effect = [None, None] - with self.assertRaises(FakeException): + with self.assertRaises(FakeException) as ex: kojihub.add_external_rpm(self.rpminfo, self.repo, strict=False) + self.assertEqual('insert failed', str(ex.exception)) self.assertEqual(len(self.inserts), 1) self.nextval.assert_called_once() - - diff --git a/tests/test_hub/test_add_host.py b/tests/test_hub/test_add_host.py index 93a0ae5c..0592d1fb 100644 --- a/tests/test_hub/test_add_host.py +++ b/tests/test_hub/test_add_host.py @@ -36,32 +36,31 @@ class TestAddHost(unittest.TestCase): self.context.session.assertPerm = mock.MagicMock() self.context.opts = {'HostPrincipalFormat': '-%s-'} self.exports = kojihub.RootExports() + self.verify_host_name = mock.patch('kojihub.verify_host_name').start() + self.verify_name_user = mock.patch('kojihub.verify_name_user').start() + self._dml = mock.patch('kojihub._dml').start() + self.get_host = mock.patch('kojihub.get_host').start() + self._singleValue = mock.patch('kojihub._singleValue').start() + self.get_user = mock.patch('kojihub.get_user').start() def tearDown(self): mock.patch.stopall() - @mock.patch('kojihub.verify_host_name') - @mock.patch('kojihub._dml') - @mock.patch('kojihub.get_host') - @mock.patch('kojihub._singleValue') - def test_add_host_exists(self, _singleValue, get_host, _dml, verify_host_name): - verify_host_name.return_value = None - get_host.return_value = {'id': 123} + def test_add_host_exists(self): + self.verify_host_name.return_value = None + self.get_host.return_value = {'id': 123} with self.assertRaises(koji.GenericError): self.exports.addHost('hostname', ['i386', 'x86_64']) - _dml.assert_not_called() - get_host.assert_called_once_with('hostname') - _singleValue.assert_not_called() + self._dml.assert_not_called() + self.get_host.assert_called_once_with('hostname') + self._singleValue.assert_not_called() - @mock.patch('kojihub.verify_host_name') - @mock.patch('kojihub._dml') - @mock.patch('kojihub.get_host') - @mock.patch('kojihub._singleValue') - def test_add_host_valid(self, _singleValue, get_host, _dml, verify_host_name): - verify_host_name.return_value = None - get_host.return_value = {} - _singleValue.side_effect = [333, 12] + def test_add_host_valid(self): + self.verify_host_name.return_value = None + self.get_host.return_value = {} + self._singleValue.side_effect = [333, 12] self.context.session.createUser.return_value = 456 + self.get_user.return_value = None r = self.exports.addHost('hostname', ['i386', 'x86_64']) self.assertEqual(r, 12) @@ -70,59 +69,48 @@ class TestAddHost(unittest.TestCase): kojihub.get_host.assert_called_once_with('hostname') self.context.session.createUser.assert_called_once_with( 'hostname', usertype=koji.USERTYPES['HOST'], krb_principal='-hostname-') - self.assertEqual(_singleValue.call_count, 2) - _singleValue.assert_has_calls([ + self.assertEqual(self._singleValue.call_count, 2) + self._singleValue.assert_has_calls([ mock.call("SELECT id FROM channels WHERE name = 'default'"), mock.call("SELECT nextval('host_id_seq')", strict=True) ]) - self.assertEqual(_dml.call_count, 1) - _dml.assert_called_once_with("INSERT INTO host (id, user_id, name) " - "VALUES (%(hostID)i, %(userID)i, %(hostname)s)", - {'hostID': 12, 'userID': 456, 'hostname': 'hostname'}) + self.assertEqual(self._dml.call_count, 1) + self._dml.assert_called_once_with("INSERT INTO host (id, user_id, name) " + "VALUES (%(hostID)i, %(userID)i, %(hostname)s)", + {'hostID': 12, 'userID': 456, 'hostname': 'hostname'}) - @mock.patch('kojihub.verify_host_name') - @mock.patch('kojihub.get_user') - @mock.patch('kojihub._dml') - @mock.patch('kojihub.get_host') - @mock.patch('kojihub._singleValue') - def test_add_host_wrong_user(self, _singleValue, get_host, _dml, get_user, verify_host_name): - verify_host_name.return_value = None - get_user.return_value = { + def test_add_host_wrong_user(self): + self.verify_host_name.return_value = None + self.get_user.return_value = { 'id': 1, 'name': 'hostname', 'usertype': koji.USERTYPES['NORMAL'] } - get_host.return_value = {} + self.get_host.return_value = {} with self.assertRaises(koji.GenericError): self.exports.addHost('hostname', ['i386', 'x86_64']) - _dml.assert_not_called() - get_user.assert_called_once_with(userInfo={'name': 'hostname'}) - get_host.assert_called_once_with('hostname') - _singleValue.assert_called_once() + self._dml.assert_not_called() + self.get_user.assert_called_once_with(userInfo={'name': 'hostname'}) + self.get_host.assert_called_once_with('hostname') + self._singleValue.assert_called_once() self.assertEqual(len(self.inserts), 0) self.assertEqual(len(self.updates), 0) - @mock.patch('kojihub.verify_host_name') - @mock.patch('kojihub.get_user') - @mock.patch('kojihub._dml') - @mock.patch('kojihub.get_host') - @mock.patch('kojihub._singleValue') - def test_add_host_wrong_user_forced(self, _singleValue, get_host, _dml, get_user, - verify_host_name): - verify_host_name.return_value = None - get_user.return_value = { + def test_add_host_wrong_user_forced(self): + self.verify_host_name.return_value = None + self.get_user.return_value = { 'id': 123, 'name': 'hostname', 'usertype': koji.USERTYPES['NORMAL'] } - get_host.return_value = {} + self.get_host.return_value = {} self.exports.addHost('hostname', ['i386', 'x86_64'], force=True) - _dml.assert_called_once() - get_user.assert_called_once_with(userInfo={'name': 'hostname'}) - get_host.assert_called_once_with('hostname') - _singleValue.assert_called() + self._dml.assert_called_once() + self.get_user.assert_called_once_with(userInfo={'name': 'hostname'}) + self.get_host.assert_called_once_with('hostname') + self._singleValue.assert_called() self.assertEqual(len(self.inserts), 2) self.assertEqual(len(self.updates), 1) update = self.updates[0] @@ -131,40 +119,50 @@ class TestAddHost(unittest.TestCase): self.assertEqual(update.clauses, ['id = %(userID)i']) self.assertEqual(update.data, {'usertype': koji.USERTYPES['HOST']}) - @mock.patch('kojihub.verify_host_name') - @mock.patch('kojihub.get_user') - @mock.patch('kojihub._dml') - @mock.patch('kojihub.get_host') - @mock.patch('kojihub._singleValue') - def test_add_host_superwrong_user_forced(self, _singleValue, get_host, _dml, get_user, - verify_host_name): - verify_host_name.return_value = None - get_user.return_value = { + def test_add_host_superwrong_user_forced(self): + self.verify_host_name.return_value = None + self.get_user.return_value = { 'id': 123, 'name': 'hostname', 'usertype': koji.USERTYPES['GROUP'] } - get_host.return_value = {} + self.get_host.return_value = {} with self.assertRaises(koji.GenericError): self.exports.addHost('hostname', ['i386', 'x86_64'], force=True) - _dml.assert_not_called() - get_user.assert_called_once_with(userInfo={'name': 'hostname'}) - get_host.assert_called_once_with('hostname') - _singleValue.assert_called() + self._dml.assert_not_called() + self.get_user.assert_called_once_with(userInfo={'name': 'hostname'}) + self.get_host.assert_called_once_with('hostname') + self._singleValue.assert_called() self.assertEqual(len(self.inserts), 0) self.assertEqual(len(self.updates), 0) - @mock.patch('kojihub.verify_host_name') - def test_add_host_wrong_format(self, verify_host_name): + def test_add_host_wrong_format(self): # name is longer as expected hostname = 'host-name+' - verify_host_name.side_effect = koji.GenericError + self.verify_host_name.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): self.exports.addHost(hostname, ['i386', 'x86_64'], force=True) # not except regex rules - verify_host_name.side_effect = koji.GenericError + self.verify_host_name.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): self.exports.addHost(hostname, ['i386', 'x86_64'], force=True) + + def test_add_host_krbprincipal_wrong_type(self): + krb_principal = ['test-krb'] + self.verify_host_name.return_value = None + self.get_host.return_value = {} + self._singleValue.side_effect = [333, 12] + self.verify_name_user.side_effect = koji.GenericError + with self.assertRaises(koji.GenericError): + self.exports.addHost('hostname', ['i386', 'x86_64'], krb_principal=krb_principal) + + self.context.session.assertPerm.assert_called_once_with('host') + kojihub.get_host.assert_called_once_with('hostname') + self.context.session.createUser.assert_not_called() + self.assertEqual(self._singleValue.call_count, 1) + self._singleValue.assert_called_once_with("SELECT id FROM channels WHERE name = 'default'") + self.verify_host_name.assert_called_once_with('hostname') + self.verify_name_user.assert_called_once_with(krb=krb_principal) diff --git a/tests/test_hub/test_add_user_krb_principal.py b/tests/test_hub/test_add_user_krb_principal.py new file mode 100644 index 00000000..dcaeee54 --- /dev/null +++ b/tests/test_hub/test_add_user_krb_principal.py @@ -0,0 +1,42 @@ +import mock +import unittest +import koji +import kojihub +import copy + + +class TestAddUserKrbPrincipal(unittest.TestCase): + + def setUp(self): + self.get_user = mock.patch('kojihub.get_user').start() + self.verify_name_user = mock.patch('kojihub.verify_name_user').start() + self.get_user_by_krb_principal = mock.patch('kojihub.get_user_by_krb_principal').start() + self.username = 'testuser' + self.krbprincipal = '%s@TEST.COM' % self.username + self.userinfo = {'id': 1, 'name': self.username} + + def tearDown(self): + mock.patch.stopall() + + def test_non_exist_user(self): + self.get_user.side_effect = koji.GenericError + with self.assertRaises(koji.GenericError): + kojihub.RootExports().addUserKrbPrincipal(self.username, self.krbprincipal) + + def test_wrong_krbprincipal_format(self): + krbprincipal = 'test-krbprincipal+' + self.get_user.return_value = self.userinfo + self.verify_name_user.side_effect = koji.GenericError + with self.assertRaises(koji.GenericError): + kojihub.RootExports().addUserKrbPrincipal(self.username, krbprincipal) + + def test_existing_krb(self): + userinfo = copy.deepcopy(self.userinfo) + userinfo['krb_principal'] = self.krbprincipal + self.get_user.return_value = self.userinfo + self.verify_name_user.return_value = None + self.get_user_by_krb_principal.return_value = userinfo + with self.assertRaises(koji.GenericError) as ex: + kojihub.RootExports().addUserKrbPrincipal(self.username, self.krbprincipal) + self.assertEqual('user with this Kerberos principal already exists: %s' + % self.krbprincipal, str(ex.exception)) diff --git a/tests/test_hub/test_build.py b/tests/test_hub/test_build.py new file mode 100644 index 00000000..e2c73829 --- /dev/null +++ b/tests/test_hub/test_build.py @@ -0,0 +1,40 @@ +import unittest +import koji +import kojihub +import mock + + +class TestBuild(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertLogin = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + self.get_channel = mock.patch('kojihub.get_channel').start() + self.make_task = mock.patch('kojihub.make_task').start() + self.src = 'test-src' + self.target = 'test-target' + + def tearDown(self): + mock.patch.stopall() + + def test_src_wrong_type(self): + src = ['test-priority'] + with self.assertRaises(koji.GenericError) as cm: + self.exports.build(src, self.target) + self.assertEqual(f"Invalid type for value '{src}': {type(src)}", str(cm.exception)) + + def test_priority_without_admin(self): + priority = -10 + self.context.session.hasPerm.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.build(self.src, self.target, priority=priority) + self.assertEqual("only admins may create high-priority tasks", str(cm.exception)) + + def test_channel_not_str(self): + priority = 10 + self.get_channel.return_value = {'comment': None, 'description': None, 'enabled': True, + 'id': 2, 'name': 'maven'} + self.make_task.return_value = 123 + self.exports.build(self.src, self.target, priority=priority, channel=2) diff --git a/tests/test_hub/test_build_image.py b/tests/test_hub/test_build_image.py new file mode 100644 index 00000000..0f81fc6e --- /dev/null +++ b/tests/test_hub/test_build_image.py @@ -0,0 +1,69 @@ +import unittest +import koji +import kojihub +import mock + + +class TestBuildImage(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertPerm = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + self.make_task = mock.patch('kojihub.make_task').start() + self.mock_parse_arches = mock.patch('koji.parse_arches').start() + self.name = 'image-name' + self.version = 'test-version' + self.arch = 'x86_64' + self.target = 'test-target' + self.ksfile = 'test-ksfile' + self.image_type = 'livecd' + + def tearDown(self): + mock.patch.stopall() + + def test_img_type_not_supported(self): + image_type = 'test-type' + with self.assertRaises(koji.GenericError) as cm: + self.exports.buildImage(self.name, self.version, self.arch, self.target, + self.ksfile, image_type) + self.assertEqual(f"Unrecognized image type: {image_type}", str(cm.exception)) + + def test_name_wrong_type(self): + name = ['test-name'] + with self.assertRaises(koji.GenericError) as cm: + self.exports.buildImage(name, self.version, self.arch, self.target, self.ksfile, + self.image_type) + self.assertEqual(f"Invalid type for value '{name}': {type(name)}", str(cm.exception)) + + def test_version_wrong_type(self): + version = ['test-version'] + with self.assertRaises(koji.GenericError) as cm: + self.exports.buildImage(self.name, version, self.arch, self.target, self.ksfile, + self.image_type) + self.assertEqual(f"Invalid type for value '{version}': {type(version)}", str(cm.exception)) + + def test_ksfile_wrong_type(self): + ksfile = ['test-ksfile'] + with self.assertRaises(koji.GenericError) as cm: + self.exports.buildImage(self.name, self.version, self.arch, self.target, ksfile, + self.image_type) + self.assertEqual(f"Invalid type for value '{ksfile}': {type(ksfile)}", str(cm.exception)) + + def test_priority_without_admin(self): + priority = -10 + image_type = 'livemedia' + self.context.session.assertPerm.side_effect = None + self.context.session.hasPerm.return_value = False + with self.assertRaises(koji.ActionNotAllowed) as cm: + self.exports.buildImage(self.name, self.version, self.arch, self.target, self.ksfile, + image_type, priority=priority) + self.assertEqual("only admins may create high-priority tasks", str(cm.exception)) + + def test_valid(self): + priority = 10 + self.context.session.assertPerm.side_effect = None + self.make_task.return_value = 123 + self.exports.buildImage(self.name, self.version, self.arch, self.target, self.ksfile, + self.image_type, priority=priority) diff --git a/tests/test_hub/test_build_image_indirection.py b/tests/test_hub/test_build_image_indirection.py new file mode 100644 index 00000000..b9637812 --- /dev/null +++ b/tests/test_hub/test_build_image_indirection.py @@ -0,0 +1,33 @@ +import unittest +import koji +import kojihub +import mock + + +class TestBuildImageIndirection(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertPerm = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + + def tearDown(self): + mock.patch.stopall() + + def test_priority_without_admin(self): + priority = -10 + self.context.session.assertPerm.side_effect = None + self.context.session.hasPerm.return_value = False + with self.assertRaises(koji.ActionNotAllowed) as cm: + self.exports.buildImageIndirection(priority=priority) + self.assertEqual("only admins may create high-priority tasks", str(cm.exception)) + + def test_opts_without_expected_keys(self): + priority = 10 + opts = {} + self.context.session.assertPerm.side_effect = None + with self.assertRaises(koji.ActionNotAllowed) as cm: + self.exports.buildImageIndirection(opts=opts, priority=priority) + self.assertEqual("Non-scratch builds must provide url for the indirection template", + str(cm.exception)) diff --git a/tests/test_hub/test_build_image_oz.py b/tests/test_hub/test_build_image_oz.py new file mode 100644 index 00000000..aa5485b8 --- /dev/null +++ b/tests/test_hub/test_build_image_oz.py @@ -0,0 +1,59 @@ +import unittest +import koji +import kojihub +import mock + + +class TestBuildImageOz(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertPerm = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + self.parse_arches = mock.patch('koji.parse_arches').start() + self.name = 'image-name' + self.version = 'test-version' + self.arches = ['x86_64', 'i386'] + self.target = 'test-target' + self.inst_tree = 'test-tree' + + def tearDown(self): + mock.patch.stopall() + + def test_name_wrong_type(self): + name = ['image-name'] + with self.assertRaises(koji.ParameterError) as cm: + self.exports.buildImageOz(name, self.version, self.arches, self.target, self.inst_tree) + self.assertEqual(f"Invalid type for value '{name}': {type(name)}", str(cm.exception)) + + def test_inst_tree_wrong_type(self): + inst_tree = ['test-tree'] + with self.assertRaises(koji.ParameterError) as cm: + self.exports.buildImageOz(self.name, self.version, self.arches, self.target, inst_tree) + self.assertEqual(f"Invalid type for value '{inst_tree}': {type(inst_tree)}", + str(cm.exception)) + + def test_version_wrong_type(self): + version = ['test-version'] + with self.assertRaises(koji.ParameterError) as cm: + self.exports.buildImageOz(self.name, version, self.arches, self.target, self.inst_tree) + self.assertEqual(f"Invalid type for value '{version}': {type(version)}", str(cm.exception)) + + def test_priority_without_admin(self): + priority = -10 + self.context.session.assertPerm.side_effect = None + self.context.session.hasPerm.return_value = False + with self.assertRaises(koji.ActionNotAllowed) as cm: + self.exports.buildImageOz(self.name, self.version, self.arches, self.target, + self.inst_tree, priority=priority) + self.assertEqual("only admins may create high-priority tasks", str(cm.exception)) + + def test_opts_without_expected_keys(self): + priority = 10 + opts = {} + self.context.session.assertPerm.side_effect = None + with self.assertRaises(koji.ActionNotAllowed) as cm: + self.exports.buildImageOz(self.name, self.version, self.arches, self.target, + self.inst_tree, opts=opts, priority=priority) + self.assertEqual("Non-scratch builds must provide ksurl", str(cm.exception)) diff --git a/tests/test_hub/test_cg_importer.py b/tests/test_hub/test_cg_importer.py index 17179631..3f236e9f 100644 --- a/tests/test_hub/test_cg_importer.py +++ b/tests/test_hub/test_cg_importer.py @@ -10,6 +10,7 @@ from koji import GenericError IP = kojihub.InsertProcessor UP = kojihub.UpdateProcessor + class TestCGImporter(unittest.TestCase): TMP_PATH = os.path.join(os.path.dirname(__file__), 'tmptest') @@ -36,7 +37,8 @@ class TestCGImporter(unittest.TestCase): metadata = 42 with self.assertRaises(GenericError) as ex: x.get_metadata(metadata, '') - self.assertEqual('Invalid type for metadata value: %s' % type(metadata), str(ex.exception)) + self.assertEqual(f"Invalid type for value '{metadata}': {type(metadata)}", + str(ex.exception)) def test_get_metadata_is_none(self): x = kojihub.CG_Importer() @@ -158,7 +160,7 @@ class TestCGImporter(unittest.TestCase): 'start_time': None, 'start_ts': None, 'completion_time': None, 'completion_ts': None, 'source': 'https://example.com', 'extra': {} - } + } new_build_id.return_value = 43 x.get_build() assert x.buildinfo @@ -187,7 +189,7 @@ class TestCGImporter(unittest.TestCase): get_metadata.return_value = metadata with self.assertRaises(koji.GenericError) as ex: x.do_import(metadata, '/test/dir') - self.assertEqual('No such metadata version: %s' % metadata['metadata_version'], + self.assertEqual(f"No such metadata version: {metadata['metadata_version']}", str(ex.exception)) def test_match_componemt_wrong_component(self): @@ -195,7 +197,7 @@ class TestCGImporter(unittest.TestCase): components = [{'type': 'type'}] with self.assertRaises(koji.GenericError) as ex: x.match_components(components) - self.assertEqual('No such component type: %s' % components[0]['type'], str(ex.exception)) + self.assertEqual(f"No such component type: {components[0]['type']}", str(ex.exception)) class TestMatchKojiFile(unittest.TestCase): @@ -203,23 +205,23 @@ class TestMatchKojiFile(unittest.TestCase): def setUp(self): self.importer = kojihub.CG_Importer() self.archive1 = { - 'id': 99, - 'build_id': 42, - 'checksum': 'e1f95555eae04b8e1ebdc5555c5555f0', - 'checksum_type': 0, - 'filename': 'foo-bar-3.0.jar', - 'size': 42710, - } + 'id': 99, + 'build_id': 42, + 'checksum': 'e1f95555eae04b8e1ebdc5555c5555f0', + 'checksum_type': 0, + 'filename': 'foo-bar-3.0.jar', + 'size': 42710, + } self.build1 = { - 'id': 79218, - 'nvr': 'foo-3.0-1', - } + 'id': 79218, + 'nvr': 'foo-3.0-1', + } self.comp1 = { - 'type': 'kojifile', - 'archive_id': self.archive1['id'], - 'nvr': self.build1['nvr'], - 'filename': self.archive1['filename'], - } + 'type': 'kojifile', + 'archive_id': self.archive1['id'], + 'nvr': self.build1['nvr'], + 'filename': self.archive1['filename'], + } self.get_archive = mock.patch('kojihub.get_archive').start() self.get_build = mock.patch('kojihub.get_build').start() @@ -270,7 +272,6 @@ class TestCGReservation(unittest.TestCase): self.updates.append(update) return update - def setUp(self): self.InsertProcessor = mock.patch('kojihub.InsertProcessor', side_effect=self.getInsert).start() @@ -298,8 +299,8 @@ class TestCGReservation(unittest.TestCase): new_build.return_value = 654 cg = 'content_generator_name' self.mock_cursor.fetchone.side_effect = [ - [333], # get pkg_id - [1234], # get nextval pkg_id + [333], # get pkg_id + [1234], # get nextval pkg_id ] self.mock_cursor.fetchall.side_effect = [ [[]], diff --git a/tests/test_hub/test_chain_build.py b/tests/test_hub/test_chain_build.py new file mode 100644 index 00000000..42ae5fe8 --- /dev/null +++ b/tests/test_hub/test_chain_build.py @@ -0,0 +1,40 @@ +import unittest +import koji +import kojihub +import mock + + +class TestChainBuild(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertLogin = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + self.get_channel = mock.patch('kojihub.get_channel').start() + self.make_task = mock.patch('kojihub.make_task').start() + self.srcs = ['pkg1'] + self.target = 'test-target' + + def tearDown(self): + mock.patch.stopall() + + def test_srcs_wrong_type(self): + srcs = 'pkg' + with self.assertRaises(koji.GenericError) as cm: + self.exports.chainBuild(srcs, self.target) + self.assertEqual(f"Invalid type for value '{srcs}': {type(srcs)}", str(cm.exception)) + + def test_priority_without_admin(self): + priority = -10 + self.context.session.hasPerm.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.chainBuild(self.srcs, self.target, priority=priority) + self.assertEqual("only admins may create high-priority tasks", str(cm.exception)) + + def test_channel_not_str(self): + priority = 10 + self.get_channel.return_value = {'comment': None, 'description': None, 'enabled': True, + 'id': 2, 'name': 'maven'} + self.make_task.return_value = 123 + self.exports.chainBuild(self.srcs, self.target, priority=priority, channel=2) diff --git a/tests/test_hub/test_chain_maven.py b/tests/test_hub/test_chain_maven.py new file mode 100644 index 00000000..f5b737d3 --- /dev/null +++ b/tests/test_hub/test_chain_maven.py @@ -0,0 +1,48 @@ +import unittest +import koji +import kojihub +import mock + + +class TestChainMaven(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertLogin = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + self.get_channel = mock.patch('kojihub.get_channel').start() + self.make_task = mock.patch('kojihub.make_task').start() + self.builds = ['build1', 'build2', 'build3'] + self.target = 'test-target' + + def tearDown(self): + mock.patch.stopall() + + def test_maven_not_supported(self): + self.context.opts.get.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.chainMaven(self.builds, self.target) + self.assertEqual("Maven support not enabled", str(cm.exception)) + + def test_builds_wrong_type(self): + builds = 'test-builds' + self.context.opts.get.return_value = True + with self.assertRaises(koji.ParameterError) as cm: + self.exports.chainMaven(builds, self.target) + self.assertEqual(f"Invalid type for value '{builds}': {type(builds)}", str(cm.exception)) + + def test_priority_without_admin(self): + priority = -10 + self.context.opts.get.return_value = True + self.context.session.hasPerm.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.chainMaven(self.builds, self.target, priority=priority) + self.assertEqual("only admins may create high-priority tasks", str(cm.exception)) + + def test_channel_not_str(self): + self.context.opts.get.return_value = True + self.make_task.return_value = 123 + self.get_channel.return_value = {'comment': None, 'description': None, 'enabled': True, + 'id': 2, 'name': 'maven'} + self.exports.chainMaven(self.builds, self.target, channel=2, priority=10) diff --git a/tests/test_hub/test_create_notification.py b/tests/test_hub/test_create_notification.py index e3ff819f..7b0dacfc 100644 --- a/tests/test_hub/test_create_notification.py +++ b/tests/test_hub/test_create_notification.py @@ -4,57 +4,168 @@ import mock import koji import kojihub +QP = kojihub.QueryProcessor +IP = kojihub.InsertProcessor +UP = kojihub.UpdateProcessor + class TestCreateNotification(unittest.TestCase): + def getInsert(self, *args, **kwargs): + insert = IP(*args, **kwargs) + insert.execute = mock.MagicMock() + self.inserts.append(insert) + return insert + + def getQuery(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query + + def getUpdate(self, *args, **kwargs): + update = UP(*args, **kwargs) + update.execute = mock.MagicMock() + self.updates.append(update) + return update def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.context.opts = { + 'EmailDomain': 'test.domain.com', + 'NotifyOnSuccess': True, + } + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.getQuery).start() + self.queries = [] + 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.get_build_notifications = mock.patch('kojihub.get_build_notifications').start() + self.get_tag_id = mock.patch('kojihub.get_tag_id').start() + self.get_package_id = mock.patch('kojihub.get_package_id').start() + self.exports = kojihub.RootExports() self.exports.getLoggedInUser = mock.MagicMock() - self.context = mock.patch('kojihub.context').start() + self.exports.getUser = mock.MagicMock() + self.exports.hasPerm = mock.MagicMock() self.cursor = mock.MagicMock() + self.user_id = 1 + self.package_id = 345 + self.tag_id = 345 + + def tearDown(self): + mock.patch.stopall() + + def test_createNotification(self): + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.getUser.return_value = {'id': 2, 'name': 'username'} + self.exports.hasPerm.return_value = True + self.get_package_id.return_value = self.package_id + self.get_tag_id.return_value = self.tag_id + self.get_build_notifications.return_value = [] + + r = self.exports.createNotification( + self.user_id, self.package_id, self.tag_id, success_only) + self.assertEqual(r, None) + + self.exports.getLoggedInUser.assert_called_once() + self.exports.getUser.asssert_called_once_with(self.user_id) + self.exports.hasPerm.asssert_called_once_with('admin') + self.get_package_id.assert_called_once_with(self.package_id, strict=True) + self.get_tag_id.assert_called_once_with(self.tag_id, strict=True) + self.get_build_notifications.assert_called_once_with(2) + self.assertEqual(len(self.inserts), 1) + insert = self.inserts[0] + self.assertEqual(insert.table, 'build_notifications') + self.assertEqual(insert.data, { + 'package_id': self.package_id, + 'user_id': 2, + 'tag_id': self.tag_id, + 'success_only': success_only, + 'email': 'username@test.domain.com', + }) + self.assertEqual(insert.rawdata, {}) + + def test_createNotification_unauthentized(self): + success_only = True + self.exports.getLoggedInUser.return_value = None - def test_non_exist_user(self): - user_id = 999 - package_id = 555 - tag_id = 111 - success_only = False - logged_user = {'authtype': 2, - 'id': 1, - 'krb_principal': None, - 'krb_principals': [], - 'name': 'kojiadmin', - 'status': 0, - 'usertype': 0} - self.cursor.fetchone.return_value = None - self.context.cnx.cursor.return_value = self.cursor - self.exports.getLoggedInUser.return_value = logged_user with self.assertRaises(koji.GenericError) as cm: - self.exports.createNotification(user_id, package_id, tag_id, success_only) - self.assertEqual('No such user ID: %s' % user_id, str(cm.exception)) + self.exports.createNotification( + self.user_id, self.package_id, self.tag_id, success_only) + self.assertEqual('Not logged-in', str(cm.exception)) + self.assertEqual(len(self.inserts), 0) -class TestCreateNotificationBlock(unittest.TestCase): + def test_createNotification_invalid_user(self): + user_id = 2 + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.getUser.return_value = None - def setUp(self): - self.exports = kojihub.RootExports() - self.exports.getLoggedInUser = mock.MagicMock() - self.context = mock.patch('kojihub.context').start() - self.cursor = mock.MagicMock() - - def test_non_exist_user(self): - user_id = 999 - package_id = 555 - tag_id = 111 - logged_user = {'authtype': 2, - 'id': 1, - 'krb_principal': None, - 'krb_principals': [], - 'name': 'kojiadmin', - 'status': 0, - 'usertype': 0} - self.cursor.fetchone.return_value = None - self.context.cnx.cursor.return_value = self.cursor - self.exports.getLoggedInUser.return_value = logged_user with self.assertRaises(koji.GenericError) as cm: - self.exports.createNotificationBlock(user_id, package_id, tag_id) - self.assertEqual('No such user ID: %s' % user_id, str(cm.exception)) + self.exports.createNotification(user_id, self.package_id, self.tag_id, success_only) + self.assertEqual(f'No such user ID: {user_id}', str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotification_no_perm(self): + user_id = 2 + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 1, 'name': 'a'} + self.exports.getUser.return_value = {'id': 2, 'name': 'b'} + self.exports.hasPerm.return_value = False + + with self.assertRaises(koji.GenericError) as cm: + self.exports.createNotification(user_id, self.package_id, self.tag_id, success_only) + self.assertEqual('user a cannot create notifications for user b', str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotification_invalid_pkg(self): + user_id = 2 + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} + self.exports.getUser.return_value = {'id': 2, 'name': 'a'} + self.get_package_id.side_effect = ValueError + + with self.assertRaises(ValueError): + self.exports.createNotification(user_id, self.package_id, self.tag_id, success_only) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotification_invalid_tag(self): + user_id = 2 + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} + self.exports.getUser.return_value = {'id': 2, 'name': 'a'} + self.get_package_id.return_value = self.package_id + self.get_tag_id.side_effect = ValueError + + with self.assertRaises(ValueError): + self.exports.createNotification(user_id, self.package_id, self.tag_id, success_only) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotification_exists(self): + user_id = 2 + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} + self.exports.getUser.return_value = {'id': 2, 'name': 'a'} + self.get_package_id.return_value = self.package_id + self.get_tag_id.return_value = self.tag_id + self.get_build_notifications.return_value = [{ + 'package_id': self.package_id, + 'tag_id': self.tag_id, + 'success_only': success_only, + }] + + with self.assertRaises(koji.GenericError) as cm: + self.exports.createNotification(user_id, self.package_id, self.tag_id, success_only) + self.assertEqual('notification already exists', str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) diff --git a/tests/test_hub/test_create_notification_block.py b/tests/test_hub/test_create_notification_block.py new file mode 100644 index 00000000..278bf542 --- /dev/null +++ b/tests/test_hub/test_create_notification_block.py @@ -0,0 +1,161 @@ +import unittest +import mock + +import koji +import kojihub + +QP = kojihub.QueryProcessor +IP = kojihub.InsertProcessor +UP = kojihub.UpdateProcessor + + +class TestCreateNotificationBlock(unittest.TestCase): + def getInsert(self, *args, **kwargs): + insert = IP(*args, **kwargs) + insert.execute = mock.MagicMock() + self.inserts.append(insert) + return insert + + def getQuery(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query + + def getUpdate(self, *args, **kwargs): + update = UP(*args, **kwargs) + update.execute = mock.MagicMock() + self.updates.append(update) + return update + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.context.opts = { + 'EmailDomain': 'test.domain.com', + 'NotifyOnSuccess': True, + } + + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.getQuery).start() + self.queries = [] + 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.get_tag_id = mock.patch('kojihub.get_tag_id').start() + self.get_package_id = mock.patch('kojihub.get_package_id').start() + self.get_build_notification_blocks = mock.patch( + 'kojihub.get_build_notification_blocks').start() + + self.exports = kojihub.RootExports() + self.exports.getLoggedInUser = mock.MagicMock() + self.exports.getUser = mock.MagicMock() + self.exports.hasPerm = mock.MagicMock() + self.cursor = mock.MagicMock() + self.user_id = 1 + self.package_id = 555 + self.tag_id = 111 + + def tearDown(self): + mock.patch.stopall() + + def test_createNotificationBlock(self): + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.getUser.return_value = {'id': 2, 'name': 'username'} + self.exports.hasPerm.return_value = True + self.get_package_id.return_value = self.package_id + self.get_tag_id.return_value = self.tag_id + self.get_build_notification_blocks.return_value = [] + + r = self.exports.createNotificationBlock(self.user_id, self.package_id, self.tag_id) + self.assertEqual(r, None) + + self.exports.getLoggedInUser.assert_called_once() + self.exports.getUser.asssert_called_once_with(self.user_id) + self.exports.hasPerm.asssert_called_once_with('admin') + self.get_package_id.assert_called_once_with(self.package_id, strict=True) + self.get_tag_id.assert_called_once_with(self.tag_id, strict=True) + self.get_build_notification_blocks.assert_called_once_with(2) + self.assertEqual(len(self.inserts), 1) + insert = self.inserts[0] + self.assertEqual(insert.table, 'build_notifications_block') + self.assertEqual(insert.data, { + 'package_id': self.package_id, + 'user_id': 2, + 'tag_id': self.tag_id, + }) + self.assertEqual(insert.rawdata, {}) + + def test_createNotificationBlock_unauthentized(self): + self.exports.getLoggedInUser.return_value = None + + with self.assertRaises(koji.GenericError) as cm: + self.exports.createNotificationBlock(self.user_id, self.package_id, self.tag_id) + self.assertEqual('Not logged-in', str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotificationBlock_invalid_user(self): + user_id = 2 + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.getUser.return_value = None + + with self.assertRaises(koji.GenericError) as cm: + self.exports.createNotificationBlock(user_id, self.package_id, self.tag_id) + self.assertEqual(f'No such user ID: {user_id}', str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotificationBlock_no_perm(self): + user_id = 2 + self.exports.getLoggedInUser.return_value = {'id': 1, 'name': 'a'} + self.exports.getUser.return_value = {'id': 2, 'name': 'b'} + self.exports.hasPerm.return_value = False + + with self.assertRaises(koji.GenericError) as cm: + self.exports.createNotificationBlock(user_id, self.package_id, self.tag_id) + self.assertEqual('user a cannot create notification blocks for user b', str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotificationBlock_invalid_pkg(self): + user_id = 2 + self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} + self.exports.getUser.return_value = {'id': 2, 'name': 'a'} + self.get_package_id.side_effect = ValueError + + with self.assertRaises(ValueError): + self.exports.createNotificationBlock(user_id, self.package_id, self.tag_id) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotificationBlock_invalid_tag(self): + user_id = 2 + self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} + self.exports.getUser.return_value = {'id': 2, 'name': 'a'} + self.get_package_id.return_value = self.package_id + self.get_tag_id.side_effect = ValueError + + with self.assertRaises(ValueError): + self.exports.createNotificationBlock(user_id, self.package_id, self.tag_id) + + self.assertEqual(len(self.inserts), 0) + + def test_createNotificationBlock_exists(self): + user_id = 2 + self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} + self.exports.getUser.return_value = {'id': 2, 'name': 'a'} + self.get_package_id.return_value = self.package_id + self.get_tag_id.return_value = self.tag_id + self.get_build_notification_blocks.return_value = [{ + 'package_id': self.package_id, + 'tag_id': self.tag_id, + }] + + with self.assertRaises(koji.GenericError) as cm: + self.exports.createNotificationBlock(user_id, self.package_id, self.tag_id) + self.assertEqual('notification already exists', str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) diff --git a/tests/test_hub/test_create_tag.py b/tests/test_hub/test_create_tag.py index 22e27771..e5498e89 100644 --- a/tests/test_hub/test_create_tag.py +++ b/tests/test_hub/test_create_tag.py @@ -24,8 +24,9 @@ class TestCreateTag(unittest.TestCase): self._dml = mock.patch('kojihub._dml').start() self.get_tag = mock.patch('kojihub.get_tag').start() self.get_tag_id = mock.patch('kojihub.get_tag_id').start() + self.get_perm_id = mock.patch('kojihub.get_perm_id').start() self.verify_name_internal = mock.patch('kojihub.verify_name_internal').start() - self.writeInheritanceData = mock.patch('kojihub.writeInheritanceData').start() + self.writeInheritanceData = mock.patch('kojihub._writeInheritanceData').start() self.context = mock.patch('kojihub.context').start() # It seems MagicMock will not automatically handle attributes that # start with "assert" @@ -42,12 +43,13 @@ class TestCreateTag(unittest.TestCase): kojihub.create_tag('duptag') def test_simple_create(self): - self.get_tag.return_value = None + self.get_tag.side_effect = [None, {'id': 1, 'name': 'parent-tag'}] self.get_tag_id.return_value = 99 self.verify_name_internal.return_value = None self.context.event_id = 42 self.context.session.user_id = 23 - kojihub.create_tag('newtag') + self.writeInheritanceData.return_value = None + kojihub.create_tag('newtag', parent='parent-tag') # check the insert self.assertEqual(len(self.inserts), 1) @@ -97,3 +99,21 @@ class TestCreateTag(unittest.TestCase): self.verify_name_internal.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): kojihub.create_tag(tag_name) + + def test_tag_non_exist_parent(self): + parent_tag = 'parent-tag' + self.verify_name_internal.return_value = None + self.get_tag.side_effect = [None, None] + with self.assertRaises(koji.GenericError) as ex: + kojihub.create_tag('new-tag', parent=parent_tag) + self.assertEqual("Parent tag '%s' could not be found" % parent_tag, str(ex.exception)) + + def test_tag_not_maven_support(self): + self.verify_name_internal.return_value = None + self.context.opts.get.return_value = False + with self.assertRaises(koji.GenericError) as ex: + kojihub.create_tag('new-tag', maven_support=True) + self.assertEqual("Maven support not enabled", str(ex.exception)) + with self.assertRaises(koji.GenericError) as ex: + kojihub.create_tag('new-tag', maven_include_all=True) + self.assertEqual("Maven support not enabled", str(ex.exception)) diff --git a/tests/test_hub/test_create_user.py b/tests/test_hub/test_create_user.py index 2ba9f33e..d991aa82 100644 --- a/tests/test_hub/test_create_user.py +++ b/tests/test_hub/test_create_user.py @@ -38,19 +38,27 @@ class TestCreateUser(unittest.TestCase): self.exports.createUser(user_name) def test_create_user_exists(self): - expected = 'user already exists: %s' % self.user_name self.verify_name_user.return_value = None self.get_user.return_value = self.user_info with self.assertRaises(koji.GenericError) as cm: self.exports.createUser(self.user_name) - self.assertEqual(expected, str(cm.exception)) + self.assertEqual(f"user already exists: {self.user_name}", str(cm.exception)) def test_create_user_exists_krb(self): krb_principal = 'test_user@fedora.org' - expected = 'user with this Kerberos principal already exists: %s' % krb_principal + expected = f"user with this Kerberos principal already exists: {krb_principal}" self.verify_name_user.return_value = None self.get_user.return_value = None self.get_user_by_krb_principal.return_value = self.user_info_krb with self.assertRaises(koji.GenericError) as cm: self.exports.createUser(self.user_name, krb_principal=krb_principal) self.assertEqual(expected, str(cm.exception)) + + def test_create_user_wrong_type_status(self): + status = 'test-status' + self.verify_name_user.return_value = None + self.get_user.return_value = None + self.get_user_by_krb_principal.return_value = self.user_info_krb + with self.assertRaises(koji.ParameterError) as cm: + self.exports.createUser(self.user_name, status=status) + self.assertEqual(f"Invalid type for value '{status}': {type(status)}", str(cm.exception)) diff --git a/tests/test_hub/test_delete_notification.py b/tests/test_hub/test_delete_notification.py new file mode 100644 index 00000000..90da1935 --- /dev/null +++ b/tests/test_hub/test_delete_notification.py @@ -0,0 +1,103 @@ +import mock +import unittest + +import koji +import kojihub + +QP = kojihub.QueryProcessor +IP = kojihub.InsertProcessor +UP = kojihub.UpdateProcessor + + +class TestDeleteNotifications(unittest.TestCase): + def getInsert(self, *args, **kwargs): + insert = IP(*args, **kwargs) + insert.execute = mock.MagicMock() + self.inserts.append(insert) + return insert + + def getQuery(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query + + def getUpdate(self, *args, **kwargs): + update = UP(*args, **kwargs) + update.execute = mock.MagicMock() + self.updates.append(update) + return update + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.context.opts = { + 'EmailDomain': 'test.domain.com', + 'NotifyOnSuccess': True, + } + + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.getQuery).start() + self.queries = [] + 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._dml = mock.patch('kojihub._dml').start() + + self.exports = kojihub.RootExports() + self.exports.getLoggedInUser = mock.MagicMock() + self.exports.hasPerm = mock.MagicMock() + self.exports.getBuildNotification = mock.MagicMock() + self.user_id = 752 + self.n_id = 543 + + def tearDown(self): + mock.patch.stopall() + + def test_deleteNotification(self): + self.exports.getBuildNotification.return_value = {'user_id': self.user_id} + + self.exports.deleteNotification(self.n_id) + + self.exports.getBuildNotification.assert_called_once_with(self.n_id, strict=True) + self.exports.getLoggedInUser.assert_called_once_with() + self._dml.assert_called_once() + + def test_deleteNotification_missing(self): + self.exports.getBuildNotification.side_effect = koji.GenericError + + with self.assertRaises(koji.GenericError): + self.exports.deleteNotification(self.n_id) + + self.exports.getBuildNotification.assert_called_once_with(self.n_id, strict=True) + + def test_deleteNotification_not_logged(self): + self.exports.getBuildNotification.return_value = {'user_id': self.user_id} + self.exports.getLoggedInUser.return_value = None + # self.set_queries = ([ + # [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], + # ]) + + with self.assertRaises(koji.GenericError) as cm: + self.exports.deleteNotification(self.n_id) + self.assertEqual('Not logged-in', str(cm.exception)) + + self.exports.getBuildNotification.assert_called_once_with(self.n_id, strict=True) + self.assertEqual(len(self.inserts), 0) + self.assertEqual(len(self.updates), 0) + self.assertEqual(len(self.queries), 0) + + def test_deleteNotification_no_perm(self): + self.exports.getBuildNotification.return_value = {'user_id': self.user_id} + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.hasPerm.return_value = False + + with self.assertRaises(koji.GenericError) as cm: + self.exports.deleteNotification(self.n_id) + self.assertEqual(f'user 1 cannot delete notifications for user {self.user_id}', + str(cm.exception)) + + self.exports.getBuildNotification.assert_called_once_with(self.n_id, strict=True) + self._dml.assert_not_called() diff --git a/tests/test_hub/test_delete_notification_block.py b/tests/test_hub/test_delete_notification_block.py new file mode 100644 index 00000000..5f5379b0 --- /dev/null +++ b/tests/test_hub/test_delete_notification_block.py @@ -0,0 +1,104 @@ +import mock +import unittest + +import koji +import kojihub + +QP = kojihub.QueryProcessor +IP = kojihub.InsertProcessor +UP = kojihub.UpdateProcessor + + +class TestDeleteNotificationsBlocks(unittest.TestCase): + def getInsert(self, *args, **kwargs): + insert = IP(*args, **kwargs) + insert.execute = mock.MagicMock() + self.inserts.append(insert) + return insert + + def getQuery(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query + + def getUpdate(self, *args, **kwargs): + update = UP(*args, **kwargs) + update.execute = mock.MagicMock() + self.updates.append(update) + return update + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.context.opts = { + 'EmailDomain': 'test.domain.com', + 'NotifyOnSuccess': True, + } + + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.getQuery).start() + self.queries = [] + 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.get_user = mock.patch('kojihub.get_user').start() + self._dml = mock.patch('kojihub._dml').start() + + self.exports = kojihub.RootExports() + self.exports.getLoggedInUser = mock.MagicMock() + self.exports.hasPerm = mock.MagicMock() + self.exports.getBuildNotificationBlock = mock.MagicMock() + self.user_id = 752 + self.n_id = 543 + + def tearDown(self): + mock.patch.stopall() + + def test_deleteNotificationBlock(self): + self.exports.getBuildNotificationBlock.return_value = {'user_id': self.user_id} + + self.exports.deleteNotificationBlock(self.n_id) + + self.exports.getBuildNotificationBlock.assert_called_once_with(self.n_id, strict=True) + self.exports.getLoggedInUser.assert_called_once_with() + self._dml.assert_called_once() + + def test_deleteNotificationBlock_missing(self): + self.exports.getBuildNotificationBlock.side_effect = koji.GenericError + + with self.assertRaises(koji.GenericError): + self.exports.deleteNotificationBlock(self.n_id) + + self.exports.getBuildNotificationBlock.assert_called_once_with(self.n_id, strict=True) + + def test_deleteNotificationBlock_not_logged(self): + self.exports.getBuildNotificationBlock.return_value = {'user_id': self.user_id} + self.exports.getLoggedInUser.return_value = None + # self.set_queries = ([ + # [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], + # ]) + + with self.assertRaises(koji.GenericError) as cm: + self.exports.deleteNotificationBlock(self.n_id) + self.assertEqual('Not logged-in', str(cm.exception)) + + self.exports.getBuildNotificationBlock.assert_called_once_with(self.n_id, strict=True) + self.assertEqual(len(self.inserts), 0) + self.assertEqual(len(self.updates), 0) + self.assertEqual(len(self.queries), 0) + + def test_deleteNotificationBlock_no_perm2(self): + self.exports.getBuildNotificationBlock.return_value = {'user_id': self.user_id} + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.hasPerm.return_value = False + + with self.assertRaises(koji.GenericError) as cm: + self.exports.deleteNotificationBlock(self.n_id) + self.assertEqual(f'user 1 cannot delete notification blocks for user {self.user_id}', + str(cm.exception)) + + self.exports.getBuildNotificationBlock.assert_called_once_with(self.n_id, strict=True) + self._dml.assert_not_called() diff --git a/tests/test_hub/test_disable_channel.py b/tests/test_hub/test_disable_channel.py index 61e28d6c..3d967ac5 100644 --- a/tests/test_hub/test_disable_channel.py +++ b/tests/test_hub/test_disable_channel.py @@ -28,7 +28,13 @@ class TestDisableChannel(unittest.TestCase): self.get_channel.return_value = None with self.assertRaises(koji.GenericError) as cm: self.exports.disableChannel(self.channelname) - self.assertEqual("No such channel: %s" % self.channelname, str(cm.exception)) + self.assertEqual(f"No such channel: {self.channelname}", str(cm.exception)) + + def test_wrong_type_channel(self): + comment = ['test-comment'] + with self.assertRaises(koji.GenericError) as cm: + self.exports.disableChannel(self.channelname, comment=comment) + self.assertEqual(f"Invalid type for value '{comment}': {type(comment)}", str(cm.exception)) def test_valid(self): self.get_channel.return_value = {'comment': None, 'description': None, diff --git a/tests/test_hub/test_dist_repo.py b/tests/test_hub/test_dist_repo.py index 9ba1be06..40b69e19 100644 --- a/tests/test_hub/test_dist_repo.py +++ b/tests/test_hub/test_dist_repo.py @@ -31,6 +31,7 @@ class TestDistRepoInit(unittest.TestCase): self.get_event = mock.patch('kojihub.get_event').start() self.nextval = mock.patch('kojihub.nextval').start() self.copyfile = mock.patch('shutil.copyfile').start() + self.lookup_name = mock.patch('kojihub.lookup_name').start() self.get_tag.return_value = {'id': 42, 'name': 'tag'} self.get_event.return_value = 12345 @@ -70,6 +71,34 @@ class TestDistRepoInit(unittest.TestCase): self.copyfile.assert_called_once() + def test_simple_dist_repo_init_wrong_type_keys(self): + keys = 'key1 key2' + with self.assertRaises(koji.ParameterError) as cm: + kojihub.dist_repo_init('tag', keys, {'arch': ['x86_64']}) + self.assertEqual(f"Invalid type for value '{keys}': {type(keys)}", str(cm.exception)) + self.InsertProcessor.assert_not_called() + + def test_simple_dist_repo_init_wrong_type_task_opts(self): + task_opts = 'opts' + with self.assertRaises(koji.ParameterError) as cm: + kojihub.dist_repo_init('tag', ['key'], task_opts) + self.assertEqual(f"Invalid type for value '{task_opts}': {type(task_opts)}", + str(cm.exception)) + self.InsertProcessor.assert_not_called() + + def test_simple_dist_repo_init_wrong_type_event(self): + event = 'test-event' + with self.assertRaises(koji.ParameterError) as cm: + kojihub.dist_repo_init('tag', ['key'], {'arch': ['x86_64'], 'event': event}) + self.assertEqual(f"Invalid type for value '{event}': {type(event)}", str(cm.exception)) + self.InsertProcessor.assert_not_called() + + def test_simple_dist_repo_init_wrong_type_volume(self): + self.lookup_name.side_effect = koji.GenericError + with self.assertRaises(koji.GenericError): + kojihub.dist_repo_init('tag', ['key'], {'arch': ['x86_64'], 'volume': 'test-volume'}) + self.InsertProcessor.assert_not_called() + class TestDistRepo(unittest.TestCase): @@ -206,7 +235,7 @@ class TestDistRepoMove(unittest.TestCase): path = os.path.join(repodir, relpath) basename = os.path.basename(path) if not os.path.exists(path): - raise Exception("Missing file: %s" % path) + raise Exception(f"Missing file: {path}") data = open(path, 'rt', encoding='utf-8').read() data.strip() self.assertEqual(data, basename) diff --git a/tests/test_hub/test_download_task_output.py b/tests/test_hub/test_download_task_output.py new file mode 100644 index 00000000..d7c9c745 --- /dev/null +++ b/tests/test_hub/test_download_task_output.py @@ -0,0 +1,36 @@ +import mock +import unittest +import koji +import kojihub + + +class TestDownloadTaskOutput(unittest.TestCase): + + def setUp(self): + self.exports = kojihub.RootExports() + self.exports.getVolume = mock.MagicMock() + self.task_id = 1 + self.filename = 'test-file' + self.volumename = 'test-volume' + + def tearDown(self): + mock.patch.stopall() + + def test_size_wrong_type(self): + size = 'test-size' + with self.assertRaises(koji.ParameterError) as cm: + self.exports.downloadTaskOutput(self.task_id, self.filename, size=size) + self.assertEqual(f"Invalid type for value '{size}': {type(size)}", str(cm.exception)) + + def test_volume_non_exist_wrong_type(self): + self.exports.getVolume.side_effect = koji.GenericError + with self.assertRaises(koji.GenericError): + self.exports.downloadTaskOutput(self.task_id, self.filename, volume=self.volumename) + + def test_filename_wrong_format(self): + filename = '../test-file' + volumeinfo = {'id': 1, 'name': self.volumename} + self.exports.getVolume.return_value = volumeinfo + with self.assertRaises(koji.GenericError) as cm: + self.exports.downloadTaskOutput(self.task_id, filename, volume=self.volumename) + self.assertEqual(f"Invalid file name: {filename}", str(cm.exception)) diff --git a/tests/test_hub/test_edit_channel.py b/tests/test_hub/test_edit_channel.py index 4196ba30..24c588a9 100644 --- a/tests/test_hub/test_edit_channel.py +++ b/tests/test_hub/test_edit_channel.py @@ -34,32 +34,28 @@ class TestEditChannel(unittest.TestCase): self.exports = kojihub.RootExports() self.channel_name = 'test-channel' self.channel_name_new = 'test-channel-2' + self.channel_info = {'id': 123, 'name': self.channel_name, 'description': 'description', + 'comment': 'comment'} + self.get_channel = mock.patch('kojihub.get_channel').start() + self.verify_name_internal = mock.patch('kojihub.verify_name_internal').start() def tearDown(self): mock.patch.stopall() - @mock.patch('kojihub.verify_name_internal') - @mock.patch('kojihub.get_channel') - def test_edit_channel_missing(self, get_channel, verify_name_internal): + def test_edit_channel_missing(self): expected = 'Invalid type for channelInfo: %s' % self.channel_name - get_channel.side_effect = koji.GenericError(expected) + self.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.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.verify_name_internal') - @mock.patch('kojihub.get_channel') - def test_edit_channel_already_exists(self, get_channel, verify_name_internal): - verify_name_internal.return_value = None - get_channel.side_effect = [ - { - 'id': 123, - 'name': self.channel_name, - 'description': 'description', - }, + def test_edit_channel_already_exists(self): + self.verify_name_internal.return_value = None + self.get_channel.side_effect = [ + self.channel_info, { 'id': 124, 'name': self.channel_name_new, @@ -70,29 +66,22 @@ class TestEditChannel(unittest.TestCase): 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.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, + self.assertEqual(f'channel {self.channel_name_new} already exists (id=124)', str(ex.exception)) - @mock.patch('kojihub.verify_name_internal') - @mock.patch('kojihub.get_channel') - def test_edit_channel_valid(self, get_channel, verify_name_internal): - verify_name_internal.return_value = None - kojihub.get_channel.side_effect = [{ - 'id': 123, - 'name': self.channel_name, - 'description': 'description', - }, - {}] + def test_edit_channel_valid(self): + self.verify_name_internal.return_value = None + kojihub.get_channel.side_effect = [self.channel_info, {}] r = self.exports.editChannel(self.channel_name, name=self.channel_name_new, description='description_new') self.assertTrue(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.get_channel.assert_has_calls(expected_calls) self.assertEqual(len(self.updates), 1) values = {'channelID': 123} @@ -103,21 +92,56 @@ class TestEditChannel(unittest.TestCase): self.assertEqual(update.values, values) self.assertEqual(update.clauses, clauses) - @mock.patch('kojihub.verify_name_internal') - @mock.patch('kojihub.get_channel') - def test_edit_channel_wrong_format(self, get_channel, verify_name_internal): + def test_edit_channel_wrong_name(self): channel_name_new = 'test-channel+' - get_channel.return_value = {'id': 123, - 'name': self.channel_name, - 'description': 'description', - } + self.get_channel.return_value = self.channel_info # name is longer as expected - verify_name_internal.side_effect = koji.GenericError + self.verify_name_internal.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): self.exports.editChannel(self.channel_name, name=channel_name_new) # not except regex rules - verify_name_internal.side_effect = koji.GenericError + self.verify_name_internal.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): self.exports.editChannel(self.channel_name, name=channel_name_new) + + def test_edit_channel_no_change(self): + self.verify_name_internal.return_value = None + kojihub.get_channel.return_value = self.channel_info + + r = self.exports.editChannel(self.channel_name, description='description') + self.assertFalse(r) + self.assertEqual(self.updates, []) + self.get_channel.assert_called_once_with(self.channel_name, strict=True) + self.verify_name_internal.assert_not_called() + + def test_edit_channel_wrong_format_new_name(self): + channel_name_new = 13568 + self.verify_name_internal.side_effect = koji.GenericError + with self.assertRaises(koji.GenericError): + self.exports.editChannel(self.channel_name, name=channel_name_new) + self.assertEqual(self.updates, []) + self.get_channel.assert_called_once_with(self.channel_name, strict=True) + self.verify_name_internal.assert_called_once_with(channel_name_new) + + def test_edit_channel_wrong_format_description(self): + description = ['description'] + self.get_channel.return_value = self.channel_info + with self.assertRaises(koji.ParameterError) as ex: + self.exports.editChannel(self.channel_name, description=description) + self.assertEqual(self.updates, []) + self.assertEqual(f"Invalid type for value '{description}': {type(description)}", + str(ex.exception)) + self.get_channel.assert_called_once_with(self.channel_name, strict=True) + self.verify_name_internal.assert_not_called() + + def test_edit_channel_wrong_format_comment(self): + comment = ['comment'] + self.get_channel.return_value = self.channel_info + with self.assertRaises(koji.ParameterError) as ex: + self.exports.editChannel(self.channel_name, comment=comment) + self.assertEqual(self.updates, []) + self.assertEqual(f"Invalid type for value '{comment}': {type(comment)}", str(ex.exception)) + self.get_channel.assert_called_once_with(self.channel_name, strict=True) + self.verify_name_internal.assert_not_called() diff --git a/tests/test_hub/test_edit_host.py b/tests/test_hub/test_edit_host.py index a687bd0c..fc9aea51 100644 --- a/tests/test_hub/test_edit_host.py +++ b/tests/test_hub/test_edit_host.py @@ -8,7 +8,7 @@ UP = kojihub.UpdateProcessor IP = kojihub.InsertProcessor -class TestSetHostEnabled(unittest.TestCase): +class TestEditHost(unittest.TestCase): def getInsert(self, *args, **kwargs): insert = IP(*args, **kwargs) insert.execute = mock.MagicMock() @@ -22,6 +22,7 @@ class TestSetHostEnabled(unittest.TestCase): return update def setUp(self): + self.diff = None self.InsertProcessor = mock.patch('kojihub.InsertProcessor', side_effect=self.getInsert).start() self.inserts = [] @@ -34,12 +35,22 @@ class TestSetHostEnabled(unittest.TestCase): self.context.session.assertLogin = mock.MagicMock() self.context.session.assertPerm = mock.MagicMock() self.exports = kojihub.RootExports() + self.get_host = mock.patch('kojihub.get_host').start() + self.hostinfo = { + 'id': 123, + 'user_id': 234, + 'name': 'hostname', + 'arches': 'x86_64', + 'capacity': 100.0, + 'description': 'description', + 'comment': 'comment', + 'enabled': False, + } def tearDown(self): mock.patch.stopall() def test_edit_host_missing(self): - kojihub.get_host = mock.MagicMock() kojihub.get_host.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): self.exports.editHost('hostname') @@ -47,23 +58,47 @@ class TestSetHostEnabled(unittest.TestCase): self.assertEqual(self.inserts, []) self.assertEqual(self.updates, []) + def test_edit_host_invalid_description(self): + description = ['description'] + kojihub.get_host.return_value = self.hostinfo + with self.assertRaises(koji.ParameterError) as ex: + self.exports.editHost('hostname', description=description) + self.assertEqual('Invalid type for description parameter: %s' % type(description), + str(ex.exception)) + kojihub.get_host.assert_called_once_with('hostname', strict=True) + self.assertEqual(self.inserts, []) + self.assertEqual(self.updates, []) + + def test_edit_host_invalid_comment_parameter(self): + comment = ['comment'] + kojihub.get_host.return_value = self.hostinfo + with self.assertRaises(koji.ParameterError) as ex: + self.exports.editHost('hostname', comment=comment) + self.assertEqual('Invalid type for comment parameter: %s' % type(comment), + str(ex.exception)) + kojihub.get_host.assert_called_once_with('hostname', strict=True) + self.assertEqual(self.inserts, []) + self.assertEqual(self.updates, []) + + def test_edit_host_invalid_arches_parameter(self): + arches = ['arches arches'] + kojihub.get_host.return_value = self.hostinfo + with self.assertRaises(koji.ParameterError) as ex: + self.exports.editHost('hostname', arches=arches) + self.assertEqual('Invalid type for arches parameter: %s' % type(arches), + str(ex.exception)) + kojihub.get_host.assert_called_once_with('hostname', strict=True) + self.assertEqual(self.inserts, []) + self.assertEqual(self.updates, []) + def test_edit_host_valid(self): kojihub.get_host = mock.MagicMock() - kojihub.get_host.return_value = { - 'id': 123, - 'user_id': 234, - 'name': 'hostname', - 'arches': ['x86_64'], - 'capacity': 100.0, - 'description': 'description', - 'comment': 'comment', - 'enabled': False, - } + kojihub.get_host.return_value = self.hostinfo self.context.event_id = 42 self.context.session.user_id = 23 - r = self.exports.editHost('hostname', arches=['x86_64', 'i386'], - capacity=12.0, comment='comment_new', non_existing_kw='bogus') + r = self.exports.editHost('hostname', arches='x86_64 i386', capacity=12.0, + comment='comment_new', non_existing_kw='bogus') self.assertTrue(r) kojihub.get_host.assert_called_once_with('hostname', strict=True) @@ -87,12 +122,11 @@ class TestSetHostEnabled(unittest.TestCase): # insert self.assertEqual(len(self.inserts), 1) insert = self.inserts[0] - #data = kojihub.get_host.return_value data = { 'create_event': 42, 'creator_id': 23, 'host_id': 123, - 'arches': ['x86_64', 'i386'], + 'arches': 'x86_64 i386', 'capacity': 12.0, 'comment': 'comment_new', 'description': 'description', @@ -105,16 +139,7 @@ class TestSetHostEnabled(unittest.TestCase): def test_edit_host_no_change(self): kojihub.get_host = mock.MagicMock() - kojihub.get_host.return_value = { - 'id': 123, - 'user_id': 234, - 'name': 'hostname', - 'arches': ['x86_64'], - 'capacity': 100.0, - 'description': 'description', - 'comment': 'comment', - 'enabled': False, - } + kojihub.get_host.return_value = self.hostinfo self.context.event_id = 42 self.context.session.user_id = 23 diff --git a/tests/test_hub/test_edit_permission.py b/tests/test_hub/test_edit_permission.py index 39a2d02c..0a6b8ffe 100644 --- a/tests/test_hub/test_edit_permission.py +++ b/tests/test_hub/test_edit_permission.py @@ -42,3 +42,12 @@ class TestEditPermission(unittest.TestCase): self.assertEqual(up.table, 'permissions') self.assertEqual(up.rawdata, {}) self.context.session.assertPerm.assert_called_with('admin') + + def test_edit_permission_wrong_type_permission(self): + description = ['test-description'] + with self.assertRaises(koji.GenericError) as ex: + self.exports.editPermission(self.perm_name, description=description) + self.assertEqual(f"Invalid type for value '{description}': {type(description)}", + str(ex.exception)) + self.update_processor.assert_not_called() + self.context.session.assertPerm.assert_called_with('admin') diff --git a/tests/test_hub/test_edit_tag.py b/tests/test_hub/test_edit_tag.py index bc7aa1ec..6112047f 100644 --- a/tests/test_hub/test_edit_tag.py +++ b/tests/test_hub/test_edit_tag.py @@ -236,7 +236,7 @@ WHERE id = %(tagID)i""", {'name': 'newtag', 'tagID': 333}) with self.assertRaises(koji.GenericError): kojihub._edit_tag('tag', **kwargs) - def test_edit_wrong_format_tag(self): + def test_edit_wrong_tag(self): tag_name_new = 'new-test-tag+' tag_name = 'tag' self.get_tag.return_value = {'id': 333, @@ -268,3 +268,36 @@ WHERE id = %(tagID)i""", {'name': 'newtag', 'tagID': 333}) self.verify_name_internal.side_effect = koji.GenericError with self.assertRaises(koji.GenericError): kojihub._edit_tag('tag', **kwargs) + + def test_edit_tag_remove_extra_wrong_format(self): + kwargs = { + 'perm': None, + 'name': 'tag_name_new', + 'arches': 'arch1 arch2', + 'locked': True, + 'maven_support': False, + 'maven_include_all': False, + 'extra': {}, + 'remove_extra': 'remove-extra' + } + with self.assertRaises(koji.ParameterError) as ex: + kojihub._edit_tag('tag', **kwargs) + self.assertEqual(f"Invalid type for value '{kwargs['remove_extra']}': " + f"{type(kwargs['remove_extra'])}", str(ex.exception)) + + def test_edit_tag_block_extra_wrong_format(self): + kwargs = { + 'perm': None, + 'name': 'tag_name_new', + 'arches': 'arch1 arch2', + 'locked': True, + 'maven_support': False, + 'maven_include_all': False, + 'extra': {}, + 'remove_extra': [], + 'block_extra': 'block-extra' + } + with self.assertRaises(koji.ParameterError) as ex: + kojihub._edit_tag('tag', **kwargs) + self.assertEqual(f"Invalid type for value '{kwargs['block_extra']}': " + f"{type(kwargs['block_extra'])}", str(ex.exception)) diff --git a/tests/test_hub/test_edit_tag_external_repo.py b/tests/test_hub/test_edit_tag_external_repo.py index c96a1733..22ff6eb9 100644 --- a/tests/test_hub/test_edit_tag_external_repo.py +++ b/tests/test_hub/test_edit_tag_external_repo.py @@ -42,8 +42,7 @@ class TestEditTagExternalRepo(unittest.TestCase): self.get_tag_external_repos.return_value = [] with self.assertRaises(koji.GenericError) as cm: kojihub.edit_tag_external_repo('tag', 'ext_repo', priority=6, merge_mode='bare') - self.assertEqual(cm.exception.args[0], - 'external repo ext_repo not associated with tag tag') + self.assertEqual('external repo ext_repo not associated with tag tag', str(cm.exception)) self.get_tag.assert_called_once_with('tag', strict=True) self.get_external_repo.assert_called_once_with('ext_repo', strict=True) self.get_tag_external_repos.assert_called_once_with(tag_info=1, repo_info=11) diff --git a/tests/test_hub/test_enable_channel.py b/tests/test_hub/test_enable_channel.py index b8a3bf66..ac529c7f 100644 --- a/tests/test_hub/test_enable_channel.py +++ b/tests/test_hub/test_enable_channel.py @@ -29,7 +29,7 @@ class TestEnableChannel(unittest.TestCase): self.get_channel.return_value = None with self.assertRaises(koji.GenericError) as cm: self.exports.enableChannel(self.channelname) - self.assertEqual("No such channel: %s" % self.channelname, str(cm.exception)) + self.assertEqual(f"No such channel: {self.channelname}", str(cm.exception)) def test_valid(self): self.get_channel.return_value = {'comment': None, 'description': None, @@ -42,3 +42,9 @@ class TestEnableChannel(unittest.TestCase): self.assertEqual(update.values, {'comment': None, 'description': None, 'enabled': False, 'id': 1, 'name': 'test-channel'}) self.assertEqual(update.clauses, ['id = %(id)i']) + + def test_wrong_type_channel(self): + comment = ['test-comment'] + with self.assertRaises(koji.GenericError) as cm: + self.exports.enableChannel(self.channelname, comment=comment) + self.assertEqual(f"Invalid type for value '{comment}': {type(comment)}", str(cm.exception)) diff --git a/tests/test_hub/test_getRPM.py b/tests/test_hub/test_getRPM.py index 7d25c55c..0f12ce61 100644 --- a/tests/test_hub/test_getRPM.py +++ b/tests/test_hub/test_getRPM.py @@ -14,7 +14,7 @@ class TestGetRPM(unittest.TestCase): rpminfo = ['test-user'] with self.assertRaises(koji.GenericError) as cm: kojihub.get_rpm(rpminfo) - self.assertEqual("Invalid type for rpminfo: %s" % type(rpminfo), str(cm.exception)) + self.assertEqual(f"Invalid type for rpminfo: {type(rpminfo)}", str(cm.exception)) class TestGetRPMHeaders(unittest.TestCase): @@ -41,7 +41,7 @@ class TestGetRPMHeaders(unittest.TestCase): filepath = '../test/path' with self.assertRaises(koji.GenericError) as cm: self.exports.getRPMHeaders(taskID=99, filepath=filepath) - self.assertEqual("Invalid filepath: %s" % filepath, str(cm.exception)) + self.assertEqual(f"Invalid filepath: {filepath}", str(cm.exception)) self.get_rpm.assert_not_called() self.get_build.assert_not_called() self.get_header_fields.assert_not_called() diff --git a/tests/test_hub/test_get_archive.py b/tests/test_hub/test_get_archive.py new file mode 100644 index 00000000..05b13bd1 --- /dev/null +++ b/tests/test_hub/test_get_archive.py @@ -0,0 +1,28 @@ +import unittest +import mock + +import koji +import kojihub + + +class TestGetArchive(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + self.list_archives = mock.patch('kojihub.list_archives').start() + + def tearDown(self): + mock.patch.stopall() + + def test_get_archive_non_exist_archive_with_strict(self): + archive_id = 1 + self.list_archives.return_value = [] + with self.assertRaises(koji.GenericError) as cm: + kojihub.get_archive(archive_id, strict=True) + self.assertEqual(f"No such archive: {archive_id}", str(cm.exception)) + + def test_get_archive_non_exist_archive_without_strict(self): + archive_id = 1 + self.list_archives.return_value = [] + rv = kojihub.get_archive(archive_id) + self.assertEqual(rv, None) diff --git a/tests/test_hub/test_get_archive_type.py b/tests/test_hub/test_get_archive_type.py new file mode 100644 index 00000000..bf27b4a3 --- /dev/null +++ b/tests/test_hub/test_get_archive_type.py @@ -0,0 +1,20 @@ +import unittest + +import koji +import kojihub + + +class TestGetArchiveType(unittest.TestCase): + + def test_get_archive_wrong_type_filename(self): + filename = ['test-filename'] + with self.assertRaises(koji.ParameterError) as cm: + kojihub.get_archive_type(filename=filename) + self.assertEqual(f"Invalid type for value '{filename}': {type(filename)}", + str(cm.exception)) + + def test_get_archive_without_opt(self): + with self.assertRaises(koji.GenericError) as cm: + kojihub.get_archive_type() + self.assertEqual("one of filename, type_name, or type_id must be specified", + str(cm.exception)) diff --git a/tests/test_hub/test_get_build_notification.py b/tests/test_hub/test_get_build_notification.py new file mode 100644 index 00000000..518e33a7 --- /dev/null +++ b/tests/test_hub/test_get_build_notification.py @@ -0,0 +1,22 @@ +import mock +import unittest +import koji +import kojihub + + +class TestGetBuildNotification(unittest.TestCase): + + def setUp(self): + self.QueryProcessor = mock.patch('kojihub.QueryProcessor').start() + self.query = self.QueryProcessor.return_value + self.exports = kojihub.RootExports() + + def tearDown(self): + mock.patch.stopall() + + def test_empty_result_with_strict(self): + notif_id = 1 + self.query.executeOne.return_value = None + with self.assertRaises(koji.GenericError) as cm: + self.exports.getBuildNotification(notif_id, strict=True) + self.assertEqual(f"No notification with ID {notif_id} found", str(cm.exception)) diff --git a/tests/test_hub/test_get_build_notification_block.py b/tests/test_hub/test_get_build_notification_block.py new file mode 100644 index 00000000..1a17072d --- /dev/null +++ b/tests/test_hub/test_get_build_notification_block.py @@ -0,0 +1,22 @@ +import mock +import unittest +import koji +import kojihub + + +class TestGetBuildNotificationBlock(unittest.TestCase): + + def setUp(self): + self.QueryProcessor = mock.patch('kojihub.QueryProcessor').start() + self.query = self.QueryProcessor.return_value + self.exports = kojihub.RootExports() + + def tearDown(self): + mock.patch.stopall() + + def test_empty_result_with_strict(self): + notif_id = 1 + self.query.executeOne.return_value = None + with self.assertRaises(koji.GenericError) as cm: + self.exports.getBuildNotificationBlock(notif_id, strict=True) + self.assertEqual(f"No notification block with ID {notif_id} found", str(cm.exception)) diff --git a/tests/test_hub/test_get_build_notification_blocks.py b/tests/test_hub/test_get_build_notification_blocks.py new file mode 100644 index 00000000..720db8eb --- /dev/null +++ b/tests/test_hub/test_get_build_notification_blocks.py @@ -0,0 +1,29 @@ +import mock +import unittest +import koji +import kojihub + + +class TestGetBuildNotificationBlocks(unittest.TestCase): + def setUp(self): + self.exports = kojihub.RootExports() + self.get_user = mock.patch('kojihub.get_user').start() + self.get_build_notification_blocks = mock.patch( + 'kojihub.get_build_notification_blocks').start() + + def tearDown(self): + mock.patch.stopall() + + def test_loggedin_user(self): + self.get_user.return_value = {'id': 1} + self.exports.getBuildNotificationBlocks(None) + self.get_user.assert_called_once_with(None, strict=True) + self.get_build_notification_blocks.assert_called_once_with(1) + + def test_user_not_found(self): + self.get_user.side_effect = koji.GenericError('error msg') + with self.assertRaises(koji.GenericError) as cm: + self.exports.getBuildNotificationBlocks(1) + self.get_user.assert_called_once_with(1, strict=True) + self.get_build_notification_blocks.assert_not_called() + self.assertEqual(cm.exception.args[0], 'error msg') diff --git a/tests/test_hub/test_get_build_notifications.py b/tests/test_hub/test_get_build_notifications.py index b4fb1765..aba3e97e 100644 --- a/tests/test_hub/test_get_build_notifications.py +++ b/tests/test_hub/test_get_build_notifications.py @@ -5,18 +5,21 @@ import kojihub class TestGetBuildNotifications(unittest.TestCase): - @mock.patch('kojihub.get_user', return_value={'id': 1}) - @mock.patch('kojihub.get_build_notifications') - def test_loggedin_user(self, get_build_notifications, get_user): - kojihub.RootExports().getBuildNotifications(None) - get_user.assert_called_once_with(None, strict=True) - get_build_notifications.assert_called_once_with(1) + def setUp(self): + self.exports = kojihub.RootExports() + self.get_user = mock.patch('kojihub.get_user').start() + self.get_build_notifications = mock.patch('kojihub.get_build_notifications').start() - @mock.patch('kojihub.get_user', side_effect=koji.GenericError('error msg')) - @mock.patch('kojihub.get_build_notifications') - def test_user_not_found(self, get_build_notifications, get_user): + def test_loggedin_user(self): + self.get_user.return_value = {'id': 1} + kojihub.RootExports().getBuildNotifications(None) + self.get_user.assert_called_once_with(None, strict=True) + self.get_build_notifications.assert_called_once_with(1) + + def test_user_not_found(self): + self.get_user.side_effect = koji.GenericError('error msg') with self.assertRaises(koji.GenericError) as cm: kojihub.RootExports().getBuildNotifications(1) - get_user.assert_called_once_with(1, strict=True) - get_build_notifications.assert_not_called() + self.get_user.assert_called_once_with(1, strict=True) + self.get_build_notifications.assert_not_called() self.assertEqual(cm.exception.args[0], 'error msg') diff --git a/tests/test_hub/test_get_changelog_entries.py b/tests/test_hub/test_get_changelog_entries.py index d75960fd..220cf149 100644 --- a/tests/test_hub/test_get_changelog_entries.py +++ b/tests/test_hub/test_get_changelog_entries.py @@ -24,13 +24,13 @@ class TestGetChangelogEntries(unittest.TestCase): self.get_build.return_value = None with self.assertRaises(koji.GenericError) as cm: self.exports.getChangelogEntries(buildID=build_id, strict=True) - self.assertEqual("No such build: %s" % build_id, str(cm.exception)) + self.assertEqual(f"No such build: {build_id}", str(cm.exception)) def test_taskid_invalid_path(self): filepath = '../test/path' with self.assertRaises(koji.GenericError) as cm: self.exports.getChangelogEntries(taskID=99, filepath=filepath) - self.assertEqual("Invalid filepath: %s" % filepath, str(cm.exception)) + self.assertEqual(f"Invalid filepath: {filepath}", str(cm.exception)) def test_taskid_without_filepath(self): with self.assertRaises(koji.GenericError) as cm: @@ -43,7 +43,7 @@ class TestGetChangelogEntries(unittest.TestCase): self.os_path_exists.return_value = True with self.assertRaises(koji.GenericError) as cm: self.exports.getChangelogEntries(taskID=99, before=before, filepath=filepath) - self.assertEqual("Invalid type for before: %s" % type(before), str(cm.exception)) + self.assertEqual(f"Invalid type for before: {type(before)}", str(cm.exception)) def test_after_invalid_type(self): after = {'after': '1133456'} @@ -51,7 +51,7 @@ class TestGetChangelogEntries(unittest.TestCase): self.os_path_exists.return_value = True with self.assertRaises(koji.GenericError) as cm: self.exports.getChangelogEntries(taskID=99, after=after, filepath=filepath) - self.assertEqual("Invalid type for after: %s" % type(after), str(cm.exception)) + self.assertEqual(f"Invalid type for after: {type(after)}", str(cm.exception)) def test_srpm_path_not_exist(self): filepath = 'test/path' @@ -62,4 +62,4 @@ class TestGetChangelogEntries(unittest.TestCase): self.os_path_exists.return_value = False with self.assertRaises(koji.GenericError) as cm: self.exports.getChangelogEntries(taskID=task_id, filepath=filepath, strict=True) - self.assertEqual("SRPM %s doesn't exist" % srpm_path, str(cm.exception)) + self.assertEqual(f"SRPM {srpm_path} doesn't exist", str(cm.exception)) diff --git a/tests/test_hub/test_get_external_repo.py b/tests/test_hub/test_get_external_repo.py index 1880edfc..cb687e40 100644 --- a/tests/test_hub/test_get_external_repo.py +++ b/tests/test_hub/test_get_external_repo.py @@ -17,7 +17,7 @@ class TestGetExternalRepo(unittest.TestCase): self.get_external_repos.return_value = [] with self.assertRaises(koji.GenericError) as cm: self.exports.getExternalRepo(repo, strict=True) - self.assertEqual("No such repo: %s" % repo, str(cm.exception)) + self.assertEqual(f"No such repo: {repo}", str(cm.exception)) def test_non_exist_repo_without_strict(self): repo = 'test-repo' diff --git a/tests/test_hub/test_get_external_repos.py b/tests/test_hub/test_get_external_repos.py index 25f09fa3..6bbd245a 100644 --- a/tests/test_hub/test_get_external_repos.py +++ b/tests/test_hub/test_get_external_repos.py @@ -1,4 +1,3 @@ -import koji import kojihub from .utils import DBQueryTestCase @@ -97,9 +96,3 @@ class TestGetExternalRepos(DBQueryTestCase): self.assertEqual(rv, [{'id': 1, 'name': 'ext_repo_1', 'url': 'http://example.com/repo/'}]) - - def test_get_external_repos_wrong_type(self): - info = {'info_key': 'info_value'} - with self.assertRaises(koji.GenericError) as cm: - kojihub.get_external_repos(info=info) - self.assertEqual("Invalid name or id value: %s" % info, str(cm.exception)) diff --git a/tests/test_hub/test_get_next_release.py b/tests/test_hub/test_get_next_release.py index 561a3f18..27a720c8 100644 --- a/tests/test_hub/test_get_next_release.py +++ b/tests/test_hub/test_get_next_release.py @@ -7,7 +7,9 @@ import kojihub class TestGetNextRelease(unittest.TestCase): def setUp(self): + self.maxDiff = None self.QueryProcessor = mock.patch('kojihub.QueryProcessor').start() + self.get_build = mock.patch('kojihub.get_build').start() self._dml = mock.patch('kojihub._dml').start() self.query = self.QueryProcessor.return_value self.binfo = {'name': 'name', 'version': 'version'} @@ -25,7 +27,7 @@ class TestGetNextRelease(unittest.TestCase): for n in [1, 2, 3, 5, 8, 13, 21, 34, 55]: self.query.executeOne.return_value = {'release': str(n)} result = kojihub.get_next_release(self.binfo) - self.assertEqual(result, str(n+1)) + self.assertEqual(result, str(n + 1)) def test_get_next_release_complex(self): data = [ @@ -59,10 +61,10 @@ class TestGetNextRelease(unittest.TestCase): # bad_incr_value "foo", None, - 1.1, - {1:1}, + {1: 1}, [1], ] for val in data: - with self.assertRaises(koji.ParameterError): + with self.assertRaises(koji.ParameterError) as ex: kojihub.get_next_release(self.binfo, incr=val) + self.assertEqual('incr parameter must be an integer', str(ex.exception)) diff --git a/tests/test_hub/test_get_notification_recipients.py b/tests/test_hub/test_get_notification_recipients.py new file mode 100644 index 00000000..d6e9e863 --- /dev/null +++ b/tests/test_hub/test_get_notification_recipients.py @@ -0,0 +1,258 @@ +import mock +import unittest + +import koji +import kojihub + +QP = kojihub.QueryProcessor +IP = kojihub.InsertProcessor +UP = kojihub.UpdateProcessor + + +class TestGetNotificationRecipients(unittest.TestCase): + def getInsert(self, *args, **kwargs): + insert = IP(*args, **kwargs) + insert.execute = mock.MagicMock() + self.inserts.append(insert) + return insert + + def getQuery(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query + + def getUpdate(self, *args, **kwargs): + update = UP(*args, **kwargs) + update.execute = mock.MagicMock() + self.updates.append(update) + return update + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.context.opts = { + 'EmailDomain': 'test.domain.com', + 'NotifyOnSuccess': True, + } + + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.getQuery).start() + self.queries = [] + 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.readPackageList = mock.patch('kojihub.readPackageList').start() + self.get_user = mock.patch('kojihub.get_user').start() + + self.exports = kojihub.RootExports() + + def tearDown(self): + mock.patch.stopall() + + def test_get_notification_recipients_watchers(self): + # without build / tag_id + build = None + tag_id = None + state = koji.BUILD_STATES['CANCELED'] + + emails = kojihub.get_notification_recipients(build, tag_id, state) + self.assertEqual(emails, []) + + # only query to watchers + self.assertEqual(len(self.queries), 1) + q = self.queries[0] + self.assertEqual(q.columns, ['email', 'user_id']) + self.assertEqual(q.tables, ['build_notifications']) + self.assertEqual(q.clauses, ['package_id IS NULL', + 'status = %(users_status)i', + 'success_only = FALSE', + 'tag_id IS NULL', + 'usertype IN %(users_usertypes)s']) + self.assertEqual(q.joins, ['JOIN users ON build_notifications.user_id = users.id']) + self.assertEqual(q.values['state'], state) + self.assertEqual(q.values['build'], build) + self.assertEqual(q.values['tag_id'], tag_id) + + ''' + q = self.queries[1] + self.assertEqual(q.columns, ['user_id']) + self.assertEqual(q.tables, ['build_notifications_block']) + self.assertEqual(q.clauses, ['user_id IN %(user_ids)s']) + self.assertEqual(q.joins, []) + self.assertEqual(q.values['user_ids'], None) + ''' + self.readPackageList.assert_not_called() + + def test_get_notification_recipients_build_without_tag(self): + ### with build without tag + tag_id = None + state = koji.BUILD_STATES['CANCELED'] + build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} + self.queries = [] + self.set_queries([ + [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], + [] + ]) + + emails = kojihub.get_notification_recipients(build, tag_id, state) + self.assertEqual(emails, ['owner_name@test.domain.com']) + + # there should be only query to watchers + self.assertEqual(len(self.queries), 2) + q = self.queries[0] + self.assertEqual(q.columns, ['email', 'user_id']) + self.assertEqual(q.tables, ['build_notifications']) + self.assertEqual(q.clauses, ['package_id = %(package_id)i OR package_id IS NULL', + 'status = %(users_status)i', + 'success_only = FALSE', + 'tag_id IS NULL', + 'usertype IN %(users_usertypes)s']) + self.assertEqual(q.joins, ['JOIN users ON build_notifications.user_id = users.id']) + self.assertEqual(q.values['package_id'], build['package_id']) + self.assertEqual(q.values['state'], state) + self.assertEqual(q.values['build'], build) + self.assertEqual(q.values['tag_id'], tag_id) + + q = self.queries[1] + self.assertEqual(q.columns, ['user_id']) + self.assertEqual(q.tables, ['build_notifications_block']) + self.assertEqual(q.clauses, ['package_id = %(package_id)i OR package_id IS NULL', + 'tag_id IS NULL', + 'user_id IN %(user_ids)s', + ]) + self.assertEqual(q.joins, None) + self.assertEqual(q.values['user_ids'], [5]) + + self.readPackageList.assert_not_called() + + def test_get_notification_recipients_tag_without_build(self): + ### with tag without build makes no sense + build = None + tag_id = 123 + state = koji.BUILD_STATES['CANCELED'] + self.queries = [] + + with self.assertRaises(koji.GenericError): + kojihub.get_notification_recipients(build, tag_id, state) + self.assertEqual(self.queries, []) + self.readPackageList.assert_not_called() + + def set_queries(self, return_values): + self.query_returns = return_values + self.query_returns.reverse() + + def getQuery(*args, **kwargs): + q = QP(*args, **kwargs) + q.execute = mock.MagicMock() + q.execute.return_value = self.query_returns.pop() + self.queries.append(q) + return q + self.QueryProcessor.side_effect = getQuery + + def test_get_notification_recipients_tag_with_build(self): + ### with tag and build + build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} + tag_id = 123 + state = koji.BUILD_STATES['CANCELED'] + self.readPackageList.return_value = {12345: {'blocked': False, 'owner_id': 'owner_id'}} + self.get_user.return_value = { + 'id': 342, + 'name': 'pkg_owner_name', + 'status': koji.USER_STATUS['NORMAL'], + 'usertype': koji.USERTYPES['NORMAL'] + } + self.set_queries([ + [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], + [] + ]) + + emails = kojihub.get_notification_recipients(build, tag_id, state) + self.assertEqual(sorted(emails), + ['owner_name@test.domain.com', 'pkg_owner_name@test.domain.com']) + + # there should be only query to watchers + self.assertEqual(len(self.queries), 2) + q = self.queries[0] + self.assertEqual(q.columns, ['email', 'user_id']) + self.assertEqual(q.tables, ['build_notifications']) + self.assertEqual(q.clauses, ['package_id = %(package_id)i OR package_id IS NULL', + 'status = %(users_status)i', + 'success_only = FALSE', + 'tag_id = %(tag_id)i OR tag_id IS NULL', + 'usertype IN %(users_usertypes)s', + ]) + self.assertEqual(q.joins, ['JOIN users ON build_notifications.user_id = users.id']) + self.assertEqual(q.values['package_id'], build['package_id']) + self.assertEqual(q.values['state'], state) + self.assertEqual(q.values['build'], build) + self.assertEqual(q.values['tag_id'], tag_id) + + q = self.queries[1] + self.assertEqual(q.columns, ['user_id']) + self.assertEqual(q.tables, ['build_notifications_block']) + self.assertEqual(q.clauses, ['package_id = %(package_id)i OR package_id IS NULL', + 'tag_id = %(tag_id)i OR tag_id IS NULL', + 'user_id IN %(user_ids)s', + ]) + self.assertEqual(q.joins, None) + self.assertEqual(sorted(q.values['user_ids']), [5, 342]) + self.readPackageList.assert_called_once_with( + pkgID=build['package_id'], tagID=tag_id, inherit=True) + self.get_user.asssert_called_once_with(342, strict=True) + + def test_get_notification_recipients_blocked_pkg_owner(self): + # blocked package owner + build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} + tag_id = 123 + state = koji.BUILD_STATES['CANCELED'] + self.get_user.return_value = { + 'id': 342, + 'name': 'pkg_owner_name', + 'status': koji.USER_STATUS['BLOCKED'], + 'usertype': koji.USERTYPES['NORMAL'] + } + self.set_queries([ + [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], + [] + ]) + emails = kojihub.get_notification_recipients(build, tag_id, state) + self.assertEqual(emails, ['owner_name@test.domain.com']) + + def test_get_notification_recipients_optout(self): + # blocked package owner + build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} + tag_id = 123 + state = koji.BUILD_STATES['CANCELED'] + self.get_user.return_value = { + 'id': 342, + 'name': 'pkg_owner_name', + 'status': koji.USER_STATUS['NORMAL'], + 'usertype': koji.USERTYPES['NORMAL'] + } + self.set_queries([ + [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], + [{'user_id': 5}] + ]) + emails = kojihub.get_notification_recipients(build, tag_id, state) + self.assertEqual(emails, []) + + def test_get_notification_recipients_machine(self): + # package owner is machine + build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} + tag_id = 123 + state = koji.BUILD_STATES['CANCELED'] + self.get_user.return_value = { + 'id': 342, + 'name': 'pkg_owner_name', + 'status': koji.USER_STATUS['NORMAL'], + 'usertype': koji.USERTYPES['HOST'] + } + self.set_queries([ + [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], + [] + ]) + emails = kojihub.get_notification_recipients(build, tag_id, state) + self.assertEqual(emails, ['owner_name@test.domain.com']) diff --git a/tests/test_hub/test_grant_permissions.py b/tests/test_hub/test_grant_permissions.py index 3745b4ab..d8371ba0 100644 --- a/tests/test_hub/test_grant_permissions.py +++ b/tests/test_hub/test_grant_permissions.py @@ -122,3 +122,13 @@ class TestGrantPermission(unittest.TestCase): self.assertEqual(ip.table, 'user_perms') self.assertEqual(ip.rawdata, {}) self.context.session.assertPerm.assert_called_with('admin') + + def test_grant_permission_description_wrong_type(self): + description = ['test-description'] + with self.assertRaises(koji.ParameterError) as ex: + self.exports.grantPermission(self.user_name, self.perms_name, + description=description, create=True) + self.assertEqual(f"Invalid type for value '{description}': {type(description)}", + str(ex.exception)) + self.insert_processor.assert_not_called() + self.context.session.assertPerm.assert_called_with('admin') diff --git a/tests/test_hub/test_import_archive.py b/tests/test_hub/test_import_archive.py new file mode 100644 index 00000000..2a6719d0 --- /dev/null +++ b/tests/test_hub/test_import_archive.py @@ -0,0 +1,40 @@ +import mock +import unittest +import koji +import kojihub + + +class TestImportArchive(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertPerm = mock.MagicMock() + self.filepath = 'path/to/file' + self.buildinfo = 'build-1-1.4' + self.type_archive = 'maven' + self.typeinfo = {'group_id': 1, 'artifact_id': 2, 'version': 1} + + def tearDown(self): + mock.patch.stopall() + + def test_maven_not_enabled(self): + self.context.opts.get.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.importArchive(self.filepath, self.buildinfo, self.type_archive, + self.typeinfo) + self.assertEqual("Maven support not enabled", str(cm.exception)) + + def test_win_not_enabled(self): + type_archive = 'win' + self.context.opts.get.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.importArchive(self.filepath, self.buildinfo, type_archive, self.typeinfo) + self.assertEqual("Windows support not enabled", str(cm.exception)) + + def test_unsupported_type(self): + type_archive = 'test-type' + self.context.opts.get.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.importArchive(self.filepath, self.buildinfo, type_archive, self.typeinfo) + self.assertEqual(f"unsupported archive type: {type_archive}", str(cm.exception)) diff --git a/tests/test_hub/test_import_archive_internal.py b/tests/test_hub/test_import_archive_internal.py new file mode 100644 index 00000000..70f64ba3 --- /dev/null +++ b/tests/test_hub/test_import_archive_internal.py @@ -0,0 +1,23 @@ +import unittest +import mock + +import koji +import kojihub + + +class TestImportArchiveInternal(unittest.TestCase): + def setUp(self): + self.os_path_exists = mock.patch('os.path.exists').start() + + def tearDown(self): + mock.patch.stopall() + + def test_import_archive_internal_non_exist_filepath(self): + self.os_path_exists.return_value = False + filepath = 'test/file/path/to/archive' + buildinfo = {'id': 1, 'name': 'test-build'} + type_archive = 'maven' + typeInfo = {'group_id': 1, 'artifact_id': 2, 'version': 3} + with self.assertRaises(koji.GenericError) as cm: + kojihub.import_archive_internal(filepath, buildinfo, type_archive, typeInfo) + self.assertEqual(f"No such file: {filepath}", str(cm.exception)) diff --git a/tests/test_hub/test_import_build.py b/tests/test_hub/test_import_build.py index 0f18a037..5de5a7a1 100644 --- a/tests/test_hub/test_import_build.py +++ b/tests/test_hub/test_import_build.py @@ -1,203 +1,13 @@ import copy import mock +import unittest import shutil import tempfile -import unittest import koji import kojihub -class TestImportRPM(unittest.TestCase): - def setUp(self): - self.tempdir = tempfile.mkdtemp() - self.filename = self.tempdir + "/name-version-release.arch.rpm" - # Touch a file - with open(self.filename, 'w'): - pass - self.src_filename = self.tempdir + "/name-version-release.src.rpm" - # Touch a file - with open(self.src_filename, 'w'): - pass - self.context = mock.patch('kojihub.context').start() - self.cursor = mock.MagicMock() - - self.rpm_header_retval = { - 'filename': 'name-version-release.arch.rpm', - 'sourcepackage': 2, - 1000: 'name', - 1001: 'version', - 1002: 'release', - 1003: 'epoch', - 1006: 'buildtime', - 1022: 'arch', - 1044: 'name-version-release.arch', - 1106: 'sourcepackage', - 261: 'payload hash', - } - - def tearDown(self): - shutil.rmtree(self.tempdir) - - def test_nonexistant_rpm(self): - with self.assertRaises(koji.GenericError): - kojihub.import_rpm("this does not exist") - - @mock.patch('kojihub.get_build') - @mock.patch('koji.get_rpm_header') - def test_import_rpm_failed_build(self, get_rpm_header, get_build): - get_rpm_header.return_value = self.rpm_header_retval - get_build.return_value = { - 'state': koji.BUILD_STATES['FAILED'], - 'name': 'name', - 'version': 'version', - 'release': 'release', - } - with self.assertRaises(koji.GenericError): - kojihub.import_rpm(self.filename) - - @mock.patch('kojihub.new_typed_build') - @mock.patch('kojihub._dml') - @mock.patch('kojihub._singleValue') - @mock.patch('kojihub.get_build') - @mock.patch('koji.get_rpm_header') - def test_import_rpm_completed_build(self, get_rpm_header, get_build, - _singleValue, _dml, - new_typed_build): - get_rpm_header.return_value = self.rpm_header_retval - get_build.return_value = { - 'state': koji.BUILD_STATES['COMPLETE'], - 'name': 'name', - 'version': 'version', - 'release': 'release', - 'id': 12345, - } - _singleValue.return_value = 9876 - kojihub.import_rpm(self.filename) - fields = [ - 'arch', - 'build_id', - 'buildroot_id', - 'buildtime', - 'epoch', - 'external_repo_id', - 'id', - 'name', - 'payloadhash', - 'release', - 'size', - 'version', - ] - statement = 'INSERT INTO rpminfo (%s) VALUES (%s)' % ( - ", ".join(fields), - ", ".join(['%%(%s)s' % field for field in fields]) - ) - values = { - 'build_id': 12345, - 'name': 'name', - 'arch': 'arch', - 'buildtime': 'buildtime', - 'payloadhash': '7061796c6f61642068617368', - 'epoch': 'epoch', - 'version': 'version', - 'buildroot_id': None, - 'release': 'release', - 'external_repo_id': 0, - 'id': 9876, - 'size': 0, - } - _dml.assert_called_once_with(statement, values) - - @mock.patch('kojihub.new_typed_build') - @mock.patch('kojihub._dml') - @mock.patch('kojihub._singleValue') - @mock.patch('kojihub.get_build') - @mock.patch('koji.get_rpm_header') - def test_import_rpm_completed_source_build(self, get_rpm_header, get_build, - _singleValue, _dml, - new_typed_build): - retval = copy.copy(self.rpm_header_retval) - retval.update({ - 'filename': 'name-version-release.arch.rpm', - 1044: 'name-version-release.src', - 1022: 'src', - 1106: 1, - }) - get_rpm_header.return_value = retval - get_build.return_value = { - 'state': koji.BUILD_STATES['COMPLETE'], - 'name': 'name', - 'version': 'version', - 'release': 'release', - 'id': 12345, - } - _singleValue.return_value = 9876 - kojihub.import_rpm(self.src_filename) - fields = [ - 'arch', - 'build_id', - 'buildroot_id', - 'buildtime', - 'epoch', - 'external_repo_id', - 'id', - 'name', - 'payloadhash', - 'release', - 'size', - 'version', - ] - statement = 'INSERT INTO rpminfo (%s) VALUES (%s)' % ( - ", ".join(fields), - ", ".join(['%%(%s)s' % field for field in fields]) - ) - values = { - 'build_id': 12345, - 'name': 'name', - 'arch': 'src', - 'buildtime': 'buildtime', - 'payloadhash': '7061796c6f61642068617368', - 'epoch': 'epoch', - 'version': 'version', - 'buildroot_id': None, - 'release': 'release', - 'external_repo_id': 0, - 'id': 9876, - 'size': 0, - } - _dml.assert_called_once_with(statement, values) - - @mock.patch('os.path.exists') - def test_non_exist_file(self, os_path_exist): - exports = kojihub.RootExports() - basename = 'rpm-1-34' - uploadpath = koji.pathinfo.work() - filepath = '%s/%s/%s' % (uploadpath, self.filename, basename) - os_path_exist.return_value = False - with self.assertRaises(koji.GenericError) as cm: - exports.importRPM(self.filename, basename) - self.assertEqual("No such file: %s" % filepath, str(cm.exception)) - - @mock.patch('koji.get_rpm_header') - @mock.patch('os.path.exists') - @mock.patch('os.path.basename') - def test_non_exist_file(self, os_path_basename, os_path_exist, get_rpm_header): - self.cursor.fetchone.return_value = None - self.context.cnx.cursor.return_value = self.cursor - retval = copy.copy(self.rpm_header_retval) - retval.update({ - 'filename': 'name-version-release.arch.rpm', - 'sourcepackage': 2 - }) - get_rpm_header.return_value = retval - os_path_exist.return_value = True - os_path_basename.return_value = 'name-version-release.arch.rpm' - kojihub.get_build.return_value = None - with self.assertRaises(koji.GenericError) as cm: - kojihub.import_rpm(self.src_filename) - self.assertEqual("No such build", str(cm.exception)) - - class TestImportBuild(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() @@ -210,6 +20,22 @@ class TestImportBuild(unittest.TestCase): with open(self.src_filename, 'w'): pass + self.check_volume_policy = mock.patch('kojihub.check_volume_policy').start() + self.new_typed_build = mock.patch('kojihub.new_typed_build').start() + self._dml = mock.patch('kojihub._dml').start() + self._singleValue = mock.patch('kojihub._singleValue').start() + self.get_build = mock.patch('kojihub.get_build').start() + self.add_rpm_sig = mock.patch('kojihub.add_rpm_sig').start() + self.rip_rpm_sighdr = mock.patch('koji.rip_rpm_sighdr').start() + self.import_rpm_file = mock.patch('kojihub.import_rpm_file').start() + self.import_rpm = mock.patch('kojihub.import_rpm').start() + self.QueryProcessor = mock.patch('kojihub.QueryProcessor').start() + self.context = mock.patch('kojihub.context').start() + self.new_package = mock.patch('kojihub.new_package').start() + self.get_rpm_header = mock.patch('koji.get_rpm_header').start() + self.pathinfo_work = mock.patch('koji.pathinfo.work').start() + self.os_path_exists = mock.patch('os.path.exists').start() + self.rpm_header_retval = { 'filename': 'name-version-release.arch.rpm', 1000: 'name', @@ -225,39 +51,21 @@ class TestImportBuild(unittest.TestCase): def tearDown(self): shutil.rmtree(self.tempdir) + mock.patch.stopall() - @mock.patch('kojihub.check_volume_policy') - @mock.patch('kojihub.new_typed_build') - @mock.patch('kojihub._dml') - @mock.patch('kojihub._singleValue') - @mock.patch('kojihub.get_build') - @mock.patch('kojihub.add_rpm_sig') - @mock.patch('koji.rip_rpm_sighdr') - @mock.patch('kojihub.import_rpm_file') - @mock.patch('kojihub.import_rpm') - @mock.patch('kojihub.QueryProcessor') - @mock.patch('kojihub.context') - @mock.patch('kojihub.new_package') - @mock.patch('koji.get_rpm_header') - @mock.patch('koji.pathinfo.work') - def test_import_build_completed_build(self, work, get_rpm_header, - new_package, context, query, - import_rpm, import_rpm_file, - rip_rpm_sighdr, add_rpm_sig, - get_build, _singleValue, _dml, - new_typed_build, check_volume_policy): + def test_import_build_completed_build(self): - rip_rpm_sighdr.return_value = (0, 0) + self.rip_rpm_sighdr.return_value = (0, 0) processor = mock.MagicMock() processor.executeOne.return_value = None - query.return_value = processor + self.QueryProcessor.return_value = processor - context.session.user_id = 99 + self.context.session.user_id = 99 - work.return_value = '/' + self.pathinfo_work.return_value = '/' - check_volume_policy.return_value = {'id':0, 'name': 'DEFAULT'} + self.check_volume_policy.return_value = {'id': 0, 'name': 'DEFAULT'} retval = copy.copy(self.rpm_header_retval) retval.update({ @@ -266,7 +74,7 @@ class TestImportBuild(unittest.TestCase): 1022: 'src', 1106: 1, }) - get_rpm_header.return_value = retval + self.get_rpm_header.return_value = retval binfo = { 'state': koji.BUILD_STATES['COMPLETE'], 'name': 'name', @@ -277,7 +85,7 @@ class TestImportBuild(unittest.TestCase): # get_build called once to check for existing, # if it doesn't exist, called another time after creating # then 3rd later to get the build info - get_build.side_effect = [None, binfo, binfo] + self.get_build.side_effect = [None, binfo, binfo] kojihub.import_build(self.src_filename, [self.filename]) @@ -315,13 +123,29 @@ class TestImportBuild(unittest.TestCase): 'pkg_id': mock.ANY, 'id': mock.ANY, } - _dml.assert_called_once_with(statement, values) + self._dml.assert_called_once_with(statement, values) - @mock.patch('os.path.exists') - def test_import_build_non_exist_file(self, os_path_exists): + def test_import_build_non_exist_file(self): uploadpath = koji.pathinfo.work() - os_path_exists.return_value = False + self.os_path_exists.return_value = False with self.assertRaises(koji.GenericError) as cm: kojihub.import_build(self.src_filename, [self.filename]) - self.assertEqual("No such file: %s/%s" % (uploadpath, self.src_filename), + self.assertEqual(f"No such file: {uploadpath}/{self.src_filename}", str(cm.exception)) + + def test_import_build_wrong_type_brmap(self): + brmap = 'test-brmap' + with self.assertRaises(koji.GenericError) as cm: + kojihub.import_build(self.src_filename, [self.filename], brmap=brmap) + self.assertEqual(f"Invalid type for value '{brmap}': {type(brmap)}", str(cm.exception)) + + def test_import_build_wrong_type_srpm(self): + srpm = ['test-srpm'] + with self.assertRaises(koji.GenericError) as cm: + kojihub.import_build(srpm, [self.filename]) + self.assertEqual(f"Invalid type for value '{srpm}': {type(srpm)}", str(cm.exception)) + + def test_import_build_wrong_type_rpms(self): + with self.assertRaises(koji.GenericError) as cm: + kojihub.import_build(self.src_filename, self.filename) + self.assertEqual(f"Invalid type for value '{self.filename}': {type(self.filename)}", str(cm.exception)) diff --git a/tests/test_hub/test_import_rpm.py b/tests/test_hub/test_import_rpm.py new file mode 100644 index 00000000..4e13dbfa --- /dev/null +++ b/tests/test_hub/test_import_rpm.py @@ -0,0 +1,187 @@ +import mock +import unittest +import koji +import kojihub +import shutil +import tempfile +import copy + + +class TestImportRPM(unittest.TestCase): + + def setUp(self): + self.exports = kojihub.RootExports() + self.tempdir = tempfile.mkdtemp() + self.filename = self.tempdir + "/name-version-release.arch.rpm" + # Touch a file + with open(self.filename, 'w'): + pass + self.src_filename = self.tempdir + "/name-version-release.src.rpm" + # Touch a file + with open(self.src_filename, 'w'): + pass + self.context = mock.patch('kojihub.context').start() + self.context.session.assertPerm = mock.MagicMock() + self.cursor = mock.MagicMock() + + self.rpm_header_retval = { + 'filename': 'name-version-release.arch.rpm', + 'sourcepackage': 2, + 1000: 'name', + 1001: 'version', + 1002: 'release', + 1003: 'epoch', + 1006: 'buildtime', + 1022: 'arch', + 1044: 'name-version-release.arch', + 1106: 'sourcepackage', + 261: 'payload hash', + } + self.get_build = mock.patch('kojihub.get_build').start() + self.get_rpm_header = mock.patch('koji.get_rpm_header').start() + self.new_typed_build = mock.patch('kojihub.new_typed_build').start() + self._dml = mock.patch('kojihub._dml').start() + self._singleValue = mock.patch('kojihub._singleValue').start() + self.os_path_exists = mock.patch('os.path.exists').start() + self.os_path_basename = mock.patch('os.path.basename').start() + + def tearDown(self): + shutil.rmtree(self.tempdir) + mock.patch.stopall() + + def test_nonexistant_rpm(self): + with self.assertRaises(koji.GenericError): + kojihub.import_rpm("this does not exist") + + def test_import_rpm_failed_build(self): + self.get_rpm_header.return_value = self.rpm_header_retval + self.get_build.return_value = { + 'state': koji.BUILD_STATES['FAILED'], + 'name': 'name', + 'version': 'version', + 'release': 'release', + } + with self.assertRaises(koji.GenericError): + kojihub.import_rpm(self.filename) + + def test_import_rpm_completed_build(self): + self.os_path_basename.return_value = 'name-version-release.arch.rpm' + self.get_rpm_header.return_value = self.rpm_header_retval + self.get_build.return_value = { + 'state': koji.BUILD_STATES['COMPLETE'], + 'name': 'name', + 'version': 'version', + 'release': 'release', + 'id': 12345, + } + self._singleValue.return_value = 9876 + kojihub.import_rpm(self.filename) + fields = [ + 'arch', + 'build_id', + 'buildroot_id', + 'buildtime', + 'epoch', + 'external_repo_id', + 'id', + 'name', + 'payloadhash', + 'release', + 'size', + 'version', + ] + statement = 'INSERT INTO rpminfo (%s) VALUES (%s)' % ( + ", ".join(fields), + ", ".join(['%%(%s)s' % field for field in fields]) + ) + values = { + 'build_id': 12345, + 'name': 'name', + 'arch': 'arch', + 'buildtime': 'buildtime', + 'payloadhash': '7061796c6f61642068617368', + 'epoch': 'epoch', + 'version': 'version', + 'buildroot_id': None, + 'release': 'release', + 'external_repo_id': 0, + 'id': 9876, + 'size': 0, + } + self._dml.assert_called_once_with(statement, values) + + def test_import_rpm_completed_source_build(self): + self.os_path_basename.return_value = 'name-version-release.src.rpm' + retval = copy.copy(self.rpm_header_retval) + retval.update({ + 'filename': 'name-version-release.arch.rpm', + 1044: 'name-version-release.src', + 1022: 'src', + 1106: 1, + }) + self.get_rpm_header.return_value = retval + self.get_build.return_value = { + 'state': koji.BUILD_STATES['COMPLETE'], + 'name': 'name', + 'version': 'version', + 'release': 'release', + 'id': 12345, + } + self._singleValue.return_value = 9876 + kojihub.import_rpm(self.src_filename) + fields = [ + 'arch', + 'build_id', + 'buildroot_id', + 'buildtime', + 'epoch', + 'external_repo_id', + 'id', + 'name', + 'payloadhash', + 'release', + 'size', + 'version', + ] + statement = 'INSERT INTO rpminfo (%s) VALUES (%s)' % ( + ", ".join(fields), + ", ".join(['%%(%s)s' % field for field in fields]) + ) + values = { + 'build_id': 12345, + 'name': 'name', + 'arch': 'src', + 'buildtime': 'buildtime', + 'payloadhash': '7061796c6f61642068617368', + 'epoch': 'epoch', + 'version': 'version', + 'buildroot_id': None, + 'release': 'release', + 'external_repo_id': 0, + 'id': 9876, + 'size': 0, + } + self._dml.assert_called_once_with(statement, values) + + def test_non_exist_file(self): + basename = 'rpm-1-34' + self.os_path_exists.return_value = False + with self.assertRaises(koji.GenericError) as cm: + kojihub.import_rpm(self.filename, basename) + self.assertEqual(f"No such file: {self.filename}", str(cm.exception)) + + def test_non_exist_build(self): + self.cursor.fetchone.return_value = None + self.context.cnx.cursor.return_value = self.cursor + retval = copy.copy(self.rpm_header_retval) + retval.update({ + 'filename': 'name-version-release.arch.rpm', + 'sourcepackage': 2 + }) + self.get_rpm_header.return_value = retval + self.os_path_exists.return_value = True + self.os_path_basename.return_value = 'name-version-release.arch.rpm' + kojihub.get_build.return_value = None + with self.assertRaises(koji.GenericError) as cm: + kojihub.import_rpm(self.src_filename) + self.assertEqual("No such build", str(cm.exception)) diff --git a/tests/test_hub/test_list_archives.py b/tests/test_hub/test_list_archives.py index 5a921b2e..854504b0 100644 --- a/tests/test_hub/test_list_archives.py +++ b/tests/test_hub/test_list_archives.py @@ -1,265 +1,224 @@ import mock -from .utils import DBQueryTestCase +import unittest import koji import kojihub +QP = kojihub.QueryProcessor -class TestListArchives(DBQueryTestCase): - maxDiff = None + +class TestListArchives(unittest.TestCase): + def setUp(self): + self.maxDiff = None + self.get_build = mock.patch('kojihub.get_build').start() + self.get_host = mock.patch('kojihub.get_host').start() + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.get_query).start() + self.queries = [] + self.exports = kojihub.RootExports() + + def tearDown(self): + mock.patch.stopall() + + def get_query(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query def test_list_archives_simple(self): - rv = kojihub.list_archives() + kojihub.list_archives() self.assertEqual(len(self.queries), 1) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=[], - values={}) - self.assertEqual(rv, []) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, []) + self.assertEqual(query.joins, ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) - def test_list_archives_strict(self): + @mock.patch('kojihub.QueryProcessor') + def test_list_archives_strict(self, QueryProcessor): + query = QueryProcessor.return_value + query.execute.return_value = None with self.assertRaises(koji.GenericError) as cm: kojihub.list_archives(strict=True) self.assertEqual(cm.exception.args[0], 'No archives found.') def test_list_archives_buildid(self): kojihub.list_archives(buildID=1) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=['build_id = %(build_id)i'], - values={'build_id': 1}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['build_id = %(build_id)i']) + self.assertEqual(query.joins, ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) + self.assertEqual(query.values, {'build_id': 1}) def test_list_archives_buildrootid(self): kojihub.list_archives(buildrootID=1) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=['buildroot_id = %(buildroot_id)i'], - values={'buildroot_id': 1}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['buildroot_id = %(buildroot_id)i']) + self.assertEqual(query.joins, ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) + self.assertEqual(query.values, {'buildroot_id': 1}) def test_list_archives_componentbuildrootid(self): kojihub.list_archives(componentBuildrootID=1) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id', - 'buildroot_archives on archiveinfo.id = buildroot_archives.archive_id'], - clauses=['buildroot_archives.buildroot_id = %(component_buildroot_id)i'], - values={'component_buildroot_id': 1}, - colsByAlias={'build_id': 'archiveinfo.build_id', - 'type_name': 'archivetypes.name', - 'component_buildroot_id': 'buildroot_archives.buildroot_id', - 'type_id': 'archiveinfo.type_id', - 'checksum': 'archiveinfo.checksum', - 'extra': 'archiveinfo.extra', - 'filename': 'archiveinfo.filename', - 'project': 'buildroot_archives.project_dep', - 'type_description': 'archivetypes.description', - 'metadata_only': 'archiveinfo.metadata_only', - 'type_extensions': 'archivetypes.extensions', - 'btype': 'btype.name', - 'checksum_type': 'archiveinfo.checksum_type', - 'btype_id': 'archiveinfo.btype_id', - 'buildroot_id': 'archiveinfo.buildroot_id', - 'id': 'archiveinfo.id', - 'size': 'archiveinfo.size'}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, + ['buildroot_archives.buildroot_id = %(component_buildroot_id)i']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id', + 'buildroot_archives on archiveinfo.id = buildroot_archives.archive_id']) + self.assertEqual(query.values, {'component_buildroot_id': 1}) def test_list_archives_imageid(self): kojihub.list_archives(imageID=1) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id', - 'archive_components ON archiveinfo.id = archive_components.component_id'], - clauses=['archive_components.archive_id = %(imageID)i'], - values={'imageID': 1}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['archive_components.archive_id = %(imageID)i']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id', + 'archive_components ON archiveinfo.id = ' + 'archive_components.component_id']) + self.assertEqual(query.values, {'imageID': 1}) def test_list_archives_hostid(self): kojihub.list_archives(hostID=1) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id', - 'standard_buildroot on archiveinfo.buildroot_id = standard_buildroot.buildroot_id'], - clauses=['standard_buildroot.host_id = %(host_id)i'], - values={'host_id': 1}, - colsByAlias={'host_id': 'standard_buildroot.host_id', - 'build_id': 'archiveinfo.build_id', - 'type_name': 'archivetypes.name', - 'type_id': 'archiveinfo.type_id', - 'checksum': 'archiveinfo.checksum', - 'extra': 'archiveinfo.extra', - 'filename': 'archiveinfo.filename', - 'type_description': 'archivetypes.description', - 'metadata_only': 'archiveinfo.metadata_only', - 'type_extensions': 'archivetypes.extensions', - 'btype': 'btype.name', - 'checksum_type': 'archiveinfo.checksum_type', - 'btype_id': 'archiveinfo.btype_id', - 'buildroot_id': 'archiveinfo.buildroot_id', - 'id': 'archiveinfo.id', - 'size': 'archiveinfo.size'}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['standard_buildroot.host_id = %(host_id)i']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id', + 'standard_buildroot on archiveinfo.buildroot_id = ' + 'standard_buildroot.buildroot_id']) + self.assertEqual(query.values, {'host_id': 1}) def test_list_archives_filename(self): kojihub.list_archives(filename='somefile.txt') - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=['filename = %(filename)s'], - values={'filename': 'somefile.txt'}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['filename = %(filename)s']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) + self.assertEqual(query.values, {'filename': 'somefile.txt'}) def test_list_archives_size(self): kojihub.list_archives(size=1231831) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=['size = %(size)i'], - values={'size': 1231831}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['size = %(size)i']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) + self.assertEqual(query.values, {'size': 1231831}) def test_list_archives_checksum(self): kojihub.list_archives(checksum='7873f0a6dbf3abc07724e000ac9b3941') - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=['checksum = %(checksum)s'], - values={'checksum': '7873f0a6dbf3abc07724e000ac9b3941'}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['checksum = %(checksum)s']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) + self.assertEqual(query.values, {'checksum': '7873f0a6dbf3abc07724e000ac9b3941'}) def test_list_archives_checksum_type(self): kojihub.list_archives(checksum_type=koji.CHECKSUM_TYPES['sha256']) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=['checksum_type = %(checksum_type)s'], - values={'checksum_type': koji.CHECKSUM_TYPES['sha256']}) - + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['checksum_type = %(checksum_type)s']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) + self.assertEqual(query.values, {'checksum_type': koji.CHECKSUM_TYPES['sha256']}) def test_list_archives_archiveid(self): kojihub.list_archives(archiveID=1) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=['archiveinfo.id = %(archive_id)s'], - values={'archive_id': 1}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['archiveinfo.id = %(archive_id)s']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) + self.assertEqual(query.values, {'archive_id': 1}) def test_list_archives_type_maven(self): kojihub.list_archives(type='maven', typeInfo={'group_id': 'gid', 'artifact_id': 'aid', 'version': '1.0.1'}) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id', - 'maven_archives ON archiveinfo.id = maven_archives.archive_id'], - clauses=['maven_archives.artifact_id = %(artifact_id)s', - 'maven_archives.group_id = %(group_id)s', - 'maven_archives.version = %(version)s'], - values={'group_id': 'gid', - 'artifact_id': 'aid', - 'version': '1.0.1'}, - colsByAlias={'group_id': 'maven_archives.group_id', - 'artifact_id': 'maven_archives.artifact_id', - 'version': 'maven_archives.version', - 'build_id': 'archiveinfo.build_id', - 'type_name': 'archivetypes.name', - 'type_id': 'archiveinfo.type_id', - 'checksum': 'archiveinfo.checksum', - 'extra': 'archiveinfo.extra', - 'filename': 'archiveinfo.filename', - 'type_description': 'archivetypes.description', - 'metadata_only': 'archiveinfo.metadata_only', - 'type_extensions': 'archivetypes.extensions', - 'btype': 'btype.name', - 'checksum_type': 'archiveinfo.checksum_type', - 'btype_id': 'archiveinfo.btype_id', - 'buildroot_id': 'archiveinfo.buildroot_id', - 'id': 'archiveinfo.id', - 'size': 'archiveinfo.size'}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['maven_archives.artifact_id = %(artifact_id)s', + 'maven_archives.group_id = %(group_id)s', + 'maven_archives.version = %(version)s']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id', + 'maven_archives ON archiveinfo.id = maven_archives.archive_id']) + self.assertEqual(query.values, {'group_id': 'gid', + 'artifact_id': 'aid', + 'version': '1.0.1'}) def test_list_archives_type_win(self): kojihub.list_archives(type='win', typeInfo={'relpath': 'somerelpath', 'platforms': 'all', 'flags': ['A', 'B']}) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=sorted([ - 'archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id', - 'win_archives ON archiveinfo.id = win_archives.archive_id']), - clauses=sorted([ - 'win_archives.relpath = %(relpath)s', - r"platforms ~ %(platforms_pattern_0)s", - r"flags ~ %(flags_pattern_0)s", - r"flags ~ %(flags_pattern_1)s"]), - values={'relpath': 'somerelpath', - 'flags_pattern_0': '\\mA\\M', - 'flags_pattern_1': '\\mB\\M', - 'platforms_pattern_0': '\\mall\\M', - }, - colsByAlias={'relpath': 'win_archives.relpath', - 'platforms': 'win_archives.platforms', - 'flags': 'win_archives.flags', - 'build_id': 'archiveinfo.build_id', - 'type_name': 'archivetypes.name', - 'type_id': 'archiveinfo.type_id', - 'checksum': 'archiveinfo.checksum', - 'extra': 'archiveinfo.extra', - 'filename': 'archiveinfo.filename', - 'type_description': 'archivetypes.description', - 'metadata_only': 'archiveinfo.metadata_only', - 'type_extensions': 'archivetypes.extensions', - 'btype': 'btype.name', - 'checksum_type': 'archiveinfo.checksum_type', - 'btype_id': 'archiveinfo.btype_id', - 'buildroot_id': 'archiveinfo.buildroot_id', - 'id': 'archiveinfo.id', - 'size': 'archiveinfo.size'}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, sorted(['win_archives.relpath = %(relpath)s', + r"platforms ~ %(platforms_pattern_0)s", + r"flags ~ %(flags_pattern_0)s", + r"flags ~ %(flags_pattern_1)s"])) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id', + 'win_archives ON archiveinfo.id = win_archives.archive_id']) + self.assertEqual(query.values, {'relpath': 'somerelpath', + 'flags_pattern_0': '\\mA\\M', + 'flags_pattern_1': '\\mB\\M', + 'platforms_pattern_0': '\\mall\\M'}) def test_list_archives_type_image(self): kojihub.list_archives(type='image', typeInfo={'arch': 'i386'}) - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id', - 'image_archives ON archiveinfo.id = image_archives.archive_id'], - clauses=['image_archives.arch = %(arch)s'], - values={'arch': 'i386'}, - colsByAlias={'arch': 'image_archives.arch', - 'build_id': 'archiveinfo.build_id', - 'type_name': 'archivetypes.name', - 'type_id': 'archiveinfo.type_id', - 'checksum': 'archiveinfo.checksum', - 'extra': 'archiveinfo.extra', - 'filename': 'archiveinfo.filename', - 'type_description': 'archivetypes.description', - 'metadata_only': 'archiveinfo.metadata_only', - 'type_extensions': 'archivetypes.extensions', - 'btype': 'btype.name', - 'checksum_type': 'archiveinfo.checksum_type', - 'btype_id': 'archiveinfo.btype_id', - 'buildroot_id': 'archiveinfo.buildroot_id', - 'id': 'archiveinfo.id', - 'size': 'archiveinfo.size'}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['image_archives.arch = %(arch)s']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id', + 'image_archives ON archiveinfo.id = image_archives.archive_id']) + self.assertEqual(query.values, {'arch': 'i386'}) @mock.patch('kojihub.lookup_name', return_value={'id': 111, 'name': 'other'}) def test_list_archives_type_others(self, lookup_name): kojihub.list_archives(type='other') - self.assertLastQueryEqual(tables=['archiveinfo'], - joins=['archivetypes on archiveinfo.type_id = archivetypes.id', - 'btype ON archiveinfo.btype_id = btype.id'], - clauses=['archiveinfo.btype_id = %(btype_id)s'], - values={'btype_id': 111}, - colsByAlias={'build_id': 'archiveinfo.build_id', - 'type_name': 'archivetypes.name', - 'type_id': 'archiveinfo.type_id', - 'checksum': 'archiveinfo.checksum', - 'extra': 'archiveinfo.extra', - 'filename': 'archiveinfo.filename', - 'type_description': 'archivetypes.description', - 'metadata_only': 'archiveinfo.metadata_only', - 'type_extensions': 'archivetypes.extensions', - 'btype': 'btype.name', - 'checksum_type': 'archiveinfo.checksum_type', - 'btype_id': 'archiveinfo.btype_id', - 'buildroot_id': 'archiveinfo.buildroot_id', - 'id': 'archiveinfo.id', - 'size': 'archiveinfo.size'}) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['archiveinfo']) + self.assertEqual(query.clauses, ['archiveinfo.btype_id = %(btype_id)s']) + self.assertEqual(query.joins, + ['archivetypes on archiveinfo.type_id = archivetypes.id', + 'btype ON archiveinfo.btype_id = btype.id']) + self.assertEqual(query.values, {'btype_id': 111}) @mock.patch('kojihub.lookup_name', return_value=None) def test_list_archives_type_not_found(self, lookup_name): diff --git a/tests/test_hub/test_list_btypes.py b/tests/test_hub/test_list_btypes.py index 82907acd..78343e00 100644 --- a/tests/test_hub/test_list_btypes.py +++ b/tests/test_hub/test_list_btypes.py @@ -8,64 +8,58 @@ QP = kojihub.QueryProcessor class TestListBTypes(unittest.TestCase): - @mock.patch('kojihub.QueryProcessor') - def test_list_btypes(self, QueryProcessor): + def setUp(self): + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.get_query).start() + self.queries = [] + self.exports = kojihub.RootExports() - # default query - query = QueryProcessor.return_value - query.execute.return_value = "return value" - ret = kojihub.list_btypes() - QueryProcessor.assert_called_once() - query.execute.assert_called_once() - self.assertEqual(ret, "return value") + def tearDown(self): + mock.patch.stopall() - args, kwargs = QueryProcessor.call_args + def get_query(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query + + def test_list_btypes_default(self): + kojihub.list_btypes() + self.QueryProcessor.assert_called_once() + + args, kwargs = self.QueryProcessor.call_args self.assertEqual(args, ()) - qp = QP(**kwargs) - self.assertEqual(qp.tables, ['btype']) - self.assertEqual(qp.columns, ['id', 'name']) - self.assertEqual(qp.clauses, []) - self.assertEqual(qp.joins, None) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['btype']) + self.assertEqual(query.columns, ['id', 'name']) + self.assertEqual(query.clauses, []) + self.assertEqual(query.joins, None) - QueryProcessor.reset_mock() + def test_list_btypes_by_name(self): + kojihub.list_btypes({'name': 'rpm'}) + self.QueryProcessor.assert_called_once() - # query by name - query = QueryProcessor.return_value - query.execute.return_value = "return value" - ret = kojihub.list_btypes({'name': 'rpm'}) - QueryProcessor.assert_called_once() - query.execute.assert_called_once() - self.assertEqual(ret, "return value") - - args, kwargs = QueryProcessor.call_args + args, kwargs = self.QueryProcessor.call_args self.assertEqual(args, ()) - qp = QP(**kwargs) - self.assertEqual(qp.tables, ['btype']) - self.assertEqual(qp.columns, ['id', 'name']) - self.assertEqual(qp.clauses, ['btype.name = %(name)s']) - self.assertEqual(qp.values, {'name': 'rpm'}) - self.assertEqual(qp.joins, None) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['btype']) + self.assertEqual(query.columns, ['id', 'name']) + self.assertEqual(query.clauses, ['btype.name = %(name)s']) + self.assertEqual(query.values, {'name': 'rpm'}) + self.assertEqual(query.joins, None) - QueryProcessor.reset_mock() + def test_list_btypes_by_it_with_opts(self): + kojihub.list_btypes({'id': 1}, {'order': 'id'}) + self.QueryProcessor.assert_called_once() - # query by id, with opts - query = QueryProcessor.return_value - query.execute.return_value = "return value" - ret = kojihub.list_btypes({'id': 1}, {'order': 'id'}) - QueryProcessor.assert_called_once() - query.execute.assert_called_once() - self.assertEqual(ret, "return value") - - args, kwargs = QueryProcessor.call_args + args, kwargs = self.QueryProcessor.call_args self.assertEqual(args, ()) - qp = QP(**kwargs) - self.assertEqual(qp.tables, ['btype']) - self.assertEqual(qp.columns, ['id', 'name']) - self.assertEqual(qp.clauses, ['btype.id = %(id)s']) - self.assertEqual(qp.values, {'id': 1}) - self.assertEqual(qp.opts, {'order': 'id'}) - self.assertEqual(qp.joins, None) - - QueryProcessor.reset_mock() - - # query by name + query = self.queries[0] + self.assertEqual(query.tables, ['btype']) + self.assertEqual(query.columns, ['id', 'name']) + self.assertEqual(query.clauses, ['btype.id = %(id)s']) + self.assertEqual(query.values, {'id': 1}) + self.assertEqual(query.opts, {'order': 'id'}) + self.assertEqual(query.joins, None) diff --git a/tests/test_hub/test_list_builds.py b/tests/test_hub/test_list_builds.py index 361fd29e..5e172f4c 100644 --- a/tests/test_hub/test_list_builds.py +++ b/tests/test_hub/test_list_builds.py @@ -25,6 +25,8 @@ class TestListBuilds(unittest.TestCase): self.queries = [] self.context = mock.patch('kojihub.context').start() + self.get_package_id = mock.patch('kojihub.get_package_id').start() + self.get_user = mock.patch('kojihub.get_user').start() self.cursor = mock.MagicMock() self.build_list = [{'build_id': 9, 'epoch': 0, @@ -41,18 +43,16 @@ class TestListBuilds(unittest.TestCase): 'volume_id': 0, 'volume_name': 'DEFAULT'}] - @mock.patch('kojihub.get_package_id') - def test_wrong_package(self, get_package_id): + def test_wrong_package(self): package = 'test-package' - get_package_id.return_value = None + self.get_package_id.return_value = None rv = self.exports.listBuilds(packageID=package) self.assertEqual(rv, []) - @mock.patch('kojihub.get_package_id') - def test_package_string(self, get_package_id): + def test_package_string(self): package = 'test-package' package_id = 1 - get_package_id.return_value = package_id + self.get_package_id.return_value = package_id self.query_executeOne.return_value = None self.exports.listBuilds(packageID=package) self.assertEqual(len(self.queries), 1) @@ -76,9 +76,8 @@ class TestListBuilds(unittest.TestCase): 'LEFT JOIN volume ON build.volume_id = volume.id', 'LEFT JOIN users ON build.owner = users.id']) - @mock.patch('kojihub.get_user') - def test_wrong_user(self, get_user): + def test_wrong_user(self): user = 'test-user' - get_user.return_value = None + self.get_user.return_value = None rv = self.exports.listBuilds(userID=user) self.assertEqual(rv, []) diff --git a/tests/test_hub/test_list_packages_simple.py b/tests/test_hub/test_list_packages_simple.py new file mode 100644 index 00000000..47cf7726 --- /dev/null +++ b/tests/test_hub/test_list_packages_simple.py @@ -0,0 +1,40 @@ +import unittest +import kojihub +import mock + +QP = kojihub.QueryProcessor + + +class TestListPackagesSimple(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.get_query).start() + self.queries = [] + self.exports = kojihub.RootExports() + + def tearDown(self): + mock.patch.stopall() + + def get_query(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query + + def test_prefix_not_none(self): + self.exports.listPackagesSimple('test-prefix') + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['package']) + self.assertEqual(query.clauses, ["package.name ILIKE %(prefix)s || '%%'"]) + self.assertEqual(query.joins, None) + + def test_prefix_is_none(self): + self.exports.listPackagesSimple() + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['package']) + self.assertEqual(query.clauses, None) + self.assertEqual(query.joins, None) diff --git a/tests/test_hub/test_list_rpms.py b/tests/test_hub/test_list_rpms.py index 88449ddc..e0d06695 100644 --- a/tests/test_hub/test_list_rpms.py +++ b/tests/test_hub/test_list_rpms.py @@ -1,14 +1,87 @@ import unittest +import mock import koji import kojihub +QP = kojihub.QueryProcessor + class TestListRpms(unittest.TestCase): + def setUp(self): + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.get_query).start() + self.queries = [] + self.get_build = mock.patch('kojihub.get_build').start() + self.get_host = mock.patch('kojihub.get_host').start() + self._dml = mock.patch('kojihub._dml').start() + self.list_rpms = {'arch': 'x86_64', + 'build_id': 1, + 'buildroot_id': 2, + 'buildtime': 1596090711, + 'epoch': 2, + 'external_repo_id': 1, + 'external_repo_name': 'fedora-34-released', + 'extra': None, + 'id': 277, + 'metadata_only': False, + 'name': 'shadow-utils', + 'nvr': 'shadow-utils-4.8.1-4.fc33', + 'payloadhash': 'c5bfe5267dc6e0ca127092a82b4f260b', + 'release': '4.fc33', + 'size': 3891272, + 'version': '4.8.1'} + + def tearDown(self): + mock.patch.stopall() + + def get_query(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query def test_wrong_type_arches(self): arches = {'test-arch': 'val'} with self.assertRaises(koji.GenericError) as cm: kojihub.list_rpms(arches=arches) - self.assertEqual('Invalid type for "arches" parameter: %s' % type(arches), - str(cm.exception)) + self.assertEqual(f'Invalid type for "arches" parameter: {type(arches)}', str(cm.exception)) + + def test_int_values(self): + build_id = 1 + buildroot_id = 1 + host_id = 1 + arches = 'x86_64' + kojihub.list_rpms(arches=arches, buildID=build_id, buildrootID=buildroot_id, + hostID=host_id) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['rpminfo']) + self.assertEqual(query.joins, + ['LEFT JOIN external_repo ON rpminfo.external_repo_id = external_repo.id', + 'standard_buildroot ON rpminfo.buildroot_id = ' + 'standard_buildroot.buildroot_id']) + self.assertEqual(query.clauses, [ + 'rpminfo.arch = %(arches)s', + 'rpminfo.build_id = %(buildID)i', + 'rpminfo.buildroot_id = %(buildrootID)i', + 'standard_buildroot.host_id = %(hostID)i', + ]) + + def test_compoenent_buldroot_image_list_arch_values(self): + comp_buildroot_id = 1 + image_id = 1 + arches = ['x86_64'] + kojihub.list_rpms(componentBuildrootID=comp_buildroot_id, imageID=image_id, arches=arches) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + self.assertEqual(query.tables, ['rpminfo']) + self.assertEqual(query.joins, + ['LEFT JOIN external_repo ON rpminfo.external_repo_id = external_repo.id', + 'buildroot_listing ON rpminfo.id = buildroot_listing.rpm_id', + 'archive_rpm_components ON rpminfo.id = archive_rpm_components.rpm_id']) + self.assertEqual(query.clauses, [ + 'archive_rpm_components.archive_id = %(imageID)i', + 'buildroot_listing.buildroot_id = %(componentBuildrootID)i', + 'rpminfo.arch IN %(arches)s', + ]) diff --git a/tests/test_hub/test_listing.py b/tests/test_hub/test_list_tasks.py similarity index 100% rename from tests/test_hub/test_listing.py rename to tests/test_hub/test_list_tasks.py diff --git a/tests/test_hub/test_lookup_name.py b/tests/test_hub/test_lookup_name.py index 42346fa4..39c9ca6d 100644 --- a/tests/test_hub/test_lookup_name.py +++ b/tests/test_hub/test_lookup_name.py @@ -44,12 +44,11 @@ class TestLookupName(unittest.TestCase): {'id': 'not a valid int'}, ['something'], set(), - ] + ] for value in bad_values: with self.assertRaises(koji.GenericError) as cm: kojihub.lookup_name('mytable', value) - self.assertEqual('Invalid name or id value: %s' % value, - str(cm.exception)) + self.assertEqual(f'Invalid name or id value: {value}', str(cm.exception)) self.assertEqual(len(self.queries), 0) self.assertEqual(len(self.inserts), 0) @@ -78,7 +77,7 @@ class TestLookupName(unittest.TestCase): self.assertEqual(len(self.inserts), 0) def test_query_by_dict(self): - kojihub.lookup_name('some_table', {'id':12345, 'name': 'whatever'}) + kojihub.lookup_name('some_table', {'id': 12345, 'name': 'whatever'}) self.assertEqual(len(self.queries), 1) query = self.queries[0] clauses = ['(some_table.id = %(some_table_id)s)'] @@ -110,7 +109,7 @@ class TestLookupName(unittest.TestCase): def test_lookup_name_strict(self): self.query_executeOne.return_value = None - with self.assertRaises(koji.GenericError) as cm: + with self.assertRaises(koji.GenericError): kojihub.lookup_name('package', 'python', strict=True) self.assertEqual(len(self.queries), 1) self.assertEqual(len(self.inserts), 0) @@ -134,7 +133,7 @@ class TestLookupName(unittest.TestCase): bad_values = [ {'id': 100}, 100 - ] + ] for value in bad_values: with self.assertRaises(koji.GenericError) as cm: kojihub.lookup_name('package', value, create=True) diff --git a/tests/test_hub/test_maven_build.py b/tests/test_hub/test_maven_build.py new file mode 100644 index 00000000..90a4d4c8 --- /dev/null +++ b/tests/test_hub/test_maven_build.py @@ -0,0 +1,49 @@ +import unittest +import koji +import kojihub +import mock + + +class TestMaven(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertLogin = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + self.get_channel = mock.patch('kojihub.get_channel').start() + self.make_task = mock.patch('kojihub.make_task').start() + self.url = 'https://test-url.com' + self.target = 'test-target' + + def tearDown(self): + mock.patch.stopall() + + def test_maven_not_supported(self): + self.context.opts.get.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.mavenBuild(self.url, self.target) + self.assertEqual("Maven support not enabled", str(cm.exception)) + + def test_url_not_str(self): + url = ['test-url'] + self.context.opts.get.return_value = True + with self.assertRaises(koji.GenericError) as cm: + self.exports.mavenBuild(url, self.target) + self.assertEqual(f"Invalid type for value '{url}': {type(url)}", str(cm.exception)) + + def test_priority_without_admin(self): + priority = -10 + self.context.opts.get.return_value = True + self.context.session.hasPerm.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.mavenBuild(self.url, self.target, priority=priority) + self.assertEqual("only admins may create high-priority tasks", str(cm.exception)) + + def test_channel_not_str(self): + priority = 10 + self.context.opts.get.return_value = True + self.get_channel.return_value = {'comment': None, 'description': None, 'enabled': True, + 'id': 2, 'name': 'maven'} + self.make_task.return_value = 123 + self.exports.mavenBuild(self.url, self.target, priority=priority, channel=2) diff --git a/tests/test_hub/test_new_build.py b/tests/test_hub/test_new_build.py index fef494bf..284f9735 100644 --- a/tests/test_hub/test_new_build.py +++ b/tests/test_hub/test_new_build.py @@ -6,6 +6,7 @@ import kojihub IP = kojihub.InsertProcessor + class TestNewBuild(unittest.TestCase): def setUp(self): self.get_rpm = mock.patch('kojihub.get_rpm').start() @@ -13,7 +14,7 @@ class TestNewBuild(unittest.TestCase): self.nextval = mock.patch('kojihub.nextval').start() self.Savepoint = mock.patch('kojihub.Savepoint').start() self.InsertProcessor = mock.patch('kojihub.InsertProcessor', - side_effect=self.getInsert).start() + side_effect=self.getInsert).start() self.inserts = [] self.insert_execute = mock.MagicMock() self.lookup_package = mock.patch('kojihub.lookup_package').start() @@ -35,7 +36,7 @@ class TestNewBuild(unittest.TestCase): def test_valid(self): self.get_build.return_value = None - self._singleValue.return_value = 65 # free build id + self._singleValue.return_value = 65 # free build id self.new_package.return_value = 54 self.get_user.return_value = {'id': 123} data = { @@ -99,10 +100,11 @@ class TestNewBuild(unittest.TestCase): 'extra': {'extra_key': 'extra_value'}, } - with self.assertRaises(koji.GenericError): + with self.assertRaises(koji.GenericError) as cm: kojihub.new_build(data) self.assertEqual(len(self.inserts), 0) + self.assertEqual("No name or package id provided for build", str(cm.exception)) def test_wrong_owner(self): self.get_user.side_effect = koji.GenericError @@ -121,7 +123,6 @@ class TestNewBuild(unittest.TestCase): self.assertEqual(len(self.inserts), 0) def test_missing_vre(self): - self.get_user.side_effect = koji.GenericError data = { 'name': 'test_name', 'version': 'test_version', diff --git a/tests/test_hub/test_notifications.py b/tests/test_hub/test_notifications.py deleted file mode 100644 index a95116ac..00000000 --- a/tests/test_hub/test_notifications.py +++ /dev/null @@ -1,821 +0,0 @@ -import mock -import unittest - -import koji -import kojihub - -QP = kojihub.QueryProcessor -IP = kojihub.InsertProcessor -UP = kojihub.UpdateProcessor - -class TestNotifications(unittest.TestCase): - def getInsert(self, *args, **kwargs): - insert = IP(*args, **kwargs) - insert.execute = mock.MagicMock() - self.inserts.append(insert) - return insert - - def getQuery(self, *args, **kwargs): - query = QP(*args, **kwargs) - query.execute = mock.MagicMock() - self.queries.append(query) - return query - - def getUpdate(self, *args, **kwargs): - update = UP(*args, **kwargs) - update.execute = mock.MagicMock() - self.updates.append(update) - return update - - def setUp(self): - self.context = mock.patch('kojihub.context').start() - self.context.opts = { - 'EmailDomain': 'test.domain.com', - 'NotifyOnSuccess': True, - } - - self.QueryProcessor = mock.patch('kojihub.QueryProcessor', - side_effect=self.getQuery).start() - self.queries = [] - 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.exports = kojihub.RootExports() - self.exports.getLoggedInUser = mock.MagicMock() - self.exports.getUser = mock.MagicMock() - self.exports.hasPerm = mock.MagicMock() - self.exports.getBuildNotification = mock.MagicMock() - self.exports.getBuildNotificationBlock = mock.MagicMock() - - def tearDown(self): - mock.patch.stopall() - - - @mock.patch('kojihub.get_user') - @mock.patch('kojihub.readPackageList') - def test_get_notification_recipients_watchers(self, readPackageList, get_user): - # without build / tag_id - build = None - tag_id = None - state = koji.BUILD_STATES['CANCELED'] - - emails = kojihub.get_notification_recipients(build, tag_id, state) - self.assertEqual(emails, []) - - # only query to watchers - self.assertEqual(len(self.queries), 1) - q = self.queries[0] - self.assertEqual(q.columns, ['email', 'user_id']) - self.assertEqual(q.tables, ['build_notifications']) - self.assertEqual(q.clauses, ['package_id IS NULL', - 'status = %(users_status)i', - 'success_only = FALSE', - 'tag_id IS NULL', - 'usertype IN %(users_usertypes)s']) - self.assertEqual(q.joins, ['JOIN users ON build_notifications.user_id = users.id']) - self.assertEqual(q.values['state'], state) - self.assertEqual(q.values['build'], build) - self.assertEqual(q.values['tag_id'], tag_id) - - ''' - q = self.queries[1] - self.assertEqual(q.columns, ['user_id']) - self.assertEqual(q.tables, ['build_notifications_block']) - self.assertEqual(q.clauses, ['user_id IN %(user_ids)s']) - self.assertEqual(q.joins, []) - self.assertEqual(q.values['user_ids'], None) - ''' - readPackageList.assert_not_called() - - - @mock.patch('kojihub.get_user') - @mock.patch('kojihub.readPackageList') - def test_get_notification_recipients_build_without_tag(self, readPackageList, get_user): - ### with build without tag - tag_id = None - state = koji.BUILD_STATES['CANCELED'] - build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} - self.queries = [] - self.set_queries([ - [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], - [] - ]) - - emails = kojihub.get_notification_recipients(build, tag_id, state) - self.assertEqual(emails, ['owner_name@test.domain.com']) - - # there should be only query to watchers - self.assertEqual(len(self.queries), 2) - q = self.queries[0] - self.assertEqual(q.columns, ['email', 'user_id']) - self.assertEqual(q.tables, ['build_notifications']) - self.assertEqual(q.clauses, ['package_id = %(package_id)i OR package_id IS NULL', - 'status = %(users_status)i', - 'success_only = FALSE', - 'tag_id IS NULL', - 'usertype IN %(users_usertypes)s']) - self.assertEqual(q.joins, ['JOIN users ON build_notifications.user_id = users.id']) - self.assertEqual(q.values['package_id'], build['package_id']) - self.assertEqual(q.values['state'], state) - self.assertEqual(q.values['build'], build) - self.assertEqual(q.values['tag_id'], tag_id) - - q = self.queries[1] - self.assertEqual(q.columns, ['user_id']) - self.assertEqual(q.tables, ['build_notifications_block']) - self.assertEqual(q.clauses, [ - 'package_id = %(package_id)i OR package_id IS NULL', - 'tag_id IS NULL', - 'user_id IN %(user_ids)s', - ]) - self.assertEqual(q.joins, None) - self.assertEqual(q.values['user_ids'], [5]) - - readPackageList.assert_not_called() - - @mock.patch('kojihub.get_user') - @mock.patch('kojihub.readPackageList') - def test_get_notification_recipients_tag_without_build(self, readPackageList, get_user): - ### with tag without build makes no sense - build = None - tag_id = 123 - state = koji.BUILD_STATES['CANCELED'] - self.queries = [] - - with self.assertRaises(koji.GenericError): - kojihub.get_notification_recipients(build, tag_id, state) - self.assertEqual(self.queries, []) - readPackageList.assert_not_called() - - def set_queries(self, return_values): - self.query_returns = return_values - self.query_returns.reverse() - def getQuery(*args, **kwargs): - q = QP(*args, **kwargs) - q.execute = mock.MagicMock() - q.execute.return_value = self.query_returns.pop() - self.queries.append(q) - return q - self.QueryProcessor.side_effect = getQuery - - @mock.patch('kojihub.get_user') - @mock.patch('kojihub.readPackageList') - def test_get_notification_recipients_tag_with_build(self, readPackageList, get_user): - ### with tag and build - build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} - tag_id = 123 - state = koji.BUILD_STATES['CANCELED'] - readPackageList.return_value = {12345: {'blocked': False, 'owner_id': 'owner_id'}} - get_user.return_value = { - 'id': 342, - 'name': 'pkg_owner_name', - 'status': koji.USER_STATUS['NORMAL'], - 'usertype': koji.USERTYPES['NORMAL'] - } - self.set_queries([ - [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], - [] - ]) - - emails = kojihub.get_notification_recipients(build, tag_id, state) - self.assertEqual(sorted(emails), ['owner_name@test.domain.com', 'pkg_owner_name@test.domain.com']) - - - # there should be only query to watchers - self.assertEqual(len(self.queries), 2) - q = self.queries[0] - self.assertEqual(q.columns, ['email', 'user_id']) - self.assertEqual(q.tables, ['build_notifications']) - self.assertEqual(q.clauses, ['package_id = %(package_id)i OR package_id IS NULL', - 'status = %(users_status)i', - 'success_only = FALSE', - 'tag_id = %(tag_id)i OR tag_id IS NULL', - 'usertype IN %(users_usertypes)s', - ]) - self.assertEqual(q.joins, ['JOIN users ON build_notifications.user_id = users.id']) - self.assertEqual(q.values['package_id'], build['package_id']) - self.assertEqual(q.values['state'], state) - self.assertEqual(q.values['build'], build) - self.assertEqual(q.values['tag_id'], tag_id) - - q = self.queries[1] - self.assertEqual(q.columns, ['user_id']) - self.assertEqual(q.tables, ['build_notifications_block']) - self.assertEqual(q.clauses, [ - 'package_id = %(package_id)i OR package_id IS NULL', - 'tag_id = %(tag_id)i OR tag_id IS NULL', - 'user_id IN %(user_ids)s', - ]) - self.assertEqual(q.joins, None) - self.assertEqual(sorted(q.values['user_ids']), [5, 342]) - readPackageList.assert_called_once_with(pkgID=build['package_id'], tagID=tag_id, inherit=True) - get_user.asssert_called_once_with(342, strict=True) - - @mock.patch('kojihub.get_user') - @mock.patch('kojihub.readPackageList') - def test_get_notification_recipients_blocked_pkg_owner(self, readPackageList, get_user): - # blocked package owner - build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} - tag_id = 123 - state = koji.BUILD_STATES['CANCELED'] - get_user.return_value = { - 'id': 342, - 'name': 'pkg_owner_name', - 'status': koji.USER_STATUS['BLOCKED'], - 'usertype': koji.USERTYPES['NORMAL'] - } - self.set_queries([ - [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], - [] - ]) - emails = kojihub.get_notification_recipients(build, tag_id, state) - self.assertEqual(emails, ['owner_name@test.domain.com']) - - @mock.patch('kojihub.get_user') - @mock.patch('kojihub.readPackageList') - def test_get_notification_recipients_optout(self, readPackageList, get_user): - # blocked package owner - build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} - tag_id = 123 - state = koji.BUILD_STATES['CANCELED'] - get_user.return_value = { - 'id': 342, - 'name': 'pkg_owner_name', - 'status': koji.USER_STATUS['NORMAL'], - 'usertype': koji.USERTYPES['NORMAL'] - } - self.set_queries([ - [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], - [{'user_id': 5}] - ]) - emails = kojihub.get_notification_recipients(build, tag_id, state) - self.assertEqual(emails, []) - - - @mock.patch('kojihub.get_user') - @mock.patch('kojihub.readPackageList') - def test_get_notification_recipients_machine(self, readPackageList, get_user): - # package owner is machine - build = {'package_id': 12345, 'owner_name': 'owner_name', 'owner_id': 5} - tag_id = 123 - state = koji.BUILD_STATES['CANCELED'] - get_user.return_value = { - 'id': 342, - 'name': 'pkg_owner_name', - 'status': koji.USER_STATUS['NORMAL'], - 'usertype': koji.USERTYPES['HOST'] - } - self.set_queries([ - [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], - [] - ]) - emails = kojihub.get_notification_recipients(build, tag_id, state) - self.assertEqual(emails, ['owner_name@test.domain.com']) - - ##################### - # Create notification - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotification(self, get_package_id, get_tag_id, - get_build_notifications): - user_id = 1 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.getUser.return_value = {'id': 2, 'name': 'username'} - self.exports.hasPerm.return_value = True - get_package_id.return_value = package_id - get_tag_id.return_value = tag_id - get_build_notifications.return_value = [] - - r = self.exports.createNotification(user_id, package_id, tag_id, success_only) - self.assertEqual(r, None) - - self.exports.getLoggedInUser.assert_called_once() - self.exports.getUser.asssert_called_once_with(user_id) - self.exports.hasPerm.asssert_called_once_with('admin') - get_package_id.assert_called_once_with(package_id, strict=True) - get_tag_id.assert_called_once_with(tag_id, strict=True) - get_build_notifications.assert_called_once_with(2) - self.assertEqual(len(self.inserts), 1) - insert = self.inserts[0] - self.assertEqual(insert.table, 'build_notifications') - self.assertEqual(insert.data, { - 'package_id': package_id, - 'user_id': 2, - 'tag_id': tag_id, - 'success_only': success_only, - 'email': 'username@test.domain.com', - }) - self.assertEqual(insert.rawdata, {}) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotification_unauthentized(self, get_package_id, get_tag_id, - get_build_notifications): - user_id = 1 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = None - - with self.assertRaises(koji.GenericError): - self.exports.createNotification(user_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotification_invalid_user(self, get_package_id, get_tag_id, - get_build_notifications): - user_id = 2 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.getUser.return_value = None - - with self.assertRaises(koji.GenericError): - self.exports.createNotification(user_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotification_no_perm(self, get_package_id, get_tag_id, - get_build_notifications): - user_id = 2 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 1, 'name': 'a'} - self.exports.getUser.return_value = {'id': 2, 'name': 'b'} - self.exports.hasPerm.return_value = False - - with self.assertRaises(koji.GenericError): - self.exports.createNotification(user_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotification_invalid_pkg(self, get_package_id, get_tag_id, - get_build_notifications): - user_id = 2 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} - self.exports.getUser.return_value = {'id': 2, 'name': 'a'} - get_package_id.side_effect = ValueError - - with self.assertRaises(ValueError): - self.exports.createNotification(user_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotification_invalid_tag(self, get_package_id, get_tag_id, - get_build_notifications): - user_id = 2 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} - self.exports.getUser.return_value = {'id': 2, 'name': 'a'} - get_package_id.return_value = package_id - get_tag_id.side_effect = ValueError - - with self.assertRaises(ValueError): - self.exports.createNotification(user_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotification_exists(self, get_package_id, get_tag_id, - get_build_notifications): - user_id = 2 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} - self.exports.getUser.return_value = {'id': 2, 'name': 'a'} - get_package_id.return_value = package_id - get_tag_id.return_value = tag_id - get_build_notifications.return_value = [{ - 'package_id': package_id, - 'tag_id': tag_id, - 'success_only': success_only, - }] - - with self.assertRaises(koji.GenericError): - self.exports.createNotification(user_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - - ##################### - # Delete notification - @mock.patch('kojihub._dml') - def test_deleteNotification(self, _dml): - user_id = 752 - n_id = 543 - self.exports.getBuildNotification.return_value = {'user_id': user_id} - - self.exports.deleteNotification(n_id) - - self.exports.getBuildNotification.assert_called_once_with(n_id, strict=True) - self.exports.getLoggedInUser.assert_called_once_with() - _dml.assert_called_once() - - def test_deleteNotification_missing(self): - n_id = 543 - self.exports.getBuildNotification.side_effect = koji.GenericError - - with self.assertRaises(koji.GenericError): - self.exports.deleteNotification(n_id) - - self.exports.getBuildNotification.assert_called_once_with(n_id, strict=True) - - def test_deleteNotification_not_logged(self): - user_id = 752 - n_id = 543 - self.exports.getBuildNotification.return_value = {'user_id': user_id} - self.exports.getLoggedInUser.return_value = None - #self.set_queries = ([ - # [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], - #]) - - with self.assertRaises(koji.GenericError): - self.exports.deleteNotification(n_id) - - self.exports.getBuildNotification.assert_called_once_with(n_id, strict=True) - self.assertEqual(len(self.inserts), 0) - self.assertEqual(len(self.updates), 0) - self.assertEqual(len(self.queries), 0) - - @mock.patch('kojihub._dml') - def test_deleteNotification_no_perm(self, _dml): - user_id = 752 - n_id = 543 - self.exports.getBuildNotification.return_value = {'user_id': user_id} - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.hasPerm.return_value = False - - with self.assertRaises(koji.GenericError): - self.exports.deleteNotification(n_id) - - self.exports.getBuildNotification.assert_called_once_with(n_id, strict=True) - _dml.assert_not_called() - - - ##################### - # Update notification - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_updateNotification(self, get_package_id, get_tag_id, - get_build_notifications): - n_id = 5432 - user_id = 1 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.hasPerm.return_value = True - get_package_id.return_value = package_id - get_tag_id.return_value = tag_id - get_build_notifications.return_value = [{ - 'tag_id': tag_id, - 'user_id': user_id, - 'package_id': package_id, - 'success_only': not success_only, - }] - self.exports.getBuildNotification.return_value = {'user_id': user_id} - - r = self.exports.updateNotification(n_id, package_id, tag_id, success_only) - self.assertEqual(r, None) - - self.exports.getLoggedInUser.assert_called_once() - self.exports.hasPerm.asssert_called_once_with('admin') - get_package_id.assert_called_once_with(package_id, strict=True) - get_tag_id.assert_called_once_with(tag_id, strict=True) - get_build_notifications.assert_called_once_with(user_id) - self.assertEqual(len(self.inserts), 0) - self.assertEqual(len(self.updates), 1) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_updateNotification_not_logged(self, get_package_id, get_tag_id, - get_build_notifications): - n_id = 5432 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = None - - with self.assertRaises(koji.GenericError): - self.exports.updateNotification(n_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - self.assertEqual(len(self.updates), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_updateNotification_missing(self, get_package_id, get_tag_id, - get_build_notifications): - n_id = 5432 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.getBuildNotification.side_effect = koji.GenericError - - with self.assertRaises(koji.GenericError): - self.exports.updateNotification(n_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - self.assertEqual(len(self.updates), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_updateNotification_no_perm(self, get_package_id, get_tag_id, - get_build_notifications): - n_id = 5432 - user_id = 1 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 132} - self.exports.getBuildNotification.return_value = {'user_id': user_id} - self.exports.hasPerm.return_value = False - - with self.assertRaises(koji.GenericError): - self.exports.updateNotification(n_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - self.assertEqual(len(self.updates), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_updateNotification_exists(self, get_package_id, get_tag_id, - get_build_notifications): - n_id = 5432 - user_id = 1 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.hasPerm.return_value = True - get_package_id.return_value = package_id - get_tag_id.return_value = tag_id - get_build_notifications.return_value = [{ - 'tag_id': tag_id, - 'user_id': user_id, - 'package_id': package_id, - 'success_only': success_only, - }] - self.exports.getBuildNotification.return_value = {'user_id': user_id} - - with self.assertRaises(koji.GenericError): - self.exports.updateNotification(n_id, package_id, tag_id, success_only) - - self.exports.getLoggedInUser.assert_called_once() - self.exports.hasPerm.asssert_called_once_with('admin') - get_package_id.assert_called_once_with(package_id, strict=True) - get_tag_id.assert_called_once_with(tag_id, strict=True) - get_build_notifications.assert_called_once_with(user_id) - self.assertEqual(len(self.inserts), 0) - self.assertEqual(len(self.updates), 0) - - @mock.patch('kojihub.get_build_notifications') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_updateNotification_not_logged(self, get_package_id, get_tag_id, - get_build_notifications): - n_id = 5432 - user_id = 1 - package_id = 234 - tag_id = 345 - success_only = True - self.exports.getLoggedInUser.return_value = None - - with self.assertRaises(koji.GenericError): - self.exports.updateNotification(n_id, package_id, tag_id, success_only) - - self.assertEqual(len(self.inserts), 0) - self.assertEqual(len(self.updates), 0) - - ########################### - # Create notification block - - @mock.patch('kojihub.get_build_notification_blocks') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotificationBlock(self, get_package_id, get_tag_id, - get_build_notification_blocks): - user_id = 1 - package_id = 234 - tag_id = 345 - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.getUser.return_value = {'id': 2, 'name': 'username'} - self.exports.hasPerm.return_value = True - get_package_id.return_value = package_id - get_tag_id.return_value = tag_id - get_build_notification_blocks.return_value = [] - - r = self.exports.createNotificationBlock(user_id, package_id, tag_id) - self.assertEqual(r, None) - - self.exports.getLoggedInUser.assert_called_once() - self.exports.getUser.asssert_called_once_with(user_id) - self.exports.hasPerm.asssert_called_once_with('admin') - get_package_id.assert_called_once_with(package_id, strict=True) - get_tag_id.assert_called_once_with(tag_id, strict=True) - get_build_notification_blocks.assert_called_once_with(2) - self.assertEqual(len(self.inserts), 1) - insert = self.inserts[0] - self.assertEqual(insert.table, 'build_notifications_block') - self.assertEqual(insert.data, { - 'package_id': package_id, - 'user_id': 2, - 'tag_id': tag_id, - }) - self.assertEqual(insert.rawdata, {}) - - @mock.patch('kojihub.get_build_notification_blocks') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotificationBlock_unauthentized(self, get_package_id, get_tag_id, - get_build_notification_blocks): - user_id = 1 - package_id = 234 - tag_id = 345 - self.exports.getLoggedInUser.return_value = None - - with self.assertRaises(koji.GenericError): - self.exports.createNotificationBlock(user_id, package_id, tag_id) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notification_blocks') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotificationBlock_invalid_user(self, get_package_id, get_tag_id, - get_build_notification_blocks): - user_id = 2 - package_id = 234 - tag_id = 345 - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.getUser.return_value = None - - with self.assertRaises(koji.GenericError): - self.exports.createNotificationBlock(user_id, package_id, tag_id) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notification_blocks') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotificationBlock_no_perm(self, get_package_id, get_tag_id, - get_build_notification_blocks): - user_id = 2 - package_id = 234 - tag_id = 345 - self.exports.getLoggedInUser.return_value = {'id': 1, 'name': 'a'} - self.exports.getUser.return_value = {'id': 2, 'name': 'b'} - self.exports.hasPerm.return_value = False - - with self.assertRaises(koji.GenericError): - self.exports.createNotificationBlock(user_id, package_id, tag_id) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notification_blocks') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotificationBlock_invalid_pkg(self, get_package_id, get_tag_id, - get_build_notification_blocks): - user_id = 2 - package_id = 234 - tag_id = 345 - self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} - self.exports.getUser.return_value = {'id': 2, 'name': 'a'} - get_package_id.side_effect = ValueError - - with self.assertRaises(ValueError): - self.exports.createNotificationBlock(user_id, package_id, tag_id) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notification_blocks') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotificationBlock_invalid_tag(self, get_package_id, get_tag_id, - get_build_notification_blocks): - user_id = 2 - package_id = 234 - tag_id = 345 - self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} - self.exports.getUser.return_value = {'id': 2, 'name': 'a'} - get_package_id.return_value = package_id - get_tag_id.side_effect = ValueError - - with self.assertRaises(ValueError): - self.exports.createNotificationBlock(user_id, package_id, tag_id) - - self.assertEqual(len(self.inserts), 0) - - @mock.patch('kojihub.get_build_notification_blocks') - @mock.patch('kojihub.get_tag_id') - @mock.patch('kojihub.get_package_id') - def test_createNotificationBlock_exists(self, get_package_id, get_tag_id, - get_build_notification_blocks): - user_id = 2 - package_id = 234 - tag_id = 345 - self.exports.getLoggedInUser.return_value = {'id': 2, 'name': 'a'} - self.exports.getUser.return_value = {'id': 2, 'name': 'a'} - get_package_id.return_value = package_id - get_tag_id.return_value = tag_id - get_build_notification_blocks.return_value = [{ - 'package_id': package_id, - 'tag_id': tag_id, - }] - - with self.assertRaises(koji.GenericError): - self.exports.createNotificationBlock(user_id, package_id, tag_id) - - self.assertEqual(len(self.inserts), 0) - - ########################### - # Delete notification block - @mock.patch('kojihub._dml') - def test_deleteNotificationBlock(self, _dml): - user_id = 752 - n_id = 543 - self.exports.getBuildNotificationBlock.return_value = {'user_id': user_id} - - self.exports.deleteNotificationBlock(n_id) - - self.exports.getBuildNotificationBlock.assert_called_once_with(n_id, strict=True) - self.exports.getLoggedInUser.assert_called_once_with() - _dml.assert_called_once() - - def test_deleteNotificationBlock_missing(self): - n_id = 543 - self.exports.getBuildNotificationBlock.side_effect = koji.GenericError - - with self.assertRaises(koji.GenericError): - self.exports.deleteNotificationBlock(n_id) - - self.exports.getBuildNotificationBlock.assert_called_once_with(n_id, strict=True) - - def test_deleteNotificationBlock_not_logged(self): - user_id = 752 - n_id = 543 - self.exports.getBuildNotificationBlock.return_value = {'user_id': user_id} - self.exports.getLoggedInUser.return_value = None - #self.set_queries = ([ - # [{'user_id': 5, 'email': 'owner_name@%s' % self.context.opts['EmailDomain']}], - #]) - - with self.assertRaises(koji.GenericError): - self.exports.deleteNotificationBlock(n_id) - - self.exports.getBuildNotificationBlock.assert_called_once_with(n_id, strict=True) - self.assertEqual(len(self.inserts), 0) - self.assertEqual(len(self.updates), 0) - self.assertEqual(len(self.queries), 0) - - @mock.patch('kojihub._dml') - def test_deleteNotificationBlock_no_perm2(self, _dml): - user_id = 752 - n_id = 543 - self.exports.getBuildNotificationBlock.return_value = {'user_id': user_id} - self.exports.getLoggedInUser.return_value = {'id': 1} - self.exports.hasPerm.return_value = False - - with self.assertRaises(koji.GenericError): - self.exports.deleteNotificationBlock(n_id) - - self.exports.getBuildNotificationBlock.assert_called_once_with(n_id, strict=True) - _dml.assert_not_called() diff --git a/tests/test_hub/test_repo_delete.py b/tests/test_hub/test_repo_delete.py new file mode 100644 index 00000000..fe87fbba --- /dev/null +++ b/tests/test_hub/test_repo_delete.py @@ -0,0 +1,13 @@ +import unittest + +import koji +import kojihub + + +class TestRepoDelete(unittest.TestCase): + + def test_repo_delete_wrong_type_typeID(self): + repo_id = 'test-repo-id' + with self.assertRaises(koji.ParameterError) as cm: + kojihub.repo_delete(repo_id) + self.assertEqual(f"Invalid type for value '{repo_id}': {type(repo_id)}", str(cm.exception)) diff --git a/tests/test_hub/test_repo_init.py b/tests/test_hub/test_repo_init.py new file mode 100644 index 00000000..b3c30c8b --- /dev/null +++ b/tests/test_hub/test_repo_init.py @@ -0,0 +1,13 @@ +import unittest + +import koji +import kojihub + + +class TestRepoInit(unittest.TestCase): + + def test_repo_init_wrong_type_typeID(self): + task_id = 'test-task_id' + with self.assertRaises(koji.ParameterError) as cm: + kojihub.repo_init('test-tag', task_id) + self.assertEqual(f"Invalid type for value '{task_id}': {type(task_id)}", str(cm.exception)) diff --git a/tests/test_hub/test_repo_set_state.py b/tests/test_hub/test_repo_set_state.py new file mode 100644 index 00000000..b6a50076 --- /dev/null +++ b/tests/test_hub/test_repo_set_state.py @@ -0,0 +1,13 @@ +import unittest + +import koji +import kojihub + + +class TestRepoSetState(unittest.TestCase): + + def test_set_state_wrong_type_typeID(self): + repo_id = 'test-repo-id' + with self.assertRaises(koji.ParameterError) as cm: + kojihub.repo_set_state(repo_id, 'failed') + self.assertEqual(f"Invalid type for value '{repo_id}': {type(repo_id)}", str(cm.exception)) diff --git a/tests/test_hub/test_repos.py b/tests/test_hub/test_repos.py index 48c7cd70..19d14ca3 100644 --- a/tests/test_hub/test_repos.py +++ b/tests/test_hub/test_repos.py @@ -26,6 +26,7 @@ class TestRepoFunctions(unittest.TestCase): self.updates = [] self._dml = mock.patch('kojihub._dml').start() self.exports = kojihub.RootExports() + self.get_tag = mock.patch('kojihub.get_tag').start() def tearDown(self): mock.patch.stopall() @@ -83,7 +84,8 @@ class TestRepoFunctions(unittest.TestCase): 'state': 0, 'task_id': 15, 'create_event': 32, - 'creation_time': datetime.datetime(2021, 3, 30, 12, 34, 5, 204023, tzinfo=datetime.timezone.utc), + 'creation_time': datetime.datetime(2021, 3, 30, 12, 34, 5, 204023, + tzinfo=datetime.timezone.utc), 'create_ts': 1617107645.204023, 'tag_id': 3, 'tag_name': 'test-tag', @@ -92,8 +94,8 @@ class TestRepoFunctions(unittest.TestCase): rv = kojihub.repo_info(3) self.assertEqual(rv, repo_row) - def test_get_repo(self): - rv = self.exports.getRepo(2) + def test_get_repo_default(self): + self.exports.getRepo(2) self.assertEqual(len(self.queries), 1) query = self.queries[0] # make sure the following does not error @@ -105,3 +107,17 @@ class TestRepoFunctions(unittest.TestCase): self.assertEqual(query.joins, ['events ON repo.create_event = events.id']) self.assertEqual(query.clauses, ['repo.dist is false', 'repo.state = %(state)s', 'repo.tag_id = %(id)i']) + + def test_get_repo_with_dist_and_event(self): + self.exports.getRepo(2, event=111, dist=True) + self.assertEqual(len(self.queries), 1) + query = self.queries[0] + # make sure the following does not error + str(query) + self.assertEqual(query.tables, ['repo']) + columns = ['repo.id', 'repo.state', 'repo.task_id', 'repo.create_event', + 'EXTRACT(EPOCH FROM events.time)', 'repo.dist', 'events.time'] + self.assertEqual(set(query.columns), set(columns)) + self.assertEqual(query.joins, ['events ON repo.create_event = events.id']) + self.assertEqual(query.clauses, ['create_event <= %(event)i', 'repo.dist is true', + 'repo.tag_id = %(id)i']) diff --git a/tests/test_hub/test_restart_hosts.py b/tests/test_hub/test_restart_hosts.py index 5da1c327..4bd6fc36 100644 --- a/tests/test_hub/test_restart_hosts.py +++ b/tests/test_hub/test_restart_hosts.py @@ -2,6 +2,7 @@ import unittest import mock +import koji import kojihub @@ -13,12 +14,18 @@ class TestRestartHosts(unittest.TestCase): self.context.session.assertPerm = mock.MagicMock() self.make_task = mock.patch('kojihub.make_task').start() - def options_is_none(self): + def test_options_is_none(self): self.make_task.return_value = 13 rv = self.exports.restartHosts() self.assertEqual(rv, 13) - def options_is_not_none(self): + def test_options_is_not_none(self): self.make_task.return_value = 13 rv = self.exports.restartHosts(options={'opt': 'open'}) self.assertEqual(rv, 13) + + def test_options_wrong_type(self): + options = 'test-options' + with self.assertRaises(koji.ParameterError) as ex: + self.exports.restartHosts(options=options) + self.assertEqual(f"Invalid type of options: {type(options)}", str(ex.exception)) diff --git a/tests/test_hub/test_search.py b/tests/test_hub/test_search.py index 72fb162d..1f8ecff7 100644 --- a/tests/test_hub/test_search.py +++ b/tests/test_hub/test_search.py @@ -18,4 +18,4 @@ class TestSearch(unittest.TestCase): type = 'test-type' with self.assertRaises(koji.GenericError) as cm: self.exports.search('item', type, 'glob') - self.assertEqual("No such search type: %s" % type, str(cm.exception)) + self.assertEqual(f"No such search type: {type}", str(cm.exception)) diff --git a/tests/test_hub/test_tag_operations.py b/tests/test_hub/test_tag_operations.py index d15de00e..381b78cc 100644 --- a/tests/test_hub/test_tag_operations.py +++ b/tests/test_hub/test_tag_operations.py @@ -252,16 +252,22 @@ class TestGetTag(unittest.TestCase): self.QueryProcessor = mock.patch('kojihub.QueryProcessor', side_effect=self.getQuery).start() self.queries = [] + self.tagname = 'test-tag' def test_get_tag_invalid_taginfo(self): taginfo = {'test-tag': 'value'} with self.assertRaises(koji.GenericError) as ex: kojihub.get_tag(taginfo, strict=True) - self.assertEqual("Invalid name or id value: %s" % taginfo, str(ex.exception)) + self.assertEqual(f"Invalid name or id value: {taginfo}", str(ex.exception)) def test_get_tag_non_exist_tag(self): - taginfo = 'test-tag' self.query_executeOne.return_value = None with self.assertRaises(koji.GenericError) as ex: - kojihub.get_tag(taginfo, strict=True) - self.assertEqual("No such tagInfo: '%s'" % taginfo, str(ex.exception)) + kojihub.get_tag(self.tagname, strict=True) + self.assertEqual(f"No such tagInfo: '{self.tagname}'", str(ex.exception)) + + def test_get_tag_wrong_event(self): + event = 'unsupported-event' + with self.assertRaises(koji.GenericError) as ex: + kojihub.get_tag(self.tagname, event=event) + self.assertEqual(f"Invalid event: '{event}'", str(ex.exception)) diff --git a/tests/test_hub/test_update_notification.py b/tests/test_hub/test_update_notification.py new file mode 100644 index 00000000..f08d8c54 --- /dev/null +++ b/tests/test_hub/test_update_notification.py @@ -0,0 +1,148 @@ +import mock +import unittest + +import koji +import kojihub + +QP = kojihub.QueryProcessor +IP = kojihub.InsertProcessor +UP = kojihub.UpdateProcessor + + +class TestUpdateNotifications(unittest.TestCase): + def getInsert(self, *args, **kwargs): + insert = IP(*args, **kwargs) + insert.execute = mock.MagicMock() + self.inserts.append(insert) + return insert + + def getQuery(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + self.queries.append(query) + return query + + def getUpdate(self, *args, **kwargs): + update = UP(*args, **kwargs) + update.execute = mock.MagicMock() + self.updates.append(update) + return update + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.context.opts = { + 'EmailDomain': 'test.domain.com', + 'NotifyOnSuccess': True, + } + + self.QueryProcessor = mock.patch('kojihub.QueryProcessor', + side_effect=self.getQuery).start() + self.queries = [] + 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.get_build_notifications = mock.patch('kojihub.get_build_notifications').start() + self.get_tag_id = mock.patch('kojihub.get_tag_id').start() + self.get_package_id = mock.patch('kojihub.get_package_id').start() + + self.exports = kojihub.RootExports() + self.exports.getLoggedInUser = mock.MagicMock() + self.exports.hasPerm = mock.MagicMock() + self.exports.getBuildNotification = mock.MagicMock() + self.user_id = 1 + self.n_id = 5432 + self.package_id = 234 + self.tag_id = 345 + + def tearDown(self): + mock.patch.stopall() + + def test_updateNotification(self): + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.hasPerm.return_value = True + self.get_package_id.return_value = self.package_id + self.get_tag_id.return_value = self.tag_id + self.get_build_notifications.return_value = [{ + 'tag_id': self.tag_id, + 'user_id': self.user_id, + 'package_id': self.package_id, + 'success_only': not success_only, + }] + self.exports.getBuildNotification.return_value = {'user_id': self.user_id} + + r = self.exports.updateNotification(self.n_id, self.package_id, self.tag_id, success_only) + self.assertEqual(r, None) + + self.exports.getLoggedInUser.assert_called_once() + self.exports.hasPerm.asssert_called_once_with('admin') + self.get_package_id.assert_called_once_with(self.package_id, strict=True) + self.get_tag_id.assert_called_once_with(self.tag_id, strict=True) + self.get_build_notifications.assert_called_once_with(self.user_id) + self.assertEqual(len(self.inserts), 0) + self.assertEqual(len(self.updates), 1) + + def test_updateNotification_not_logged(self): + success_only = True + self.exports.getLoggedInUser.return_value = None + + with self.assertRaises(koji.GenericError) as cm: + self.exports.updateNotification(self.n_id, self.package_id, self.tag_id, success_only) + self.assertEqual('Not logged-in', str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) + self.assertEqual(len(self.updates), 0) + + def test_updateNotification_missing(self): + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.getBuildNotification.side_effect = koji.GenericError + + with self.assertRaises(koji.GenericError): + self.exports.updateNotification(self.n_id, self.package_id, self.tag_id, success_only) + + self.assertEqual(len(self.inserts), 0) + self.assertEqual(len(self.updates), 0) + + def test_updateNotification_no_perm(self): + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 132} + self.exports.getBuildNotification.return_value = {'user_id': self.user_id} + self.exports.hasPerm.return_value = False + + with self.assertRaises(koji.GenericError) as cm: + self.exports.updateNotification(self.n_id, self.package_id, self.tag_id, success_only) + self.assertEqual(f'user 132 cannot update notifications for user {self.user_id}', + str(cm.exception)) + + self.assertEqual(len(self.inserts), 0) + self.assertEqual(len(self.updates), 0) + + def test_updateNotification_exists(self): + success_only = True + self.exports.getLoggedInUser.return_value = {'id': 1} + self.exports.hasPerm.return_value = True + self.get_package_id.return_value = self.package_id + self.get_tag_id.return_value = self.tag_id + self.get_build_notifications.return_value = [{ + 'tag_id': self.tag_id, + 'user_id': self.user_id, + 'package_id': self.package_id, + 'success_only': success_only, + }] + self.exports.getBuildNotification.return_value = {'user_id': self.user_id} + + with self.assertRaises(koji.GenericError) as cm: + self.exports.updateNotification(self.n_id, self.package_id, self.tag_id, success_only) + self.assertEqual('notification already exists', str(cm.exception)) + + self.exports.getLoggedInUser.assert_called_once() + self.exports.hasPerm.asssert_called_once_with('admin') + self.get_package_id.assert_called_once_with(self.package_id, strict=True) + self.get_tag_id.assert_called_once_with(self.tag_id, strict=True) + self.get_build_notifications.assert_called_once_with(self.user_id) + self.assertEqual(len(self.inserts), 0) + self.assertEqual(len(self.updates), 0) diff --git a/tests/test_hub/test_win_build.py b/tests/test_hub/test_win_build.py new file mode 100644 index 00000000..a2adcd7a --- /dev/null +++ b/tests/test_hub/test_win_build.py @@ -0,0 +1,68 @@ +import unittest +import koji +import kojihub +import mock + + +class TestWinBuild(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertLogin = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + self.get_channel = mock.patch('kojihub.get_channel').start() + self.assert_policy = mock.patch('kojihub.assert_policy').start() + self.get_build_target = mock.patch('kojihub.get_build_target').start() + self.make_task = mock.patch('kojihub.make_task').start() + self.vm = 'test-vm' + self.url = 'https://test-url.com' + self.target = 'test-target' + self.targetinfo = {'build_tag': 444, + 'build_tag_name': 'test-tag', + 'dest_tag': 445, + 'dest_tag_name': 'dest-test-tag', + 'id': 1, + 'name': self.target} + + def tearDown(self): + mock.patch.stopall() + + def test_win_not_supported(self): + self.context.opts.get.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.winBuild(self.vm, self.url, self.target) + self.assertEqual("Windows support not enabled", str(cm.exception)) + + def test_vm_wrong_type(self): + vm = ['test-vm'] + self.context.opts.get.return_value = True + with self.assertRaises(koji.GenericError) as cm: + self.exports.winBuild(vm, self.url, self.target) + self.assertEqual(f"Invalid type for value '{vm}': {type(vm)}", str(cm.exception)) + + def test_url_wrong_type(self): + url = ['test-url'] + self.context.opts.get.return_value = True + with self.assertRaises(koji.GenericError) as cm: + self.exports.winBuild(self.vm, url, self.target) + self.assertEqual(f"Invalid type for value '{url}': {type(url)}", str(cm.exception)) + + def test_priority_without_admin(self): + priority = -10 + self.context.opts.get.return_value = True + self.get_build_target.return_value = self.targetinfo + self.assert_policy.return_value = True + self.context.session.hasPerm.return_value = False + with self.assertRaises(koji.GenericError) as cm: + self.exports.winBuild(self.vm, self.url, self.target, priority=priority) + self.assertEqual("only admins may create high-priority tasks", str(cm.exception)) + + def test_channel_not_str(self): + self.context.opts.get.return_value = True + self.get_build_target.return_value = self.targetinfo + self.assert_policy.return_value = True + self.make_task.return_value = 123 + self.get_channel.return_value = {'comment': None, 'description': None, 'enabled': True, + 'id': 1, 'name': 'vm'} + self.exports.winBuild(self.vm, self.url, self.target, channel=1, priority=10) diff --git a/tests/test_hub/test_wrapper_rpm.py b/tests/test_hub/test_wrapper_rpm.py new file mode 100644 index 00000000..06982853 --- /dev/null +++ b/tests/test_hub/test_wrapper_rpm.py @@ -0,0 +1,56 @@ +import unittest +import koji +import kojihub +import mock + + +class TestWrapperRPM(unittest.TestCase): + + def setUp(self): + self.context = mock.patch('kojihub.context').start() + self.exports = kojihub.RootExports() + self.context.session.assertLogin = mock.MagicMock() + self.context.session.hasPerm = mock.MagicMock() + self.get_channel = mock.patch('kojihub.get_channel').start() + self.exports.getBuild = mock.MagicMock() + self.make_task = mock.patch('kojihub.make_task').start() + self.list_rpms = mock.patch('kojihub.list_rpms').start() + self.exports.getTag = mock.MagicMock() + self.exports.getBuildTarget = mock.MagicMock() + self.exports.getRepo = mock.MagicMock() + self.build = 'testbuild-1-1.4' + self.target = 'test-target' + self.url = 'https://test-url.com' + self.buildinfo = {'name': 'testbuild', 'version': '1', 'release': '1.4', + 'nvr': self.build, 'id': 123} + self.targetinfo = {'build_tag': 444, + 'build_tag_name': 'test-tag', + 'dest_tag': 445, + 'dest_tag_name': 'dest-test-tag', + 'id': 1, + 'name': self.target} + self.taginfo = {'id': 159, 'name': 'test-tag'} + self.repoinfo = {'id': 753} + + def tearDown(self): + mock.patch.stopall() + + def test_url_wrong_type(self): + url = ['test-url'] + self.context.opts.get.return_value = True + with self.assertRaises(koji.GenericError) as cm: + self.exports.wrapperRPM(self.build, url, self.target) + self.assertEqual(f"Invalid type for value '{url}': {type(url)}", str(cm.exception)) + + def test_channel_not_str(self): + priority = 10 + self.context.opts.get.return_value = True + self.exports.getBuild.return_value = self.buildinfo + self.list_rpms.return_value = [] + self.exports.getBuildTarget.return_value = self.targetinfo + self.exports.getRepo.return_value = self.taginfo + self.exports.getRepo.return_value = self.repoinfo + self.make_task.return_value = 123 + self.get_channel.return_value = {'comment': None, 'description': None, 'enabled': True, + 'id': 2, 'name': 'maven'} + self.exports.wrapperRPM(self.build, self.url, self.target, priority=priority, channel=2) diff --git a/tests/test_hub/test_write_signed_rpm.py b/tests/test_hub/test_write_signed_rpm.py new file mode 100644 index 00000000..818ea226 --- /dev/null +++ b/tests/test_hub/test_write_signed_rpm.py @@ -0,0 +1,23 @@ +import unittest +import mock + +import koji +import kojihub + + +class TestWriteSignedRPM(unittest.TestCase): + def setUp(self): + self.get_rpm = mock.patch('kojihub.get_rpm').start() + + def tearDown(self): + mock.patch.stopall() + + def test_write_signed_rpm_not_internal_rpm(self): + sigkey = 'test-sigkey' + rpm_id = 1 + rpminfo = {'external_repo_id': 1, 'external_repo_name': 'test-external-repo'} + self.get_rpm.return_value = rpminfo + with self.assertRaises(koji.GenericError) as cm: + kojihub.write_signed_rpm(rpm_id, sigkey) + self.assertEqual(f"Not an internal rpm: {rpm_id} (from {rpminfo['external_repo_name']})", + str(cm.exception)) diff --git a/tests/test_plugins/test_runroot_hub.py b/tests/test_plugins/test_runroot_hub.py index f78b2e0f..14d7874e 100644 --- a/tests/test_plugins/test_runroot_hub.py +++ b/tests/test_plugins/test_runroot_hub.py @@ -48,8 +48,8 @@ class TestRunrootHub(unittest.TestCase): context.handlers = mock.MagicMock() context.handlers.call = mock.MagicMock() context.handlers.call.side_effect = [ - {'id': 2, 'name': 'runroot'}, # getChannel - [ # listHosts + {'id': 2, 'name': 'runroot'}, # getChannel + [ # listHosts { 'arches': 'i386 x86_64', 'capacity': 20.0, @@ -105,8 +105,8 @@ class TestRunrootHub(unittest.TestCase): context.handlers = mock.MagicMock() context.handlers.call = mock.MagicMock() context.handlers.call.side_effect = [ - {'id': 2, 'name': 'runroot'}, # getChannel - [ # listHosts + {'id': 2, 'name': 'runroot'}, # getChannel + [ # listHosts { 'arches': 'i386 x86_64', 'capacity': 20.0, @@ -147,3 +147,31 @@ class TestRunrootHub(unittest.TestCase): mock.call('listHosts', channelID=2, enabled=True), ]) make_task.assert_not_called() + + @mock.patch('kojihub.get_channel') + @mock.patch('kojihub.get_tag') + @mock.patch('kojihub.make_task') + @mock.patch('runroot_hub.context') + def test_non_exist_channel(self, context, make_task, get_tag, get_channel): + context.session.assertPerm = mock.MagicMock() + get_channel.side_effect = koji.GenericError + with self.assertRaises(koji.GenericError): + runroot_hub.runroot(tagInfo='some_tag', arch='x86_64', command='ls', + channel='non-exist-channel') + make_task.assert_not_called() + get_tag.assert_not_called() + + @mock.patch('kojihub.get_channel') + @mock.patch('kojihub.get_tag') + @mock.patch('kojihub.make_task') + @mock.patch('runroot_hub.context') + def test_commang_wrong_format(self, context, make_task, get_tag, get_channel): + context.session.assertPerm = mock.MagicMock() + command = ['ls'] + with self.assertRaises(koji.GenericError) as ex: + runroot_hub.runroot(tagInfo='some_tag', arch='x86_64', command=command, + channel='non-exist-channel') + self.assertEqual(f"Invalid type for value '{command}': {type(command)}", str(ex.exception)) + make_task.assert_not_called() + get_tag.assert_not_called() + get_channel.assert_not_called() diff --git a/util/koji-gc b/util/koji-gc index 79d589fc..6013c277 100755 --- a/util/koji-gc +++ b/util/koji-gc @@ -168,6 +168,8 @@ def get_options(): # figure out actions actions = ('prune', 'trash', 'delete', 'salvage') if options.action: + if not isinstance(options.action, str): + raise koji.ParameterError('Invalid type of action: %s' % type(options.action)) options.action = options.action.lower().replace(',', ' ').split() for x in options.action: if x not in actions: @@ -177,6 +179,9 @@ def get_options(): # split patterns for unprotected keys if options.unprotected_keys: + if not isinstance(options.unprotected_keys, str): + raise koji.ParameterError('Invalid type of unprotected_keys: %s' + % type(options.unprotected_keys)) options.unprotected_key_patterns = options.unprotected_keys.replace(',', ' ').split() else: options.unprotected_key_patterns = [] @@ -250,10 +255,15 @@ def check_tag(name): Returns True if we should process the tag, False otherwise """ if options.ignore_tags: + if not isinstance(options.ignore_tags, list): + raise koji.ParameterError('Invalid type of ignore_tags: %s' + % type(options.ignore_tags)) for pattern in options.ignore_tags: if fnmatch.fnmatch(name, pattern): return False if options.tag_filter: + if not isinstance(options.tag_filter, list): + raise koji.ParameterError('Invalid type of tag_filter: %s' % type(options.tag_filter)) for pattern in options.tag_filter: if fnmatch.fnmatch(name, pattern): return True @@ -270,6 +280,8 @@ def check_package(name): Returns True if we should process the package, False otherwise """ if options.pkg_filter: + if not isinstance(options.pkg_filter, list): + raise koji.ParameterError('Invalid type of pkg_filter: %s' % type(options.pkg_filter)) for pattern in options.pkg_filter: if fnmatch.fnmatch(name, pattern): return True @@ -417,13 +429,21 @@ Build: %%(name)s-%%(version)s-%%(release)s msg['Subject'] = "1 build marked for deletion" else: msg['Subject'] = "%i builds marked for deletion" % len(builds) + if not isinstance(options.from_addr, str): + raise koji.ParameterError('Invalid type of from_addr: %s' % type(options.from_addr)) msg['From'] = options.from_addr + if not isinstance(options.email_domain, str): + raise koji.ParameterError('Invalid type of email_domain: %s' % type(options.email_domain)) msg['To'] = "%s@%s" % (owner_name, options.email_domain) # XXX! emails = [msg['To']] if options.cc_addr: + if not isinstance(options.cc_addr, str): + raise koji.ParameterError('Invalid type of cc_addr: %s' % type(options.cc_addr)) msg['Cc'] = ','.join(options.cc_addr) emails += options.cc_addr if options.bcc_addr: + if not isinstance(options.bcc_addr, str): + raise koji.ParameterError('Invalid type of bcc_addr: %s' % type(options.bcc_addr)) emails += options.bcc_addr msg['X-Koji-Builder'] = owner_name if options.test: @@ -894,6 +914,9 @@ def handle_prune(): bypass = False if taginfo['locked']: if options.bypass_locks: + if not isinstance(options.bypass_locks, list): + raise koji.ParameterError('Invalid type of bypass_locks: %s' + % type(options.bypass_locks)) for pattern in options.bypass_locks: if fnmatch.fnmatch(tagname, pattern): bypass = True diff --git a/util/koji-shadow b/util/koji-shadow index 0f25329d..211d7ad2 100755 --- a/util/koji-shadow +++ b/util/koji-shadow @@ -389,6 +389,8 @@ class TrackedBuild(object): log("Downloading %s" % url) # XXX - this is not really the right place for this resp = request_with_retry().get(url, stream=True) + if not isinstance(options.workpath, str): + raise koji.ParameterError('Invalid type of workpath: %s' % type(options.workpath)) fn = "%s/%s.src.rpm" % (options.workpath, self.nvr) koji.ensuredir(os.path.dirname(fn)) try: diff --git a/util/koji-sidetag-cleanup b/util/koji-sidetag-cleanup index a881a21f..1bdc6d3a 100644 --- a/util/koji-sidetag-cleanup +++ b/util/koji-sidetag-cleanup @@ -141,6 +141,11 @@ def activate_session(session): elif options.keytab and options.principal: try: if options.keytab and options.principal: + if not isinstance(options.keytab, str): + raise koji.ParameterError('Invalid type of keytab: %s' % type(options.keytab)) + if not isinstance(options.principal, str): + raise koji.ParameterError('Invalid type of principal: %s' + % type(options.principal)) session.gssapi_login( principal=options.principal, keytab=options.keytab,