From 9e1106f16421f8b0434eee78c785501d4d2f3bc2 Mon Sep 17 00:00:00 2001 From: Charlton Austin Date: Fri, 21 Jul 2017 14:04:59 -0400 Subject: [PATCH 1/2] refactor(endpoints/api/repository*): added in pre_oci_model abstraction this is a part of getting ready for oci stuff [TESTING->using new PR stack] Issue: https://coreosdev.atlassian.net/browse/QUAY-633 - [ ] It works! - [ ] Comments provide sufficient explanations for the next contributor - [ ] Tests cover changes and corner cases - [ ] Follows Quay syntax patterns and format --- endpoints/api/logs.py | 2 + endpoints/api/repository.py | 265 ++++--------------- endpoints/api/repository_models_interface.py | 252 ++++++++++++++++++ endpoints/api/repository_models_pre_oci.py | 167 ++++++++++++ endpoints/api/test/test_repository.py | 4 +- test/test_api_usage.py | 4 +- 6 files changed, 473 insertions(+), 221 deletions(-) create mode 100644 endpoints/api/repository_models_interface.py create mode 100644 endpoints/api/repository_models_pre_oci.py diff --git a/endpoints/api/logs.py b/endpoints/api/logs.py index 9966aaa6c..b7f645bf1 100644 --- a/endpoints/api/logs.py +++ b/endpoints/api/logs.py @@ -16,6 +16,8 @@ SERVICE_LEVEL_LOG_KINDS = set(['service_key_create', 'service_key_approve', 'ser 'service_key_modify', 'service_key_extend', 'service_key_rotate']) + + def _validate_logs_arguments(start_time, end_time): if start_time: try: diff --git a/endpoints/api/repository.py b/endpoints/api/repository.py index f8f41de44..6ce980d31 100644 --- a/endpoints/api/repository.py +++ b/endpoints/api/repository.py @@ -10,12 +10,12 @@ from datetime import timedelta, datetime from flask import request, abort from app import dockerfile_build_queue, tuf_metadata_api -from data import model, oci_model from endpoints.api import (format_date, nickname, log_action, validate_json_request, require_repo_read, require_repo_write, require_repo_admin, RepositoryParamResource, resource, parse_args, ApiResource, request_error, require_scope, path_param, page_support, query_param, truthy_bool, show_if) +from endpoints.api.repository_models_pre_oci import pre_oci_model as model from endpoints.exception import (Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException, DownstreamIssue) from endpoints.api.billing import lookup_allowed_private_repos, get_namespace_plan @@ -27,12 +27,12 @@ from auth.auth_context import get_authenticated_user from auth import scopes from util.names import REPOSITORY_NAME_REGEX - logger = logging.getLogger(__name__) REPOS_PER_PAGE = 100 MAX_DAYS_IN_3_MONTHS = 92 + def check_allowed_private_repos(namespace): """ Checks to see if the given namespace has reached its private repository limit. If so, raises a ExceedsLicenseException. @@ -106,8 +106,7 @@ class RepositoryList(ApiResource): repository_name = req['repository'] visibility = req['visibility'] - existing = model.repository.get_repository(namespace_name, repository_name) - if existing: + if model.repo_exists(namespace_name, repository_name): raise request_error(message='Repository already exists') visibility = req['visibility'] @@ -119,22 +118,19 @@ class RepositoryList(ApiResource): raise InvalidRequest('Invalid repository name') kind = req.get('repo_kind', 'image') or 'image' - repo = model.repository.create_repository(namespace_name, repository_name, owner, visibility, - repo_kind=kind) - repo.description = req['description'] - repo.save() + model.create_repo(namespace_name, repository_name, owner, req['description'], visibility=visibility, + repo_kind=kind) log_action('create_repo', namespace_name, {'repo': repository_name, - 'namespace': namespace_name}, repo=repo) + 'namespace': namespace_name}, repo_name=repository_name) return { - 'namespace': namespace_name, - 'name': repository_name, - 'kind': kind, - }, 201 + 'namespace': namespace_name, + 'name': repository_name, + 'kind': kind, + }, 201 raise Unauthorized() - @require_scope(scopes.READ_REPO) @nickname('listRepos') @parse_args() @@ -160,89 +156,18 @@ class RepositoryList(ApiResource): user = get_authenticated_user() username = user.username if user else None - next_page_token = None - repos = None - repo_kind = parsed_args['repo_kind'] + last_modified = parsed_args['last_modified'] + popularity = parsed_args['popularity'] - # Lookup the requested repositories (either starred or non-starred.) - if parsed_args['starred']: - if not username: - # No repositories should be returned, as there is no user. - abort(400) + if parsed_args['starred'] and not username: + # No repositories should be returned, as there is no user. + abort(400) - # Return the full list of repos starred by the current user that are still visible to them. - def can_view_repo(repo): - return ReadRepositoryPermission(repo.namespace_user.username, repo.name).can() + repos, next_page_token = model.get_repo_list( + parsed_args['starred'], user, parsed_args['repo_kind'], parsed_args['namespace'], username, + parsed_args['public'], page_token, last_modified, popularity) - unfiltered_repos = model.repository.get_user_starred_repositories(user, kind_filter=repo_kind) - repos = [repo for repo in unfiltered_repos if can_view_repo(repo)] - elif parsed_args['namespace']: - # Repositories filtered by namespace do not need pagination (their results are fairly small), - # so we just do the lookup directly. - repos = list(model.repository.get_visible_repositories(username=username, - include_public=parsed_args['public'], - namespace=parsed_args['namespace'], - kind_filter=repo_kind)) - else: - # Determine the starting offset for pagination. Note that we don't use the normal - # model.modelutil.paginate method here, as that does not operate over UNION queries, which - # get_visible_repositories will return if there is a logged-in user (for performance reasons). - # - # Also note the +1 on the limit, as paginate_query uses the extra result to determine whether - # there is a next page. - start_id = model.modelutil.pagination_start(page_token) - repo_query = model.repository.get_visible_repositories(username=username, - include_public=parsed_args['public'], - start_id=start_id, - limit=REPOS_PER_PAGE+1, - kind_filter=repo_kind) - - repos, next_page_token = model.modelutil.paginate_query(repo_query, limit=REPOS_PER_PAGE, - id_alias='rid') - - # Collect the IDs of the repositories found for subequent lookup of popularity - # and/or last modified. - if parsed_args['last_modified'] or parsed_args['popularity']: - repository_ids = [repo.rid for repo in repos] - - if parsed_args['last_modified']: - last_modified_map = model.repository.get_when_last_modified(repository_ids) - - if parsed_args['popularity']: - action_sum_map = model.log.get_repositories_action_sums(repository_ids) - - # Collect the IDs of the repositories that are starred for the user, so we can mark them - # in the returned results. - star_set = set() - if username: - starred_repos = model.repository.get_user_starred_repositories(user) - star_set = {starred.id for starred in starred_repos} - - def repo_view(repo_obj): - repo = { - 'namespace': repo_obj.namespace_user.username, - 'name': repo_obj.name, - 'description': repo_obj.description, - 'is_public': repo_obj.visibility_id == model.repository.get_public_repo_visibility().id, - 'kind': repo_kind, - } - - repo_id = repo_obj.rid - - if parsed_args['last_modified']: - repo['last_modified'] = last_modified_map.get(repo_id) - - if parsed_args['popularity']: - repo['popularity'] = float(action_sum_map.get(repo_id, 0)) - - if username: - repo['is_starred'] = repo_id in star_set - - return repo - - return { - 'repositories': [repo_view(repo) for repo in repos] - }, next_page_token + return {'repositories': [repo.to_dict() for repo in repos]}, next_page_token @resource('/v1/repository/') @@ -273,154 +198,63 @@ class Repository(RepositoryParamResource): def get(self, namespace, repository, parsed_args): """Fetch the specified repository.""" logger.debug('Get repo: %s/%s' % (namespace, repository)) - - repo = model.repository.get_repository(namespace, repository) + repo = model.get_repo(namespace, repository, get_authenticated_user()) if repo is None: raise NotFound() - can_write = ModifyRepositoryPermission(namespace, repository).can() - can_admin = AdministerRepositoryPermission(namespace, repository).can() + repo_data = repo.to_dict() + repo_data['can_write'] = ModifyRepositoryPermission(namespace, repository).can() + repo_data['can_admin'] = AdministerRepositoryPermission(namespace, repository).can() - is_starred = (model.repository.repository_is_starred(get_authenticated_user(), repo) - if get_authenticated_user() else False) - is_public = model.repository.is_repository_public(repo) - - # Note: This is *temporary* code for the new OCI model stuff. - if repo.kind.name == 'application': - def channel_view(channel): - return { - 'name': channel.name, - 'release': channel.linked_tag.name, - 'last_modified': format_date(datetime.fromtimestamp(channel.linked_tag.lifetime_start / 1000)), - } - - def release_view(release): - return { - 'name': release.name, - 'last_modified': format_date(datetime.fromtimestamp(release.lifetime_start / 1000)), - 'channels': releases_channels_map[release.name], - } - - channels = oci_model.channel.get_repo_channels(repo) - releases_channels_map = defaultdict(list) - for channel in channels: - releases_channels_map[channel.linked_tag.name].append(channel.name) - - repo_data = { - 'namespace': namespace, - 'name': repository, - 'kind': repo.kind.name, - 'description': repo.description, - 'can_write': can_write, - 'can_admin': can_admin, - 'is_public': is_public, - 'is_organization': repo.namespace_user.organization, - 'is_starred': is_starred, - 'channels': [channel_view(chan) for chan in channels], - 'releases': [release_view(release) for release in oci_model.release.get_release_objs(repo)], - } - - return repo_data - - # Older image-only repo code. - def tag_view(tag): - tag_info = { - 'name': tag.name, - 'image_id': tag.image.docker_image_id, - 'size': tag.image.aggregate_size - } - - if tag.lifetime_start_ts > 0: - last_modified = format_date(datetime.fromtimestamp(tag.lifetime_start_ts)) - tag_info['last_modified'] = last_modified - - if tag.lifetime_end_ts: - expiration = format_date(datetime.fromtimestamp(tag.lifetime_end_ts)) - tag_info['expiration'] = expiration - - if tag.tagmanifest is not None: - tag_info['manifest_digest'] = tag.tagmanifest.digest - - return tag_info - - stats = None - tags = model.tag.list_active_repo_tags(repo) - tag_dict = {tag.name: tag_view(tag) for tag in tags} - if parsed_args['includeStats']: + if parsed_args['includeStats'] and repo.repository_base_elements.kind_name != 'application': stats = [] found_dates = {} - start_date = datetime.now() - timedelta(days=MAX_DAYS_IN_3_MONTHS) - counts = model.log.get_repository_action_counts(repo, start_date) - for count in counts: - stats.append({ - 'date': count.date.isoformat(), - 'count': count.count, - }) - + for count in repo.counts: + stats.append(count.to_dict()) found_dates['%s/%s' % (count.date.month, count.date.day)] = True # Fill in any missing stats with zeros. for day in range(1, MAX_DAYS_IN_3_MONTHS): day_date = datetime.now() - timedelta(days=day) key = '%s/%s' % (day_date.month, day_date.day) - if not key in found_dates: + if key not in found_dates: stats.append({ 'date': day_date.date().isoformat(), 'count': 0, }) - repo_data = { - 'namespace': namespace, - 'name': repository, - 'kind': repo.kind.name, - 'description': repo.description, - 'tags': tag_dict, - 'can_write': can_write, - 'can_admin': can_admin, - 'is_public': is_public, - 'is_organization': repo.namespace_user.organization, - 'is_starred': is_starred, - 'status_token': repo.badge_token if not is_public else '', - 'trust_enabled': bool(features.SIGNING) and repo.trust_enabled, - 'tag_expiration_s': repo.namespace_user.removed_tag_expiration_s, - } - - if stats is not None: repo_data['stats'] = stats - return repo_data - @require_repo_write @nickname('updateRepo') @validate_json_request('RepoUpdate') def put(self, namespace, repository): """ Update the description in the specified repository. """ - repo = model.repository.get_repository(namespace, repository) - if repo: - values = request.get_json() - repo.description = values['description'] - repo.save() + if not model.repo_exists(namespace, repository): + raise NotFound() + + values = request.get_json() + model.set_description(namespace, repository, values['description']) + + log_action('set_repo_description', namespace, + {'repo': repository, 'namespace': namespace, 'description': values['description']}, + repo_name=repository) + return { + 'success': True + } - log_action('set_repo_description', namespace, - {'repo': repository, 'namespace': namespace, 'description': values['description']}, - repo=repo) - return { - 'success': True - } - raise NotFound() @require_repo_admin @nickname('deleteRepository') def delete(self, namespace, repository): """ Delete a repository. """ - model.repository.purge_repository(namespace, repository) - user = model.user.get_namespace_user(namespace) + username = model.purge_repository(namespace, repository) if features.BILLING: plan = get_namespace_plan(namespace) - check_repository_usage(user, plan) + model.check_repository_usage(username, plan) # Remove any builds from the queue. dockerfile_build_queue.delete_namespaced_items(namespace, repository) @@ -459,17 +293,16 @@ class RepositoryVisibility(RepositoryParamResource): @validate_json_request('ChangeVisibility') def post(self, namespace, repository): """ Change the visibility of a repository. """ - repo = model.repository.get_repository(namespace, repository) - if repo: + if model.repo_exists(namespace, repository): values = request.get_json() visibility = values['visibility'] if visibility == 'private': check_allowed_private_repos(namespace) - model.repository.set_repository_visibility(repo, visibility) + model.set_repository_visibility(namespace, repository, visibility) log_action('change_repo_visibility', namespace, {'repo': repository, 'namespace': namespace, 'visibility': values['visibility']}, - repo=repo) + repo_name=repository) return {'success': True} @@ -499,19 +332,17 @@ class RepositoryTrust(RepositoryParamResource): @validate_json_request('ChangeRepoTrust') def post(self, namespace, repository): """ Change the visibility of a repository. """ - repo = model.repository.get_repository(namespace, repository) - if not repo: + if not model.repo_exists(namespace, repository): raise NotFound() tags, _ = tuf_metadata_api.get_default_tags_with_expiration(namespace, repository) if tags and not tuf_metadata_api.delete_metadata(namespace, repository): - raise DownstreamIssue({'message': 'Unable to delete downstream trust metadata'}) - + raise DownstreamIssue({'message': 'Unable to delete downstream trust metadata'}) values = request.get_json() - model.repository.set_trust(repo, values['trust_enabled']) + model.set_trust(namespace, repository, values['trust_enabled']) log_action('change_repo_trust', namespace, {'repo': repository, 'namespace': namespace, 'trust_enabled': values['trust_enabled']}, - repo=repo) + repo_name=repository) return {'success': True} diff --git a/endpoints/api/repository_models_interface.py b/endpoints/api/repository_models_interface.py new file mode 100644 index 000000000..e823a6b66 --- /dev/null +++ b/endpoints/api/repository_models_interface.py @@ -0,0 +1,252 @@ +from abc import ABCMeta, abstractmethod +from collections import namedtuple, defaultdict + +from datetime import datetime +from six import add_metaclass + +import features +from endpoints.api import format_date + + +class RepositoryBaseElement(namedtuple('RepositoryBaseElement', + ['namespace_name', 'repository_name', 'is_starred', 'is_public', 'kind_name', + 'description', 'namespace_user_organization', + 'namespace_user_removed_tag_expiration_s', 'last_modified', 'action_count'])): + """ + Repository a single quay repository + :type namespace_name: string + :type repository_name: string + :type is_starred: boolean + :type is_public: boolean + :type kind_name: string + :type description: string + :type namespace_user_organization: boolean + :type should_last_modified: boolean + :type should_popularity: boolean + :type should_is_starred: boolean + """ + + def to_dict(self): + repo = { + 'namespace': self.namespace_name, + 'name': self.repository_name, + 'description': self.description, + 'is_public': self.is_public, + 'kind': self.kind_name, + } + + if self.should_last_modified: + repo['last_modified'] = self.last_modified + + if self.should_popularity: + repo['popularity'] = float(self.action_count if self.action_count else 0) + + if self.should_is_starred: + repo['is_starred'] = self.is_starred + + return repo + + +class ApplicationRepository( + namedtuple('ApplicationRepository', ['repository_base_elements', 'channels', 'releases'])): + """ + Repository a single quay repository + :type repository_base_elements: RepositoryBaseElement + :type channels: [Channel] + :type releases: [Release] + """ + + def to_dict(self, can_write, can_admin): + releases_channels_map = defaultdict(list) + for channel in self.channels: + releases_channels_map[channel.linked_tag_name].append(channel.name) + + repo_data = { + 'namespace': self.repository_base_elements.namespace_name, + 'name': self.repository_base_elements.repository_name, + 'kind': self.repository_base_elements.kind_name, + 'description': self.repository_base_elements.description, + 'can_write': can_write, + 'can_admin': can_admin, + 'is_public': self.repository_base_elements.is_public, + 'is_organization': self.repository_base_elements.namespace_user_organization, + 'is_starred': self.repository_base_elements.is_starred, + 'channels': [chan.to_dict() for chan in self.channels], + 'releases': [release.to_dict(releases_channels_map) for release in self.channels], + } + + return repo_data + + +class NonApplicationRepository(namedtuple('NonApplicationRepository', + ['repository_base_elements', 'tags', 'counts', 'badge_token', + 'trust_enabled'])): + """ + Repository a single quay repository + :type repository_base_elements: RepositoryBaseElement + :type tags: [Tag] + :type counts: [count] + :type badge_token: string + :type trust_enabled: boolean + """ + + def to_dict(self, can_write, can_admin): + return { + 'namespace': self.repository_base_elements.namespace_name, + 'name': self.repository_base_elements.repository_name, + 'kind': self.repository_base_elements.kind_name, + 'description': self.repository_base_elements.description, + 'can_write': can_write, + 'can_admin': can_admin, + 'is_public': self.repository_base_elements.is_public, + 'is_organization': self.repository_base_elements.namespace_user_organization, + 'is_starred': self.repository_base_elements.is_starred, + 'tags': {tag.name: tag.to_dict() for tag in self.tags}, + 'status_token': self.badge_token if not self.repository_base_elements.is_public else '', + 'trust_enabled': bool(features.SIGNING) and self.trust_enabled, + 'tag_expiration_s': self.repository_base_elements.namespace_user_removed_tag_expiration_s, + } + + +class Repository(namedtuple('Repository', ['namespace_name', 'repository_name', ])): + """ + Repository a single quay repository + :type namespace_name: string + :type repository_name: string + """ + + +class Channel(namedtuple('Channel', ['name', 'linked_tag_name', 'linked_tag_lifetime_start'])): + """ + Repository a single quay repository + :type name: string + :type linked_tag_name: string + :type linked_tag_lifetime_start: string + """ + + def to_dict(self): + return { + 'name': self.name, + 'release': self.linked_tag_name, + 'last_modified': format_date(datetime.fromtimestamp(self.linked_tag_lifetime_start / 1000)), + } + + +class Release(namedtuple('Channel', ['name', 'released', 'lifetime_start'])): + """ + Repository a single quay repository + :type name: string + :type released: string + :type last_modified: string + """ + + def to_dict(self, releases_channels_map): + return { + 'name': self.name, + 'last_modified': format_date(datetime.fromtimestamp(self.lifetime_start / 1000)), + 'channels': releases_channels_map[self.name], + } + + +class Tag(namedtuple('Tag', ['name', 'image_docker_image_id', 'image_aggregate_size', 'lifetime_start_ts', + 'tag_manifest_digest'])): + """ + :type name: string + :type image_docker_image_id: string + :type image_aggregate_size: int + :type lifetime_start_ts: int + :type tag_manifest_digest: string + + """ + + def to_dict(self): + tag_info = { + 'name': self.name, + 'image_id': self.image_docker_image_id, + 'size': self.image_aggregate_size + } + + if self.lifetime_start_ts > 0: + last_modified = format_date(datetime.fromtimestamp(self.lifetime_start_ts)) + tag_info['last_modified'] = last_modified + + if self.tag_manifest_digest is not None: + tag_info['manifest_digest'] = self.tag_manifest_digest + + return tag_info + + +class Count(namedtuple('Count', ['date', 'count'])): + """ + date: DateTime + count: int + """ + + def to_dict(self): + return { + 'date': self.date.isoformat(), + 'count': self.count, + } + + +@add_metaclass(ABCMeta) +class RepositoryDataInterface(object): + """ + Interface that represents all data store interactions required by a Repository. + """ + + @abstractmethod + def get_repo(self, namespace_name, repository_name, user): + """ + Returns a repository + """ + + @abstractmethod + def repo_exists(self, namespace_name, repository_name): + """ + Returns true if a repo exists and false if not + """ + + @abstractmethod + def create_repo(self, namespace, name, creating_user, description, visibility='private', + repo_kind='image'): + """ + Returns creates a new repo + """ + + @abstractmethod + def get_repo_list(self, starred, user, repo_kind, namespace, username, public, page_token, + last_modified, popularity): + """ + Returns a RepositoryBaseElement + """ + + @abstractmethod + def set_repository_visibility(self, namespace_name, repository_name, visibility): + """ + Sets a repository's visibility if it is found + """ + + @abstractmethod + def set_trust(self, namespace_name, repository_name, trust): + """ + Sets a repository's trust_enabled field if it is found + """ + + @abstractmethod + def set_description(self, namespace_name, repository_name, description): + """ + Sets a repository's description if it is found. + """ + + @abstractmethod + def purge_repository(self, namespace_name, repository_name): + """ + Removes a repository + """ + + @abstractmethod + def check_repository_usage(self, user_name, plan_found): + """ + Creates a notification for a user if they are over or under on their repository usage + """ diff --git a/endpoints/api/repository_models_pre_oci.py b/endpoints/api/repository_models_pre_oci.py new file mode 100644 index 000000000..ba5b1a81e --- /dev/null +++ b/endpoints/api/repository_models_pre_oci.py @@ -0,0 +1,167 @@ +from collections import defaultdict + +from datetime import datetime, timedelta + +from auth.permissions import ReadRepositoryPermission +from data import model, oci_model +from endpoints.api.repository_models_interface import RepositoryDataInterface, RepositoryBaseElement, Repository, \ + ApplicationRepository, ImageRepositoryRepository, Tag, Channel, Release, Count + +MAX_DAYS_IN_3_MONTHS = 92 +REPOS_PER_PAGE = 100 + + +def create_channel(channel, releases_channels_map): + releases_channels_map[channel.linked_tag_name].append(channel.name) + return Channel(channel.name, channel.linked_tag.name, channel.linked_tag.lifetime_start) + + +class PreOCIModel(RepositoryDataInterface): + """ + PreOCIModel implements the data model for the Repo Email using a database schema + before it was changed to support the OCI specification. + """ + + def check_repository_usage(self, username, plan_found): + private_repos = model.user.get_private_repo_count(username) + if plan_found is None: + repos_allowed = 0 + else: + repos_allowed = plan_found['privateRepos'] + + user_or_org = model.user.get_namespace_user(username) + if private_repos > repos_allowed: + model.notification.create_unique_notification('over_private_usage', user_or_org, + {'namespace': username}) + else: + model.notification.delete_notifications_by_kind(user_or_org, 'over_private_usage') + + def purge_repository(self, namespace_name, repository_name): + model.repository.purge_repository(namespace_name, repository_name) + user = model.user.get_namespace_user(namespace_name) + return user.username + + def set_description(self, namespace_name, repository_name, description): + repo = model.repository.get_repository(namespace_name, repository_name) + model.repository.set_description(repo, description) + + def set_trust(self, namespace_name, repository_name, trust): + repo = model.repository.get_repository(namespace_name, repository_name) + model.repository.set_trust(repo, trust) + + def set_repository_visibility(self, namespace_name, repository_name, visibility): + repo = model.repository.get_repository(namespace_name, repository_name) + model.repository.set_repository_visibility(repo, visibility) + + def get_repo_list(self, starred, user, repo_kind, namespace, username, public, page_token, + last_modified, popularity): + next_page_token = None + # Lookup the requested repositories (either starred or non-starred.) + if starred: + # Return the full list of repos starred by the current user that are still visible to them. + def can_view_repo(repo): + return ReadRepositoryPermission(repo.namespace_user.username, repo.name).can() + + unfiltered_repos = model.repository.get_user_starred_repositories(user, + kind_filter=repo_kind) + repos = [repo for repo in unfiltered_repos if can_view_repo(repo)] + elif namespace: + # Repositories filtered by namespace do not need pagination (their results are fairly small), + # so we just do the lookup directly. + repos = list( + model.repository.get_visible_repositories(username=username, include_public=public, + namespace=namespace, kind_filter=repo_kind)) + else: + # Determine the starting offset for pagination. Note that we don't use the normal + # model.modelutil.paginate method here, as that does not operate over UNION queries, which + # get_visible_repositories will return if there is a logged-in user (for performance reasons). + # + # Also note the +1 on the limit, as paginate_query uses the extra result to determine whether + # there is a next page. + start_id = model.modelutil.pagination_start(page_token) + repo_query = model.repository.get_visible_repositories(username=username, + include_public=public, + start_id=start_id, + limit=REPOS_PER_PAGE + 1, + kind_filter=repo_kind) + + repos, next_page_token = model.modelutil.paginate_query(repo_query, limit=REPOS_PER_PAGE, + id_alias='rid') + + # Collect the IDs of the repositories found for subequent lookup of popularity + # and/or last modified. + last_modified_map = {} + action_sum_map = {} + if last_modified or popularity: + repository_ids = [repo.rid for repo in repos] + + if last_modified: + last_modified_map = model.repository.get_when_last_modified(repository_ids) + + if popularity: + action_sum_map = model.log.get_repositories_action_sums(repository_ids) + + # Collect the IDs of the repositories that are starred for the user, so we can mark them + # in the returned results. + star_set = set() + if username: + starred_repos = model.repository.get_user_starred_repositories(user) + star_set = {starred.id for starred in starred_repos} + + return [ + RepositoryBaseElement(repo.namespace_user.username, repo.name, repo.id in star_set, + repo.visibility_id == model.repository.get_public_repo_visibility().id, + repo_kind, repo.description, repo.namespace_user.organization, + repo.namespace_user.removed_tag_expiration_s, + last_modified_map.get(repo.rid), + action_sum_map.get(repo.rid), last_modified, popularity, username) + for repo in repos + ], next_page_token + + def repo_exists(self, namespace_name, repository_name): + repo = model.repository.get_repository(namespace_name, repository_name) + if repo is None: + return False + + return True + + def create_repo(self, namespace_name, repository_name, owner, description, visibility='private', + repo_kind='image'): + repo = model.repository.create_repository(namespace_name, repository_name, owner, visibility, + repo_kind=repo_kind) + model.repository.set_description(repo, description) + return Repository(namespace_name, repository_name) + + def get_repo(self, namespace_name, repository_name, user): + repo = model.repository.get_repository(namespace_name, repository_name) + if repo is None: + return None + + is_starred = model.repository.repository_is_starred(user, repo) if user else False + is_public = model.repository.is_repository_public(repo) + base = RepositoryBaseElement(namespace_name, repository_name, is_starred, is_public, repo.kind.name, + repo.description, repo.namespace_user.organization, + repo.namespace_user.removed_tag_expiration_s, None, None) + + # Note: This is *temporary* code for the new OCI model stuff. + if base.kind_name == 'application': + channels = oci_model.channel.get_repo_channels(repo) + releases = oci_model.release.get_release_objs(repo) + + return ApplicationRepository(base, + [Channel(channel.name, channel.linked_tag.name, channel.linked_tag.lifetime_start) + for channel in channels], + [Release(release.name, release.released, release.lifetime_start) + for release in releases]) + + tags = model.tag.list_active_repo_tags(repo) + start_date = datetime.now() - timedelta(days=MAX_DAYS_IN_3_MONTHS) + counts = model.log.get_repository_action_counts(repo, start_date) + return NonApplicationRepository(base, + [Tag(tag.name, tag.image.docker_image_id, tag.image.aggregate_size, + tag.lifetime_start_ts, tag.tagmanifest.digest) for tag in tags], + [Count(count.date, count.count) for count in counts], + repo.badge_token, repo.trust_enabled) + + +pre_oci_model = PreOCIModel() diff --git a/endpoints/api/test/test_repository.py b/endpoints/api/test/test_repository.py index 999beb00d..f85cb0026 100644 --- a/endpoints/api/test/test_repository.py +++ b/endpoints/api/test/test_repository.py @@ -38,8 +38,8 @@ NOT_FOUND_RESPONSE = { ]) def test_post_changetrust(trust_enabled, repo_found, expected_body, expected_status, client): with patch('endpoints.api.repository.tuf_metadata_api') as mock_tuf: - with patch('endpoints.api.repository.model') as mock_model: - mock_model.repository.get_repository.return_value = MagicMock() if repo_found else None + with patch('endpoints.api.repository_models_pre_oci.model.repository.get_repository') as mock_model: + mock_model.return_value = MagicMock() if repo_found else None mock_tuf.get_default_tags_with_expiration.return_value = ['tags', 'expiration'] with client_with_identity('devtable', client) as cl: params = {'repository': 'devtable/repo'} diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 75fed8888..de1c7e081 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -2431,12 +2431,12 @@ class TestGetRepository(ApiTestCase): self.login(ADMIN_ACCESS_USER) # base + repo + is_starred + tags - with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 4): + with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 5): self.getJsonResponse(Repository, params=dict(repository=ADMIN_ACCESS_USER + '/simple')) # base + repo + is_starred + tags - with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 4): + with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 5): json = self.getJsonResponse(Repository, params=dict(repository=ADMIN_ACCESS_USER + '/gargantuan')) From 8f1200b00dcc68cf1b353d2174d544438cf619cb Mon Sep 17 00:00:00 2001 From: Charlton Austin Date: Mon, 24 Jul 2017 11:05:15 -0400 Subject: [PATCH 2/2] style(data, endpoints, test): ran yapf against changed files ### Description of Changes Issue: https://coreosdev.atlassian.net/browse/QUAY-633 ## Reviewer Checklist - [ ] It works! - [ ] Comments provide sufficient explanations for the next contributor - [ ] Tests cover changes and corner cases - [ ] Follows Quay syntax patterns and format --- data/model/repository.py | 217 +-- endpoints/api/logs.py | 2 - endpoints/api/repository.py | 71 +- endpoints/api/repository_models_interface.py | 53 +- endpoints/api/repository_models_pre_oci.py | 37 +- endpoints/api/test/test_repository.py | 38 +- test/test_api_usage.py | 1785 +++++++----------- 7 files changed, 871 insertions(+), 1332 deletions(-) diff --git a/data/model/repository.py b/data/model/repository.py index 2cd2b6976..2ef204862 100644 --- a/data/model/repository.py +++ b/data/model/repository.py @@ -7,17 +7,16 @@ from peewee import JOIN_LEFT_OUTER, fn, SQL, IntegrityError from playhouse.shortcuts import case from cachetools import ttl_cache -from data.model import (config, DataModelException, tag, db_transaction, storage, permission, - _basequery) -from data.database import (Repository, Namespace, RepositoryTag, Star, Image, ImageStorage, User, - Visibility, RepositoryPermission, RepositoryActionCount, - Role, RepositoryAuthorizedEmail, TagManifest, DerivedStorageForImage, - Label, TagManifestLabel, db_for_update, get_epoch_timestamp, - db_random_func, db_concat_func, RepositorySearchScore) +from data.model import ( + config, DataModelException, tag, db_transaction, storage, permission, _basequery) +from data.database import ( + Repository, Namespace, RepositoryTag, Star, Image, ImageStorage, User, Visibility, + RepositoryPermission, RepositoryActionCount, Role, RepositoryAuthorizedEmail, TagManifest, + DerivedStorageForImage, Label, TagManifestLabel, db_for_update, get_epoch_timestamp, + db_random_func, db_concat_func, RepositorySearchScore) from data.text import prefix_search from util.itertoolrecipes import take - logger = logging.getLogger(__name__) SEARCH_FIELDS = Enum("SearchFields", ["name", "description"]) @@ -87,8 +86,7 @@ def purge_repository(namespace_name, repository_name): unreferenced_image_q = Image.select(Image.id).where(Image.repository == repo) if len(previously_referenced) > 0: - unreferenced_image_q = (unreferenced_image_q - .where(~(Image.id << list(previously_referenced)))) + unreferenced_image_q = (unreferenced_image_q.where(~(Image.id << list(previously_referenced)))) unreferenced_candidates = set(img[0] for img in unreferenced_image_q.tuples()) @@ -116,11 +114,10 @@ def purge_repository(namespace_name, repository_name): @ttl_cache(maxsize=1, ttl=600) def _get_gc_expiration_policies(): - policy_tuples_query = (Namespace - .select(Namespace.removed_tag_expiration_s) - .distinct() - .limit(100) # This sucks but it's the only way to limit memory - .tuples()) + policy_tuples_query = ( + Namespace.select(Namespace.removed_tag_expiration_s).distinct() + .limit(100) # This sucks but it's the only way to limit memory + .tuples()) return [policy[0] for policy in policy_tuples_query] @@ -134,22 +131,15 @@ def find_repository_with_garbage(limit_to_gc_policy_s): expiration_timestamp = get_epoch_timestamp() - limit_to_gc_policy_s try: - candidates = (RepositoryTag - .select(RepositoryTag.repository) - .join(Repository) + candidates = (RepositoryTag.select(RepositoryTag.repository).join(Repository) .join(Namespace, on=(Repository.namespace_user == Namespace.id)) .where(~(RepositoryTag.lifetime_end_ts >> None), (RepositoryTag.lifetime_end_ts <= expiration_timestamp), - (Namespace.removed_tag_expiration_s == limit_to_gc_policy_s)) - .limit(500) - .distinct() - .alias('candidates')) + (Namespace.removed_tag_expiration_s == limit_to_gc_policy_s)).limit(500) + .distinct().alias('candidates')) - found = (RepositoryTag - .select(candidates.c.repository_id) - .from_(candidates) - .order_by(db_random_func()) - .get()) + found = (RepositoryTag.select(candidates.c.repository_id).from_(candidates) + .order_by(db_random_func()).get()) if found is None: return @@ -186,10 +176,8 @@ def garbage_collect_repo(repo, extra_candidate_set=None): all_unreferenced_candidates = set() # Remove any images directly referenced by tags, to prune the working set. - direct_referenced = (RepositoryTag - .select(RepositoryTag.image) - .where(RepositoryTag.repository == repo.id, - RepositoryTag.image << list(candidate_orphan_image_set))) + direct_referenced = (RepositoryTag.select(RepositoryTag.image).where( + RepositoryTag.repository == repo.id, RepositoryTag.image << list(candidate_orphan_image_set))) candidate_orphan_image_set.difference_update([t.image_id for t in direct_referenced]) # Iteratively try to remove images from the database. The only images we can remove are those @@ -205,26 +193,20 @@ def garbage_collect_repo(repo, extra_candidate_set=None): with db_transaction(): # Any image directly referenced by a tag that still exists, cannot be GCed. - direct_referenced = (RepositoryTag - .select(RepositoryTag.image) - .where(RepositoryTag.repository == repo.id, - RepositoryTag.image << candidates_orphans)) + direct_referenced = (RepositoryTag.select(RepositoryTag.image).where( + RepositoryTag.repository == repo.id, RepositoryTag.image << candidates_orphans)) # Any image which is the parent of another image, cannot be GCed. - parent_referenced = (Image - .select(Image.parent) - .where(Image.repository == repo.id, - Image.parent << candidates_orphans)) + parent_referenced = (Image.select(Image.parent).where(Image.repository == repo.id, + Image.parent << candidates_orphans)) referenced_candidates = (direct_referenced | parent_referenced) # We desire a few pieces of information from the database from the following # query: all of the image ids which are associated with this repository, # and the storages which are associated with those images. - unreferenced_candidates = (Image - .select(Image.id, Image.docker_image_id, - ImageStorage.id, ImageStorage.uuid) - .join(ImageStorage) + unreferenced_candidates = (Image.select(Image.id, Image.docker_image_id, ImageStorage.id, + ImageStorage.uuid).join(ImageStorage) .where(Image.id << candidates_orphans, ~(Image.id << referenced_candidates))) @@ -238,8 +220,8 @@ def garbage_collect_repo(repo, extra_candidate_set=None): storage_id_whitelist = set([candidate.storage_id for candidate in unreferenced_candidates]) # Lookup any derived images for the images to remove. - derived = DerivedStorageForImage.select().where( - DerivedStorageForImage.source_image << image_ids_to_remove) + derived = DerivedStorageForImage.select().where(DerivedStorageForImage.source_image << + image_ids_to_remove) has_derived = False for derived_image in derived: @@ -249,10 +231,8 @@ def garbage_collect_repo(repo, extra_candidate_set=None): # Delete any derived images and the images themselves. if has_derived: try: - (DerivedStorageForImage - .delete() - .where(DerivedStorageForImage.source_image << image_ids_to_remove) - .execute()) + (DerivedStorageForImage.delete() + .where(DerivedStorageForImage.source_image << image_ids_to_remove).execute()) except IntegrityError: logger.info('Could not GC derived images %s; will try again soon', image_ids_to_remove) return False @@ -278,8 +258,10 @@ def garbage_collect_repo(repo, extra_candidate_set=None): # If any storages were removed and cleanup callbacks are registered, call them with # the images+storages removed. if storage_ids_removed and config.image_cleanup_callbacks: - image_storages_removed = [candidate for candidate in all_unreferenced_candidates - if candidate.storage_id in storage_ids_removed] + image_storages_removed = [ + candidate for candidate in all_unreferenced_candidates + if candidate.storage_id in storage_ids_removed + ] for callback in config.image_cleanup_callbacks: callback(image_storages_removed) @@ -295,10 +277,7 @@ def star_repository(user, repository): def unstar_repository(user, repository): """ Unstars a repository. """ try: - (Star - .delete() - .where(Star.repository == repository.id, Star.user == user.id) - .execute()) + (Star.delete().where(Star.repository == repository.id, Star.user == user.id).execute()) except Star.DoesNotExist: raise DataModelException('Star not found.') @@ -308,6 +287,11 @@ def set_trust(repo, trust_enabled): repo.save() +def set_description(repo, description): + repo.description = description + repo.save() + + def get_user_starred_repositories(user, kind_filter='image'): """ Retrieves all of the repositories a user has starred. """ try: @@ -315,13 +299,8 @@ def get_user_starred_repositories(user, kind_filter='image'): except RepositoryKind.DoesNotExist: raise DataModelException('Unknown kind of repository') - query = (Repository - .select(Repository, User, Visibility, Repository.id.alias('rid')) - .join(Star) - .switch(Repository) - .join(User) - .switch(Repository) - .join(Visibility) + query = (Repository.select(Repository, User, Visibility, Repository.id.alias('rid')).join(Star) + .switch(Repository).join(User).switch(Repository).join(Visibility) .where(Star.user == user, Repository.kind == repo_kind)) return query @@ -330,10 +309,7 @@ def get_user_starred_repositories(user, kind_filter='image'): def repository_is_starred(user, repository): """ Determines whether a user has starred a repository or not. """ try: - (Star - .select() - .where(Star.repository == repository.id, Star.user == user.id) - .get()) + (Star.select().where(Star.repository == repository.id, Star.user == user.id).get()) return True except Star.DoesNotExist: return False @@ -346,10 +322,8 @@ def get_when_last_modified(repository_ids): if not repository_ids: return {} - tuples = (RepositoryTag - .select(RepositoryTag.repository, fn.Max(RepositoryTag.lifetime_start_ts)) - .where(RepositoryTag.repository << repository_ids) - .group_by(RepositoryTag.repository) + tuples = (RepositoryTag.select(RepositoryTag.repository, fn.Max(RepositoryTag.lifetime_start_ts)) + .where(RepositoryTag.repository << repository_ids).group_by(RepositoryTag.repository) .tuples()) last_modified_map = {} @@ -366,11 +340,8 @@ def get_stars(repository_ids): if not repository_ids: return {} - tuples = (Star - .select(Star.repository, fn.Count(Star.id)) - .where(Star.repository << repository_ids) - .group_by(Star.repository) - .tuples()) + tuples = (Star.select(Star.repository, fn.Count(Star.id)) + .where(Star.repository << repository_ids).group_by(Star.repository).tuples()) star_map = {} for record in tuples: @@ -388,12 +359,10 @@ def get_visible_repositories(username, namespace=None, kind_filter='image', incl # here, as it will be modified by other queries later on. return Repository.select(Repository.id.alias('rid')).where(Repository.id == -1) - query = (Repository - .select(Repository.name, Repository.id.alias('rid'), - Repository.description, Namespace.username, Repository.visibility, - Repository.kind) - .switch(Repository) - .join(Namespace, on=(Repository.namespace_user == Namespace.id))) + query = (Repository.select(Repository.name, + Repository.id.alias('rid'), Repository.description, + Namespace.username, Repository.visibility, Repository.kind) + .switch(Repository).join(Namespace, on=(Repository.namespace_user == Namespace.id))) if username: # Note: We only need the permissions table if we will filter based on a user's permissions. @@ -422,8 +391,8 @@ def get_app_search(lookup, search_fields=None, username=None, limit=50): search_fields = set([SEARCH_FIELDS.name.name]) return get_filtered_matching_repositories(lookup, filter_username=username, - search_fields=search_fields, - repo_kind='application', offset=0, limit=limit) + search_fields=search_fields, repo_kind='application', + offset=0, limit=limit) def get_filtered_matching_repositories(lookup_value, filter_username=None, repo_kind='image', @@ -460,7 +429,7 @@ def _filter_repositories_visible_to_username(unfiltered_query, filter_username, unfiltered_page = 0 iteration_count = 0 - while iteration_count < 10: # Just to be safe + while iteration_count < 10: # Just to be safe # Find the next chunk's worth of repository IDs, paginated by the chunk size. unfiltered_page = unfiltered_page + 1 found_ids = [r.id for r in unfiltered_query.paginate(unfiltered_page, chunk_count)] @@ -476,13 +445,9 @@ def _filter_repositories_visible_to_username(unfiltered_query, filter_username, encountered.update(new_unfiltered_ids) # Filter the repositories found to only those visible to the current user. - query = (Repository - .select(Repository, Namespace) - .distinct() - .join(Namespace, on=(Namespace.id == Repository.namespace_user)) - .switch(Repository) - .join(RepositoryPermission) - .where(Repository.id << list(new_unfiltered_ids))) + query = (Repository.select(Repository, Namespace).distinct() + .join(Namespace, on=(Namespace.id == Repository.namespace_user)).switch(Repository) + .join(RepositoryPermission).where(Repository.id << list(new_unfiltered_ids))) filtered = _basequery.filter_to_repos_for_user(query, filter_username, repo_kind=repo_kind) @@ -520,16 +485,12 @@ def _get_sorted_matching_repositories(lookup_value, repo_kind='image', include_p if SEARCH_FIELDS.description.name in search_fields: clause = Repository.description.match(lookup_value) | clause - cases = [ - (Repository.name.match(lookup_value), 100 * RepositorySearchScore.score), - ] + cases = [(Repository.name.match(lookup_value), 100 * RepositorySearchScore.score),] computed_score = case(None, cases, RepositorySearchScore.score).alias('score') - query = (Repository - .select(Repository, Namespace, computed_score) - .join(Namespace, on=(Namespace.id == Repository.namespace_user)) - .where(clause) + query = (Repository.select(Repository, Namespace, computed_score) + .join(Namespace, on=(Namespace.id == Repository.namespace_user)).where(clause) .group_by(Repository.id, Namespace.id)) if repo_kind is not None: @@ -538,11 +499,8 @@ def _get_sorted_matching_repositories(lookup_value, repo_kind='image', include_p if not include_private: query = query.where(Repository.visibility == _basequery.get_public_repo_visibility()) - query = (query - .switch(Repository) - .join(RepositorySearchScore) - .group_by(Repository, Namespace, RepositorySearchScore) - .order_by(SQL('score').desc())) + query = (query.switch(Repository).join(RepositorySearchScore) + .group_by(Repository, Namespace, RepositorySearchScore).order_by(SQL('score').desc())) return query @@ -560,15 +518,10 @@ def is_repository_public(repository): def repository_is_public(namespace_name, repository_name): try: - (Repository - .select() - .join(Namespace, on=(Repository.namespace_user == Namespace.id)) - .switch(Repository) - .join(Visibility) - .where(Namespace.username == namespace_name, - Repository.name == repository_name, - Visibility.name == 'public') - .get()) + (Repository.select().join(Namespace, on=(Repository.namespace_user == Namespace.id)) + .switch(Repository).join(Visibility).where(Namespace.username == namespace_name, + Repository.name == repository_name, + Visibility.name == 'public').get()) return True except Repository.DoesNotExist: return False @@ -585,14 +538,10 @@ def set_repository_visibility(repo, visibility): def get_email_authorized_for_repo(namespace, repository, email): try: - return (RepositoryAuthorizedEmail - .select(RepositoryAuthorizedEmail, Repository, Namespace) - .join(Repository) - .join(Namespace, on=(Repository.namespace_user == Namespace.id)) - .where(Namespace.username == namespace, - Repository.name == repository, - RepositoryAuthorizedEmail.email == email) - .get()) + return (RepositoryAuthorizedEmail.select(RepositoryAuthorizedEmail, Repository, Namespace) + .join(Repository).join(Namespace, on=(Repository.namespace_user == Namespace.id)) + .where(Namespace.username == namespace, Repository.name == repository, + RepositoryAuthorizedEmail.email == email).get()) except RepositoryAuthorizedEmail.DoesNotExist: return None @@ -601,20 +550,16 @@ def create_email_authorization_for_repo(namespace_name, repository_name, email): try: repo = _basequery.get_existing_repository(namespace_name, repository_name) except Repository.DoesNotExist: - raise DataModelException('Invalid repository %s/%s' % - (namespace_name, repository_name)) + raise DataModelException('Invalid repository %s/%s' % (namespace_name, repository_name)) return RepositoryAuthorizedEmail.create(repository=repo, email=email, confirmed=False) def confirm_email_authorization_for_repo(code): try: - found = (RepositoryAuthorizedEmail - .select(RepositoryAuthorizedEmail, Repository, Namespace) - .join(Repository) - .join(Namespace, on=(Repository.namespace_user == Namespace.id)) - .where(RepositoryAuthorizedEmail.code == code) - .get()) + found = (RepositoryAuthorizedEmail.select(RepositoryAuthorizedEmail, Repository, Namespace) + .join(Repository).join(Namespace, on=(Repository.namespace_user == Namespace.id)) + .where(RepositoryAuthorizedEmail.code == code).get()) except RepositoryAuthorizedEmail.DoesNotExist: raise DataModelException('Invalid confirmation code.') @@ -626,17 +571,13 @@ def confirm_email_authorization_for_repo(code): def list_popular_public_repos(action_count_threshold, time_span, repo_kind='image'): cutoff = datetime.now() - time_span - return (Repository - .select(Namespace.username, Repository.name) - .join(Namespace, on=(Repository.namespace_user == Namespace.id)) - .switch(Repository) - .join(RepositoryActionCount) - .where(RepositoryActionCount.date >= cutoff, - Repository.visibility == get_public_repo_visibility(), - Repository.kind == Repository.kind.get_id(repo_kind)) + return (Repository.select(Namespace.username, Repository.name) + .join(Namespace, on=(Repository.namespace_user == Namespace.id)).switch(Repository) + .join(RepositoryActionCount).where(RepositoryActionCount.date >= cutoff, + Repository.visibility == get_public_repo_visibility(), + Repository.kind == Repository.kind.get_id(repo_kind)) .group_by(RepositoryActionCount.repository, Repository.name, Namespace.username) - .having(fn.Sum(RepositoryActionCount.count) >= action_count_threshold) - .tuples()) + .having(fn.Sum(RepositoryActionCount.count) >= action_count_threshold).tuples()) def is_empty(namespace_name, repository_name): diff --git a/endpoints/api/logs.py b/endpoints/api/logs.py index b7f645bf1..9966aaa6c 100644 --- a/endpoints/api/logs.py +++ b/endpoints/api/logs.py @@ -16,8 +16,6 @@ SERVICE_LEVEL_LOG_KINDS = set(['service_key_create', 'service_key_approve', 'ser 'service_key_modify', 'service_key_extend', 'service_key_rotate']) - - def _validate_logs_arguments(start_time, end_time): if start_time: try: diff --git a/endpoints/api/repository.py b/endpoints/api/repository.py index 6ce980d31..a782018ea 100644 --- a/endpoints/api/repository.py +++ b/endpoints/api/repository.py @@ -10,14 +10,13 @@ from datetime import timedelta, datetime from flask import request, abort from app import dockerfile_build_queue, tuf_metadata_api -from endpoints.api import (format_date, nickname, log_action, validate_json_request, - require_repo_read, require_repo_write, require_repo_admin, - RepositoryParamResource, resource, parse_args, ApiResource, - request_error, require_scope, path_param, page_support, - query_param, truthy_bool, show_if) +from endpoints.api import ( + format_date, nickname, log_action, validate_json_request, require_repo_read, require_repo_write, + require_repo_admin, RepositoryParamResource, resource, parse_args, ApiResource, request_error, + require_scope, path_param, page_support, query_param, truthy_bool, show_if) from endpoints.api.repository_models_pre_oci import pre_oci_model as model -from endpoints.exception import (Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException, - DownstreamIssue) +from endpoints.exception import ( + Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException, DownstreamIssue) from endpoints.api.billing import lookup_allowed_private_repos, get_namespace_plan from endpoints.api.subscribe import check_repository_usage @@ -71,7 +70,8 @@ class RepositoryList(ApiResource): ], }, 'namespace': { - 'type': 'string', + 'type': + 'string', 'description': ('Namespace in which the repository should be created. If omitted, the ' 'username of the caller is used'), }, @@ -118,16 +118,17 @@ class RepositoryList(ApiResource): raise InvalidRequest('Invalid repository name') kind = req.get('repo_kind', 'image') or 'image' - model.create_repo(namespace_name, repository_name, owner, req['description'], visibility=visibility, - repo_kind=kind) + model.create_repo(namespace_name, repository_name, owner, req['description'], + visibility=visibility, repo_kind=kind) - log_action('create_repo', namespace_name, {'repo': repository_name, - 'namespace': namespace_name}, repo_name=repository_name) + log_action('create_repo', namespace_name, + {'repo': repository_name, + 'namespace': namespace_name}, repo_name=repository_name) return { - 'namespace': namespace_name, - 'name': repository_name, - 'kind': kind, - }, 201 + 'namespace': namespace_name, + 'name': repository_name, + 'kind': kind, + }, 201 raise Unauthorized() @@ -178,9 +179,7 @@ class Repository(RepositoryParamResource): 'RepoUpdate': { 'type': 'object', 'description': 'Fields which can be updated in a repository.', - 'required': [ - 'description', - ], + 'required': ['description',], 'properties': { 'description': { 'type': 'string', @@ -239,12 +238,10 @@ class Repository(RepositoryParamResource): model.set_description(namespace, repository, values['description']) log_action('set_repo_description', namespace, - {'repo': repository, 'namespace': namespace, 'description': values['description']}, - repo_name=repository) - return { - 'success': True - } - + {'repo': repository, + 'namespace': namespace, + 'description': values['description']}, repo_name=repository) + return {'success': True} @require_repo_admin @nickname('deleteRepository') @@ -259,8 +256,7 @@ class Repository(RepositoryParamResource): # Remove any builds from the queue. dockerfile_build_queue.delete_namespaced_items(namespace, repository) - log_action('delete_repo', namespace, - {'repo': repository, 'namespace': namespace}) + log_action('delete_repo', namespace, {'repo': repository, 'namespace': namespace}) return '', 204 @@ -272,9 +268,7 @@ class RepositoryVisibility(RepositoryParamResource): 'ChangeVisibility': { 'type': 'object', 'description': 'Change the visibility for the repository.', - 'required': [ - 'visibility', - ], + 'required': ['visibility',], 'properties': { 'visibility': { 'type': 'string', @@ -301,8 +295,9 @@ class RepositoryVisibility(RepositoryParamResource): model.set_repository_visibility(namespace, repository, visibility) log_action('change_repo_visibility', namespace, - {'repo': repository, 'namespace': namespace, 'visibility': values['visibility']}, - repo_name=repository) + {'repo': repository, + 'namespace': namespace, + 'visibility': values['visibility']}, repo_name=repository) return {'success': True} @@ -314,9 +309,7 @@ class RepositoryTrust(RepositoryParamResource): 'ChangeRepoTrust': { 'type': 'object', 'description': 'Change the trust settings for the repository.', - 'required': [ - 'trust_enabled', - ], + 'required': ['trust_enabled',], 'properties': { 'trust_enabled': { 'type': 'boolean', @@ -341,8 +334,10 @@ class RepositoryTrust(RepositoryParamResource): values = request.get_json() model.set_trust(namespace, repository, values['trust_enabled']) - log_action('change_repo_trust', namespace, - {'repo': repository, 'namespace': namespace, 'trust_enabled': values['trust_enabled']}, - repo_name=repository) + log_action( + 'change_repo_trust', namespace, + {'repo': repository, + 'namespace': namespace, + 'trust_enabled': values['trust_enabled']}, repo_name=repository) return {'success': True} diff --git a/endpoints/api/repository_models_interface.py b/endpoints/api/repository_models_interface.py index e823a6b66..ac54769d7 100644 --- a/endpoints/api/repository_models_interface.py +++ b/endpoints/api/repository_models_interface.py @@ -8,10 +8,12 @@ import features from endpoints.api import format_date -class RepositoryBaseElement(namedtuple('RepositoryBaseElement', - ['namespace_name', 'repository_name', 'is_starred', 'is_public', 'kind_name', - 'description', 'namespace_user_organization', - 'namespace_user_removed_tag_expiration_s', 'last_modified', 'action_count'])): +class RepositoryBaseElement( + namedtuple('RepositoryBaseElement', [ + 'namespace_name', 'repository_name', 'is_starred', 'is_public', 'kind_name', 'description', + 'namespace_user_organization', 'namespace_user_removed_tag_expiration_s', 'last_modified', + 'action_count', 'should_last_modified', 'should_popularity', 'should_is_starred' + ])): """ Repository a single quay repository :type namespace_name: string @@ -56,31 +58,25 @@ class ApplicationRepository( :type releases: [Release] """ - def to_dict(self, can_write, can_admin): - releases_channels_map = defaultdict(list) - for channel in self.channels: - releases_channels_map[channel.linked_tag_name].append(channel.name) - + def to_dict(self): repo_data = { 'namespace': self.repository_base_elements.namespace_name, 'name': self.repository_base_elements.repository_name, 'kind': self.repository_base_elements.kind_name, 'description': self.repository_base_elements.description, - 'can_write': can_write, - 'can_admin': can_admin, 'is_public': self.repository_base_elements.is_public, 'is_organization': self.repository_base_elements.namespace_user_organization, 'is_starred': self.repository_base_elements.is_starred, 'channels': [chan.to_dict() for chan in self.channels], - 'releases': [release.to_dict(releases_channels_map) for release in self.channels], + 'releases': [release.to_dict() for release in self.releases], } return repo_data -class NonApplicationRepository(namedtuple('NonApplicationRepository', - ['repository_base_elements', 'tags', 'counts', 'badge_token', - 'trust_enabled'])): +class ImageRepositoryRepository( + namedtuple('NonApplicationRepository', + ['repository_base_elements', 'tags', 'counts', 'badge_token', 'trust_enabled'])): """ Repository a single quay repository :type repository_base_elements: RepositoryBaseElement @@ -90,25 +86,27 @@ class NonApplicationRepository(namedtuple('NonApplicationRepository', :type trust_enabled: boolean """ - def to_dict(self, can_write, can_admin): + def to_dict(self): return { 'namespace': self.repository_base_elements.namespace_name, 'name': self.repository_base_elements.repository_name, 'kind': self.repository_base_elements.kind_name, 'description': self.repository_base_elements.description, - 'can_write': can_write, - 'can_admin': can_admin, 'is_public': self.repository_base_elements.is_public, 'is_organization': self.repository_base_elements.namespace_user_organization, 'is_starred': self.repository_base_elements.is_starred, - 'tags': {tag.name: tag.to_dict() for tag in self.tags}, + 'tags': {tag.name: tag.to_dict() + for tag in self.tags}, 'status_token': self.badge_token if not self.repository_base_elements.is_public else '', 'trust_enabled': bool(features.SIGNING) and self.trust_enabled, 'tag_expiration_s': self.repository_base_elements.namespace_user_removed_tag_expiration_s, } -class Repository(namedtuple('Repository', ['namespace_name', 'repository_name', ])): +class Repository(namedtuple('Repository', [ + 'namespace_name', + 'repository_name', +])): """ Repository a single quay repository :type namespace_name: string @@ -132,24 +130,29 @@ class Channel(namedtuple('Channel', ['name', 'linked_tag_name', 'linked_tag_life } -class Release(namedtuple('Channel', ['name', 'released', 'lifetime_start'])): +class Release( + namedtuple('Channel', ['name', 'released', 'lifetime_start', 'releases_channels_map'])): """ Repository a single quay repository :type name: string :type released: string :type last_modified: string + :type releases_channels_map: {string -> string} """ - def to_dict(self, releases_channels_map): + def to_dict(self): return { 'name': self.name, 'last_modified': format_date(datetime.fromtimestamp(self.lifetime_start / 1000)), - 'channels': releases_channels_map[self.name], + 'channels': self.releases_channels_map[self.name], } -class Tag(namedtuple('Tag', ['name', 'image_docker_image_id', 'image_aggregate_size', 'lifetime_start_ts', - 'tag_manifest_digest'])): +class Tag( + namedtuple('Tag', [ + 'name', 'image_docker_image_id', 'image_aggregate_size', 'lifetime_start_ts', + 'tag_manifest_digest' + ])): """ :type name: string :type image_docker_image_id: string diff --git a/endpoints/api/repository_models_pre_oci.py b/endpoints/api/repository_models_pre_oci.py index ba5b1a81e..d25ef432d 100644 --- a/endpoints/api/repository_models_pre_oci.py +++ b/endpoints/api/repository_models_pre_oci.py @@ -79,11 +79,9 @@ class PreOCIModel(RepositoryDataInterface): # Also note the +1 on the limit, as paginate_query uses the extra result to determine whether # there is a next page. start_id = model.modelutil.pagination_start(page_token) - repo_query = model.repository.get_visible_repositories(username=username, - include_public=public, - start_id=start_id, - limit=REPOS_PER_PAGE + 1, - kind_filter=repo_kind) + repo_query = model.repository.get_visible_repositories( + username=username, include_public=public, start_id=start_id, limit=REPOS_PER_PAGE + 1, + kind_filter=repo_kind) repos, next_page_token = model.modelutil.paginate_query(repo_query, limit=REPOS_PER_PAGE, id_alias='rid') @@ -139,29 +137,30 @@ class PreOCIModel(RepositoryDataInterface): is_starred = model.repository.repository_is_starred(user, repo) if user else False is_public = model.repository.is_repository_public(repo) - base = RepositoryBaseElement(namespace_name, repository_name, is_starred, is_public, repo.kind.name, - repo.description, repo.namespace_user.organization, - repo.namespace_user.removed_tag_expiration_s, None, None) + base = RepositoryBaseElement( + namespace_name, repository_name, is_starred, is_public, repo.kind.name, repo.description, + repo.namespace_user.organization, repo.namespace_user.removed_tag_expiration_s, None, None, + False, False, False) # Note: This is *temporary* code for the new OCI model stuff. if base.kind_name == 'application': channels = oci_model.channel.get_repo_channels(repo) releases = oci_model.release.get_release_objs(repo) - - return ApplicationRepository(base, - [Channel(channel.name, channel.linked_tag.name, channel.linked_tag.lifetime_start) - for channel in channels], - [Release(release.name, release.released, release.lifetime_start) - for release in releases]) + releases_channels_map = defaultdict(list) + return ApplicationRepository( + base, [create_channel(channel, releases_channels_map) for channel in channels], [ + Release(release.name, release.released, release.lifetime_start, releases_channels_map) + for release in releases + ]) tags = model.tag.list_active_repo_tags(repo) start_date = datetime.now() - timedelta(days=MAX_DAYS_IN_3_MONTHS) counts = model.log.get_repository_action_counts(repo, start_date) - return NonApplicationRepository(base, - [Tag(tag.name, tag.image.docker_image_id, tag.image.aggregate_size, - tag.lifetime_start_ts, tag.tagmanifest.digest) for tag in tags], - [Count(count.date, count.count) for count in counts], - repo.badge_token, repo.trust_enabled) + + return ImageRepositoryRepository(base, [ + Tag(tag.name, tag.image.docker_image_id, tag.image.aggregate_size, tag.lifetime_start_ts, + tag.tagmanifest.digest) for tag in tags + ], [Count(count.date, count.count) for count in counts], repo.badge_token, repo.trust_enabled) pre_oci_model = PreOCIModel() diff --git a/endpoints/api/test/test_repository.py b/endpoints/api/test/test_repository.py index f85cb0026..4addeffb9 100644 --- a/endpoints/api/test/test_repository.py +++ b/endpoints/api/test/test_repository.py @@ -9,7 +9,6 @@ from features import FeatureNameValue from test.fixtures import * - INVALID_RESPONSE = { u'detail': u"u'invalid_req' is not of type 'boolean'", u'error_message': u"u'invalid_req' is not of type 'boolean'", @@ -20,31 +19,44 @@ INVALID_RESPONSE = { } NOT_FOUND_RESPONSE = { - u'detail': u'Not Found', - u'error_message': u'Not Found', - u'error_type': u'not_found', - u'message': u'You have requested this URI [/api/v1/repository/devtable/repo/changetrust] but did you mean /api/v1/repository//changetrust or /api/v1/repository//changevisibility or /api/v1/repository//tag//images ?', - u'status': 404, - u'title': u'not_found', - u'type': u'http://localhost/api/v1/error/not_found' + u'detail': + u'Not Found', + u'error_message': + u'Not Found', + u'error_type': + u'not_found', + u'message': + u'You have requested this URI [/api/v1/repository/devtable/repo/changetrust] but did you mean /api/v1/repository//changetrust or /api/v1/repository//changevisibility or /api/v1/repository//tag//images ?', + u'status': + 404, + u'title': + u'not_found', + u'type': + u'http://localhost/api/v1/error/not_found' } @pytest.mark.parametrize('trust_enabled,repo_found,expected_body,expected_status', [ - (True, True,{'success': True}, 200), - (False, True, {'success': True}, 200), + (True, True, { + 'success': True + }, 200), + (False, True, { + 'success': True + }, 200), (False, False, NOT_FOUND_RESPONSE, 404), - ('invalid_req', False, INVALID_RESPONSE , 400), + ('invalid_req', False, INVALID_RESPONSE, 400), ]) def test_post_changetrust(trust_enabled, repo_found, expected_body, expected_status, client): with patch('endpoints.api.repository.tuf_metadata_api') as mock_tuf: - with patch('endpoints.api.repository_models_pre_oci.model.repository.get_repository') as mock_model: + with patch( + 'endpoints.api.repository_models_pre_oci.model.repository.get_repository') as mock_model: mock_model.return_value = MagicMock() if repo_found else None mock_tuf.get_default_tags_with_expiration.return_value = ['tags', 'expiration'] with client_with_identity('devtable', client) as cl: params = {'repository': 'devtable/repo'} request_body = {'trust_enabled': trust_enabled} - assert expected_body == conduct_api_call(cl, RepositoryTrust, 'POST', params, request_body, expected_status).json + assert expected_body == conduct_api_call(cl, RepositoryTrust, 'POST', params, request_body, + expected_status).json def test_signing_disabled(client): diff --git a/test/test_api_usage.py b/test/test_api_usage.py index de1c7e081..b0a577300 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -31,56 +31,56 @@ from data.database import RepositoryActionCount, Repository as RepositoryTable from test.helpers import assert_action_logged from util.secscan.fake import fake_security_scanner -from endpoints.api.team import (TeamMember, TeamMemberList, TeamMemberInvite, OrganizationTeam, - TeamPermissions, InviteTeamMember) +from endpoints.api.team import ( + TeamMember, TeamMemberList, TeamMemberInvite, OrganizationTeam, TeamPermissions, InviteTeamMember +) from endpoints.api.tag import RepositoryTagImages, RepositoryTag, RestoreTag, ListRepositoryTags from endpoints.api.search import EntitySearch, ConductSearch from endpoints.api.image import RepositoryImage, RepositoryImageList from endpoints.api.build import RepositoryBuildStatus, RepositoryBuildList, RepositoryBuildResource -from endpoints.api.robot import (UserRobotList, OrgRobot, OrgRobotList, UserRobot, - RegenerateUserRobot, RegenerateOrgRobot) -from endpoints.api.trigger import (BuildTriggerActivate, BuildTriggerSources, BuildTriggerSubdirs, - TriggerBuildList, ActivateBuildTrigger, BuildTrigger, - BuildTriggerList, BuildTriggerAnalyze, BuildTriggerFieldValues, - BuildTriggerSourceNamespaces) +from endpoints.api.robot import ( + UserRobotList, OrgRobot, OrgRobotList, UserRobot, RegenerateUserRobot, RegenerateOrgRobot) +from endpoints.api.trigger import ( + BuildTriggerActivate, BuildTriggerSources, BuildTriggerSubdirs, TriggerBuildList, + ActivateBuildTrigger, BuildTrigger, BuildTriggerList, BuildTriggerAnalyze, + BuildTriggerFieldValues, BuildTriggerSourceNamespaces) from endpoints.api.repoemail import RepositoryAuthorizedEmail -from endpoints.api.repositorynotification import (RepositoryNotification, - RepositoryNotificationList, - TestRepositoryNotification) +from endpoints.api.repositorynotification import ( + RepositoryNotification, RepositoryNotificationList, TestRepositoryNotification) from endpoints.api.user import (PrivateRepositories, ConvertToOrganization, Signout, Signin, User, UserAuthorizationList, UserAuthorization, UserNotification, UserNotificationList, StarredRepositoryList, StarredRepository) from endpoints.api.repotoken import RepositoryToken, RepositoryTokenList from endpoints.api.prototype import PermissionPrototype, PermissionPrototypeList -from endpoints.api.logs import (UserLogs, OrgLogs, OrgAggregateLogs, UserAggregateLogs, - RepositoryLogs, RepositoryAggregateLogs) -from endpoints.api.billing import (UserCard, UserPlan, ListPlans, OrganizationCard, - OrganizationPlan) +from endpoints.api.logs import ( + UserLogs, OrgLogs, OrgAggregateLogs, UserAggregateLogs, RepositoryLogs, RepositoryAggregateLogs) +from endpoints.api.billing import ( + UserCard, UserPlan, ListPlans, OrganizationCard, OrganizationPlan) from endpoints.api.discovery import DiscoveryResource from endpoints.api.error import Error -from endpoints.api.organization import (OrganizationList, OrganizationMember, - OrgPrivateRepositories, OrganizationMemberList, - Organization, ApplicationInformation, - OrganizationApplications, OrganizationApplicationResource, - OrganizationApplicationResetClientSecret, Organization) -from endpoints.api.repository import (RepositoryList, RepositoryVisibility, Repository, - REPOS_PER_PAGE) +from endpoints.api.organization import ( + OrganizationList, OrganizationMember, OrgPrivateRepositories, OrganizationMemberList, + Organization, ApplicationInformation, OrganizationApplications, OrganizationApplicationResource, + OrganizationApplicationResetClientSecret, Organization) +from endpoints.api.repository import ( + RepositoryList, RepositoryVisibility, Repository, REPOS_PER_PAGE) from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPermission, RepositoryTeamPermissionList, RepositoryUserPermissionList) -from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement, - SuperUserServiceKeyManagement, SuperUserServiceKey, - SuperUserServiceKeyApproval, SuperUserTakeOwnership, - SuperUserCustomCertificates, SuperUserCustomCertificate) -from endpoints.api.globalmessages import (GlobalUserMessage, GlobalUserMessages,) +from endpoints.api.superuser import ( + SuperUserLogs, SuperUserList, SuperUserManagement, SuperUserServiceKeyManagement, + SuperUserServiceKey, SuperUserServiceKeyApproval, SuperUserTakeOwnership, + SuperUserCustomCertificates, SuperUserCustomCertificate) +from endpoints.api.globalmessages import ( + GlobalUserMessage, + GlobalUserMessages,) from endpoints.api.secscan import RepositoryImageSecurity, RepositoryManifestSecurity -from endpoints.api.suconfig import (SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile, - SuperUserCreateInitialSuperUser) +from endpoints.api.suconfig import ( + SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile, SuperUserCreateInitialSuperUser) from endpoints.api.manifest import RepositoryManifestLabels, ManageRepositoryManifestLabel from test.test_ssl_util import generate_test_cert from util.morecollections import AttrDict - try: app.register_blueprint(api_bp, url_prefix='/api') except ValueError: @@ -125,6 +125,7 @@ CSRF_TOKEN = '123csrfforme' class AppConfigChange(object): """ AppConfigChange takes a dictionary that overrides the global app config for a given block of code. The values are restored on exit. """ + def __init__(self, changes=None): self._changes = changes or {} self._originals = {} @@ -215,9 +216,9 @@ class ApiTestCase(unittest.TestCase): return rv.data def putResponse(self, resource_name, params={}, data={}, expected_code=200): - rv = self.app.put(self.url_for(resource_name, params), - data=py_json.dumps(data), - headers={"Content-Type": "application/json"}) + rv = self.app.put( + self.url_for(resource_name, params), data=py_json.dumps(data), + headers={"Content-Type": "application/json"}) self.assertEquals(rv.status_code, expected_code) return rv.data @@ -233,14 +234,13 @@ class ApiTestCase(unittest.TestCase): def deleteEmptyResponse(self, resource_name, params={}, expected_code=204): rv = self.app.delete(self.url_for(resource_name, params)) self.assertEquals(rv.status_code, expected_code) - self.assertEquals(rv.data, '') # ensure response body empty + self.assertEquals(rv.data, '') # ensure response body empty return - def postJsonResponse(self, resource_name, params={}, data={}, - expected_code=200): - rv = self.app.post(self.url_for(resource_name, params), - data=py_json.dumps(data), - headers={"Content-Type": "application/json"}) + def postJsonResponse(self, resource_name, params={}, data={}, expected_code=200): + rv = self.app.post( + self.url_for(resource_name, params), data=py_json.dumps(data), + headers={"Content-Type": "application/json"}) if rv.status_code != expected_code: print 'Mismatch data for resource POST %s: %s' % (resource_name, rv.data) @@ -250,11 +250,10 @@ class ApiTestCase(unittest.TestCase): parsed = py_json.loads(data) return parsed - def putJsonResponse(self, resource_name, params={}, data={}, - expected_code=200, skip_csrf=False): - rv = self.app.put(self.url_for(resource_name, params, skip_csrf), - data=py_json.dumps(data), - headers={"Content-Type": "application/json"}) + def putJsonResponse(self, resource_name, params={}, data={}, expected_code=200, skip_csrf=False): + rv = self.app.put( + self.url_for(resource_name, params, skip_csrf), data=py_json.dumps(data), + headers={"Content-Type": "application/json"}) if rv.status_code != expected_code: print 'Mismatch data for resource PUT %s: %s' % (resource_name, rv.data) @@ -264,7 +263,6 @@ class ApiTestCase(unittest.TestCase): parsed = py_json.loads(data) return parsed - def assertNotInTeam(self, data, membername): for memberData in data['members']: if memberData['name'] == membername: @@ -365,12 +363,10 @@ class TestUserStarredRepositoryList(ApiTestCase): self.getJsonResponse(StarredRepositoryList, expected_code=200) def test_star_repo_guest(self): - self.postJsonResponse(StarredRepositoryList, - data={ - 'namespace': 'public', - 'repository': 'publicrepo', - }, - expected_code=401) + self.postJsonResponse(StarredRepositoryList, data={ + 'namespace': 'public', + 'repository': 'publicrepo', + }, expected_code=401) def test_star_and_unstar_repo_user(self): self.login(READ_ACCESS_USER) @@ -380,17 +376,15 @@ class TestUserStarredRepositoryList(ApiTestCase): json = self.getJsonResponse(StarredRepositoryList) assert json['repositories'] == [] - json = self.postJsonResponse(StarredRepositoryList, - data={ - 'namespace': 'public', - 'repository': 'publicrepo', - }, - expected_code=201) + json = self.postJsonResponse(StarredRepositoryList, data={ + 'namespace': 'public', + 'repository': 'publicrepo', + }, expected_code=201) assert json['namespace'] == 'public' assert json['repository'] == 'publicrepo' self.deleteEmptyResponse(StarredRepository, params=dict(repository='public/publicrepo'), - expected_code=204) + expected_code=204) json = self.getJsonResponse(StarredRepositoryList) assert json['repositories'] == [] @@ -462,45 +456,43 @@ class TestGetUserPrivateAllowed(ApiTestCase): class TestConvertToOrganization(ApiTestCase): def test_sameadminuser(self): self.login(READ_ACCESS_USER) - json = self.postJsonResponse(ConvertToOrganization, - data={'adminUser': READ_ACCESS_USER, - 'adminPassword': 'password', - 'plan': 'free'}, - expected_code=400) + json = self.postJsonResponse(ConvertToOrganization, data={ + 'adminUser': READ_ACCESS_USER, + 'adminPassword': 'password', + 'plan': 'free' + }, expected_code=400) self.assertEqual('The admin user is not valid', json['detail']) def test_sameadminuser_by_email(self): self.login(READ_ACCESS_USER) - json = self.postJsonResponse(ConvertToOrganization, - data={'adminUser': 'no1@thanks.com', - 'adminPassword': 'password', - 'plan': 'free'}, - expected_code=400) + json = self.postJsonResponse(ConvertToOrganization, data={ + 'adminUser': 'no1@thanks.com', + 'adminPassword': 'password', + 'plan': 'free' + }, expected_code=400) self.assertEqual('The admin user is not valid', json['detail']) def test_invalidadminuser(self): self.login(READ_ACCESS_USER) - json = self.postJsonResponse(ConvertToOrganization, - data={'adminUser': 'unknownuser', - 'adminPassword': 'password', - 'plan': 'free'}, - expected_code=400) + json = self.postJsonResponse(ConvertToOrganization, data={ + 'adminUser': 'unknownuser', + 'adminPassword': 'password', + 'plan': 'free' + }, expected_code=400) - self.assertEqual('The admin user credentials are not valid', - json['detail']) + self.assertEqual('The admin user credentials are not valid', json['detail']) def test_invalidadminpassword(self): self.login(READ_ACCESS_USER) - json = self.postJsonResponse(ConvertToOrganization, - data={'adminUser': ADMIN_ACCESS_USER, - 'adminPassword': 'invalidpass', - 'plan': 'free'}, - expected_code=400) + json = self.postJsonResponse(ConvertToOrganization, data={ + 'adminUser': ADMIN_ACCESS_USER, + 'adminPassword': 'invalidpass', + 'plan': 'free' + }, expected_code=400) - self.assertEqual('The admin user credentials are not valid', - json['detail']) + self.assertEqual('The admin user credentials are not valid', json['detail']) def test_convert(self): self.login(READ_ACCESS_USER) @@ -513,10 +505,11 @@ class TestConvertToOrganization(ApiTestCase): database.RepositoryPermission.create(user=read_user, repository=simple_repo, role=read_role) # Convert the read user into an organization. - json = self.postJsonResponse(ConvertToOrganization, - data={'adminUser': ADMIN_ACCESS_USER, - 'adminPassword': 'password', - 'plan': 'free'}) + json = self.postJsonResponse(ConvertToOrganization, data={ + 'adminUser': ADMIN_ACCESS_USER, + 'adminPassword': 'password', + 'plan': 'free' + }) self.assertEqual(True, json['success']) @@ -526,26 +519,23 @@ class TestConvertToOrganization(ApiTestCase): # Verify the admin user is the org's admin. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(Organization, - params=dict(orgname=READ_ACCESS_USER)) + json = self.getJsonResponse(Organization, params=dict(orgname=READ_ACCESS_USER)) self.assertEquals(READ_ACCESS_USER, json['name']) self.assertEquals(True, json['is_admin']) # Verify the now-org has no permissions. - count = (database - .RepositoryPermission - .select() - .where(database.RepositoryPermission.user == organization) - .count()) + count = (database.RepositoryPermission.select() + .where(database.RepositoryPermission.user == organization).count()) self.assertEquals(0, count) def test_convert_via_email(self): self.login(READ_ACCESS_USER) - json = self.postJsonResponse(ConvertToOrganization, - data={'adminUser': ADMIN_ACCESS_EMAIL, - 'adminPassword': 'password', - 'plan': 'free'}) + json = self.postJsonResponse(ConvertToOrganization, data={ + 'adminUser': ADMIN_ACCESS_EMAIL, + 'adminPassword': 'password', + 'plan': 'free' + }) self.assertEqual(True, json['success']) @@ -555,8 +545,7 @@ class TestConvertToOrganization(ApiTestCase): # Verify the admin user is the org's admin. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(Organization, - params=dict(orgname=READ_ACCESS_USER)) + json = self.getJsonResponse(Organization, params=dict(orgname=READ_ACCESS_USER)) self.assertEquals(READ_ACCESS_USER, json['name']) self.assertEquals(True, json['is_admin']) @@ -565,31 +554,26 @@ class TestConvertToOrganization(ApiTestCase): class TestChangeUserDetails(ApiTestCase): def test_changepassword(self): self.login(READ_ACCESS_USER) - self.putJsonResponse(User, - data=dict(password='newpasswordiscool')) + self.putJsonResponse(User, data=dict(password='newpasswordiscool')) self.login(READ_ACCESS_USER, password='newpasswordiscool') def test_changepassword_unicode(self): self.login(READ_ACCESS_USER) - self.putJsonResponse(User, - data=dict(password=u'someunicode北京市pass')) + self.putJsonResponse(User, data=dict(password=u'someunicode北京市pass')) self.login(READ_ACCESS_USER, password=u'someunicode北京市pass') def test_changeeemail(self): self.login(READ_ACCESS_USER) - self.putJsonResponse(User, - data=dict(email='test+foo@devtable.com')) + self.putJsonResponse(User, data=dict(email='test+foo@devtable.com')) def test_changeinvoiceemail(self): self.login(READ_ACCESS_USER) - json = self.putJsonResponse(User, - data=dict(invoice_email=True)) + json = self.putJsonResponse(User, data=dict(invoice_email=True)) self.assertEquals(True, json['invoice_email']) - json = self.putJsonResponse(User, - data=dict(invoice_email=False)) + json = self.putJsonResponse(User, data=dict(invoice_email=False)) self.assertEquals(False, json['invoice_email']) def test_changeusername_temp(self): @@ -665,33 +649,26 @@ class TestChangeUserDetails(ApiTestCase): class TestCreateNewUser(ApiTestCase): def test_existingusername(self): - json = self.postJsonResponse(User, - data=dict(username=READ_ACCESS_USER, - password='password', - email='test@example.com'), - expected_code=400) + json = self.postJsonResponse(User, data=dict(username=READ_ACCESS_USER, password='password', + email='test@example.com'), expected_code=400) self.assertEquals('The username already exists', json['detail']) def test_trycreatetooshort(self): - json = self.postJsonResponse(User, - data=dict(username='a', - password='password', - email='test@example.com'), - expected_code=400) + json = self.postJsonResponse(User, data=dict(username='a', password='password', + email='test@example.com'), expected_code=400) - self.assertEquals('Invalid namespace a: Namespace must be between 2 and 255 characters in length', - json['detail']) + self.assertEquals( + 'Invalid namespace a: Namespace must be between 2 and 255 characters in length', + json['detail']) def test_trycreateregexmismatch(self): - json = self.postJsonResponse(User, - data=dict(username='auserName', - password='password', - email='test@example.com'), - expected_code=400) + json = self.postJsonResponse(User, data=dict(username='auserName', password='password', + email='test@example.com'), expected_code=400) - self.assertEquals('Invalid namespace auserName: Namespace must match expression ^([a-z0-9]+(?:[._-][a-z0-9]+)*)$', - json['detail']) + self.assertEquals( + 'Invalid namespace auserName: Namespace must match expression ^([a-z0-9]+(?:[._-][a-z0-9]+)*)$', + json['detail']) def test_createuser(self): data = self.postJsonResponse(User, data=NEW_USER_DETAILS, expected_code=200) @@ -725,9 +702,7 @@ class TestCreateNewUser(ApiTestCase): team = model.team.get_organization_team(ORGANIZATION, 'owners') invite = model.team.add_or_invite_to_team(inviter, team, None, NEW_USER_DETAILS['email']) - details = { - 'invite_code': invite.invite_token - } + details = {'invite_code': invite.invite_token} details.update(NEW_USER_DETAILS) data = self.postJsonResponse(User, data=details, expected_code=200) @@ -738,9 +713,8 @@ class TestCreateNewUser(ApiTestCase): # Make sure the user was not (yet) added to the team. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertNotInTeam(json, NEW_USER_DETAILS['username']) def test_createuser_withteaminvite_differentemails(self): @@ -748,9 +722,7 @@ class TestCreateNewUser(ApiTestCase): team = model.team.get_organization_team(ORGANIZATION, 'owners') invite = model.team.add_or_invite_to_team(inviter, team, None, 'differentemail@example.com') - details = { - 'invite_code': invite.invite_token - } + details = {'invite_code': invite.invite_token} details.update(NEW_USER_DETAILS) data = self.postJsonResponse(User, data=details, expected_code=200) @@ -761,9 +733,8 @@ class TestCreateNewUser(ApiTestCase): # Make sure the user was not (yet) added to the team. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertNotInTeam(json, NEW_USER_DETAILS['username']) def test_createuser_withmultipleteaminvites(self): @@ -782,9 +753,7 @@ class TestCreateNewUser(ApiTestCase): NEW_USER_DETAILS['email']) # Create the user and ensure they have a verified email address. - details = { - 'invite_code': owners_invite.invite_token - } + details = {'invite_code': owners_invite.invite_token} details.update(NEW_USER_DETAILS) data = self.postJsonResponse(User, data=details, expected_code=200) @@ -795,19 +764,15 @@ class TestCreateNewUser(ApiTestCase): # Make sure the user was not (yet) added to the teams. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertNotInTeam(json, NEW_USER_DETAILS['username']) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='readers')) self.assertNotInTeam(json, NEW_USER_DETAILS['username']) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname='library', - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname='library', teamname='owners')) self.assertNotInTeam(json, NEW_USER_DETAILS['username']) # Accept the first invitation. @@ -824,19 +789,15 @@ class TestCreateNewUser(ApiTestCase): # Make sure the user is now in the two invited teams under the organization, but not # in the other org's team. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertInTeam(json, NEW_USER_DETAILS['username']) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='readers')) self.assertInTeam(json, NEW_USER_DETAILS['username']) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname='library', - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname='library', teamname='owners')) self.assertNotInTeam(json, NEW_USER_DETAILS['username']) # Accept the second invitation. @@ -845,9 +806,7 @@ class TestCreateNewUser(ApiTestCase): # Make sure the user was added to the other organization. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname='library', - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname='library', teamname='owners')) self.assertInTeam(json, NEW_USER_DETAILS['username']) # Make sure the invitation codes are now invalid. @@ -866,9 +825,9 @@ class TestDeleteNamespace(ApiTestCase): # Delete the three orgs, checking in between. with check_transitive_modifications(): self.deleteEmptyResponse(Organization, params=dict(orgname=ORGANIZATION), expected_code=204) - self.deleteResponse(User, expected_code=400) # Should still fail. + self.deleteResponse(User, expected_code=400) # Should still fail. self.deleteEmptyResponse(Organization, params=dict(orgname='library'), expected_code=204) - self.deleteResponse(User, expected_code=400) # Should still fail. + self.deleteResponse(User, expected_code=400) # Should still fail. self.deleteEmptyResponse(Organization, params=dict(orgname='titi'), expected_code=204) # Add some queue items for the user. @@ -914,9 +873,7 @@ class TestSignin(ApiTestCase): self.assertTrue(data['needsEmailVerification']) # Try to sign in with an invalid invite code. - details = { - 'invite_code': 'someinvalidcode' - } + details = {'invite_code': 'someinvalidcode'} details.update(NEW_USER_DETAILS) data = self.postJsonResponse(Signin, data=details, expected_code=403) @@ -927,16 +884,13 @@ class TestSignin(ApiTestCase): team = model.team.get_organization_team(ORGANIZATION, 'owners') invite = model.team.add_or_invite_to_team(inviter, team, None, NEW_USER_DETAILS['email']) - details = { - 'invite_code': invite.invite_token - } + details = {'invite_code': invite.invite_token} details.update(NEW_USER_DETAILS) data = self.postJsonResponse(Signin, data=details, expected_code=200) self.assertFalse('needsEmailVerification' in data) - class TestSignout(ApiTestCase): def test_signout(self): self.login(READ_ACCESS_USER) @@ -954,25 +908,20 @@ class TestConductSearch(ApiTestCase): def test_noaccess(self): self.login(NO_ACCESS_USER) - json = self.getJsonResponse(ConductSearch, - params=dict(query='read')) + json = self.getJsonResponse(ConductSearch, params=dict(query='read')) self.assertEquals(0, len(json['results'])) - json = self.getJsonResponse(ConductSearch, - params=dict(query='owners')) + json = self.getJsonResponse(ConductSearch, params=dict(query='owners')) self.assertEquals(0, len(json['results'])) - def test_nouser(self): - json = self.getJsonResponse(ConductSearch, - params=dict(query='read')) + json = self.getJsonResponse(ConductSearch, params=dict(query='read')) self.assertEquals(0, len(json['results'])) - json = self.getJsonResponse(ConductSearch, - params=dict(query='public')) + json = self.getJsonResponse(ConductSearch, params=dict(query='public')) self.assertEquals(2, len(json['results'])) self.assertEquals(json['results'][0]['kind'], 'repository') @@ -981,69 +930,57 @@ class TestConductSearch(ApiTestCase): self.assertEquals(json['results'][1]['kind'], 'user') self.assertEquals(json['results'][1]['name'], 'public') - json = self.getJsonResponse(ConductSearch, - params=dict(query='owners')) + json = self.getJsonResponse(ConductSearch, params=dict(query='owners')) self.assertEquals(0, len(json['results'])) - def test_orgmember(self): self.login(READ_ACCESS_USER) - json = self.getJsonResponse(ConductSearch, - params=dict(query='owners')) + json = self.getJsonResponse(ConductSearch, params=dict(query='owners')) self.assertEquals(0, len(json['results'])) - json = self.getJsonResponse(ConductSearch, - params=dict(query='readers')) + json = self.getJsonResponse(ConductSearch, params=dict(query='readers')) self.assertEquals(1, len(json['results'])) self.assertEquals(json['results'][0]['kind'], 'team') self.assertEquals(json['results'][0]['name'], 'readers') - def test_orgadmin(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(ConductSearch, - params=dict(query='owners')) + json = self.getJsonResponse(ConductSearch, params=dict(query='owners')) self.assertEquals(4, len(json['results'])) self.assertEquals(json['results'][0]['kind'], 'team') self.assertEquals(json['results'][0]['name'], 'owners') - json = self.getJsonResponse(ConductSearch, - params=dict(query='readers')) + json = self.getJsonResponse(ConductSearch, params=dict(query='readers')) self.assertEquals(1, len(json['results'])) self.assertEquals(json['results'][0]['kind'], 'team') self.assertEquals(json['results'][0]['name'], 'readers') - def test_explicit_permission(self): self.login('reader') - json = self.getJsonResponse(ConductSearch, - params=dict(query='shared')) + json = self.getJsonResponse(ConductSearch, params=dict(query='shared')) self.assertEquals(1, len(json['results'])) self.assertEquals(json['results'][0]['kind'], 'repository') self.assertEquals(json['results'][0]['name'], 'shared') - def test_full_text(self): self.login(ADMIN_ACCESS_USER) # Make sure the repository is found via `full` and `text search`. - json = self.getJsonResponse(ConductSearch, - params=dict(query='full')) + json = self.getJsonResponse(ConductSearch, params=dict(query='full')) self.assertEquals(1, len(json['results'])) self.assertEquals(json['results'][0]['kind'], 'repository') self.assertEquals(json['results'][0]['name'], 'text-full-repo') - json = self.getJsonResponse(ConductSearch, - params=dict(query='text search')) + json = self.getJsonResponse(ConductSearch, params=dict(query='text search')) self.assertEquals(1, len(json['results'])) self.assertEquals(json['results'][0]['kind'], 'repository') self.assertEquals(json['results'][0]['name'], 'text-full-repo') @@ -1053,32 +990,28 @@ class TestGetMatchingEntities(ApiTestCase): def test_simple_lookup(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(EntitySearch, - params=dict(prefix=ADMIN_ACCESS_USER, namespace=ORGANIZATION, - includeTeams='true')) + json = self.getJsonResponse(EntitySearch, params=dict( + prefix=ADMIN_ACCESS_USER, namespace=ORGANIZATION, includeTeams='true')) self.assertEquals(1, len(json['results'])) def test_simple_lookup_noorg(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(EntitySearch, - params=dict(prefix=ADMIN_ACCESS_USER)) + json = self.getJsonResponse(EntitySearch, params=dict(prefix=ADMIN_ACCESS_USER)) self.assertEquals(1, len(json['results'])) def test_unicode_search(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(EntitySearch, - params=dict(prefix='北京市', namespace=ORGANIZATION, - includeTeams='true')) + json = self.getJsonResponse(EntitySearch, params=dict(prefix='北京市', namespace=ORGANIZATION, + includeTeams='true')) self.assertEquals(0, len(json['results'])) def test_notinorg(self): self.login(NO_ACCESS_USER) - json = self.getJsonResponse(EntitySearch, - params=dict(prefix='o', namespace=ORGANIZATION, - includeTeams='true')) + json = self.getJsonResponse(EntitySearch, params=dict(prefix='o', namespace=ORGANIZATION, + includeTeams='true')) names = set([r['name'] for r in json['results']]) assert 'outsideorg' in names @@ -1087,9 +1020,8 @@ class TestGetMatchingEntities(ApiTestCase): def test_inorg(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(EntitySearch, - params=dict(prefix='o', namespace=ORGANIZATION, - includeTeams='true')) + json = self.getJsonResponse(EntitySearch, params=dict(prefix='o', namespace=ORGANIZATION, + includeTeams='true')) names = set([r['name'] for r in json['results']]) assert 'outsideorg' in names @@ -1098,9 +1030,8 @@ class TestGetMatchingEntities(ApiTestCase): def test_inorg_withorgs(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(EntitySearch, - params=dict(prefix=ORGANIZATION[0], namespace=ORGANIZATION, - includeOrgs='true')) + json = self.getJsonResponse(EntitySearch, params=dict( + prefix=ORGANIZATION[0], namespace=ORGANIZATION, includeOrgs='true')) names = set([r['name'] for r in json['results']]) assert ORGANIZATION in names @@ -1114,8 +1045,7 @@ class TestCreateOrganization(ApiTestCase): data=dict(name=ADMIN_ACCESS_USER, email='testorg@example.com'), expected_code=400) - self.assertEquals('A user or organization with this name already exists', - json['detail']) + self.assertEquals('A user or organization with this name already exists', json['detail']) def test_existingorg(self): self.login(ADMIN_ACCESS_USER) @@ -1124,16 +1054,13 @@ class TestCreateOrganization(ApiTestCase): data=dict(name=ORGANIZATION, email='testorg@example.com'), expected_code=400) - self.assertEquals('A user or organization with this name already exists', - json['detail']) + self.assertEquals('A user or organization with this name already exists', json['detail']) def test_createorg(self): self.login(ADMIN_ACCESS_USER) - data = self.postResponse(OrganizationList, - data=dict(name='neworg', - email='testorg@example.com'), - expected_code=201) + data = self.postResponse(OrganizationList, data=dict( + name='neworg', email='testorg@example.com'), expected_code=201) self.assertEquals('"Created"', data.strip()) @@ -1142,39 +1069,30 @@ class TestCreateOrganization(ApiTestCase): assert organization is not None # Verify the admin user is the org's admin. - json = self.getJsonResponse(Organization, - params=dict(orgname='neworg')) + json = self.getJsonResponse(Organization, params=dict(orgname='neworg')) self.assertEquals('neworg', json['name']) self.assertEquals(True, json['is_admin']) - def test_createorg_viaoauth(self): # Attempt with no auth. - self.postResponse(OrganizationList, - data=dict(name='neworg', - email='testorg@example.com'), + self.postResponse(OrganizationList, data=dict(name='neworg', email='testorg@example.com'), expected_code=401) # Attempt with auth with invalid scope. dt_user = model.user.get_user(ADMIN_ACCESS_USER) token = model.oauth.create_access_token_for_testing(dt_user, 'deadbeef', 'repo:read', access_token='foo') - self.postResponse(OrganizationList, - data=dict(name='neworg', - email='testorg@example.com'), + self.postResponse(OrganizationList, data=dict(name='neworg', email='testorg@example.com'), headers=dict(Authorization='Bearer ' + token.access_token), expected_code=403) - # Create OAuth token with user:admin scope. token = model.oauth.create_access_token_for_testing(dt_user, 'deadbeef', 'user:admin', access_token='bar') - data = self.postResponse(OrganizationList, - data=dict(name='neworg', - email='testorg@example.com'), - headers=dict(Authorization='Bearer ' + token.access_token), - expected_code=201) + data = self.postResponse( + OrganizationList, data=dict(name='neworg', email='testorg@example.com'), + headers=dict(Authorization='Bearer ' + token.access_token), expected_code=201) self.assertEquals('"Created"', data.strip()) @@ -1190,16 +1108,14 @@ class TestGetOrganization(ApiTestCase): def test_getorganization(self): self.login(READ_ACCESS_USER) - json = self.getJsonResponse(Organization, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(Organization, params=dict(orgname=ORGANIZATION)) self.assertEquals(ORGANIZATION, json['name']) self.assertEquals(False, json['is_admin']) def test_getorganization_asadmin(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(Organization, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(Organization, params=dict(orgname=ORGANIZATION)) self.assertEquals(ORGANIZATION, json['name']) self.assertEquals(True, json['is_admin']) @@ -1209,23 +1125,19 @@ class TestChangeOrganizationDetails(ApiTestCase): def test_changeinvoiceemail(self): self.login(ADMIN_ACCESS_USER) - json = self.putJsonResponse(Organization, - params=dict(orgname=ORGANIZATION), + json = self.putJsonResponse(Organization, params=dict(orgname=ORGANIZATION), data=dict(invoice_email=True)) self.assertEquals(True, json['invoice_email']) - json = self.putJsonResponse(Organization, - params=dict(orgname=ORGANIZATION), + json = self.putJsonResponse(Organization, params=dict(orgname=ORGANIZATION), data=dict(invoice_email=False)) self.assertEquals(False, json['invoice_email']) - def test_changemail(self): self.login(ADMIN_ACCESS_USER) - json = self.putJsonResponse(Organization, - params=dict(orgname=ORGANIZATION), + json = self.putJsonResponse(Organization, params=dict(orgname=ORGANIZATION), data=dict(email='newemail@example.com')) self.assertEquals('newemail@example.com', json['email']) @@ -1234,8 +1146,7 @@ class TestChangeOrganizationDetails(ApiTestCase): class TestGetOrganizationPrototypes(ApiTestCase): def test_getprototypes(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(PermissionPrototypeList, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION)) assert len(json['prototypes']) > 0 @@ -1244,30 +1155,23 @@ class TestCreateOrganizationPrototypes(ApiTestCase): def test_invaliduser(self): self.login(ADMIN_ACCESS_USER) - json = self.postJsonResponse(PermissionPrototypeList, - params=dict(orgname=ORGANIZATION), - data=dict(activating_user={'name': 'unknownuser'}, - role='read', - delegate={'kind': 'team', 'name': 'owners'}), - expected_code=400) + json = self.postJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION), + data=dict(activating_user={'name': 'unknownuser'}, role='read', + delegate={'kind': 'team', + 'name': 'owners'}), expected_code=400) self.assertEquals('Unknown activating user', json['detail']) - def test_missingdelegate(self): self.login(ADMIN_ACCESS_USER) - self.postJsonResponse(PermissionPrototypeList, - params=dict(orgname=ORGANIZATION), - data=dict(role='read'), - expected_code=400) - + self.postJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION), + data=dict(role='read'), expected_code=400) def test_createprototype(self): self.login(ADMIN_ACCESS_USER) - json = self.postJsonResponse(PermissionPrototypeList, - params=dict(orgname=ORGANIZATION), + json = self.postJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION), data=dict(role='read', delegate={'kind': 'team', 'name': 'readers'})) @@ -1276,8 +1180,7 @@ class TestCreateOrganizationPrototypes(ApiTestCase): pid = json['id'] # Verify the prototype exists. - json = self.getJsonResponse(PermissionPrototypeList, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION)) ids = set([p['id'] for p in json['prototypes']]) assert pid in ids @@ -1288,19 +1191,17 @@ class TestDeleteOrganizationPrototypes(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Get the existing prototypes - json = self.getJsonResponse(PermissionPrototypeList, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION)) ids = [p['id'] for p in json['prototypes']] pid = ids[0] # Delete a prototype. self.deleteEmptyResponse(PermissionPrototype, - params=dict(orgname=ORGANIZATION, prototypeid=pid)) + params=dict(orgname=ORGANIZATION, prototypeid=pid)) # Verify the prototype no longer exists. - json = self.getJsonResponse(PermissionPrototypeList, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION)) newids = [p['id'] for p in json['prototypes']] assert not pid in newids @@ -1311,8 +1212,7 @@ class TestUpdateOrganizationPrototypes(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Get the existing prototypes - json = self.getJsonResponse(PermissionPrototypeList, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION)) ids = [p['id'] for p in json['prototypes']] pid = ids[0] @@ -1329,8 +1229,7 @@ class TestGetOrganizationMembers(ApiTestCase): def test_getmembers(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(OrganizationMemberList, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(OrganizationMemberList, params=dict(orgname=ORGANIZATION)) membernames = [member['name'] for member in json['members']] assert ADMIN_ACCESS_USER in membernames @@ -1356,33 +1255,32 @@ class TestRemoveOrganizationMember(ApiTestCase): def test_remove_member(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(OrganizationMemberList, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(OrganizationMemberList, params=dict(orgname=ORGANIZATION)) membernames = [member['name'] for member in json['members']] assert ADMIN_ACCESS_USER in membernames assert READ_ACCESS_USER in membernames self.deleteEmptyResponse(OrganizationMember, - params=dict(orgname=ORGANIZATION, membername=READ_ACCESS_USER)) + params=dict(orgname=ORGANIZATION, membername=READ_ACCESS_USER)) - json = self.getJsonResponse(OrganizationMemberList, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(OrganizationMemberList, params=dict(orgname=ORGANIZATION)) membernames = [member['name'] for member in json['members']] assert ADMIN_ACCESS_USER in membernames assert not READ_ACCESS_USER in membernames - def test_remove_member_repo_permission(self): self.login(ADMIN_ACCESS_USER) # Add read user as a direct permission on the admin user's repo. - model.permission.set_user_repo_permission(READ_ACCESS_USER, ADMIN_ACCESS_USER, 'simple', 'read') + model.permission.set_user_repo_permission(READ_ACCESS_USER, ADMIN_ACCESS_USER, 'simple', + 'read') # Verify the user has a permission on the admin user's repo. - admin_perms = [p.user.username - for p in model.user.get_all_repo_users(ADMIN_ACCESS_USER, 'simple')] + admin_perms = [ + p.user.username for p in model.user.get_all_repo_users(ADMIN_ACCESS_USER, 'simple') + ] assert READ_ACCESS_USER in admin_perms # Add read user as a direct permission on the org repo. @@ -1394,15 +1292,16 @@ class TestRemoveOrganizationMember(ApiTestCase): # Remove the user from the org. self.deleteEmptyResponse(OrganizationMember, - params=dict(orgname=ORGANIZATION, membername=READ_ACCESS_USER)) + params=dict(orgname=ORGANIZATION, membername=READ_ACCESS_USER)) # Verify that the user's permission on the org repo is gone, but it is still # present on the other repo. org_perms = [p.user.username for p in model.user.get_all_repo_users(ORGANIZATION, ORG_REPO)] assert not READ_ACCESS_USER in org_perms - admin_perms = [p.user.username - for p in model.user.get_all_repo_users(ADMIN_ACCESS_USER, 'simple')] + admin_perms = [ + p.user.username for p in model.user.get_all_repo_users(ADMIN_ACCESS_USER, 'simple') + ] assert READ_ACCESS_USER in admin_perms @@ -1410,23 +1309,18 @@ class TestGetOrganizationPrivateAllowed(ApiTestCase): def test_existingorg(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(OrgPrivateRepositories, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(OrgPrivateRepositories, params=dict(orgname=ORGANIZATION)) self.assertEquals(True, json['privateAllowed']) assert not 'reposAllowed' in json - def test_neworg(self): self.login(ADMIN_ACCESS_USER) - data = self.postResponse(OrganizationList, - data=dict(name='neworg', - email='test@example.com'), + data = self.postResponse(OrganizationList, data=dict(name='neworg', email='test@example.com'), expected_code=201) - json = self.getJsonResponse(OrgPrivateRepositories, - params=dict(orgname='neworg')) + json = self.getJsonResponse(OrgPrivateRepositories, params=dict(orgname='neworg')) self.assertEquals(False, json['privateAllowed']) @@ -1435,11 +1329,9 @@ class TestUpdateOrganizationTeam(ApiTestCase): def test_updateexisting(self): self.login(ADMIN_ACCESS_USER) - data = self.putJsonResponse(OrganizationTeam, - params=dict(orgname=ORGANIZATION, - teamname='readers'), - data=dict(description='My cool team', - role='creator')) + data = self.putJsonResponse(OrganizationTeam, params=dict(orgname=ORGANIZATION, + teamname='readers'), + data=dict(description='My cool team', role='creator')) self.assertEquals('My cool team', data['description']) self.assertEquals('creator', data['role']) @@ -1447,26 +1339,21 @@ class TestUpdateOrganizationTeam(ApiTestCase): def test_attemptchangeroleonowners(self): self.login(ADMIN_ACCESS_USER) - self.putJsonResponse(OrganizationTeam, - params=dict(orgname=ORGANIZATION, teamname='owners'), - data=dict(role='creator'), - expected_code=400) + self.putJsonResponse(OrganizationTeam, params=dict(orgname=ORGANIZATION, teamname='owners'), + data=dict(role='creator'), expected_code=400) def test_createnewteam(self): self.login(ADMIN_ACCESS_USER) - data = self.putJsonResponse(OrganizationTeam, - params=dict(orgname=ORGANIZATION, - teamname='newteam'), - data=dict(description='My cool team', - role='member')) + data = self.putJsonResponse(OrganizationTeam, params=dict(orgname=ORGANIZATION, + teamname='newteam'), + data=dict(description='My cool team', role='member')) self.assertEquals('My cool team', data['description']) self.assertEquals('member', data['role']) # Verify the team was created. - json = self.getJsonResponse(Organization, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(Organization, params=dict(orgname=ORGANIZATION)) assert 'newteam' in json['teams'] @@ -1475,19 +1362,17 @@ class TestDeleteOrganizationTeam(ApiTestCase): self.login(ADMIN_ACCESS_USER) self.deleteEmptyResponse(OrganizationTeam, - params=dict(orgname=ORGANIZATION, teamname='readers')) + params=dict(orgname=ORGANIZATION, teamname='readers')) # Make sure the team was deleted - json = self.getJsonResponse(Organization, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(Organization, params=dict(orgname=ORGANIZATION)) assert not 'readers' in json['teams'] def test_attemptdeleteowners(self): self.login(ADMIN_ACCESS_USER) - resp = self.deleteResponse(OrganizationTeam, - params=dict(orgname=ORGANIZATION, teamname='owners'), - expected_code=400) + resp = self.deleteResponse( + OrganizationTeam, params=dict(orgname=ORGANIZATION, teamname='owners'), expected_code=400) data = py_json.loads(resp) msg = ("Deleting team 'owners' would remove admin ability for user " + "'devtable' in organization 'buynlarge'") @@ -1508,16 +1393,14 @@ class TestGetOrganizationTeamMembers(ApiTestCase): def test_invalidteam(self): self.login(ADMIN_ACCESS_USER) - self.getResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, teamname='notvalid'), + self.getResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, teamname='notvalid'), expected_code=404) def test_getmembers(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='readers')) self.assertEquals(READ_ACCESS_USER, json['members'][1]['name']) @@ -1527,120 +1410,93 @@ class TestUpdateOrganizationTeamMember(ApiTestCase): self.login(ADMIN_ACCESS_USER) membername = READ_ACCESS_USER - self.putResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='readers', - membername=membername), - expected_code=400) - + self.putResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='readers', + membername=membername), expected_code=400) def test_addmember_orgmember(self): self.login(ADMIN_ACCESS_USER) membername = READ_ACCESS_USER - self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - membername=membername)) + self.putJsonResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='owners', + membername=membername)) # Verify the user was added to the team. - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertInTeam(json, membername) - def test_addmember_robot(self): self.login(ADMIN_ACCESS_USER) membername = ORGANIZATION + '+coolrobot' - self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='readers', - membername=membername)) - + self.putJsonResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='readers', + membername=membername)) # Verify the user was added to the team. - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='readers')) self.assertInTeam(json, membername) - def test_addmember_invalidrobot(self): self.login(ADMIN_ACCESS_USER) membername = 'freshuser+anotherrobot' - self.putResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='readers', - membername=membername), - expected_code=400) - + self.putResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='readers', + membername=membername), expected_code=400) def test_addmember_nonorgmember(self): self.login(ADMIN_ACCESS_USER) membername = NO_ACCESS_USER - response = self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - membername=membername)) - + response = self.putJsonResponse(TeamMember, params=dict( + orgname=ORGANIZATION, teamname='owners', membername=membername)) self.assertEquals(True, response['invited']) # Make sure the user is not (yet) part of the team. - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='readers')) for member in json['members']: self.assertNotEqual(membername, member['name']) - def test_updatemembers_syncedteam(self): self.login(ADMIN_ACCESS_USER) with patch('endpoints.api.team.authentication', AttrDict({'federated_service': 'foobar'})): # Add the user to a non-synced team, which should succeed. - self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - membername=READ_ACCESS_USER)) + self.putJsonResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='owners', + membername=READ_ACCESS_USER)) # Remove the user from the non-synced team, which should succeed. - self.deleteEmptyResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - membername=READ_ACCESS_USER)) + self.deleteEmptyResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='owners', + membername=READ_ACCESS_USER)) # Attempt to add the user to a synced team, which should fail. - self.putResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='synced', - membername=READ_ACCESS_USER), - expected_code=400) + self.putResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='synced', + membername=READ_ACCESS_USER), expected_code=400) # Attempt to remove the user from the synced team, which should fail. - self.deleteResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='synced', - membername=READ_ACCESS_USER), - expected_code=400) + self.deleteResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='synced', + membername=READ_ACCESS_USER), expected_code=400) # Add a robot to the synced team, which should succeed. - self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='synced', - membername=ORGANIZATION + '+coolrobot')) + self.putJsonResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='synced', + membername=ORGANIZATION + '+coolrobot')) # Remove the robot from the non-synced team, which should succeed. - self.deleteEmptyResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='synced', - membername=ORGANIZATION + '+coolrobot')) + self.deleteEmptyResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='synced', + membername=ORGANIZATION + '+coolrobot')) # Invite a team member to a non-synced team, which should succeed. - self.putJsonResponse(InviteTeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - email='someguy+new@devtable.com')) + self.putJsonResponse(InviteTeamMember, params=dict(orgname=ORGANIZATION, teamname='owners', + email='someguy+new@devtable.com')) # Attempt to invite a team member to a synced team, which should fail. - self.putResponse(InviteTeamMember, - params=dict(orgname=ORGANIZATION, teamname='synced', - email='someguy+new@devtable.com'), + self.putResponse(InviteTeamMember, params=dict(orgname=ORGANIZATION, teamname='synced', + email='someguy+new@devtable.com'), expected_code=400) @@ -1650,9 +1506,8 @@ class TestAcceptTeamMemberInvite(ApiTestCase): # Create the invite. membername = NO_ACCESS_USER - response = self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - membername=membername)) + response = self.putJsonResponse(TeamMember, params=dict( + orgname=ORGANIZATION, teamname='owners', membername=membername)) self.assertEquals(True, response['invited']) @@ -1667,15 +1522,13 @@ class TestAcceptTeamMemberInvite(ApiTestCase): self.putJsonResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token)) # Verify the user is now on the team. - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertInTeam(json, membername) # Verify the accept now fails. - self.putResponse(TeamMemberInvite, - params=dict(code=invites[0].invite_token), + self.putResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400) def test_accept_via_email(self): @@ -1683,9 +1536,8 @@ class TestAcceptTeamMemberInvite(ApiTestCase): # Create the invite. member = model.user.get_user(NO_ACCESS_USER) - response = self.putJsonResponse(InviteTeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - email=member.email)) + response = self.putJsonResponse(InviteTeamMember, params=dict( + orgname=ORGANIZATION, teamname='owners', email=member.email)) self.assertEquals(True, response['invited']) @@ -1699,25 +1551,21 @@ class TestAcceptTeamMemberInvite(ApiTestCase): self.putJsonResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token)) # Verify the user is now on the team. - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertInTeam(json, member.username) # Verify the accept now fails. - self.putResponse(TeamMemberInvite, - params=dict(code=invites[0].invite_token), + self.putResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400) - def test_accept_invite_different_user(self): self.login(ADMIN_ACCESS_USER) # Create the invite. - response = self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - membername=NO_ACCESS_USER)) + response = self.putJsonResponse(TeamMember, params=dict( + orgname=ORGANIZATION, teamname='owners', membername=NO_ACCESS_USER)) self.assertEquals(True, response['invited']) @@ -1739,18 +1587,16 @@ class TestAcceptTeamMemberInvite(ApiTestCase): # Ensure the user is *not* a member of the team. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertNotInTeam(json, PUBLIC_USER) def test_accept_invite_different_email(self): self.login(ADMIN_ACCESS_USER) # Create the invite. - response = self.putJsonResponse(InviteTeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - email='someemail@example.com')) + response = self.putJsonResponse(InviteTeamMember, params=dict( + orgname=ORGANIZATION, teamname='owners', email='someemail@example.com')) self.assertEquals(True, response['invited']) @@ -1770,20 +1616,19 @@ class TestAcceptTeamMemberInvite(ApiTestCase): # Ensure the user is *not* a member of the team. self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='owners')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='owners')) self.assertNotInTeam(json, PUBLIC_USER) + class TestDeclineTeamMemberInvite(ApiTestCase): def test_decline_wronguser(self): self.login(ADMIN_ACCESS_USER) # Create the invite. membername = NO_ACCESS_USER - response = self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - membername=membername)) + response = self.putJsonResponse(TeamMember, params=dict( + orgname=ORGANIZATION, teamname='owners', membername=membername)) self.assertEquals(True, response['invited']) @@ -1792,19 +1637,16 @@ class TestDeclineTeamMemberInvite(ApiTestCase): invites = list(model.team.lookup_team_invites(user)) self.assertEquals(1, len(invites)) - self.deleteResponse(TeamMemberInvite, - params=dict(code=invites[0].invite_token), + self.deleteResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400) - def test_decline(self): self.login(ADMIN_ACCESS_USER) # Create the invite. membername = NO_ACCESS_USER - response = self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='owners', - membername=membername)) + response = self.putJsonResponse(TeamMember, params=dict( + orgname=ORGANIZATION, teamname='owners', membername=membername)) self.assertEquals(True, response['invited']) @@ -1816,12 +1658,10 @@ class TestDeclineTeamMemberInvite(ApiTestCase): invites = list(model.team.lookup_team_invites(user)) self.assertEquals(1, len(invites)) - self.deleteEmptyResponse(TeamMemberInvite, - params=dict(code=invites[0].invite_token)) + self.deleteEmptyResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token)) # Make sure the invite was deleted. - self.deleteResponse(TeamMemberInvite, - params=dict(code=invites[0].invite_token), + self.deleteResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token), expected_code=400) @@ -1830,56 +1670,42 @@ class TestDeleteOrganizationTeamMember(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Verify the initial member count - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers', - includePending=True)) + json = self.getJsonResponse(TeamMemberList, params=dict( + orgname=ORGANIZATION, teamname='readers', includePending=True)) self.assertEquals(len(json['members']), 3) membername = NO_ACCESS_USER - response = self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='readers', - membername=membername)) - + response = self.putJsonResponse(TeamMember, params=dict( + orgname=ORGANIZATION, teamname='readers', membername=membername)) self.assertEquals(True, response['invited']) # Verify the invite was added. - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers', - includePending=True)) + json = self.getJsonResponse(TeamMemberList, params=dict( + orgname=ORGANIZATION, teamname='readers', includePending=True)) self.assertEquals(len(json['members']), 4) # Delete the invite. - self.deleteEmptyResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='readers', - membername=membername)) - + self.deleteEmptyResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='readers', + membername=membername)) # Verify the user was removed from the team. - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers', - includePending=True)) + json = self.getJsonResponse(TeamMemberList, params=dict( + orgname=ORGANIZATION, teamname='readers', includePending=True)) self.assertEquals(len(json['members']), 3) - def test_deletemember(self): self.login(ADMIN_ACCESS_USER) - self.deleteEmptyResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='readers', - membername=READ_ACCESS_USER)) - + self.deleteEmptyResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='readers', + membername=READ_ACCESS_USER)) # Verify the user was removed from the team. - json = self.getJsonResponse(TeamMemberList, - params=dict(orgname=ORGANIZATION, - teamname='readers')) + json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION, + teamname='readers')) self.assertEquals(len(json['members']), 1) @@ -1888,35 +1714,24 @@ class TestCreateRepo(ApiTestCase): def test_invalidreponame(self): self.login(ADMIN_ACCESS_USER) - json = self.postJsonResponse(RepositoryList, - data=dict(repository='some/repo', - visibility='public', - description=''), - expected_code=400) + json = self.postJsonResponse(RepositoryList, data=dict( + repository='some/repo', visibility='public', description=''), expected_code=400) self.assertEquals('Invalid repository name', json['detail']) def test_duplicaterepo(self): self.login(ADMIN_ACCESS_USER) - json = self.postJsonResponse(RepositoryList, - data=dict(repository='simple', - visibility='public', - description=''), - expected_code=400) + json = self.postJsonResponse(RepositoryList, data=dict( + repository='simple', visibility='public', description=''), expected_code=400) self.assertEquals('Repository already exists', json['detail']) - def test_createrepo(self): self.login(ADMIN_ACCESS_USER) - json = self.postJsonResponse(RepositoryList, - data=dict(repository='newrepo', - visibility='public', - description=''), - expected_code=201) - + json = self.postJsonResponse(RepositoryList, data=dict( + repository='newrepo', visibility='public', description=''), expected_code=201) self.assertEquals(ADMIN_ACCESS_USER, json['namespace']) self.assertEquals('newrepo', json['name']) @@ -1924,14 +1739,10 @@ class TestCreateRepo(ApiTestCase): def test_create_app_repo(self): self.login(ADMIN_ACCESS_USER) - json = self.postJsonResponse(RepositoryList, - data=dict(repository='newrepo', - visibility='public', - description='', - repo_kind='application'), + json = self.postJsonResponse(RepositoryList, data=dict( + repository='newrepo', visibility='public', description='', repo_kind='application'), expected_code=201) - self.assertEquals(ADMIN_ACCESS_USER, json['namespace']) self.assertEquals('newrepo', json['name']) self.assertEquals('application', json['kind']) @@ -1939,11 +1750,8 @@ class TestCreateRepo(ApiTestCase): def test_createrepo_underorg(self): self.login(ADMIN_ACCESS_USER) - json = self.postJsonResponse(RepositoryList, - data=dict(namespace=ORGANIZATION, - repository='newrepo', - visibility='private', - description=''), + json = self.postJsonResponse(RepositoryList, data=dict( + namespace=ORGANIZATION, repository='newrepo', visibility='private', description=''), expected_code=201) self.assertEquals(ORGANIZATION, json['namespace']) @@ -1955,22 +1763,16 @@ class TestListRepos(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Create an app repo. - self.postJsonResponse(RepositoryList, - data=dict(repository='newrepo', - visibility='public', - description='', - repo_kind='application'), + self.postJsonResponse(RepositoryList, data=dict(repository='newrepo', visibility='public', + description='', repo_kind='application'), expected_code=201) - json = self.getJsonResponse(RepositoryList, - params=dict(namespace=ADMIN_ACCESS_USER, - public=False, - repo_kind='application')) + json = self.getJsonResponse(RepositoryList, params=dict(namespace=ADMIN_ACCESS_USER, + public=False, repo_kind='application')) self.assertEquals(1, len(json['repositories'])) self.assertEquals('application', json['repositories'][0]['kind']) - def test_listrepos_asguest(self): # Queries: Base + the list query with assert_query_count(BASE_QUERY_COUNT + 1): @@ -1981,15 +1783,15 @@ class TestListRepos(ApiTestCase): public_user = model.user.get_user('public') # Delete all existing repos under the namespace. - for repo in list(RepositoryTable.select().where(RepositoryTable.namespace_user == public_user)): + for repo in list( + RepositoryTable.select().where(RepositoryTable.namespace_user == public_user)): model.repository.purge_repository(public_user.username, repo.name) # Add public repos until we have enough for a few pages. required = set() for i in range(0, REPOS_PER_PAGE * 3): name = 'publicrepo%s' % i - model.repository.create_repository('public', name, public_user, - visibility='public') + model.repository.create_repository('public', name, public_user, visibility='public') required.add(name) # Request results until we no longer have any. @@ -2019,9 +1821,7 @@ class TestListRepos(ApiTestCase): def test_listrepos_filter(self): self.login(READ_ACCESS_USER) - json = self.getJsonResponse(RepositoryList, - params=dict(namespace=ORGANIZATION, - public=False)) + json = self.getJsonResponse(RepositoryList, params=dict(namespace=ORGANIZATION, public=False)) self.assertGreater(len(json['repositories']), 0) @@ -2037,11 +1837,8 @@ class TestListRepos(ApiTestCase): # Queries: Base + the list query + the popularity and last modified queries + full perms load with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 4): - json = self.getJsonResponse(RepositoryList, - params=dict(namespace=ORGANIZATION, - public=False, - last_modified=True, - popularity=True)) + json = self.getJsonResponse(RepositoryList, params=dict(namespace=ORGANIZATION, public=False, + last_modified=True, popularity=True)) self.assertGreater(len(json['repositories']), 0) @@ -2050,21 +1847,15 @@ class TestListRepos(ApiTestCase): if repository['name'] == ORG_REPO: self.assertGreater(repository['popularity'], 0) - def test_listrepos_starred_nouser(self): - self.getResponse(RepositoryList, - params=dict(last_modified=True, - popularity=True, - starred=True), - expected_code=400) + self.getResponse(RepositoryList, params=dict(last_modified=True, popularity=True, + starred=True), expected_code=400) def test_listrepos_starred(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(RepositoryList, - params=dict(last_modified=True, - popularity=True, - starred=True)) + json = self.getJsonResponse(RepositoryList, params=dict(last_modified=True, popularity=True, + starred=True)) self.assertTrue(len(json['repositories']) > 0) @@ -2072,18 +1863,14 @@ class TestListRepos(ApiTestCase): self.assertTrue(repo['is_starred']) def test_listrepos_asguest_allparams(self): - json = self.getJsonResponse(RepositoryList, - params=dict(namespace=ORGANIZATION, - public=False, - last_modified=True)) + json = self.getJsonResponse(RepositoryList, params=dict(namespace=ORGANIZATION, public=False, + last_modified=True)) for repo in json['repositories']: self.assertEquals(ORGANIZATION, repo['namespace']) def assertRepositoryVisible(self, namespace, name): - json = self.getJsonResponse(RepositoryList, - params=dict(namespace=namespace, - public=False)) + json = self.getJsonResponse(RepositoryList, params=dict(namespace=namespace, public=False)) self.assertEquals(1, len(json['repositories'])) self.assertEquals(name, json['repositories'][0]['name']) @@ -2166,7 +1953,8 @@ class TestViewPublicRepository(ApiTestCase): self.assertFalse('stats' in resp) def test_normalview_withstats(self): - resp = self.getJsonResponse(Repository, params=dict(repository='public/publicrepo', includeStats=True)) + resp = self.getJsonResponse(Repository, params=dict(repository='public/publicrepo', + includeStats=True)) self.assertTrue('stats' in resp) def test_anon_access_disabled(self): @@ -2180,16 +1968,15 @@ class TestViewPublicRepository(ApiTestCase): class TestUpdateRepo(ApiTestCase): SIMPLE_REPO = ADMIN_ACCESS_USER + '/simple' + def test_updatedescription(self): self.login(ADMIN_ACCESS_USER) - self.putJsonResponse(Repository, - params=dict(repository=self.SIMPLE_REPO), + self.putJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO), data=dict(description='Some cool repo')) # Verify the repo description was updated. - json = self.getJsonResponse(Repository, - params=dict(repository=self.SIMPLE_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO)) self.assertEquals('Some cool repo', json['description']) @@ -2201,13 +1988,11 @@ class TestChangeRepoVisibility(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Make public. - self.postJsonResponse(RepositoryVisibility, - params=dict(repository=self.SIMPLE_REPO), + self.postJsonResponse(RepositoryVisibility, params=dict(repository=self.SIMPLE_REPO), data=dict(visibility='public')) # Verify the visibility. - json = self.getJsonResponse(Repository, - params=dict(repository=self.SIMPLE_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO)) self.assertEquals(True, json['is_public']) @@ -2215,14 +2000,11 @@ class TestChangeRepoVisibility(ApiTestCase): self.putJsonResponse(UserPlan, data=dict(plan='personal-30')) # Try to make private. - self.postJsonResponse(RepositoryVisibility, - params=dict(repository=self.SIMPLE_REPO), - data=dict(visibility='private'), - expected_code=402) + self.postJsonResponse(RepositoryVisibility, params=dict(repository=self.SIMPLE_REPO), + data=dict(visibility='private'), expected_code=402) # Verify the visibility. - json = self.getJsonResponse(Repository, - params=dict(repository=self.SIMPLE_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO)) self.assertEquals(True, json['is_public']) @@ -2230,30 +2012,24 @@ class TestChangeRepoVisibility(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Make public. - self.postJsonResponse(RepositoryVisibility, - params=dict(repository=self.SIMPLE_REPO), + self.postJsonResponse(RepositoryVisibility, params=dict(repository=self.SIMPLE_REPO), data=dict(visibility='public')) # Verify the visibility. - json = self.getJsonResponse(Repository, - params=dict(repository=self.SIMPLE_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO)) self.assertEquals(True, json['is_public']) # Make private. - self.postJsonResponse(RepositoryVisibility, - params=dict(repository=self.SIMPLE_REPO), + self.postJsonResponse(RepositoryVisibility, params=dict(repository=self.SIMPLE_REPO), data=dict(visibility='private')) # Verify the visibility. - json = self.getJsonResponse(Repository, - params=dict(repository=self.SIMPLE_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO)) self.assertEquals(False, json['is_public']) - - class log_queries(object): def __init__(self, query_filters=None): self.filters = query_filters @@ -2301,8 +2077,7 @@ class TestDeleteRepository(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Verify the repo exists. - self.getResponse(Repository, - params=dict(repository=self.SIMPLE_REPO)) + self.getResponse(Repository, params=dict(repository=self.SIMPLE_REPO)) # Add a build queue item for the repo. dockerfile_build_queue.put([ADMIN_ACCESS_USER, 'simple'], '{}') @@ -2314,16 +2089,13 @@ class TestDeleteRepository(ApiTestCase): self.assertIsNone(dockerfile_build_queue.get()) # Verify the repo was deleted. - self.getResponse(Repository, - params=dict(repository=self.SIMPLE_REPO), - expected_code=404) + self.getResponse(Repository, params=dict(repository=self.SIMPLE_REPO), expected_code=404) def test_verify_queue_removal(self): self.login(ADMIN_ACCESS_USER) # Verify the repo exists. - self.getResponse(Repository, - params=dict(repository=self.SIMPLE_REPO)) + self.getResponse(Repository, params=dict(repository=self.SIMPLE_REPO)) # Add a build queue item for the repo and another repo. dockerfile_build_queue.put([ADMIN_ACCESS_USER, 'simple'], '{}', available_after=-1) @@ -2339,22 +2111,18 @@ class TestDeleteRepository(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Verify the repo exists. - self.getResponse(Repository, - params=dict(repository=self.COMPLEX_REPO)) + self.getResponse(Repository, params=dict(repository=self.COMPLEX_REPO)) self.deleteEmptyResponse(Repository, params=dict(repository=self.COMPLEX_REPO)) # Verify the repo was deleted. - self.getResponse(Repository, - params=dict(repository=self.COMPLEX_REPO), - expected_code=404) + self.getResponse(Repository, params=dict(repository=self.COMPLEX_REPO), expected_code=404) def test_populate_and_delete_repo(self): self.login(ADMIN_ACCESS_USER) # Verify the repo exists. - self.getResponse(Repository, - params=dict(repository=self.COMPLEX_REPO)) + self.getResponse(Repository, params=dict(repository=self.COMPLEX_REPO)) # Make sure the repository has some images and tags. self.assertTrue(len(list(model.image.get_repository_images(ADMIN_ACCESS_USER, 'complex'))) > 0) @@ -2394,10 +2162,10 @@ class TestDeleteRepository(ApiTestCase): # Create some repository action count entries. RepositoryActionCount.create(repository=repository, date=datetime.datetime.now(), count=1) - RepositoryActionCount.create(repository=repository, - date=datetime.datetime.now() - datetime.timedelta(days=2), count=2) - RepositoryActionCount.create(repository=repository, - date=datetime.datetime.now() - datetime.timedelta(days=5), count=6) + RepositoryActionCount.create( + repository=repository, date=datetime.datetime.now() - datetime.timedelta(days=2), count=2) + RepositoryActionCount.create( + repository=repository, date=datetime.datetime.now() - datetime.timedelta(days=5), count=6) # Create some labels. pre_delete_label_count = database.Label.select().count() @@ -2415,9 +2183,7 @@ class TestDeleteRepository(ApiTestCase): self.deleteEmptyResponse(Repository, params=dict(repository=self.COMPLEX_REPO)) # Verify the repo was deleted. - self.getResponse(Repository, - params=dict(repository=self.COMPLEX_REPO), - expected_code=404) + self.getResponse(Repository, params=dict(repository=self.COMPLEX_REPO), expected_code=404) # Verify the labels are gone. post_delete_label_count = database.Label.select().count() @@ -2432,8 +2198,7 @@ class TestGetRepository(ApiTestCase): # base + repo + is_starred + tags with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 5): - self.getJsonResponse(Repository, - params=dict(repository=ADMIN_ACCESS_USER + '/simple')) + self.getJsonResponse(Repository, params=dict(repository=ADMIN_ACCESS_USER + '/simple')) # base + repo + is_starred + tags with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 5): @@ -2452,9 +2217,8 @@ class TestGetRepository(ApiTestCase): # For each bad name, create the repo. for bad_name in bad_names: - json = self.postJsonResponse(RepositoryList, expected_code=201, - data=dict(repository=bad_name, visibility='public', - description='')) + json = self.postJsonResponse(RepositoryList, expected_code=201, data=dict( + repository=bad_name, visibility='public', description='')) # Make sure we can retrieve its information. json = self.getJsonResponse(Repository, @@ -2464,10 +2228,8 @@ class TestGetRepository(ApiTestCase): self.assertEquals(bad_name, json['name']) self.assertEquals(True, json['is_public']) - def test_getrepo_public_asguest(self): - json = self.getJsonResponse(Repository, - params=dict(repository=self.PUBLIC_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=self.PUBLIC_REPO)) self.assertEquals(PUBLIC_USER, json['namespace']) self.assertEquals('publicrepo', json['name']) @@ -2483,8 +2245,7 @@ class TestGetRepository(ApiTestCase): def test_getrepo_public_asowner(self): self.login(PUBLIC_USER) - json = self.getJsonResponse(Repository, - params=dict(repository=self.PUBLIC_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=self.PUBLIC_REPO)) self.assertEquals(False, json['is_organization']) self.assertEquals(True, json['can_write']) @@ -2501,15 +2262,13 @@ class TestGetRepository(ApiTestCase): self.assertEquals(False, json['is_organization']) def test_getrepo_org_asnonmember(self): - self.getResponse(Repository, - params=dict(repository=ORGANIZATION + '/' + ORG_REPO), + self.getResponse(Repository, params=dict(repository=ORGANIZATION + '/' + ORG_REPO), expected_code=401) def test_getrepo_org_asreader(self): self.login(READ_ACCESS_USER) - json = self.getJsonResponse(Repository, - params=dict(repository=ORGANIZATION + '/' + ORG_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=ORGANIZATION + '/' + ORG_REPO)) self.assertEquals(ORGANIZATION, json['namespace']) self.assertEquals(ORG_REPO, json['name']) @@ -2522,8 +2281,7 @@ class TestGetRepository(ApiTestCase): def test_getrepo_org_asadmin(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(Repository, - params=dict(repository=ORGANIZATION + '/' + ORG_REPO)) + json = self.getJsonResponse(Repository, params=dict(repository=ORGANIZATION + '/' + ORG_REPO)) self.assertEquals(True, json['can_write']) self.assertEquals(True, json['can_admin']) @@ -2532,21 +2290,20 @@ class TestGetRepository(ApiTestCase): class TestRepositoryBuildResource(ApiTestCase): - def test_repo_build_invalid_url(self): self.login(ADMIN_ACCESS_USER) self.postJsonResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(archive_url='hppt://quay.io'), - expected_code=400) + data=dict(archive_url='hppt://quay.io'), expected_code=400) def test_cancel_invalidbuild(self): self.login(ADMIN_ACCESS_USER) - self.deleteResponse(RepositoryBuildResource, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', build_uuid='invalid'), - expected_code=404) + self.deleteResponse( + RepositoryBuildResource, + params=dict(repository=ADMIN_ACCESS_USER + '/simple', build_uuid='invalid'), + expected_code=404) def test_cancel_waitingbuild(self): self.login(ADMIN_ACCESS_USER) @@ -2554,8 +2311,7 @@ class TestRepositoryBuildResource(ApiTestCase): # Request a (fake) build. json = self.postJsonResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(file_id='foobarbaz'), - expected_code=201) + data=dict(file_id='foobarbaz'), expected_code=201) uuid = json['id'] @@ -2598,8 +2354,7 @@ class TestRepositoryBuildResource(ApiTestCase): # Request a (fake) build. json = self.postJsonResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(file_id='foobarbaz'), - expected_code=201) + data=dict(file_id='foobarbaz'), expected_code=201) uuid = json['id'] @@ -2621,15 +2376,13 @@ class TestRepositoryBuildResource(ApiTestCase): params=dict(repository=ADMIN_ACCESS_USER + '/simple', build_uuid=uuid), expected_code=201) - def test_attemptcancel_workingbuild(self): self.login(ADMIN_ACCESS_USER) # Request a (fake) build. json = self.postJsonResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(file_id='foobarbaz'), - expected_code=201) + data=dict(file_id='foobarbaz'), expected_code=201) uuid = json['id'] @@ -2677,9 +2430,8 @@ class TestRepoBuilds(ApiTestCase): assert 'status' in build # Check the status endpoint. - status_json = self.getJsonResponse(RepositoryBuildStatus, - params=dict(repository=ADMIN_ACCESS_USER + '/building', - build_uuid=build['id'])) + status_json = self.getJsonResponse(RepositoryBuildStatus, params=dict( + repository=ADMIN_ACCESS_USER + '/building', build_uuid=build['id'])) self.assertEquals(status_json['id'], build['id']) self.assertEquals(status_json['resource_key'], build['resource_key']) @@ -2691,24 +2443,18 @@ class TestRequestRepoBuild(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Request a (fake) build without a file ID or URL. - self.postResponse(RepositoryBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(), - expected_code=400) + self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(), expected_code=400) def test_requestbuild_invalidurls(self): self.login(ADMIN_ACCESS_USER) # Request a (fake) build with and invalid URL. - self.postResponse(RepositoryBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(archive_url='foobarbaz'), - expected_code=400) + self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(archive_url='foobarbaz'), expected_code=400) - self.postResponse(RepositoryBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(archive_url='file://foobarbaz'), - expected_code=400) + self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(archive_url='file://foobarbaz'), expected_code=400) def test_requestrepobuild_withurl(self): self.login(ADMIN_ACCESS_USER) @@ -2720,10 +2466,8 @@ class TestRequestRepoBuild(ApiTestCase): assert len(json['builds']) == 0 # Request a (fake) build. - self.postResponse(RepositoryBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(archive_url='http://quay.io/robots.txt'), - expected_code=201) + self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(archive_url='http://quay.io/robots.txt'), expected_code=201) # Check for the build. json = self.getJsonResponse(RepositoryBuildList, @@ -2732,7 +2476,6 @@ class TestRequestRepoBuild(ApiTestCase): assert len(json['builds']) > 0 self.assertEquals('http://quay.io/robots.txt', json['builds'][0]['archive_url']) - def test_requestrepobuild_withfile(self): self.login(ADMIN_ACCESS_USER) @@ -2743,10 +2486,8 @@ class TestRequestRepoBuild(ApiTestCase): assert len(json['builds']) == 0 # Request a (fake) build. - self.postResponse(RepositoryBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(file_id='foobarbaz'), - expected_code=201) + self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(file_id='foobarbaz'), expected_code=201) # Check for the build. json = self.getJsonResponse(RepositoryBuildList, @@ -2765,10 +2506,8 @@ class TestRequestRepoBuild(ApiTestCase): # Request a (fake) build. pull_robot = ADMIN_ACCESS_USER + '+dtrobot' - self.postResponse(RepositoryBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(file_id='foobarbaz', pull_robot=pull_robot), - expected_code=201) + self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(file_id='foobarbaz', pull_robot=pull_robot), expected_code=201) # Check for the build. json = self.getJsonResponse(RepositoryBuildList, @@ -2776,26 +2515,21 @@ class TestRequestRepoBuild(ApiTestCase): assert len(json['builds']) > 0 - def test_requestrepobuild_with_invalid_robot(self): self.login(ADMIN_ACCESS_USER) # Request a (fake) build. pull_robot = ADMIN_ACCESS_USER + '+invalidrobot' - self.postResponse(RepositoryBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(file_id='foobarbaz', pull_robot=pull_robot), - expected_code=404) + self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(file_id='foobarbaz', pull_robot=pull_robot), expected_code=404) def test_requestrepobuild_with_unauthorized_robot(self): self.login(ADMIN_ACCESS_USER) # Request a (fake) build. pull_robot = 'freshuser+anotherrobot' - self.postResponse(RepositoryBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(file_id='foobarbaz', pull_robot=pull_robot), - expected_code=403) + self.postResponse(RepositoryBuildList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(file_id='foobarbaz', pull_robot=pull_robot), expected_code=403) class TestRepositoryEmail(ApiTestCase): @@ -2803,44 +2537,37 @@ class TestRepositoryEmail(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Verify the e-mail address is not authorized. - self.getResponse(RepositoryAuthorizedEmail, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - email='test@example.com'), - expected_code=404) + self.getResponse(RepositoryAuthorizedEmail, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', email='test@example.com'), expected_code=404) def test_emailnotauthorized_butsent(self): self.login(ADMIN_ACCESS_USER) # Verify the e-mail address is not authorized. - json = self.getJsonResponse(RepositoryAuthorizedEmail, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - email='jschorr+other@devtable.com')) + json = self.getJsonResponse(RepositoryAuthorizedEmail, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', email='jschorr+other@devtable.com')) self.assertEquals(False, json['confirmed']) self.assertEquals(ADMIN_ACCESS_USER, json['namespace']) self.assertEquals('simple', json['repository']) - def test_emailauthorized(self): self.login(ADMIN_ACCESS_USER) # Verify the e-mail address is authorized. - json = self.getJsonResponse(RepositoryAuthorizedEmail, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - email='jschorr@devtable.com')) + json = self.getJsonResponse(RepositoryAuthorizedEmail, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', email='jschorr@devtable.com')) self.assertEquals(True, json['confirmed']) self.assertEquals(ADMIN_ACCESS_USER, json['namespace']) self.assertEquals('simple', json['repository']) - def test_send_email_authorization(self): self.login(ADMIN_ACCESS_USER) # Send the email. - json = self.postJsonResponse(RepositoryAuthorizedEmail, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - email='jschorr+foo@devtable.com')) + json = self.postJsonResponse(RepositoryAuthorizedEmail, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', email='jschorr+foo@devtable.com')) self.assertEquals(False, json['confirmed']) self.assertEquals(ADMIN_ACCESS_USER, json['namespace']) @@ -2855,8 +2582,7 @@ class TestRepositoryNotifications(ApiTestCase): json = self.postJsonResponse(RepositoryNotificationList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), data=dict(config={'url': 'http://example.com'}, event='repo_push', - method='webhook', eventConfig={}), - expected_code=201) + method='webhook', eventConfig={}), expected_code=201) uuid = json['uuid'] self.assertIsNone(notification_queue.get()) @@ -2866,7 +2592,7 @@ class TestRepositoryNotifications(ApiTestCase): params=dict(repository=ADMIN_ACCESS_USER + '/simple', uuid=uuid)) # Ensure the item is in the queue. - time.sleep(1) # Makes sure the queue get works on MySQL with its second-level precision. + time.sleep(1) # Makes sure the queue get works on MySQL with its second-level precision. found = notification_queue.get() self.assertIsNotNone(found) self.assertTrue('notification_uuid' in found['body']) @@ -2878,8 +2604,7 @@ class TestRepositoryNotifications(ApiTestCase): json = self.postJsonResponse(RepositoryNotificationList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), data=dict(config={'url': 'http://example.com'}, event='repo_push', - method='webhook', eventConfig={}), - expected_code=201) + method='webhook', eventConfig={}), expected_code=201) self.assertEquals('repo_push', json['event']) self.assertEquals('webhook', json['method']) @@ -2906,8 +2631,8 @@ class TestRepositoryNotifications(ApiTestCase): # Delete the notification. self.deleteEmptyResponse(RepositoryNotification, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', uuid=wid), - expected_code=204) + params=dict(repository=ADMIN_ACCESS_USER + '/simple', uuid=wid), + expected_code=204) # Verify the notification is gone. self.getResponse(RepositoryNotification, @@ -2915,12 +2640,10 @@ class TestRepositoryNotifications(ApiTestCase): expected_code=404) # Add another notification. - json = self.postJsonResponse(RepositoryNotificationList, - params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(config={'url': 'http://example.com'}, event='repo_push', - method='webhook', title='Some Notification', - eventConfig={}), - expected_code=201) + json = self.postJsonResponse( + RepositoryNotificationList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), + data=dict(config={'url': 'http://example.com'}, event='repo_push', method='webhook', + title='Some Notification', eventConfig={}), expected_code=201) self.assertEquals('repo_push', json['event']) self.assertEquals('webhook', json['method']) @@ -2957,9 +2680,8 @@ class TestListAndGetImage(ApiTestCase): assert 'ancestors' in image assert 'size' in image - ijson = self.getJsonResponse(RepositoryImage, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - image_id=image['id'])) + ijson = self.getJsonResponse(RepositoryImage, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', image_id=image['id'])) self.assertEquals(image['id'], ijson['id']) @@ -2987,44 +2709,38 @@ class TestRestoreTag(ApiTestCase): self.postResponse(RestoreTag, params=dict(repository=ADMIN_ACCESS_USER + '/history', tag='invalidtag'), - data=dict(image='invalid_image'), - expected_code=400) + data=dict(image='invalid_image'), expected_code=400) def test_restoretag_invalidimage(self): self.login(ADMIN_ACCESS_USER) self.postResponse(RestoreTag, params=dict(repository=ADMIN_ACCESS_USER + '/history', tag='latest'), - data=dict(image='invalid_image'), - expected_code=400) + data=dict(image='invalid_image'), expected_code=400) def test_restoretag_invalidmanifest(self): self.login(ADMIN_ACCESS_USER) self.postResponse(RestoreTag, params=dict(repository=ADMIN_ACCESS_USER + '/history', tag='latest'), - data=dict(manifest_digest='invalid_digest'), - expected_code=400) + data=dict(manifest_digest='invalid_digest'), expected_code=400) def test_restoretag(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(ListRepositoryTags, - params=dict(repository=ADMIN_ACCESS_USER + '/history', - tag='latest')) + json = self.getJsonResponse(ListRepositoryTags, params=dict( + repository=ADMIN_ACCESS_USER + '/history', tag='latest')) self.assertEquals(2, len(json['tags'])) self.assertFalse('end_ts' in json['tags'][0]) previous_image_id = json['tags'][1]['docker_image_id'] - self.postJsonResponse(RestoreTag, params=dict(repository=ADMIN_ACCESS_USER + '/history', - tag='latest'), - data=dict(image=previous_image_id)) + self.postJsonResponse(RestoreTag, params=dict( + repository=ADMIN_ACCESS_USER + '/history', tag='latest'), data=dict(image=previous_image_id)) - json = self.getJsonResponse(ListRepositoryTags, - params=dict(repository=ADMIN_ACCESS_USER + '/history', - tag='latest')) + json = self.getJsonResponse(ListRepositoryTags, params=dict( + repository=ADMIN_ACCESS_USER + '/history', tag='latest')) self.assertEquals(3, len(json['tags'])) self.assertFalse('end_ts' in json['tags'][0]) self.assertEquals(previous_image_id, json['tags'][0]['docker_image_id']) @@ -3032,9 +2748,8 @@ class TestRestoreTag(ApiTestCase): def test_restoretag_to_digest(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(ListRepositoryTags, - params=dict(repository=ADMIN_ACCESS_USER + '/history', - tag='latest')) + json = self.getJsonResponse(ListRepositoryTags, params=dict( + repository=ADMIN_ACCESS_USER + '/history', tag='latest')) self.assertEquals(2, len(json['tags'])) self.assertFalse('end_ts' in json['tags'][0]) @@ -3042,41 +2757,34 @@ class TestRestoreTag(ApiTestCase): previous_manifest = json['tags'][1]['manifest_digest'] self.postJsonResponse(RestoreTag, params=dict(repository=ADMIN_ACCESS_USER + '/history', - tag='latest'), + tag='latest'), data=dict(image='foo', manifest_digest=previous_manifest)) - json = self.getJsonResponse(ListRepositoryTags, - params=dict(repository=ADMIN_ACCESS_USER + '/history', - tag='latest')) + json = self.getJsonResponse(ListRepositoryTags, params=dict( + repository=ADMIN_ACCESS_USER + '/history', tag='latest')) self.assertEquals(3, len(json['tags'])) self.assertFalse('end_ts' in json['tags'][0]) self.assertEquals(previous_manifest, json['tags'][0]['manifest_digest']) - class TestListAndDeleteTag(ApiTestCase): def test_invalid_tags(self): self.login(ADMIN_ACCESS_USER) # List the images for staging. - json = self.getJsonResponse(RepositoryTagImages, - params=dict(repository=ADMIN_ACCESS_USER + '/complex', - tag='staging')) + json = self.getJsonResponse(RepositoryTagImages, params=dict( + repository=ADMIN_ACCESS_USER + '/complex', tag='staging')) staging_images = json['images'] # Try to add some invalid tags. self.putResponse(RepositoryTag, params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='-fail'), - data=dict(image=staging_images[0]['id']), - expected_code=400) + data=dict(image=staging_images[0]['id']), expected_code=400) self.putResponse(RepositoryTag, params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='北京'), - data=dict(image=staging_images[0]['id']), - expected_code=400) - - + data=dict(image=staging_images[0]['id']), expected_code=400) def test_listdeletecreateandmovetag(self): self.login(ADMIN_ACCESS_USER) @@ -3089,17 +2797,16 @@ class TestListAndDeleteTag(ApiTestCase): assert len(prod_images) > 0 # List the images for staging. - json = self.getJsonResponse(RepositoryTagImages, - params=dict(repository=ADMIN_ACCESS_USER + '/complex', - tag='staging')) + json = self.getJsonResponse(RepositoryTagImages, params=dict( + repository=ADMIN_ACCESS_USER + '/complex', tag='staging')) staging_images = json['images'] assert len(prod_images) == len(staging_images) + 1 # Delete prod. self.deleteEmptyResponse(RepositoryTag, - params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='prod'), - expected_code=204) + params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='prod'), + expected_code=204) # Make sure the tag is gone. self.getResponse(RepositoryTagImages, @@ -3107,28 +2814,24 @@ class TestListAndDeleteTag(ApiTestCase): expected_code=404) # Make the sure the staging images are still there. - json = self.getJsonResponse(RepositoryTagImages, - params=dict(repository=ADMIN_ACCESS_USER + '/complex', - tag='staging')) + json = self.getJsonResponse(RepositoryTagImages, params=dict( + repository=ADMIN_ACCESS_USER + '/complex', tag='staging')) self.assertEquals(staging_images, json['images']) # Require a valid tag name. self.putResponse(RepositoryTag, params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='-fail'), - data=dict(image=staging_images[0]['id']), - expected_code=400) + data=dict(image=staging_images[0]['id']), expected_code=400) # Add a new tag to the staging image. self.putResponse(RepositoryTag, params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='sometag'), - data=dict(image=staging_images[0]['id']), - expected_code=201) + data=dict(image=staging_images[0]['id']), expected_code=201) # Make sure the tag is present. - json = self.getJsonResponse(RepositoryTagImages, - params=dict(repository=ADMIN_ACCESS_USER + '/complex', - tag='sometag')) + json = self.getJsonResponse(RepositoryTagImages, params=dict( + repository=ADMIN_ACCESS_USER + '/complex', tag='sometag')) sometag_images = json['images'] self.assertEquals(sometag_images, staging_images) @@ -3136,19 +2839,16 @@ class TestListAndDeleteTag(ApiTestCase): # Move the tag. self.putResponse(RepositoryTag, params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='sometag'), - data=dict(image=staging_images[-1]['id']), - expected_code=201) + data=dict(image=staging_images[-1]['id']), expected_code=201) # Make sure the tag has moved. - json = self.getJsonResponse(RepositoryTagImages, - params=dict(repository=ADMIN_ACCESS_USER + '/complex', - tag='sometag')) + json = self.getJsonResponse(RepositoryTagImages, params=dict( + repository=ADMIN_ACCESS_USER + '/complex', tag='sometag')) sometag_new_images = json['images'] self.assertEquals(1, len(sometag_new_images)) self.assertEquals(staging_images[-1], sometag_new_images[0]) - def test_deletesubtag(self): self.login(ADMIN_ACCESS_USER) @@ -3161,8 +2861,8 @@ class TestListAndDeleteTag(ApiTestCase): # Delete staging. self.deleteEmptyResponse(RepositoryTag, - params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='staging'), - expected_code=204) + params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='staging'), + expected_code=204) # Make sure the prod images are still around. json = self.getJsonResponse(RepositoryTagImages, @@ -3172,9 +2872,8 @@ class TestListAndDeleteTag(ApiTestCase): def test_listtag_digest(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(ListRepositoryTags, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', page=1, - limit=1)) + json = self.getJsonResponse(ListRepositoryTags, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', page=1, limit=1)) self.assertTrue('manifest_digest' in json['tags'][0]) def test_listtagpagination(self): @@ -3192,13 +2891,11 @@ class TestListAndDeleteTag(ApiTestCase): for i in xrange(1, 11): tag_name = "tag" + str(i) remaining_tags.add(tag_name) - model.tag.create_or_update_tag(ADMIN_ACCESS_USER, "empty", tag_name, - image.docker_image_id) + model.tag.create_or_update_tag(ADMIN_ACCESS_USER, "empty", tag_name, image.docker_image_id) # Make sure we can iterate over all of them. - json = self.getJsonResponse(ListRepositoryTags, - params=dict(repository=ADMIN_ACCESS_USER + '/empty', page=1, - limit=5)) + json = self.getJsonResponse(ListRepositoryTags, params=dict( + repository=ADMIN_ACCESS_USER + '/empty', page=1, limit=5)) self.assertEquals(1, json['page']) self.assertEquals(5, len(json['tags'])) self.assertTrue(json['has_additional']) @@ -3207,9 +2904,8 @@ class TestListAndDeleteTag(ApiTestCase): remaining_tags = remaining_tags - names self.assertEquals(5, len(remaining_tags)) - json = self.getJsonResponse(ListRepositoryTags, - params=dict(repository=ADMIN_ACCESS_USER + '/empty', page=2, - limit=5)) + json = self.getJsonResponse(ListRepositoryTags, params=dict( + repository=ADMIN_ACCESS_USER + '/empty', page=2, limit=5)) self.assertEquals(2, json['page']) self.assertEquals(5, len(json['tags'])) @@ -3219,9 +2915,8 @@ class TestListAndDeleteTag(ApiTestCase): remaining_tags = remaining_tags - names self.assertEquals(0, len(remaining_tags)) - json = self.getJsonResponse(ListRepositoryTags, - params=dict(repository=ADMIN_ACCESS_USER + '/empty', page=3, - limit=5)) + json = self.getJsonResponse(ListRepositoryTags, params=dict( + repository=ADMIN_ACCESS_USER + '/empty', page=3, limit=5)) self.assertEquals(3, json['page']) self.assertEquals(0, len(json['tags'])) @@ -3251,8 +2946,7 @@ class TestRepoPermissions(ApiTestCase): # Add another user. self.putJsonResponse(RepositoryUserPermission, params=dict(repository=ORGANIZATION + '/' + ORG_REPO, - username=ADMIN_ACCESS_USER), - data=dict(role='admin')) + username=ADMIN_ACCESS_USER), data=dict(role='admin')) # Verify the user is present. permissions = self.listUserPermissions(namespace=ORGANIZATION, repo=ORG_REPO) @@ -3262,7 +2956,6 @@ class TestRepoPermissions(ApiTestCase): self.assertEquals('admin', permissions[ADMIN_ACCESS_USER]['role']) self.assertEquals(True, permissions[ADMIN_ACCESS_USER]['is_org_member']) - def test_userpermissions(self): self.login(ADMIN_ACCESS_USER) @@ -3275,10 +2968,8 @@ class TestRepoPermissions(ApiTestCase): self.assertFalse('is_org_member' in permissions[ADMIN_ACCESS_USER]) # Add another user. - self.putJsonResponse(RepositoryUserPermission, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - username=NO_ACCESS_USER), - data=dict(role='read')) + self.putJsonResponse(RepositoryUserPermission, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', username=NO_ACCESS_USER), data=dict(role='read')) # Verify the user is present. permissions = self.listUserPermissions() @@ -3288,16 +2979,13 @@ class TestRepoPermissions(ApiTestCase): self.assertEquals('read', permissions[NO_ACCESS_USER]['role']) self.assertFalse('is_org_member' in permissions[NO_ACCESS_USER]) - json = self.getJsonResponse(RepositoryUserPermission, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - username=NO_ACCESS_USER)) + json = self.getJsonResponse(RepositoryUserPermission, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', username=NO_ACCESS_USER)) self.assertEquals('read', json['role']) # Change the user's permissions. - self.putJsonResponse(RepositoryUserPermission, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - username=NO_ACCESS_USER), - data=dict(role='admin')) + self.putJsonResponse(RepositoryUserPermission, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', username=NO_ACCESS_USER), data=dict(role='admin')) # Verify. permissions = self.listUserPermissions() @@ -3307,9 +2995,8 @@ class TestRepoPermissions(ApiTestCase): self.assertEquals('admin', permissions[NO_ACCESS_USER]['role']) # Delete the user's permission. - self.deleteEmptyResponse(RepositoryUserPermission, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - username=NO_ACCESS_USER)) + self.deleteEmptyResponse(RepositoryUserPermission, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', username=NO_ACCESS_USER)) # Verify. permissions = self.listUserPermissions() @@ -3317,7 +3004,6 @@ class TestRepoPermissions(ApiTestCase): self.assertEquals(1, len(permissions)) assert not NO_ACCESS_USER in permissions - def test_teampermissions(self): self.login(ADMIN_ACCESS_USER) @@ -3340,9 +3026,8 @@ class TestRepoPermissions(ApiTestCase): assert 'owners' in permissions self.assertEquals('write', permissions['owners']['role']) - json = self.getJsonResponse(RepositoryTeamPermission, - params=dict(repository=ORGANIZATION + '/' + ORG_REPO, - teamname='owners')) + json = self.getJsonResponse(RepositoryTeamPermission, params=dict( + repository=ORGANIZATION + '/' + ORG_REPO, teamname='owners')) self.assertEquals('write', json['role']) # Change the team's permissions. @@ -3358,8 +3043,9 @@ class TestRepoPermissions(ApiTestCase): self.assertEquals('admin', permissions['owners']['role']) # Delete the team's permission. - self.deleteEmptyResponse(RepositoryTeamPermission, - params=dict(repository=ORGANIZATION + '/' + ORG_REPO, teamname='owners')) + self.deleteEmptyResponse( + RepositoryTeamPermission, + params=dict(repository=ORGANIZATION + '/' + ORG_REPO, teamname='owners')) # Verify. permissions = self.listTeamPermissions() @@ -3379,8 +3065,7 @@ class TestApiTokens(ApiTestCase): # Create a new token. json = self.postJsonResponse(RepositoryTokenList, params=dict(repository=ADMIN_ACCESS_USER + '/simple'), - data=dict(role='read', friendlyName='mytoken'), - expected_code=201) + data=dict(role='read', friendlyName='mytoken'), expected_code=201) self.assertEquals('mytoken', json['friendlyName']) self.assertEquals('read', json['role']) @@ -3391,9 +3076,8 @@ class TestApiTokens(ApiTestCase): assert token_code in tokens self.assertEquals('mytoken', tokens[token_code]['friendlyName']) - json = self.getJsonResponse(RepositoryToken, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - code=token_code)) + json = self.getJsonResponse(RepositoryToken, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', code=token_code)) self.assertEquals(tokens[token_code], json) # Change the token's permission. @@ -3402,15 +3086,13 @@ class TestApiTokens(ApiTestCase): data=dict(role='write')) # Verify. - json = self.getJsonResponse(RepositoryToken, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - code=token_code)) + json = self.getJsonResponse(RepositoryToken, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', code=token_code)) self.assertEquals('write', json['role']) # Delete the token. - self.deleteEmptyResponse(RepositoryToken, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - code=token_code)) + self.deleteEmptyResponse(RepositoryToken, params=dict(repository=ADMIN_ACCESS_USER + '/simple', + code=token_code)) # Verify. self.getResponse(RepositoryToken, @@ -3428,17 +3110,14 @@ class TestUserCard(ApiTestCase): def test_setusercard_error(self): self.login(ADMIN_ACCESS_USER) - json = self.postJsonResponse(UserCard, - data=dict(token='sometoken'), - expected_code=402) + json = self.postJsonResponse(UserCard, data=dict(token='sometoken'), expected_code=402) assert 'carderror' in json class TestOrgCard(ApiTestCase): def test_getorgcard(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(OrganizationCard, - params=dict(orgname=ORGANIZATION)) + json = self.getJsonResponse(OrganizationCard, params=dict(orgname=ORGANIZATION)) self.assertEquals('4242', json['card']['last4']) self.assertEquals('Visa', json['card']['type']) @@ -3452,16 +3131,14 @@ class TestUserSubscription(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Change the plan. - self.putJsonResponse(UserPlan, - data=dict(plan='free')) + self.putJsonResponse(UserPlan, data=dict(plan='free')) # Verify sub = self.getSubscription() self.assertEquals('free', sub['plan']) # Change the plan. - self.putJsonResponse(UserPlan, - data=dict(plan='bus-large-30')) + self.putJsonResponse(UserPlan, data=dict(plan='bus-large-30')) # Verify sub = self.getSubscription() @@ -3476,8 +3153,7 @@ class TestOrgSubscription(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Change the plan. - self.putJsonResponse(OrganizationPlan, - params=dict(orgname=ORGANIZATION), + self.putJsonResponse(OrganizationPlan, params=dict(orgname=ORGANIZATION), data=dict(plan='free')) # Verify @@ -3485,8 +3161,7 @@ class TestOrgSubscription(ApiTestCase): self.assertEquals('free', sub['plan']) # Change the plan. - self.putJsonResponse(OrganizationPlan, - params=dict(orgname=ORGANIZATION), + self.putJsonResponse(OrganizationPlan, params=dict(orgname=ORGANIZATION), data=dict(plan='bus-large-30')) # Verify @@ -3502,17 +3177,11 @@ class TestUserRobots(ApiTestCase): self.login(NO_ACCESS_USER) # Create some robots. - self.putJsonResponse(UserRobot, - params=dict(robot_shortname='bender'), - expected_code=201) + self.putJsonResponse(UserRobot, params=dict(robot_shortname='bender'), expected_code=201) - self.putJsonResponse(UserRobot, - params=dict(robot_shortname='goldy'), - expected_code=201) + self.putJsonResponse(UserRobot, params=dict(robot_shortname='goldy'), expected_code=201) - self.putJsonResponse(UserRobot, - params=dict(robot_shortname='coolbot'), - expected_code=201) + self.putJsonResponse(UserRobot, params=dict(robot_shortname='coolbot'), expected_code=201) # Queries: Base + the lookup query with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 1): @@ -3522,13 +3191,11 @@ class TestUserRobots(ApiTestCase): with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 1): self.getJsonResponse(UserRobotList, params=dict(permissions=True)) - def test_robots(self): self.login(NO_ACCESS_USER) # Create a robot. - json = self.putJsonResponse(UserRobot, - params=dict(robot_shortname='bender'), + json = self.putJsonResponse(UserRobot, params=dict(robot_shortname='bender'), expected_code=201) self.assertEquals(NO_ACCESS_USER + '+bender', json['name']) @@ -3538,8 +3205,7 @@ class TestUserRobots(ApiTestCase): assert NO_ACCESS_USER + '+bender' in robots # Delete the robot. - self.deleteEmptyResponse(UserRobot, - params=dict(robot_shortname='bender')) + self.deleteEmptyResponse(UserRobot, params=dict(robot_shortname='bender')) # Verify. robots = self.getRobotNames() @@ -3549,8 +3215,7 @@ class TestUserRobots(ApiTestCase): self.login(NO_ACCESS_USER) # Create a robot. - json = self.putJsonResponse(UserRobot, - params=dict(robot_shortname='bender'), + json = self.putJsonResponse(UserRobot, params=dict(robot_shortname='bender'), expected_code=201) token = json['token'] @@ -3562,8 +3227,7 @@ class TestUserRobots(ApiTestCase): # Verify the token changed. self.assertNotEquals(token, json['token']) - json2 = self.getJsonResponse(UserRobot, - params=dict(robot_shortname='bender'), + json2 = self.getJsonResponse(UserRobot, params=dict(robot_shortname='bender'), expected_code=200) self.assertEquals(json['token'], json2['token']) @@ -3571,60 +3235,55 @@ class TestUserRobots(ApiTestCase): class TestOrgRobots(ApiTestCase): def getRobotNames(self): - return [r['name'] for r in self.getJsonResponse(OrgRobotList, - params=dict(orgname=ORGANIZATION))['robots']] + return [ + r['name'] + for r in self.getJsonResponse(OrgRobotList, params=dict(orgname=ORGANIZATION))['robots'] + ] def test_create_robot_with_underscores(self): self.login(ADMIN_ACCESS_USER) # Create the robot. - self.putJsonResponse(OrgRobot, - params=dict(orgname=ORGANIZATION, robot_shortname='mr_bender'), + self.putJsonResponse(OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='mr_bender'), expected_code=201) # Add the robot to a team. membername = ORGANIZATION + '+mr_bender' - self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='readers', - membername=membername)) + self.putJsonResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='readers', + membername=membername)) # Retrieve the robot's details. - self.getJsonResponse(OrgRobot, - params=dict(orgname=ORGANIZATION, robot_shortname='mr_bender'), + self.getJsonResponse(OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='mr_bender'), expected_code=200) - def test_delete_robot_after_use(self): self.login(ADMIN_ACCESS_USER) # Create the robot. - self.putJsonResponse(OrgRobot, - params=dict(orgname=ORGANIZATION, robot_shortname='bender'), + self.putJsonResponse(OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='bender'), expected_code=201) # Add the robot to a team. membername = ORGANIZATION + '+bender' - self.putJsonResponse(TeamMember, - params=dict(orgname=ORGANIZATION, teamname='readers', - membername=membername)) + self.putJsonResponse(TeamMember, params=dict(orgname=ORGANIZATION, teamname='readers', + membername=membername)) # Add a repository permission. - self.putJsonResponse(RepositoryUserPermission, - params=dict(repository=ORGANIZATION + '/' + ORG_REPO, username=membername), - data=dict(role='read')) + self.putJsonResponse( + RepositoryUserPermission, + params=dict(repository=ORGANIZATION + '/' + ORG_REPO, username=membername), + data=dict(role='read')) # Add a permission prototype with the robot as the activating user. self.postJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION), - data=dict(role='read', - activating_user={'name': membername}, + data=dict(role='read', activating_user={'name': membername}, delegate={'kind': 'user', 'name': membername})) # Add a permission prototype with the robot as the delegating user. self.postJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION), - data=dict(role='read', - delegate={'kind': 'user', - 'name': membername})) + data=dict(role='read', delegate={'kind': 'user', + 'name': membername})) # Add a build trigger with the robot as the pull robot. database.BuildTriggerService.create(name='fakeservice') @@ -3647,8 +3306,7 @@ class TestOrgRobots(ApiTestCase): model.log.log_action('pull_repo', ORGANIZATION, performer=pull_robot, repository=repo) # Delete the robot and verify it works. - self.deleteEmptyResponse(OrgRobot, - params=dict(orgname=ORGANIZATION, robot_shortname='bender')) + self.deleteEmptyResponse(OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='bender')) # Verify the build is still present. self.assertIsNotNone(model.build.get_repository_build(build.uuid)) @@ -3658,21 +3316,20 @@ class TestOrgRobots(ApiTestCase): # Check the team. team = model.team.get_organization_team(ORGANIZATION, 'readers') - members = [member.username - for member in model.organization.get_organization_team_members(team.id)] + members = [ + member.username for member in model.organization.get_organization_team_members(team.id) + ] self.assertFalse(membername in members) # Check the robot itself. self.assertIsNone(model.user.get_user(membername)) - def test_robots(self): self.login(ADMIN_ACCESS_USER) # Create a robot. - json = self.putJsonResponse(OrgRobot, - params=dict(orgname=ORGANIZATION, robot_shortname='bender'), - expected_code=201) + json = self.putJsonResponse( + OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='bender'), expected_code=201) self.assertEquals(ORGANIZATION + '+bender', json['name']) @@ -3681,21 +3338,18 @@ class TestOrgRobots(ApiTestCase): assert ORGANIZATION + '+bender' in robots # Delete the robot. - self.deleteEmptyResponse(OrgRobot, - params=dict(orgname=ORGANIZATION, robot_shortname='bender')) + self.deleteEmptyResponse(OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='bender')) # Verify. robots = self.getRobotNames() assert not ORGANIZATION + '+bender' in robots - def test_regenerate(self): self.login(ADMIN_ACCESS_USER) # Create a robot. - json = self.putJsonResponse(OrgRobot, - params=dict(orgname=ORGANIZATION, robot_shortname='bender'), - expected_code=201) + json = self.putJsonResponse( + OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='bender'), expected_code=201) token = json['token'] @@ -3707,9 +3361,8 @@ class TestOrgRobots(ApiTestCase): # Verify the token changed. self.assertNotEquals(token, json['token']) - json2 = self.getJsonResponse(OrgRobot, - params=dict(orgname=ORGANIZATION, robot_shortname='bender'), - expected_code=200) + json2 = self.getJsonResponse( + OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='bender'), expected_code=200) self.assertEquals(json['token'], json2['token']) @@ -3726,9 +3379,8 @@ class TestLogs(ApiTestCase): def test_repo_logs_crossyear(self): self.login(ADMIN_ACCESS_USER) - json = self.getJsonResponse(RepositoryLogs, params=dict(repository='devtable/simple', - starttime='12/01/2016', - endtime='1/09/2017')) + json = self.getJsonResponse(RepositoryLogs, params=dict( + repository='devtable/simple', starttime='12/01/2016', endtime='1/09/2017')) self.assertEquals('Thu, 01 Dec 2016 00:00:00 -0000', json['start_time']) self.assertEquals('Tue, 10 Jan 2017 00:00:00 -0000', json['end_time']) @@ -3761,14 +3413,12 @@ class TestLogs(ApiTestCase): json = self.getJsonResponse(UserAggregateLogs) assert 'aggregated' in json - def test_org_logs(self): self.login(ADMIN_ACCESS_USER) json = self.getJsonResponse(OrgAggregateLogs, params=dict(orgname=ORGANIZATION)) assert 'aggregated' in json - def test_performer(self): self.login(ADMIN_ACCESS_USER) @@ -3829,19 +3479,16 @@ class TestOrganizationApplicationResource(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Retrieve the application. - json = self.getJsonResponse(OrganizationApplicationResource, - params=dict(orgname=ORGANIZATION, - client_id=FAKE_APPLICATION_CLIENT_ID)) + json = self.getJsonResponse(OrganizationApplicationResource, params=dict( + orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID)) self.assertEquals(FAKE_APPLICATION_CLIENT_ID, json['client_id']) # Edit the application. - edit_json = self.putJsonResponse(OrganizationApplicationResource, - params=dict(orgname=ORGANIZATION, - client_id=FAKE_APPLICATION_CLIENT_ID), - data=dict(name="Some App", description="foo", - application_uri="bar", redirect_uri="baz", - avatar_email="meh")) + edit_json = self.putJsonResponse(OrganizationApplicationResource, params=dict( + orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID), data=dict( + name="Some App", description="foo", application_uri="bar", redirect_uri="baz", + avatar_email="meh")) self.assertEquals(FAKE_APPLICATION_CLIENT_ID, edit_json['client_id']) self.assertEquals("Some App", edit_json['name']) @@ -3851,15 +3498,15 @@ class TestOrganizationApplicationResource(ApiTestCase): self.assertEquals("meh", edit_json['avatar_email']) # Retrieve the application again. - json = self.getJsonResponse(OrganizationApplicationResource, - params=dict(orgname=ORGANIZATION, - client_id=FAKE_APPLICATION_CLIENT_ID)) + json = self.getJsonResponse(OrganizationApplicationResource, params=dict( + orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID)) self.assertEquals(json, edit_json) # Delete the application. - self.deleteEmptyResponse(OrganizationApplicationResource, - params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID)) + self.deleteEmptyResponse( + OrganizationApplicationResource, + params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID)) # Make sure the application is gone. self.getJsonResponse(OrganizationApplicationResource, @@ -3879,28 +3526,24 @@ class TestOrganizationApplicationResetClientSecret(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Retrieve the application. - json = self.getJsonResponse(OrganizationApplicationResource, - params=dict(orgname=ORGANIZATION, - client_id=FAKE_APPLICATION_CLIENT_ID)) + json = self.getJsonResponse(OrganizationApplicationResource, params=dict( + orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID)) self.assertEquals(FAKE_APPLICATION_CLIENT_ID, json['client_id']) # Reset the client secret. - reset_json = self.postJsonResponse(OrganizationApplicationResetClientSecret, - params=dict(orgname=ORGANIZATION, - client_id=FAKE_APPLICATION_CLIENT_ID)) + reset_json = self.postJsonResponse(OrganizationApplicationResetClientSecret, params=dict( + orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID)) self.assertEquals(FAKE_APPLICATION_CLIENT_ID, reset_json['client_id']) self.assertNotEquals(reset_json['client_secret'], json['client_secret']) # Verify it was changed in the DB. - json = self.getJsonResponse(OrganizationApplicationResource, - params=dict(orgname=ORGANIZATION, - client_id=FAKE_APPLICATION_CLIENT_ID)) + json = self.getJsonResponse(OrganizationApplicationResource, params=dict( + orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID)) self.assertEquals(reset_json['client_secret'], json['client_secret']) - class FakeBuildTrigger(BuildTriggerHandler): @classmethod def service_name(cls): @@ -3908,19 +3551,21 @@ class FakeBuildTrigger(BuildTriggerHandler): def list_build_source_namespaces(self): return [ - {'name': 'first', 'id': 'first'}, - {'name': 'second', 'id': 'second'}, + { + 'name': 'first', + 'id': 'first' + }, + { + 'name': 'second', + 'id': 'second' + }, ] def list_build_sources_for_namespace(self, namespace): if namespace == "first": - return [{ - 'name': 'source', - }] + return [{'name': 'source',}] elif namespace == "second": - return [{ - 'name': self.auth_token, - }] + return [{'name': self.auth_token,}] else: return [] @@ -3997,16 +3642,14 @@ class TestBuildTriggers(ApiTestCase): assert 'service' in trigger # Verify the get trigger method. - trigger_json = self.getJsonResponse(BuildTrigger, - params=dict(repository=ADMIN_ACCESS_USER + '/building', - trigger_uuid=trigger['id'])) + trigger_json = self.getJsonResponse(BuildTrigger, params=dict( + repository=ADMIN_ACCESS_USER + '/building', trigger_uuid=trigger['id'])) self.assertEquals(trigger, trigger_json) # Check the recent builds for the trigger. - builds_json = self.getJsonResponse(TriggerBuildList, - params=dict(repository=ADMIN_ACCESS_USER + '/building', - trigger_uuid=trigger['id'])) + builds_json = self.getJsonResponse(TriggerBuildList, params=dict( + repository=ADMIN_ACCESS_USER + '/building', trigger_uuid=trigger['id'])) assert 'builds' in builds_json @@ -4019,16 +3662,14 @@ class TestBuildTriggers(ApiTestCase): trigger = json['triggers'][0] # Delete the trigger. - self.deleteEmptyResponse(BuildTrigger, - params=dict(repository=ADMIN_ACCESS_USER + '/building', - trigger_uuid=trigger['id'])) + self.deleteEmptyResponse(BuildTrigger, params=dict(repository=ADMIN_ACCESS_USER + '/building', + trigger_uuid=trigger['id'])) # Verify it was deleted. json = self.getJsonResponse(BuildTriggerList, params=dict(repository=ADMIN_ACCESS_USER + '/building')) self.assertEquals(0, len(json['triggers'])) - def test_analyze_fake_trigger(self): self.login(ADMIN_ACCESS_USER) @@ -4041,9 +3682,8 @@ class TestBuildTriggers(ApiTestCase): # Analyze the trigger's dockerfile: First, no dockerfile. trigger_config = {} - analyze_json = self.postJsonResponse(BuildTriggerAnalyze, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + analyze_json = self.postJsonResponse(BuildTriggerAnalyze, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'config': trigger_config}) self.assertEquals('warning', analyze_json['status']) @@ -4052,9 +3692,8 @@ class TestBuildTriggers(ApiTestCase): # Analyze the trigger's dockerfile: Second, missing FROM in dockerfile. trigger_config = {'dockerfile': 'MAINTAINER me'} - analyze_json = self.postJsonResponse(BuildTriggerAnalyze, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + analyze_json = self.postJsonResponse(BuildTriggerAnalyze, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'config': trigger_config}) self.assertEquals('warning', analyze_json['status']) @@ -4062,18 +3701,16 @@ class TestBuildTriggers(ApiTestCase): # Analyze the trigger's dockerfile: Third, dockerfile with public repo. trigger_config = {'dockerfile': 'FROM somerepo'} - analyze_json = self.postJsonResponse(BuildTriggerAnalyze, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + analyze_json = self.postJsonResponse(BuildTriggerAnalyze, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'config': trigger_config}) self.assertEquals('publicbase', analyze_json['status']) # Analyze the trigger's dockerfile: Fourth, dockerfile with private repo with an invalid path. trigger_config = {'dockerfile': 'FROM localhost:5000/somepath'} - analyze_json = self.postJsonResponse(BuildTriggerAnalyze, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + analyze_json = self.postJsonResponse(BuildTriggerAnalyze, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'config': trigger_config}) self.assertEquals('warning', analyze_json['status']) @@ -4082,9 +3719,8 @@ class TestBuildTriggers(ApiTestCase): # Analyze the trigger's dockerfile: Fifth, dockerfile with private repo that does not exist. trigger_config = {'dockerfile': 'FROM localhost:5000/nothere/randomrepo'} - analyze_json = self.postJsonResponse(BuildTriggerAnalyze, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + analyze_json = self.postJsonResponse(BuildTriggerAnalyze, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'config': trigger_config}) self.assertEquals('error', analyze_json['status']) @@ -4093,9 +3729,8 @@ class TestBuildTriggers(ApiTestCase): # Analyze the trigger's dockerfile: Sixth, dockerfile with private repo that the user cannot see trigger_config = {'dockerfile': 'FROM localhost:5000/randomuser/randomrepo'} - analyze_json = self.postJsonResponse(BuildTriggerAnalyze, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + analyze_json = self.postJsonResponse(BuildTriggerAnalyze, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'config': trigger_config}) self.assertEquals('error', analyze_json['status']) @@ -4103,9 +3738,8 @@ class TestBuildTriggers(ApiTestCase): # Analyze the trigger's dockerfile: Seventh, dockerfile with private repo that the user see. trigger_config = {'dockerfile': 'FROM localhost:5000/devtable/complex'} - analyze_json = self.postJsonResponse(BuildTriggerAnalyze, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + analyze_json = self.postJsonResponse(BuildTriggerAnalyze, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'config': trigger_config}) self.assertEquals('requiresrobot', analyze_json['status']) @@ -4113,7 +3747,6 @@ class TestBuildTriggers(ApiTestCase): self.assertEquals('complex', analyze_json['name']) self.assertEquals(ADMIN_ACCESS_USER + '+dtrobot', analyze_json['robots'][0]['name']) - def test_fake_trigger(self): self.login(ADMIN_ACCESS_USER) @@ -4133,38 +3766,41 @@ class TestBuildTriggers(ApiTestCase): self.assertEquals(False, json['triggers'][0]['is_active']) # List the trigger's source namespaces. - namespace_json = self.getJsonResponse(BuildTriggerSourceNamespaces, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid)) - self.assertEquals([{'id': 'first', 'name': 'first'}, {'id': 'second', 'name': 'second'}], namespace_json['namespaces']) + namespace_json = self.getJsonResponse(BuildTriggerSourceNamespaces, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid)) + self.assertEquals([{ + 'id': 'first', + 'name': 'first' + }, { + 'id': 'second', + 'name': 'second' + }], namespace_json['namespaces']) - - source_json = self.postJsonResponse(BuildTriggerSources, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), - data=dict(namespace='first')) + source_json = self.postJsonResponse(BuildTriggerSources, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), + data=dict(namespace='first')) self.assertEquals([{'name': 'source'}], source_json['sources']) # List the trigger's subdirs. - subdir_json = self.postJsonResponse(BuildTriggerSubdirs, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + subdir_json = self.postJsonResponse(BuildTriggerSubdirs, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'somevalue': 'meh'}) - self.assertEquals({'status': 'success', 'dockerfile_paths': ['/sometoken', '/foo', '/bar', '/meh'], - 'contextMap': {'/bar': ['/'], - '/foo': ['/'], - '/meh': ['/'], - '/sometoken': ['/']}}, - subdir_json) + self.assertEquals({ + 'status': 'success', + 'dockerfile_paths': ['/sometoken', '/foo', '/bar', '/meh'], + 'contextMap': { + '/bar': ['/'], + '/foo': ['/'], + '/meh': ['/'], + '/sometoken': ['/'] + } + }, subdir_json) # Activate the trigger. - trigger_config = { - 'build_source': 'somesource' - } - activate_json = self.postJsonResponse(BuildTriggerActivate, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + trigger_config = {'build_source': 'somesource'} + activate_json = self.postJsonResponse(BuildTriggerActivate, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data={'config': trigger_config}) self.assertEquals(True, activate_json['is_active']) @@ -4175,11 +3811,9 @@ class TestBuildTriggers(ApiTestCase): self.assertEquals(True, py_json.loads(trigger.config)['active']) # Make sure we cannot activate again. - self.postResponse(BuildTriggerActivate, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), - data={'config': trigger_config}, - expected_code=400) + self.postResponse(BuildTriggerActivate, params=dict(repository=ADMIN_ACCESS_USER + '/simple', + trigger_uuid=trigger.uuid), + data={'config': trigger_config}, expected_code=400) # Retrieve values for a field. result = self.postJsonResponse(BuildTriggerFieldValues, @@ -4188,16 +3822,13 @@ class TestBuildTriggers(ApiTestCase): self.assertEquals(result['values'], [1, 2, 3]) - self.postResponse(BuildTriggerFieldValues, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid, field_name="another_field"), - expected_code=404) + self.postResponse(BuildTriggerFieldValues, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid, + field_name="another_field"), expected_code=404) # Start a manual build. - start_json = self.postJsonResponse(ActivateBuildTrigger, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), - data=dict(), + start_json = self.postJsonResponse(ActivateBuildTrigger, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data=dict(), expected_code=201) assert 'id' in start_json @@ -4211,17 +3842,14 @@ class TestBuildTriggers(ApiTestCase): self.assertEquals('bar', py_json.loads(build_obj.job_config)['trigger_metadata']['foo']) # Start another manual build, with a ref. - self.postJsonResponse(ActivateBuildTrigger, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), - data=dict(refs={'kind': 'branch', 'name': 'foobar'}), - expected_code=201) + self.postJsonResponse(ActivateBuildTrigger, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), + data=dict(refs={'kind': 'branch', + 'name': 'foobar'}), expected_code=201) # Start another manual build with a null ref. - self.postJsonResponse(ActivateBuildTrigger, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), - data=dict(refs=None), + self.postJsonResponse(ActivateBuildTrigger, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data=dict(refs=None), expected_code=201) def test_invalid_robot_account(self): @@ -4236,11 +3864,10 @@ class TestBuildTriggers(ApiTestCase): # Try to activate it with an invalid robot account. trigger_config = {} - self.postJsonResponse(BuildTriggerActivate, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), - data={'config': trigger_config, 'pull_robot': 'someinvalidrobot'}, - expected_code=404) + self.postJsonResponse(BuildTriggerActivate, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), + data={'config': trigger_config, + 'pull_robot': 'someinvalidrobot'}, expected_code=404) def test_unauthorized_robot_account(self): self.login(ADMIN_ACCESS_USER) @@ -4254,11 +3881,10 @@ class TestBuildTriggers(ApiTestCase): # Try to activate it with a robot account in the wrong namespace. trigger_config = {} - self.postJsonResponse(BuildTriggerActivate, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), - data={'config':trigger_config, 'pull_robot':'freshuser+anotherrobot'}, - expected_code=403) + self.postJsonResponse(BuildTriggerActivate, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), + data={'config': trigger_config, + 'pull_robot': 'freshuser+anotherrobot'}, expected_code=403) def test_robot_account(self): self.login(ADMIN_ACCESS_USER) @@ -4272,20 +3898,19 @@ class TestBuildTriggers(ApiTestCase): # Try to activate it with a robot account. trigger_config = {} - activate_json = self.postJsonResponse(BuildTriggerActivate, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), - data={'config':trigger_config, - 'pull_robot':ADMIN_ACCESS_USER + '+dtrobot'}) + activate_json = self.postJsonResponse( + BuildTriggerActivate, params=dict(repository=ADMIN_ACCESS_USER + '/simple', + trigger_uuid=trigger.uuid), + data={'config': trigger_config, + 'pull_robot': ADMIN_ACCESS_USER + '+dtrobot'}) # Verify that the robot was saved. self.assertEquals(True, activate_json['is_active']) self.assertEquals(ADMIN_ACCESS_USER + '+dtrobot', activate_json['pull_robot']['name']) # Start a manual build. - start_json = self.postJsonResponse(ActivateBuildTrigger, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - trigger_uuid=trigger.uuid), + start_json = self.postJsonResponse(ActivateBuildTrigger, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid), data=dict(refs=dict(kind='branch', name='foobar')), expected_code=201) @@ -4314,7 +3939,8 @@ class TestUserAuthorizations(ApiTestCase): self.assertEquals(authorization, get_json) # Delete the authorization. - self.deleteEmptyResponse(UserAuthorization, params=dict(access_token_uuid=authorization['uuid'])) + self.deleteEmptyResponse(UserAuthorization, params=dict( + access_token_uuid=authorization['uuid'])) # Verify it has been deleted. self.getJsonResponse(UserAuthorization, params=dict(access_token_uuid=authorization['uuid']), @@ -4408,7 +4034,8 @@ class TestSuperUserConfig(ApiTestCase): 'SECRET_KEY': 'fakekey', } - json = self.putJsonResponse(SuperUserConfig, data=dict(config=fake_config, hostname='fakehost')) + json = self.putJsonResponse(SuperUserConfig, data=dict(config=fake_config, + hostname='fakehost')) self.assertEquals('fakekey', json['config']['SECRET_KEY']) self.assertEquals('fakehost', json['config']['SERVER_HOSTNAME']) self.assertEquals('Database', json['config']['AUTHENTICATION_TYPE']) @@ -4444,7 +4071,9 @@ class TestSuperUserConfig(ApiTestCase): # Run a mock LDAP. mockldap = MockLdap({ - 'dc=quay,dc=io': {'dc': ['quay', 'io']}, + 'dc=quay,dc=io': { + 'dc': ['quay', 'io'] + }, 'ou=employees,dc=quay,dc=io': { 'dc': ['quay', 'io'], 'ou': 'employees' @@ -4471,16 +4100,22 @@ class TestSuperUserConfig(ApiTestCase): mockldap.start() try: # Try writing some config with an invalid password. - self.putResponse(SuperUserConfig, data={'config': config, 'hostname': 'foo'}, expected_code=400) + self.putResponse(SuperUserConfig, data={'config': config, + 'hostname': 'foo'}, expected_code=400) self.putResponse(SuperUserConfig, - data={'config': config, 'password': 'invalid', 'hostname': 'foo'}, expected_code=400) + data={'config': config, + 'password': 'invalid', + 'hostname': 'foo'}, expected_code=400) # Write the config with the valid password. self.putResponse(SuperUserConfig, - data={'config': config, 'password': 'password', 'hostname': 'foo'}, expected_code=200) + data={'config': config, + 'password': 'password', + 'hostname': 'foo'}, expected_code=200) # Ensure that the user row has been linked. - self.assertEquals(ADMIN_ACCESS_USER, model.user.verify_federated_login('ldap', ADMIN_ACCESS_USER).username) + self.assertEquals(ADMIN_ACCESS_USER, + model.user.verify_federated_login('ldap', ADMIN_ACCESS_USER).username) finally: mockldap.stop() @@ -4495,15 +4130,13 @@ class TestRepositoryImageSecurity(ApiTestCase): tag_manifest = database.TagManifest.get(tag=tag) # Grab the security info for the tag. It should be queued. - manifest_response = self.getJsonResponse(RepositoryManifestSecurity, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - manifestref=tag_manifest.digest, - vulnerabilities='true')) + manifest_response = self.getJsonResponse(RepositoryManifestSecurity, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', manifestref=tag_manifest.digest, + vulnerabilities='true')) - image_response = self.getJsonResponse(RepositoryImageSecurity, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - imageid=layer.docker_image_id, - vulnerabilities='true')) + image_response = self.getJsonResponse( + RepositoryImageSecurity, params=dict(repository=ADMIN_ACCESS_USER + '/simple', + imageid=layer.docker_image_id, vulnerabilities='true')) self.assertEquals(manifest_response, image_response) self.assertEquals('queued', image_response['status']) @@ -4517,15 +4150,13 @@ class TestRepositoryImageSecurity(ApiTestCase): with fake_security_scanner() as security_scanner: security_scanner.add_layer(security_scanner.layer_id(layer)) - manifest_response = self.getJsonResponse(RepositoryManifestSecurity, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - manifestref=tag_manifest.digest, - vulnerabilities='true')) + manifest_response = self.getJsonResponse(RepositoryManifestSecurity, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', manifestref=tag_manifest.digest, + vulnerabilities='true')) - image_response = self.getJsonResponse(RepositoryImageSecurity, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - imageid=layer.docker_image_id, - vulnerabilities='true')) + image_response = self.getJsonResponse(RepositoryImageSecurity, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', imageid=layer.docker_image_id, + vulnerabilities='true')) self.assertEquals(manifest_response, image_response) self.assertEquals('scanned', image_response['status']) @@ -4542,24 +4173,22 @@ class TestRepositoryImageSecurity(ApiTestCase): with fake_security_scanner(hostname='failoverscanner') as security_scanner: # Query the wrong security scanner URL without failover. - self.getResponse(RepositoryImageSecurity, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - imageid=layer.docker_image_id, vulnerabilities='true'), - expected_code=520) + self.getResponse(RepositoryImageSecurity, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', imageid=layer.docker_image_id, + vulnerabilities='true'), expected_code=520) # Set the failover URL in the global config. - with AppConfigChange({'SECURITY_SCANNER_READONLY_FAILOVER_ENDPOINTS': ['https://failoverscanner']}): + with AppConfigChange({ + 'SECURITY_SCANNER_READONLY_FAILOVER_ENDPOINTS': ['https://failoverscanner'] + }): # Configure the API to return 200 for this layer. layer_id = security_scanner.layer_id(layer) security_scanner.set_ok_layer_id(layer_id) # Call the API and succeed on failover. - self.getResponse(RepositoryImageSecurity, - params=dict(repository=ADMIN_ACCESS_USER + '/simple', - imageid=layer.docker_image_id, vulnerabilities='true'), - expected_code=200) - - + self.getResponse(RepositoryImageSecurity, params=dict( + repository=ADMIN_ACCESS_USER + '/simple', imageid=layer.docker_image_id, + vulnerabilities='true'), expected_code=200) class TestSuperUserCustomCertificates(ApiTestCase): @@ -4647,9 +4276,7 @@ class TestSuperUserTakeOwnership(ApiTestCase): def test_take_ownership_invalid_namespace(self): self.login(ADMIN_ACCESS_USER) - self.postResponse(SuperUserTakeOwnership, params=dict(namespace='invalid'), - expected_code=404) - + self.postResponse(SuperUserTakeOwnership, params=dict(namespace='invalid'), expected_code=404) def test_take_ownership_non_superuser(self): self.login(READ_ACCESS_USER) @@ -4743,7 +4370,6 @@ class TestSuperUserKeyManagement(ApiTestCase): json = self.getJsonResponse(SuperUserServiceKeyManagement) self.assertEquals(key_count - 1, len(json['keys'])) - def test_approve_key(self): self.login(ADMIN_ACCESS_USER) @@ -4765,7 +4391,6 @@ class TestSuperUserKeyManagement(ApiTestCase): self.assertEquals(ADMIN_ACCESS_USER, json['approval']['approver']['username']) self.assertEquals('testapprove', json['approval']['notes']) - def test_approve_preapproved(self): self.login(ADMIN_ACCESS_USER) @@ -4803,7 +4428,8 @@ class TestSuperUserKeyManagement(ApiTestCase): self.assertTrue('private_key' in json) # Verify the private key is a valid PEM. - serialization.load_pem_private_key(json['private_key'].encode('utf-8'), None, default_backend()) + serialization.load_pem_private_key(json['private_key'].encode('utf-8'), None, + default_backend()) # Verify the key. kid = json['kid'] @@ -4829,56 +4455,41 @@ class TestRepositoryManifestLabels(ApiTestCase): repository = ADMIN_ACCESS_USER + '/complex' # Check the existing labels on the complex repo, which should be empty - json = self.getJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, manifestref=tag_manifest.digest)) + json = self.getJsonResponse( + RepositoryManifestLabels, + params=dict(repository=repository, manifestref=tag_manifest.digest)) self.assertEquals(0, len(json['labels'])) - self.postJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), + self.postJsonResponse(RepositoryManifestLabels, params=dict(repository=repository, + manifestref=tag_manifest.digest), data=dict(key='bad_label', value='world', - media_type='text/plain'), - expected_code=400) + media_type='text/plain'), expected_code=400) - self.postJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), + self.postJsonResponse(RepositoryManifestLabels, params=dict(repository=repository, + manifestref=tag_manifest.digest), data=dict(key='hello', value='world', - media_type='bad_media_type'), - expected_code=400) + media_type='bad_media_type'), expected_code=400) # Add some labels to the manifest. with assert_action_logged('manifest_label_add'): - label1 = self.postJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), - data=dict(key='hello', value='world', - media_type='text/plain'), - expected_code=201) - + label1 = self.postJsonResponse(RepositoryManifestLabels, params=dict( + repository=repository, manifestref=tag_manifest.digest), data=dict( + key='hello', value='world', media_type='text/plain'), expected_code=201) with assert_action_logged('manifest_label_add'): - label2 = self.postJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), - data=dict(key='hi', value='there', - media_type='text/plain'), - expected_code=201) + label2 = self.postJsonResponse(RepositoryManifestLabels, params=dict( + repository=repository, manifestref=tag_manifest.digest), data=dict( + key='hi', value='there', media_type='text/plain'), expected_code=201) with assert_action_logged('manifest_label_add'): - label3 = self.postJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), - data=dict(key='hello', value='someone', - media_type='application/json'), - expected_code=201) - + label3 = self.postJsonResponse(RepositoryManifestLabels, params=dict( + repository=repository, manifestref=tag_manifest.digest), data=dict( + key='hello', value='someone', media_type='application/json'), expected_code=201) # Ensure we have *3* labels - json = self.getJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest)) + json = self.getJsonResponse(RepositoryManifestLabels, params=dict( + repository=repository, manifestref=tag_manifest.digest)) self.assertEquals(3, len(json['labels'])) @@ -4892,35 +4503,27 @@ class TestRepositoryManifestLabels(ApiTestCase): # Ensure we can retrieve each of the labels. for label in json['labels']: - label_json = self.getJsonResponse(ManageRepositoryManifestLabel, - params=dict(repository=repository, - manifestref=tag_manifest.digest, - labelid=label['id'])) + label_json = self.getJsonResponse(ManageRepositoryManifestLabel, params=dict( + repository=repository, manifestref=tag_manifest.digest, labelid=label['id'])) self.assertEquals(label['id'], label_json['id']) # Delete a label. with assert_action_logged('manifest_label_delete'): - self.deleteEmptyResponse(ManageRepositoryManifestLabel, - params=dict(repository=repository, - manifestref=tag_manifest.digest, - labelid=label1['label']['id'])) + self.deleteEmptyResponse(ManageRepositoryManifestLabel, params=dict( + repository=repository, manifestref=tag_manifest.digest, labelid=label1['label']['id'])) # Ensure the label is gone. - json = self.getJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest)) + json = self.getJsonResponse(RepositoryManifestLabels, params=dict( + repository=repository, manifestref=tag_manifest.digest)) self.assertEquals(2, len(json['labels'])) # Check filtering. - json = self.getJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest, - filter='hello')) + json = self.getJsonResponse(RepositoryManifestLabels, params=dict( + repository=repository, manifestref=tag_manifest.digest, filter='hello')) self.assertEquals(1, len(json['labels'])) - def test_prefixed_labels(self): self.login(ADMIN_ACCESS_USER) @@ -4928,22 +4531,15 @@ class TestRepositoryManifestLabels(ApiTestCase): tag_manifest = model.tag.load_tag_manifest(ADMIN_ACCESS_USER, 'complex', 'prod') repository = ADMIN_ACCESS_USER + '/complex' - self.postJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), + self.postJsonResponse(RepositoryManifestLabels, params=dict(repository=repository, + manifestref=tag_manifest.digest), data=dict(key='com.dockers.whatever', value='pants', - media_type='text/plain'), - expected_code=201) + media_type='text/plain'), expected_code=201) - - self.postJsonResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), + self.postJsonResponse(RepositoryManifestLabels, params=dict(repository=repository, + manifestref=tag_manifest.digest), data=dict(key='my.cool.prefix.for.my.label', value='value', - media_type='text/plain'), - expected_code=201) - - + media_type='text/plain'), expected_code=201) def test_add_invalid_media_type(self): self.login(ADMIN_ACCESS_USER) @@ -4951,13 +4547,11 @@ class TestRepositoryManifestLabels(ApiTestCase): tag_manifest = model.tag.load_tag_manifest(ADMIN_ACCESS_USER, 'complex', 'prod') repository = ADMIN_ACCESS_USER + '/complex' - self.postResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), + self.postResponse(RepositoryManifestLabels, params=dict(repository=repository, + manifestref=tag_manifest.digest), data=dict(key='hello', value='world', media_type='some/invalid'), expected_code=400) - def test_add_invalid_key(self): self.login(ADMIN_ACCESS_USER) @@ -4965,25 +4559,19 @@ class TestRepositoryManifestLabels(ApiTestCase): repository = ADMIN_ACCESS_USER + '/complex' # Try to add an empty label key. - self.postResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), - data=dict(key='', value='world'), - expected_code=400) + self.postResponse(RepositoryManifestLabels, params=dict(repository=repository, + manifestref=tag_manifest.digest), + data=dict(key='', value='world'), expected_code=400) # Try to add an invalid label key. - self.postResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), - data=dict(key='invalid___key', value='world'), - expected_code=400) + self.postResponse(RepositoryManifestLabels, params=dict(repository=repository, + manifestref=tag_manifest.digest), + data=dict(key='invalid___key', value='world'), expected_code=400) # Try to add a label key in a reserved namespace. - self.postResponse(RepositoryManifestLabels, - params=dict(repository=repository, - manifestref=tag_manifest.digest), - data=dict(key='io.docker.whatever', value='world'), - expected_code=400) + self.postResponse(RepositoryManifestLabels, params=dict(repository=repository, + manifestref=tag_manifest.digest), + data=dict(key='io.docker.whatever', value='world'), expected_code=400) class TestSuperUserManagement(ApiTestCase): @@ -5003,7 +4591,8 @@ class TestSuperUserManagement(ApiTestCase): self.assertEquals('freshuser', json['username']) # Delete the user. - self.deleteEmptyResponse(SuperUserManagement, params=dict(username='freshuser'), expected_code=204) + self.deleteEmptyResponse(SuperUserManagement, params=dict(username='freshuser'), + expected_code=204) # Verify the user no longer exists. self.getResponse(SuperUserManagement, params=dict(username='freshuser'), expected_code=404) @@ -5058,7 +4647,8 @@ class TestSuperUserManagement(ApiTestCase): has_matching_message = True break - self.assertTrue(has_matching_message, "Could not find matching message in: " + str(json["messages"])) + self.assertTrue(has_matching_message, + "Could not find matching message in: " + str(json["messages"])) self.assertNotEqual(json['messages'][0]["content"], json['messages'][2]["content"]) self.assertTrue(json['messages'][2]["uuid"]) @@ -5071,5 +4661,6 @@ class TestSuperUserManagement(ApiTestCase): self.assertEquals(len(json['messages']), 1) + if __name__ == '__main__': unittest.main()