This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/test/test_api_usage.py

4346 lines
165 KiB
Python

# coding=utf-8
import unittest
import datetime
import logging
import time
import re
import json as py_json
from mock import patch
from calendar import timegm
from contextlib import contextmanager
from httmock import urlmatch, HTTMock, all_requests
from urllib import urlencode
from urlparse import urlparse, urlunparse, parse_qs
from playhouse.test_utils import assert_query_count, _QueryLogHandler
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from endpoints.api import api_bp, api
from endpoints.building import PreparedBuild
from endpoints.webhooks import webhooks
from app import (app, config_provider, all_queues, dockerfile_build_queue, notification_queue,
storage, docker_v2_signing_key)
from buildtrigger.basehandler import BuildTriggerHandler
from initdb import setup_database_for_testing, finished_database_for_testing
from data import database, model, appr_model
from data.appr_model.models import NEW_MODELS
from data.database import RepositoryActionCount, Repository as RepositoryTable
from data.logs_model import logs_model
from data.registry_model import registry_model
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.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.repoemail import RepositoryAuthorizedEmail
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.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.permission import (RepositoryUserPermission, RepositoryTeamPermission,
RepositoryTeamPermissionList, RepositoryUserPermissionList)
from endpoints.api.superuser import (
SuperUserLogs, SuperUserManagement, SuperUserServiceKeyManagement,
SuperUserServiceKey, SuperUserServiceKeyApproval, SuperUserTakeOwnership)
from endpoints.api.globalmessages import (
GlobalUserMessage,
GlobalUserMessages,)
from endpoints.api.secscan import RepositoryImageSecurity, RepositoryManifestSecurity
from endpoints.api.manifest import RepositoryManifestLabels, ManageRepositoryManifestLabel
from util.morecollections import AttrDict
try:
app.register_blueprint(api_bp, url_prefix='/api')
except ValueError:
# This blueprint was already registered
pass
app.register_blueprint(webhooks, url_prefix='/webhooks')
# The number of queries we run for guests on API calls.
BASE_QUERY_COUNT = 0
# The number of queries we run for logged in users on API calls.
BASE_LOGGEDIN_QUERY_COUNT = BASE_QUERY_COUNT + 1
# The number of queries we run for logged in users on API calls that check
# access permissions.
BASE_PERM_ACCESS_QUERY_COUNT = BASE_LOGGEDIN_QUERY_COUNT + 2
NO_ACCESS_USER = 'freshuser'
READ_ACCESS_USER = 'reader'
ADMIN_ACCESS_USER = 'devtable'
PUBLIC_USER = 'public'
ADMIN_ACCESS_EMAIL = 'jschorr@devtable.com'
ORG_REPO = 'orgrepo'
ORGANIZATION = 'buynlarge'
NEW_USER_DETAILS = {
'username': 'bobby',
'password': 'password',
'email': 'bobby@tables.com',
}
FAKE_APPLICATION_CLIENT_ID = 'deadbeef'
CSRF_TOKEN_KEY = '_csrf_token'
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 = {}
self._to_rm = []
def __enter__(self):
for key in self._changes.keys():
try:
self._originals[key] = app.config[key]
except KeyError:
self._to_rm.append(key)
app.config[key] = self._changes[key]
def __exit__(self, type, value, traceback):
for key in self._originals.keys():
app.config[key] = self._originals[key]
for key in self._to_rm:
del app.config[key]
class ApiTestCase(unittest.TestCase):
maxDiff = None
def _add_csrf(self, without_csrf, explicit_csrf=None):
parts = urlparse(without_csrf)
query = parse_qs(parts[4])
with self.app.session_transaction() as sess:
if explicit_csrf is not None:
query[CSRF_TOKEN_KEY] = explicit_csrf
else:
sess[CSRF_TOKEN_KEY] = 'something'
query[CSRF_TOKEN_KEY] = sess[CSRF_TOKEN_KEY]
return urlunparse(list(parts[0:4]) + [urlencode(query)] + list(parts[5:]))
def url_for(self, resource_name, params=None, skip_csrf=False, explicit_csrf=None):
params = params or {}
url = api.url_for(resource_name, **params)
if not skip_csrf:
url = self._add_csrf(url, explicit_csrf)
return url
def setUp(self):
setup_database_for_testing(self)
self.app = app.test_client()
self.ctx = app.test_request_context()
self.ctx.__enter__()
def tearDown(self):
finished_database_for_testing(self)
config_provider.clear()
self.ctx.__exit__(True, None, None)
def setCsrfToken(self, token):
with self.app.session_transaction() as sess:
sess[CSRF_TOKEN_KEY] = token
@contextmanager
def toggleFeature(self, name, enabled):
import features
previous_value = getattr(features, name)
setattr(features, name, enabled)
yield
setattr(features, name, previous_value)
def getJsonResponse(self, resource_name, params={}, expected_code=200):
rv = self.app.get(api.url_for(resource_name, **params))
self.assertEquals(expected_code, rv.status_code)
data = rv.data
parsed = py_json.loads(data)
return parsed
def postResponse(self, resource_name, params={}, data={}, file=None, headers=None,
expected_code=200):
data = py_json.dumps(data)
headers = headers or {}
headers.update({"Content-Type": "application/json"})
if file is not None:
data = {'file': file}
headers = None
rv = self.app.post(self.url_for(resource_name, params), data=data, headers=headers)
self.assertEquals(rv.status_code, expected_code)
return rv.data
def getResponse(self, resource_name, params={}, expected_code=200):
rv = self.app.get(api.url_for(resource_name, **params))
self.assertEquals(rv.status_code, expected_code)
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"})
self.assertEquals(rv.status_code, expected_code)
return rv.data
def deleteResponse(self, resource_name, params={}, expected_code=204):
rv = self.app.delete(self.url_for(resource_name, params))
if rv.status_code != expected_code:
print 'Mismatch data for resource DELETE %s: %s' % (resource_name, rv.data)
self.assertEquals(rv.status_code, expected_code)
return rv.data
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
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"})
if rv.status_code != expected_code:
print 'Mismatch data for resource POST %s: %s' % (resource_name, rv.data)
self.assertEquals(rv.status_code, expected_code)
data = rv.data
parsed = py_json.loads(data)
return parsed
def putJsonResponse(self, resource_name, params={}, data={}, expected_code=200, skip_csrf=False,
explicit_csrf=None):
rv = self.app.put(
self.url_for(resource_name, params, skip_csrf, explicit_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)
self.assertEquals(rv.status_code, expected_code)
data = rv.data
parsed = py_json.loads(data)
return parsed
def assertNotInTeam(self, data, membername):
for memberData in data['members']:
if memberData['name'] == membername:
self.fail(membername + ' found in team: ' + data['name'])
def assertInTeam(self, data, membername):
for member_data in data['members']:
if member_data['name'] == membername:
return
self.fail(membername + ' not found in team: ' + data['name'])
def login(self, username, password='password'):
return self.postJsonResponse(Signin, data=dict(username=username, password=password))
class TestCSRFFailure(ApiTestCase):
def test_csrf_failure(self):
self.login(READ_ACCESS_USER)
# Make sure a simple post call succeeds.
self.putJsonResponse(User, data=dict(password='newpasswordiscool'))
# Change the session's CSRF token.
self.setCsrfToken('someinvalidtoken')
# Verify that the call now fails.
self.putJsonResponse(User, data=dict(password='newpasswordiscool'), expected_code=403,
explicit_csrf='foobar')
def test_csrf_failure_empty_token(self):
self.login(READ_ACCESS_USER)
# Change the session's CSRF token to be empty.
self.setCsrfToken('')
# Verify that the call now fails.
self.putJsonResponse(User, data=dict(password='newpasswordiscool'), expected_code=403,
explicit_csrf='foobar')
def test_csrf_failure_missing_token(self):
self.login(READ_ACCESS_USER)
# Make sure a simple post call without a token at all fails.
self.putJsonResponse(User, data=dict(password='newpasswordiscool'), skip_csrf=True,
expected_code=403)
# Change the session's CSRF token to be empty.
self.setCsrfToken('')
# Verify that the call still fails.
self.putJsonResponse(User, data=dict(password='newpasswordiscool'), skip_csrf=True,
expected_code=403, explicit_csrf='foobar')
class TestDiscovery(ApiTestCase):
def test_discovery(self):
json = self.getJsonResponse(DiscoveryResource)
assert 'paths' in json
class TestErrorDescription(ApiTestCase):
def test_get_error(self):
json = self.getJsonResponse(Error, params=dict(error_type='not_found'))
assert json['title'] == 'not_found'
assert 'type' in json
assert 'description' in json
class TestPlans(ApiTestCase):
def test_plans(self):
json = self.getJsonResponse(ListPlans)
found = set([])
for method_info in json['plans']:
found.add(method_info['stripeId'])
assert 'free' in found
class TestLoggedInUser(ApiTestCase):
def test_guest(self):
self.getJsonResponse(User, expected_code=401)
def test_user(self):
self.login(READ_ACCESS_USER)
json = self.getJsonResponse(User)
assert json['anonymous'] == False
assert json['username'] == READ_ACCESS_USER
class TestUserStarredRepositoryList(ApiTestCase):
def test_get_stars_guest(self):
self.getJsonResponse(StarredRepositoryList, expected_code=401)
def test_get_stars_user(self):
self.login(READ_ACCESS_USER)
# Queries: Base + the list query
with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 1):
self.getJsonResponse(StarredRepositoryList, expected_code=200)
def test_star_repo_guest(self):
self.postJsonResponse(StarredRepositoryList, data={
'namespace': 'public',
'repository': 'publicrepo',
}, expected_code=401)
def test_star_and_unstar_repo_user(self):
self.login(READ_ACCESS_USER)
# Queries: Base + the list query
with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 1):
json = self.getJsonResponse(StarredRepositoryList)
assert json['repositories'] == []
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)
json = self.getJsonResponse(StarredRepositoryList)
assert json['repositories'] == []
class TestUserNotification(ApiTestCase):
def test_get(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(UserNotificationList)
# Make sure each notification can be retrieved.
for notification in json['notifications']:
njson = self.getJsonResponse(UserNotification, params=dict(uuid=notification['id']))
self.assertEquals(notification['id'], njson['id'])
# Update a notification.
assert json['notifications']
assert not json['notifications'][0]['dismissed']
notification = json['notifications'][0]
pjson = self.putJsonResponse(UserNotification, params=dict(uuid=notification['id']),
data=dict(dismissed=True))
self.assertEquals(True, pjson['dismissed'])
def test_org_notifications(self):
# Create a notification on the organization.
org = model.user.get_user_or_org(ORGANIZATION)
model.notification.create_notification('test_notification', org, {'org': 'notification'})
# Ensure it is visible to the org admin.
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(UserNotificationList)
notification = json['notifications'][0]
self.assertEquals(notification['kind'], 'test_notification')
self.assertEquals(notification['metadata'], {'org': 'notification'})
# Ensure it is not visible to an org member.
self.login(READ_ACCESS_USER)
json = self.getJsonResponse(UserNotificationList)
self.assertEquals(0, len(json['notifications']))
class TestGetUserPrivateAllowed(ApiTestCase):
def test_nonallowed(self):
self.login(READ_ACCESS_USER)
json = self.getJsonResponse(PrivateRepositories)
assert json['privateCount'] == 0
assert not json['privateAllowed']
def test_allowed(self):
self.login(ADMIN_ACCESS_USER)
# Change the subscription of the namespace.
self.putJsonResponse(UserPlan, data=dict(plan='personal-2018'))
json = self.getJsonResponse(PrivateRepositories)
assert json['privateCount'] >= 6
assert not json['privateAllowed']
# Change the subscription of the namespace.
self.putJsonResponse(UserPlan, data=dict(plan='bus-large-2018'))
json = self.getJsonResponse(PrivateRepositories)
assert json['privateAllowed']
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)
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)
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)
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)
self.assertEqual('The admin user credentials are not valid', json['detail'])
def test_convert(self):
self.login(READ_ACCESS_USER)
# Add at least one permission for the read-user.
read_user = model.user.get_user(READ_ACCESS_USER)
simple_repo = model.repository.get_repository(ADMIN_ACCESS_USER, 'simple')
read_role = database.Role.get(name='read')
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'
})
self.assertEqual(True, json['success'])
# Verify the organization exists.
organization = model.organization.get_organization(READ_ACCESS_USER)
assert organization is not None
# Verify the admin user is the org's admin.
self.login(ADMIN_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())
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'
})
self.assertEqual(True, json['success'])
# Verify the organization exists.
organization = model.organization.get_organization(READ_ACCESS_USER)
assert organization is not None
# Verify the admin user is the org's admin.
self.login(ADMIN_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'])
class TestChangeUserDetails(ApiTestCase):
def test_changepassword(self):
self.login(READ_ACCESS_USER)
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.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'))
def test_changeinvoiceemail(self):
self.login(READ_ACCESS_USER)
json = self.putJsonResponse(User, data=dict(invoice_email=True))
self.assertEquals(True, json['invoice_email'])
json = self.putJsonResponse(User, data=dict(invoice_email=False))
self.assertEquals(False, json['invoice_email'])
def test_changeusername_temp(self):
self.login(READ_ACCESS_USER)
user = model.user.get_user(READ_ACCESS_USER)
model.user.create_user_prompt(user, 'confirm_username')
self.assertTrue(model.user.has_user_prompt(user, 'confirm_username'))
# Add a robot under the user's namespace.
model.user.create_robot('somebot', user)
# Rename the user.
json = self.putJsonResponse(User, data=dict(username='someotherusername'))
# Ensure the username was changed.
self.assertEquals('someotherusername', json['username'])
self.assertFalse(model.user.has_user_prompt(user, 'confirm_username'))
# Ensure the robot was changed.
self.assertIsNone(model.user.get_user(READ_ACCESS_USER + '+somebot'))
self.assertIsNotNone(model.user.get_user('someotherusername+somebot'))
def test_changeusername_temp_samename(self):
self.login(READ_ACCESS_USER)
user = model.user.get_user(READ_ACCESS_USER)
model.user.create_user_prompt(user, 'confirm_username')
self.assertTrue(model.user.has_user_prompt(user, 'confirm_username'))
json = self.putJsonResponse(User, data=dict(username=READ_ACCESS_USER))
# Ensure the username was not changed but they are no longer temporarily named.
self.assertEquals(READ_ACCESS_USER, json['username'])
self.assertFalse(model.user.has_user_prompt(user, 'confirm_username'))
def test_changeusername_notallowed(self):
with self.toggleFeature('USER_RENAME', False):
self.login(ADMIN_ACCESS_USER)
user = model.user.get_user(ADMIN_ACCESS_USER)
self.assertFalse(model.user.has_user_prompt(user, 'confirm_username'))
json = self.putJsonResponse(User, data=dict(username='someotherusername'))
self.assertEquals(ADMIN_ACCESS_USER, json['username'])
self.assertTrue('prompts' in json)
self.assertIsNone(model.user.get_user('someotherusername'))
self.assertIsNotNone(model.user.get_user(ADMIN_ACCESS_USER))
def test_changeusername_allowed(self):
with self.toggleFeature('USER_RENAME', True):
self.login(ADMIN_ACCESS_USER)
user = model.user.get_user(ADMIN_ACCESS_USER)
self.assertFalse(model.user.has_user_prompt(user, 'confirm_username'))
json = self.putJsonResponse(User, data=dict(username='someotherusername'))
self.assertEquals('someotherusername', json['username'])
self.assertTrue('prompts' in json)
self.assertIsNotNone(model.user.get_user('someotherusername'))
self.assertIsNone(model.user.get_user(ADMIN_ACCESS_USER))
def test_changeusername_already_used(self):
self.login(READ_ACCESS_USER)
user = model.user.get_user(READ_ACCESS_USER)
model.user.create_user_prompt(user, 'confirm_username')
self.assertTrue(model.user.has_user_prompt(user, 'confirm_username'))
# Try to change to a used username.
self.putJsonResponse(User, data=dict(username=ADMIN_ACCESS_USER), expected_code=400)
# Change to a new username.
self.putJsonResponse(User, data=dict(username='unusedusername'))
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)
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)
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)
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)
self.assertEquals(True, data['awaiting_verification'])
def test_createuser_captcha(self):
@urlmatch(netloc=r'(.*\.)?google.com', path='/recaptcha/api/siteverify')
def captcha_endpoint(url, request):
if url.query.find('response=somecode') > 0:
return {'status_code': 200, 'content': py_json.dumps({'success': True})}
else:
return {'status_code': 400, 'content': py_json.dumps({'success': False})}
with HTTMock(captcha_endpoint):
with self.toggleFeature('RECAPTCHA', True):
# Try with a missing captcha.
self.postResponse(User, data=NEW_USER_DETAILS, expected_code=400)
# Try with an invalid captcha.
details = dict(NEW_USER_DETAILS)
details['recaptcha_response'] = 'someinvalidcode'
self.postResponse(User, data=details, expected_code=400)
# Try with a valid captcha.
details = dict(NEW_USER_DETAILS)
details['recaptcha_response'] = 'somecode'
self.postResponse(User, data=details, expected_code=200)
def test_createuser_withteaminvite(self):
inviter = model.user.get_user(ADMIN_ACCESS_USER)
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.update(NEW_USER_DETAILS)
data = self.postJsonResponse(User, data=details, expected_code=200)
# Make sure the user is verified since the email address of the user matches
# that of the team invite.
self.assertFalse('awaiting_verification' in data)
# 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'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
def test_createuser_withteaminvite_differentemails(self):
inviter = model.user.get_user(ADMIN_ACCESS_USER)
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.update(NEW_USER_DETAILS)
data = self.postJsonResponse(User, data=details, expected_code=200)
# Make sure the user is *not* verified since the email address of the user
# does not match that of the team invite.
self.assertTrue(data['awaiting_verification'])
# 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'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
def test_createuser_withmultipleteaminvites(self):
inviter = model.user.get_user(ADMIN_ACCESS_USER)
owners_team = model.team.get_organization_team(ORGANIZATION, 'owners')
readers_team = model.team.get_organization_team(ORGANIZATION, 'readers')
other_owners_team = model.team.get_organization_team('library', 'owners')
owners_invite = model.team.add_or_invite_to_team(inviter, owners_team, None,
NEW_USER_DETAILS['email'])
readers_invite = model.team.add_or_invite_to_team(inviter, readers_team, None,
NEW_USER_DETAILS['email'])
other_owners_invite = model.team.add_or_invite_to_team(inviter, other_owners_team, None,
NEW_USER_DETAILS['email'])
# Create the user and ensure they have a verified email address.
details = {'invite_code': owners_invite.invite_token}
details.update(NEW_USER_DETAILS)
data = self.postJsonResponse(User, data=details, expected_code=200)
# Make sure the user is verified since the email address of the user matches
# that of the team invite.
self.assertFalse('awaiting_verification' in data)
# 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'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
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'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
# Accept the first invitation.
self.login(NEW_USER_DETAILS['username'])
self.putJsonResponse(TeamMemberInvite, params=dict(code=owners_invite.invite_token))
# Make sure both codes are now invalid.
self.putResponse(TeamMemberInvite, params=dict(code=owners_invite.invite_token),
expected_code=400)
self.putResponse(TeamMemberInvite, params=dict(code=readers_invite.invite_token),
expected_code=400)
# 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'))
self.assertInTeam(json, NEW_USER_DETAILS['username'])
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'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
# Accept the second invitation.
self.login(NEW_USER_DETAILS['username'])
self.putJsonResponse(TeamMemberInvite, params=dict(code=other_owners_invite.invite_token))
# 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'))
self.assertInTeam(json, NEW_USER_DETAILS['username'])
# Make sure the invitation codes are now invalid.
self.putResponse(TeamMemberInvite, params=dict(code=other_owners_invite.invite_token),
expected_code=400)
class TestDeleteNamespace(ApiTestCase):
def test_deletenamespaces(self):
self.login(ADMIN_ACCESS_USER)
# Try to first delete the user. Since they are the sole admin of three orgs, it should fail.
with check_transitive_modifications():
self.deleteResponse(User, expected_code=400)
# 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.deleteEmptyResponse(Organization, params=dict(orgname='library'), expected_code=204)
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.
notification_queue.put([ADMIN_ACCESS_USER, 'somerepo', 'somename'], '{}')
dockerfile_build_queue.put([ADMIN_ACCESS_USER, 'anotherrepo'], '{}')
# Now delete the user.
with check_transitive_modifications():
self.deleteEmptyResponse(User, expected_code=204)
# Ensure the queue items are gone.
self.assertIsNone(notification_queue.get())
self.assertIsNone(dockerfile_build_queue.get())
def test_delete_federateduser(self):
self.login(PUBLIC_USER)
# Add some federated logins.
user = model.user.get_user(PUBLIC_USER)
model.user.attach_federated_login(user, 'github', 'something', {})
with check_transitive_modifications():
self.deleteEmptyResponse(User, expected_code=204)
def test_delete_prompted_user(self):
self.login('randomuser')
with check_transitive_modifications():
self.deleteEmptyResponse(User, expected_code=204)
class TestSignin(ApiTestCase):
def test_signin_unicode(self):
self.postResponse(Signin, data=dict(username=u'\xe5\x8c\x97\xe4\xba\xac\xe5\xb8\x82',
password='password'), expected_code=403)
def test_signin_invitecode(self):
# Create a new user (unverified)
data = self.postJsonResponse(User, data=NEW_USER_DETAILS, expected_code=200)
self.assertTrue(data['awaiting_verification'])
# Try to sign in without an invite code.
data = self.postJsonResponse(Signin, data=NEW_USER_DETAILS, expected_code=403)
self.assertTrue(data['needsEmailVerification'])
# Try to sign in with an invalid invite code.
details = {'invite_code': 'someinvalidcode'}
details.update(NEW_USER_DETAILS)
data = self.postJsonResponse(Signin, data=details, expected_code=403)
self.assertTrue(data['needsEmailVerification'])
# Sign in with an invite code and ensure the user becomes verified.
inviter = model.user.get_user(ADMIN_ACCESS_USER)
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.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)
read_user = model.user.get_user(READ_ACCESS_USER)
json = self.getJsonResponse(User)
assert json['username'] == READ_ACCESS_USER
self.postResponse(Signout)
# Make sure we're now signed out.
self.getJsonResponse(User, expected_code=401)
# Make sure the user's UUID has rotated, to ensure sessions are no longer valid.
read_user_again = model.user.get_user(READ_ACCESS_USER)
self.assertNotEquals(read_user.uuid, read_user_again.uuid)
class TestConductSearch(ApiTestCase):
def test_noaccess(self):
self.login(NO_ACCESS_USER)
json = self.getJsonResponse(ConductSearch, params=dict(query='read'))
self.assertEquals(0, len(json['results']))
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'))
self.assertEquals(0, len(json['results']))
json = self.getJsonResponse(ConductSearch, params=dict(query='public'))
self.assertEquals(2, len(json['results']))
self.assertEquals(json['results'][0]['kind'], 'repository')
self.assertEquals(json['results'][0]['name'], 'publicrepo')
self.assertEquals(json['results'][1]['kind'], 'user')
self.assertEquals(json['results'][1]['name'], 'public')
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'))
self.assertEquals(0, len(json['results']))
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'))
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'))
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'))
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'))
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'))
self.assertEquals(1, len(json['results']))
self.assertEquals(json['results'][0]['kind'], 'repository')
self.assertEquals(json['results'][0]['name'], 'text-full-repo')
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'))
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))
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'))
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'))
names = set([r['name'] for r in json['results']])
assert 'outsideorg' in names
assert not 'owners' in names
def test_prefix_disabled(self):
with patch('features.PARTIAL_USER_AUTOCOMPLETE', False):
self.login(NO_ACCESS_USER)
json = self.getJsonResponse(EntitySearch, params=dict(prefix='o', namespace=ORGANIZATION,
includeTeams='true'))
names = set([r['name'] for r in json['results']])
assert not 'outsideorg' in names
assert not 'owners' in names
json = self.getJsonResponse(EntitySearch, params=dict(prefix='outsideorg', namespace=ORGANIZATION,
includeTeams='true'))
names = set([r['name'] for r in json['results']])
assert 'outsideorg' in names
assert not 'owners' in names
def test_inorg(self):
self.login(ADMIN_ACCESS_USER)
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
assert 'owners' in names
def test_inorg_withorgs(self):
self.login(ADMIN_ACCESS_USER)
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
class TestCreateOrganization(ApiTestCase):
def test_existinguser(self):
self.login(ADMIN_ACCESS_USER)
json = self.postJsonResponse(OrganizationList,
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'])
def test_existingorg(self):
self.login(ADMIN_ACCESS_USER)
json = self.postJsonResponse(OrganizationList,
data=dict(name=ORGANIZATION, email='testorg@example.com'),
expected_code=400)
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)
self.assertEquals('"Created"', data.strip())
# Ensure the org was created.
organization = model.organization.get_organization('neworg')
assert organization is not None
# Verify the admin user is the org's admin.
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'),
expected_code=401)
# Attempt with auth with invalid scope.
dt_user = model.user.get_user(ADMIN_ACCESS_USER)
token, code = model.oauth.create_access_token_for_testing(dt_user, 'deadbeef', 'repo:read')
self.postResponse(OrganizationList, data=dict(name='neworg', email='testorg@example.com'),
headers=dict(Authorization='Bearer ' + code),
expected_code=403)
# Create OAuth token with user:admin scope.
token, code = model.oauth.create_access_token_for_testing(dt_user, 'deadbeef', 'user:admin',
access_token='d' * 40)
data = self.postResponse(
OrganizationList, data=dict(name='neworg', email='testorg@example.com'),
headers=dict(Authorization='Bearer ' + code), expected_code=201)
self.assertEquals('"Created"', data.strip())
class TestGetOrganization(ApiTestCase):
def test_unknownorg(self):
self.login(ADMIN_ACCESS_USER)
self.getResponse(Organization, params=dict(orgname='notvalid'), expected_code=404)
def test_cannotaccess(self):
self.login(NO_ACCESS_USER)
self.getResponse(Organization, params=dict(orgname=ORGANIZATION), expected_code=200)
def test_getorganization(self):
self.login(READ_ACCESS_USER)
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))
self.assertEquals(ORGANIZATION, json['name'])
self.assertEquals(True, json['is_admin'])
class TestChangeOrganizationDetails(ApiTestCase):
def test_changeinvoiceemail(self):
self.login(ADMIN_ACCESS_USER)
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),
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),
data=dict(email='newemail@example.com'))
self.assertEquals('newemail@example.com', json['email'])
class TestGetOrganizationPrototypes(ApiTestCase):
def test_getprototypes(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))
assert len(json['prototypes']) > 0
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)
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)
def test_createprototype(self):
self.login(ADMIN_ACCESS_USER)
json = self.postJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION),
data=dict(role='read',
delegate={'kind': 'team',
'name': 'readers'}))
self.assertEquals('read', json['role'])
pid = json['id']
# Verify the prototype exists.
json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))
ids = set([p['id'] for p in json['prototypes']])
assert pid in ids
class TestDeleteOrganizationPrototypes(ApiTestCase):
def test_deleteprototype(self):
self.login(ADMIN_ACCESS_USER)
# Get the existing prototypes
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))
# Verify the prototype no longer exists.
json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))
newids = [p['id'] for p in json['prototypes']]
assert not pid in newids
class TestUpdateOrganizationPrototypes(ApiTestCase):
def test_updateprototype(self):
self.login(ADMIN_ACCESS_USER)
# Get the existing prototypes
json = self.getJsonResponse(PermissionPrototypeList, params=dict(orgname=ORGANIZATION))
ids = [p['id'] for p in json['prototypes']]
pid = ids[0]
# Update a prototype.
json = self.putJsonResponse(PermissionPrototype,
params=dict(orgname=ORGANIZATION, prototypeid=pid),
data=dict(role='admin'))
self.assertEquals('admin', json['role'])
class TestGetOrganizationMembers(ApiTestCase):
def test_getmembers(self):
self.login(ADMIN_ACCESS_USER)
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
assert not NO_ACCESS_USER in membernames
for member in json['members']:
membername = member['name']
response = self.getJsonResponse(OrganizationMember,
params=dict(orgname=ORGANIZATION, membername=membername))
self.assertEquals(member, response)
class TestRemoveOrganizationMember(ApiTestCase):
def test_try_remove_only_admin(self):
self.login(ADMIN_ACCESS_USER)
self.deleteResponse(OrganizationMember,
params=dict(orgname=ORGANIZATION, membername=ADMIN_ACCESS_USER),
expected_code=400)
def test_remove_member(self):
self.login(ADMIN_ACCESS_USER)
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))
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')
# 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')
]
assert READ_ACCESS_USER in admin_perms
# Add read user as a direct permission on the org repo.
model.permission.set_user_repo_permission(READ_ACCESS_USER, ORGANIZATION, ORG_REPO, 'read')
# Verify the user has a permission on the org repo.
org_perms = [p.user.username for p in model.user.get_all_repo_users(ORGANIZATION, ORG_REPO)]
assert READ_ACCESS_USER in org_perms
# Remove the user from the org.
self.deleteEmptyResponse(OrganizationMember,
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')
]
assert READ_ACCESS_USER in admin_perms
class TestGetOrganizationPrivateAllowed(ApiTestCase):
def test_existingorg(self):
self.login(ADMIN_ACCESS_USER)
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'),
expected_code=201)
json = self.getJsonResponse(OrgPrivateRepositories, params=dict(orgname='neworg'))
self.assertEquals(False, json['privateAllowed'])
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'))
self.assertEquals('My cool team', data['description'])
self.assertEquals('creator', data['role'])
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)
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'))
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))
assert 'newteam' in json['teams']
class TestDeleteOrganizationTeam(ApiTestCase):
def test_deleteteam(self):
self.login(ADMIN_ACCESS_USER)
self.deleteEmptyResponse(OrganizationTeam,
params=dict(orgname=ORGANIZATION, teamname='readers'))
# Make sure the team was deleted
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)
data = py_json.loads(resp)
msg = ("Deleting team 'owners' would remove admin ability for user " +
"'devtable' in organization 'buynlarge'")
self.assertEquals(msg, data['message'])
class TestTeamPermissions(ApiTestCase):
def test_team_permissions(self):
self.login(ADMIN_ACCESS_USER)
resp = self.getJsonResponse(TeamPermissions,
params=dict(orgname=ORGANIZATION, teamname='readers'))
self.assertEquals(1, len(resp['permissions']))
class TestGetOrganizationTeamMembers(ApiTestCase):
def test_invalidteam(self):
self.login(ADMIN_ACCESS_USER)
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'))
self.assertInTeam(json, READ_ACCESS_USER)
class TestUpdateOrganizationTeamMember(ApiTestCase):
def test_addmember_alreadyteammember(self):
self.login(ADMIN_ACCESS_USER)
membername = READ_ACCESS_USER
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))
# Verify the user was added to the team.
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))
# Verify the user was added to the team.
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)
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))
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'))
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))
# Remove the user from the non-synced team, which should succeed.
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)
# 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)
# Add a robot to the synced team, which should succeed.
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'))
# 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'))
# 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'),
expected_code=400)
class TestAcceptTeamMemberInvite(ApiTestCase):
def test_accept(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))
self.assertEquals(True, response['invited'])
# Login as the user.
self.login(membername)
# Accept the invite.
user = model.user.get_user(membername)
invites = list(model.team.lookup_team_invites(user))
self.assertEquals(1, len(invites))
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'))
self.assertInTeam(json, membername)
# Verify the accept now fails.
self.putResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token),
expected_code=400)
def test_accept_via_email(self):
self.login(ADMIN_ACCESS_USER)
# Create the invite.
member = model.user.get_user(NO_ACCESS_USER)
response = self.putJsonResponse(InviteTeamMember, params=dict(
orgname=ORGANIZATION, teamname='owners', email=member.email))
self.assertEquals(True, response['invited'])
# Login as the user.
self.login(member.username)
# Accept the invite.
invites = list(model.team.lookup_team_invites_by_email(member.email))
self.assertEquals(1, len(invites))
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'))
self.assertInTeam(json, member.username)
# Verify the accept now fails.
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))
self.assertEquals(True, response['invited'])
# Login as a different user.
self.login(PUBLIC_USER)
# Try to accept the invite.
user = model.user.get_user(NO_ACCESS_USER)
invites = list(model.team.lookup_team_invites(user))
self.assertEquals(1, len(invites))
self.putResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token),
expected_code=400)
# Ensure the invite is still valid.
user = model.user.get_user(NO_ACCESS_USER)
invites = list(model.team.lookup_team_invites(user))
self.assertEquals(1, len(invites))
# 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'))
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'))
self.assertEquals(True, response['invited'])
# Login as a different user.
self.login(PUBLIC_USER)
# Try to accept the invite.
invites = list(model.team.lookup_team_invites_by_email('someemail@example.com'))
self.assertEquals(1, len(invites))
self.putResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token),
expected_code=400)
# Ensure the invite is still valid.
invites = list(model.team.lookup_team_invites_by_email('someemail@example.com'))
self.assertEquals(1, len(invites))
# 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'))
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))
self.assertEquals(True, response['invited'])
# Try to decline the invite.
user = model.user.get_user(membername)
invites = list(model.team.lookup_team_invites(user))
self.assertEquals(1, len(invites))
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))
self.assertEquals(True, response['invited'])
# Login as the user.
self.login(membername)
# Decline the invite.
user = model.user.get_user(membername)
invites = list(model.team.lookup_team_invites(user))
self.assertEquals(1, len(invites))
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),
expected_code=400)
class TestDeleteOrganizationTeamMember(ApiTestCase):
def test_deletememberinvite(self):
self.login(ADMIN_ACCESS_USER)
# Verify the initial member count
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))
self.assertEquals(True, response['invited'])
# Verify the invite was added.
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))
# Verify the user was removed from the team.
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))
# Verify the user was removed from the team.
json = self.getJsonResponse(TeamMemberList, params=dict(orgname=ORGANIZATION,
teamname='readers'))
self.assertEquals(len(json['members']), 1)
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)
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)
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)
self.assertEquals(ADMIN_ACCESS_USER, json['namespace'])
self.assertEquals('newrepo', json['name'])
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'),
expected_code=201)
self.assertEquals(ADMIN_ACCESS_USER, json['namespace'])
self.assertEquals('newrepo', json['name'])
self.assertEquals('application', json['kind'])
def test_createrepo_underorg(self):
self.login(ADMIN_ACCESS_USER)
json = self.postJsonResponse(RepositoryList, data=dict(
namespace=ORGANIZATION, repository='newrepo', visibility='private', description=''),
expected_code=201)
self.assertEquals(ORGANIZATION, json['namespace'])
self.assertEquals('newrepo', json['name'])
class TestListRepos(ApiTestCase):
def test_list_app_repos(self):
self.login(ADMIN_ACCESS_USER)
# Create an app repo.
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'))
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):
json = self.getJsonResponse(RepositoryList, params=dict(public=True))
self.assertEquals(len(json['repositories']), 1)
def assertPublicRepos(self, has_extras=False):
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)):
model.gc.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')
required.add(name)
# Request results until we no longer have any.
next_page = None
while True:
json = self.getJsonResponse(RepositoryList, params=dict(public=True, next_page=next_page))
for repo in json['repositories']:
name = repo['name']
if name in required:
required.remove(name)
else:
self.assertTrue(has_extras, "Could not find name %s in repos created" % name)
if 'next_page' in json:
self.assertEquals(len(json['repositories']), REPOS_PER_PAGE)
else:
break
next_page = json['next_page']
def test_listrepos_asguest_withpages(self):
self.assertPublicRepos()
def test_listrepos_asorgmember_withpages(self):
self.login(READ_ACCESS_USER)
self.assertPublicRepos(has_extras=True)
def test_listrepos_filter(self):
self.login(READ_ACCESS_USER)
json = self.getJsonResponse(RepositoryList, params=dict(namespace=ORGANIZATION, public=False))
self.assertGreater(len(json['repositories']), 0)
for repo in json['repositories']:
self.assertEquals(ORGANIZATION, repo['namespace'])
def test_listrepos_allparams(self):
# Add a repository action count entry for one of the org repos.
repo = model.repository.get_repository(ORGANIZATION, ORG_REPO)
RepositoryActionCount.create(repository=repo, count=10, date=datetime.datetime.utcnow())
self.login(ADMIN_ACCESS_USER)
# Queries: Base + the list query + the popularity and last modified queries + full perms load
with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 5):
json = self.getJsonResponse(RepositoryList, params=dict(namespace=ORGANIZATION, public=False,
last_modified=True, popularity=True))
self.assertGreater(len(json['repositories']), 0)
for repository in json['repositories']:
self.assertEquals(ORGANIZATION, repository['namespace'])
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)
def test_listrepos_starred(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(RepositoryList, params=dict(last_modified=True, popularity=True,
starred=True))
self.assertTrue(len(json['repositories']) > 0)
for repo in json['repositories']:
self.assertTrue(repo['is_starred'])
def test_listrepos_asguest_allparams(self):
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))
self.assertEquals(1, len(json['repositories']))
self.assertEquals(name, json['repositories'][0]['name'])
def assertRepositoryNotVisible(self, namespace, name):
json = self.getJsonResponse(RepositoryList, params=dict(namespace=namespace, public=False))
for repo in json['repositories']:
self.assertNotEquals(name, repo['name'])
json = self.getJsonResponse(RepositoryList, params=dict(starred=True))
for repo in json['repositories']:
self.assertNotEquals(name, repo['name'])
def test_listrepos_starred_filtered(self):
admin_user = model.user.get_user(ADMIN_ACCESS_USER)
reader_user = model.user.get_user(READ_ACCESS_USER)
# Create a new organization.
new_org = model.organization.create_organization('neworg', 'neworg@devtable.com', admin_user)
admin_team = model.team.create_team('admin', new_org, 'admin')
# Add a repository to the organization.
repo = model.repository.create_repository('neworg', 'somerepo', admin_user)
with self.add_to_team_temporarily(reader_user, admin_team):
# Star the repository for the user.
model.repository.star_repository(reader_user, repo)
# Verify that the user cannot see the repo, since they are no longer allowed to do so.
self.login(READ_ACCESS_USER)
self.assertRepositoryNotVisible('neworg', 'somerepo')
@contextmanager
def add_to_team_temporarily(self, user, team):
model.team.add_user_to_team(user, team)
yield
model.team.remove_user_from_team(team.organization.username, team.name, user.username,
ADMIN_ACCESS_USER)
def test_listrepos_org_filtered(self):
admin_user = model.user.get_user(ADMIN_ACCESS_USER)
reader_user = model.user.get_user(READ_ACCESS_USER)
# Create a new organization.
new_org = model.organization.create_organization('neworg', 'neworg@devtable.com', admin_user)
admin_team = model.team.create_team('admin', new_org, 'admin')
creator_team = model.team.create_team('creators', new_org, 'creator')
member_team = model.team.create_team('members', new_org, 'member')
# Add a repository to the organization.
model.repository.create_repository('neworg', 'somerepo', admin_user)
# Verify that the admin user can view it.
self.login(ADMIN_ACCESS_USER)
self.assertRepositoryVisible('neworg', 'somerepo')
# Add reader to a creator team under the org and verify they *cannot* see the repository.
with self.add_to_team_temporarily(reader_user, creator_team):
self.login(READ_ACCESS_USER)
self.assertRepositoryNotVisible('neworg', 'somerepo')
# Add reader to a member team under the org and verify they *cannot* see the repository.
with self.add_to_team_temporarily(reader_user, member_team):
self.login(READ_ACCESS_USER)
self.assertRepositoryNotVisible('neworg', 'somerepo')
# Add reader to an admin team under the org and verify they *can* see the repository.
with self.add_to_team_temporarily(reader_user, admin_team):
self.login(READ_ACCESS_USER)
self.assertRepositoryVisible('neworg', 'somerepo')
# Verify that the public user cannot see the repository.
self.login(PUBLIC_USER)
self.assertRepositoryNotVisible('neworg', 'somerepo')
class TestViewPublicRepository(ApiTestCase):
def test_normalview(self):
resp = self.getJsonResponse(Repository, params=dict(repository='public/publicrepo'))
self.assertFalse('stats' in resp)
def test_normalview_withstats(self):
resp = self.getJsonResponse(Repository, params=dict(repository='public/publicrepo',
includeStats=True))
self.assertTrue('stats' in resp)
def test_anon_access_disabled(self):
import features
features.ANONYMOUS_ACCESS = False
try:
self.getResponse(Repository, params=dict(repository='public/publicrepo'), expected_code=401)
finally:
features.ANONYMOUS_ACCESS = True
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),
data=dict(description='Some cool repo'))
# Verify the repo description was updated.
json = self.getJsonResponse(Repository, params=dict(repository=self.SIMPLE_REPO))
self.assertEquals('Some cool repo', json['description'])
class TestChangeRepoVisibility(ApiTestCase):
SIMPLE_REPO = ADMIN_ACCESS_USER + '/simple'
def test_trychangevisibility(self):
self.login(ADMIN_ACCESS_USER)
# Make public.
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))
self.assertEquals(True, json['is_public'])
# Change the subscription of the namespace.
self.putJsonResponse(UserPlan, data=dict(plan='personal-2018'))
# Try to make private.
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))
self.assertEquals(True, json['is_public'])
def test_changevisibility(self):
self.login(ADMIN_ACCESS_USER)
# Make public.
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))
self.assertEquals(True, json['is_public'])
# Make private.
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))
self.assertEquals(False, json['is_public'])
class log_queries(object):
def __init__(self, query_filters=None):
self.filters = query_filters
def get_queries(self):
queries = [q.msg[0] for q in self._handler.queries]
if not self.filters:
return queries
filtered_queries = []
for query_filter in self.filters:
filtered_queries.extend([q for q in queries if re.match(query_filter, q)])
return filtered_queries
def __enter__(self):
logger = logging.getLogger('peewee')
self._handler = _QueryLogHandler()
logger.setLevel(logging.DEBUG)
logger.addHandler(self._handler)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
logger = logging.getLogger('peewee')
logger.removeHandler(self._handler)
class check_transitive_modifications(log_queries):
def __init__(self):
filters = [r'^DELETE.+IN \(SELECT.+$', r'^UPDATE.+IN \(SELECT.+$']
super(check_transitive_modifications, self).__init__(query_filters=filters)
def __exit__(self, exc_type, exc_val, exc_tb):
super(check_transitive_modifications, self).__exit__(exc_type, exc_val, exc_tb)
queries = self.get_queries()
if queries:
raise Exception('Detected transitive deletion or update in queries: %s' % queries)
class TestDeleteRepository(ApiTestCase):
SIMPLE_REPO = ADMIN_ACCESS_USER + '/simple'
COMPLEX_REPO = ADMIN_ACCESS_USER + '/complex'
def test_deleterepo(self):
self.login(ADMIN_ACCESS_USER)
# Verify the repo exists.
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'], '{}')
# Delete the repository.
self.deleteEmptyResponse(Repository, params=dict(repository=self.SIMPLE_REPO))
# Ensure the queue item is gone.
self.assertIsNone(dockerfile_build_queue.get())
# Verify the repo was deleted.
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))
# Add a build queue item for the repo and another repo.
dockerfile_build_queue.put([ADMIN_ACCESS_USER, 'simple'], '{}', available_after=-1)
dockerfile_build_queue.put([ADMIN_ACCESS_USER, 'anotherrepo'], '{}', available_after=-1)
# Delete the repository.
self.deleteEmptyResponse(Repository, params=dict(repository=self.SIMPLE_REPO))
# Ensure the other queue item is still present.
self.assertIsNotNone(dockerfile_build_queue.get())
def test_deleterepo2(self):
self.login(ADMIN_ACCESS_USER)
# Verify the repo exists.
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)
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))
# Make sure the repository has some images and tags.
repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, 'complex')
self.assertTrue(len(list(registry_model.get_legacy_images(repo_ref))) > 0)
self.assertTrue(len(list(registry_model.list_all_active_repository_tags(repo_ref))) > 0)
# Add some data for the repository, in addition to is already existing images and tags.
repository = model.repository.get_repository(ADMIN_ACCESS_USER, 'complex')
# Create some access tokens.
access_token = model.token.create_access_token(repository, 'read')
model.token.create_access_token(repository, 'write')
delegate_token = model.token.create_delegate_token(ADMIN_ACCESS_USER, 'complex', 'sometoken',
'read')
model.token.create_delegate_token(ADMIN_ACCESS_USER, 'complex', 'sometoken', 'write')
# Create some repository builds.
model.build.create_repository_build(repository, access_token, {}, 'someid', 'foobar')
model.build.create_repository_build(repository, delegate_token, {}, 'someid2', 'foobar2')
# Create some notifications.
model.notification.create_repo_notification(repository, 'repo_push', 'hipchat', {}, {})
model.notification.create_repo_notification(repository, 'build_queued', 'slack', {}, {})
# Create some logs.
logs_model.log_action('push_repo', ADMIN_ACCESS_USER, repository=repository)
logs_model.log_action('push_repo', ADMIN_ACCESS_USER, repository=repository)
# Create some build triggers.
user = model.user.get_user(ADMIN_ACCESS_USER)
model.build.create_build_trigger(repository, 'github', 'sometoken', user)
model.build.create_build_trigger(repository, 'github', 'anothertoken', user)
# Create some email authorizations.
model.repository.create_email_authorization_for_repo(ADMIN_ACCESS_USER, 'complex', 'a@b.com')
model.repository.create_email_authorization_for_repo(ADMIN_ACCESS_USER, 'complex', 'b@c.com')
# 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)
repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, 'complex')
tag = registry_model.get_repo_tag(repo_ref, 'prod')
manifest = registry_model.get_manifest_for_tag(tag)
# Create some labels.
registry_model.create_manifest_label(manifest, 'foo', 'bar', 'manifest')
registry_model.create_manifest_label(manifest, 'foo', 'baz', 'manifest')
registry_model.create_manifest_label(manifest, 'something', '{}', 'api',
media_type_name='application/json')
registry_model.create_manifest_label(manifest, 'something', '{"some": "json"}', 'manifest')
# Delete the repository.
with check_transitive_modifications():
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)
class TestGetRepository(ApiTestCase):
PUBLIC_REPO = PUBLIC_USER + '/publicrepo'
def test_get_largerepo(self):
self.login(ADMIN_ACCESS_USER)
offset = 0 if registry_model.supports_schema2(ADMIN_ACCESS_USER) else 2
# base + repo + is_starred + tags
with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 4 + offset + 1):
self.getJsonResponse(Repository, params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
# base + repo + is_starred + tags
with assert_query_count(BASE_LOGGEDIN_QUERY_COUNT + 4 + offset + 1):
json = self.getJsonResponse(Repository,
params=dict(repository=ADMIN_ACCESS_USER + '/gargantuan'))
self.assertEquals(ADMIN_ACCESS_USER, json['namespace'])
self.assertEquals('gargantuan', json['name'])
self.assertEquals(False, json['is_public'])
def test_getrepo_badnames(self):
self.login(ADMIN_ACCESS_USER)
bad_names = ['logs', 'build', 'tokens', 'foo.bar', 'foo-bar', 'foo_bar']
# 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=''))
# Make sure we can retrieve its information.
json = self.getJsonResponse(Repository,
params=dict(repository=ADMIN_ACCESS_USER + '/' + bad_name))
self.assertEquals(ADMIN_ACCESS_USER, json['namespace'])
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))
self.assertEquals(PUBLIC_USER, json['namespace'])
self.assertEquals('publicrepo', json['name'])
self.assertEquals(True, json['is_public'])
self.assertEquals(False, json['is_organization'])
self.assertEquals(False, json['can_write'])
self.assertEquals(False, json['can_admin'])
assert 'latest' in json['tags']
def test_getrepo_public_asowner(self):
self.login(PUBLIC_USER)
json = self.getJsonResponse(Repository, params=dict(repository=self.PUBLIC_REPO))
self.assertEquals(False, json['is_organization'])
self.assertEquals(True, json['can_write'])
self.assertEquals(True, json['can_admin'])
def test_getrepo_building(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(Repository,
params=dict(repository=ADMIN_ACCESS_USER + '/building'))
self.assertEquals(True, json['can_write'])
self.assertEquals(True, json['can_admin'])
self.assertEquals(False, json['is_organization'])
def test_getrepo_org_asnonmember(self):
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))
self.assertEquals(ORGANIZATION, json['namespace'])
self.assertEquals(ORG_REPO, json['name'])
self.assertEquals(False, json['can_write'])
self.assertEquals(False, json['can_admin'])
self.assertEquals(True, json['is_organization'])
def test_getrepo_org_asadmin(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(Repository, params=dict(repository=ORGANIZATION + '/' + ORG_REPO))
self.assertEquals(True, json['can_write'])
self.assertEquals(True, json['can_admin'])
self.assertEquals(True, json['is_organization'])
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)
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)
def test_cancel_waitingbuild(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)
uuid = json['id']
# Check for the build.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
self.assertEquals(1, len(json['builds']))
self.assertEquals(uuid, json['builds'][0]['id'])
# Find the build's queue item.
build_ref = database.RepositoryBuild.get(uuid=uuid)
queue_item = database.QueueItem.get(id=build_ref.queue_id)
self.assertTrue(queue_item.available)
self.assertTrue(queue_item.retries_remaining > 0)
# Cancel the build.
self.deleteResponse(RepositoryBuildResource,
params=dict(repository=ADMIN_ACCESS_USER + '/simple', build_uuid=uuid),
expected_code=201)
# Check for the build.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
self.assertEquals(1, len(json['builds']))
self.assertEquals('cancelled', json['builds'][0]['phase'])
# Check for the build's queue item.
try:
database.QueueItem.get(id=build_ref.queue_id)
self.fail('QueueItem still exists for build')
except database.QueueItem.DoesNotExist:
pass
def test_attemptcancel_scheduledbuild(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)
uuid = json['id']
# Check for the build.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
self.assertEquals(1, len(json['builds']))
self.assertEquals(uuid, json['builds'][0]['id'])
# Set queue item to be picked up.
build_ref = database.RepositoryBuild.get(uuid=uuid)
qi = database.QueueItem.get(id=build_ref.queue_id)
qi.available = False
qi.save()
# Try to cancel the build.
self.deleteResponse(RepositoryBuildResource,
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)
uuid = json['id']
# Check for the build.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
self.assertEquals(1, len(json['builds']))
self.assertEquals(uuid, json['builds'][0]['id'])
# Set the build to a different phase.
rb = database.RepositoryBuild.get(uuid=uuid)
rb.phase = database.BUILD_PHASE.BUILDING
rb.save()
# Try to cancel the build.
self.deleteResponse(RepositoryBuildResource,
params=dict(repository=ADMIN_ACCESS_USER + '/simple', build_uuid=uuid),
expected_code=400)
class TestRepoBuilds(ApiTestCase):
def test_getrepo_nobuilds(self):
self.login(ADMIN_ACCESS_USER)
# Queries: Permission + the list query + app check
with assert_query_count(3):
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
assert len(json['builds']) == 0
def test_getrepobuilds(self):
self.login(ADMIN_ACCESS_USER)
# Queries: Permission + the list query + app check
with assert_query_count(3):
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/building'))
assert len(json['builds']) > 0
build = json['builds'][-1]
assert 'id' in build
assert 'status' in build
# Check the status endpoint.
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'])
self.assertEquals(status_json['trigger'], build['trigger'])
class TestRequestRepoBuild(ApiTestCase):
def test_requestbuild_noidurl(self):
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)
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='file://foobarbaz'), expected_code=400)
def test_requestrepobuild_withurl(self):
self.login(ADMIN_ACCESS_USER)
# Ensure we are not yet building.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
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)
# Check for the build.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
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)
# Ensure we are not yet building.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
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)
# Check for the build.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
assert len(json['builds']) > 0
def test_requestrepobuild_with_robot(self):
self.login(ADMIN_ACCESS_USER)
# Ensure we are not yet building.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
assert len(json['builds']) == 0
# 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)
# Check for the build.
json = self.getJsonResponse(RepositoryBuildList,
params=dict(repository=ADMIN_ACCESS_USER + '/building'))
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)
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)
class TestRepositoryEmail(ApiTestCase):
def test_emailnotauthorized(self):
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)
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'))
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'))
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'))
self.assertEquals(False, json['confirmed'])
self.assertEquals(ADMIN_ACCESS_USER, json['namespace'])
self.assertEquals('simple', json['repository'])
class TestRepositoryNotifications(ApiTestCase):
def test_testnotification(self):
self.login(ADMIN_ACCESS_USER)
# Add a notification.
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)
uuid = json['uuid']
self.assertIsNone(notification_queue.get())
# Issue a test notification.
self.postJsonResponse(TestRepositoryNotification,
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.
found = notification_queue.get()
self.assertIsNotNone(found)
self.assertTrue('notification_uuid' in found['body'])
def test_webhooks(self):
self.login(ADMIN_ACCESS_USER)
# Add a notification.
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)
self.assertEquals('repo_push', json['event'])
self.assertEquals('webhook', json['method'])
self.assertEquals('http://example.com', json['config']['url'])
self.assertIsNone(json['title'])
wid = json['uuid']
# Get the notification.
json = self.getJsonResponse(RepositoryNotification,
params=dict(repository=ADMIN_ACCESS_USER + '/simple', uuid=wid))
self.assertEquals(wid, json['uuid'])
self.assertEquals('repo_push', json['event'])
self.assertEquals('webhook', json['method'])
self.assertIsNone(json['title'])
# Verify the notification is listed.
json = self.getJsonResponse(RepositoryNotificationList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
ids = [w['uuid'] for w in json['notifications']]
assert wid in ids
# Delete the notification.
self.deleteEmptyResponse(RepositoryNotification,
params=dict(repository=ADMIN_ACCESS_USER + '/simple', uuid=wid),
expected_code=204)
# Verify the notification is gone.
self.getResponse(RepositoryNotification,
params=dict(repository=ADMIN_ACCESS_USER + '/simple', uuid=wid),
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)
self.assertEquals('repo_push', json['event'])
self.assertEquals('webhook', json['method'])
self.assertEquals('http://example.com', json['config']['url'])
self.assertEquals('Some Notification', json['title'])
wid = json['uuid']
# Get the notification.
json = self.getJsonResponse(RepositoryNotification,
params=dict(repository=ADMIN_ACCESS_USER + '/simple', uuid=wid))
self.assertEquals(wid, json['uuid'])
self.assertEquals('repo_push', json['event'])
self.assertEquals('webhook', json['method'])
self.assertEquals('Some Notification', json['title'])
class TestListAndGetImage(ApiTestCase):
def test_listandgetimages(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(RepositoryImageList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
assert len(json['images']) > 0
for image in json['images']:
assert 'id' in image
assert 'tags' in image
assert 'created' in image
assert 'comment' in image
assert 'command' in image
assert 'ancestors' in image
assert 'size' in image
ijson = self.getJsonResponse(RepositoryImage, params=dict(
repository=ADMIN_ACCESS_USER + '/simple', image_id=image['id']))
self.assertEquals(image['id'], ijson['id'])
class TestGetImageChanges(ApiTestCase):
def test_getimagechanges(self):
self.login(ADMIN_ACCESS_USER)
# Find an image to check.
json = self.getJsonResponse(RepositoryImageList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
image_id = json['images'][0]['id']
# Lookup the image's changes.
# TODO: Fix me once we can get fake changes into the test data
#self.getJsonResponse(RepositoryImageChanges,
# params=dict(repository=ADMIN_ACCESS_USER + '/simple',
# image_id=image_id))
class TestRestoreTag(ApiTestCase):
def test_restoretag_invalidtag(self):
self.login(ADMIN_ACCESS_USER)
self.postResponse(RestoreTag,
params=dict(repository=ADMIN_ACCESS_USER + '/history', tag='invalidtag'),
data=dict(image='invalid_image'), expected_code=404)
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=404)
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=404)
def test_restoretag(self):
self.login(ADMIN_ACCESS_USER)
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))
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'])
def test_restoretag_to_digest(self):
self.login(ADMIN_ACCESS_USER)
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_manifest = json['tags'][1]['manifest_digest']
self.postJsonResponse(RestoreTag, params=dict(repository=ADMIN_ACCESS_USER + '/history',
tag='latest'),
data=dict(image='foo', manifest_digest=previous_manifest))
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'))
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)
self.putResponse(RepositoryTag,
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='北京'),
data=dict(image=staging_images[0]['id']), expected_code=400)
def test_listdeletecreateandmovetag(self):
self.login(ADMIN_ACCESS_USER)
# List the images for prod.
json = self.getJsonResponse(RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='prod'))
prod_images = json['images']
assert len(prod_images) > 0
# List the images for 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) + 2
# Delete prod.
self.deleteEmptyResponse(RepositoryTag,
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='prod'),
expected_code=204)
# Make sure the tag is gone.
self.getResponse(RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='prod'),
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'))
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)
# 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)
# Make sure the tag is present.
json = self.getJsonResponse(RepositoryTagImages, params=dict(
repository=ADMIN_ACCESS_USER + '/complex', tag='sometag'))
sometag_images = json['images']
self.assertEquals(sometag_images, staging_images)
# 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)
# Make sure the tag has moved.
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)
# List the images for prod.
json = self.getJsonResponse(RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='prod'))
prod_images = json['images']
assert len(prod_images) > 0
# Delete staging.
self.deleteEmptyResponse(RepositoryTag,
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='staging'),
expected_code=204)
# Make sure the prod images are still around.
json = self.getJsonResponse(RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='prod'))
self.assertEquals(prod_images, json['images'])
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))
self.assertTrue('manifest_digest' in json['tags'][0])
def test_listtagpagination(self):
self.login(ADMIN_ACCESS_USER)
repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, "simple")
latest_tag = registry_model.get_repo_tag(repo_ref, 'latest', include_legacy_image=True)
# Create 8 tags in the simple repo.
remaining_tags = {'latest', 'prod'}
for i in xrange(1, 9):
tag_name = "tag" + str(i)
remaining_tags.add(tag_name)
assert registry_model.retarget_tag(repo_ref, tag_name, latest_tag.legacy_image, storage,
docker_v2_signing_key)
# Make sure we can iterate over all of them.
json = self.getJsonResponse(ListRepositoryTags, params=dict(
repository=ADMIN_ACCESS_USER + '/simple', page=1, limit=5))
self.assertEquals(1, json['page'])
self.assertEquals(5, len(json['tags']))
self.assertTrue(json['has_additional'])
names = {tag['name'] for tag in json['tags']}
remaining_tags = remaining_tags - names
self.assertEquals(5, len(remaining_tags))
json = self.getJsonResponse(ListRepositoryTags, params=dict(
repository=ADMIN_ACCESS_USER + '/simple', page=2, limit=5))
self.assertEquals(2, json['page'])
self.assertEquals(5, len(json['tags']))
self.assertFalse(json['has_additional'])
names = {tag['name'] for tag in json['tags']}
remaining_tags = remaining_tags - names
self.assertEquals(0, len(remaining_tags))
json = self.getJsonResponse(ListRepositoryTags, params=dict(
repository=ADMIN_ACCESS_USER + '/simple', page=3, limit=5))
self.assertEquals(3, json['page'])
self.assertEquals(0, len(json['tags']))
self.assertFalse(json['has_additional'])
class TestRepoPermissions(ApiTestCase):
def listUserPermissions(self, namespace=ADMIN_ACCESS_USER, repo='simple'):
return self.getJsonResponse(RepositoryUserPermissionList,
params=dict(repository=namespace + '/' + repo))['permissions']
def listTeamPermissions(self):
response = self.getJsonResponse(RepositoryTeamPermissionList,
params=dict(repository=ORGANIZATION + '/' + ORG_REPO))
return response['permissions']
def test_userpermissions_underorg(self):
self.login(ADMIN_ACCESS_USER)
permissions = self.listUserPermissions(namespace=ORGANIZATION, repo=ORG_REPO)
self.assertEquals(1, len(permissions))
assert 'outsideorg' in permissions
self.assertEquals('read', permissions['outsideorg']['role'])
self.assertEquals(False, permissions['outsideorg']['is_org_member'])
# Add another user.
self.putJsonResponse(RepositoryUserPermission,
params=dict(repository=ORGANIZATION + '/' + ORG_REPO,
username=ADMIN_ACCESS_USER), data=dict(role='admin'))
# Verify the user is present.
permissions = self.listUserPermissions(namespace=ORGANIZATION, repo=ORG_REPO)
self.assertEquals(2, len(permissions))
assert ADMIN_ACCESS_USER in permissions
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)
# The repo should start with just the admin as a user perm.
permissions = self.listUserPermissions()
self.assertEquals(1, len(permissions))
assert ADMIN_ACCESS_USER in permissions
self.assertEquals('admin', permissions[ADMIN_ACCESS_USER]['role'])
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'))
# Verify the user is present.
permissions = self.listUserPermissions()
self.assertEquals(2, len(permissions))
assert NO_ACCESS_USER in permissions
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))
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'))
# Verify.
permissions = self.listUserPermissions()
self.assertEquals(2, len(permissions))
assert NO_ACCESS_USER in permissions
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))
# Verify.
permissions = self.listUserPermissions()
self.assertEquals(1, len(permissions))
assert not NO_ACCESS_USER in permissions
def test_teampermissions(self):
self.login(ADMIN_ACCESS_USER)
# The repo should start with just the readers as a team perm.
permissions = self.listTeamPermissions()
self.assertEquals(1, len(permissions))
assert 'readers' in permissions
self.assertEquals('read', permissions['readers']['role'])
# Add another team.
self.putJsonResponse(RepositoryTeamPermission,
params=dict(repository=ORGANIZATION + '/' + ORG_REPO, teamname='owners'),
data=dict(role='write'))
# Verify the team is present.
permissions = self.listTeamPermissions()
self.assertEquals(2, len(permissions))
assert 'owners' in permissions
self.assertEquals('write', permissions['owners']['role'])
json = self.getJsonResponse(RepositoryTeamPermission, params=dict(
repository=ORGANIZATION + '/' + ORG_REPO, teamname='owners'))
self.assertEquals('write', json['role'])
# Change the team's permissions.
self.putJsonResponse(RepositoryTeamPermission,
params=dict(repository=ORGANIZATION + '/' + ORG_REPO, teamname='owners'),
data=dict(role='admin'))
# Verify.
permissions = self.listTeamPermissions()
self.assertEquals(2, len(permissions))
assert 'owners' in permissions
self.assertEquals('admin', permissions['owners']['role'])
# Delete the team's permission.
self.deleteEmptyResponse(
RepositoryTeamPermission,
params=dict(repository=ORGANIZATION + '/' + ORG_REPO, teamname='owners'))
# Verify.
permissions = self.listTeamPermissions()
self.assertEquals(1, len(permissions))
assert not 'owners' in permissions
class TestApiTokens(ApiTestCase):
def listTokens(self):
return self.getJsonResponse(RepositoryTokenList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))['tokens']
class TestUserCard(ApiTestCase):
def test_getusercard(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(UserCard)
self.assertEquals('4242', json['card']['last4'])
self.assertEquals('Visa', json['card']['type'])
def test_setusercard_error(self):
self.login(ADMIN_ACCESS_USER)
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))
self.assertEquals('4242', json['card']['last4'])
self.assertEquals('Visa', json['card']['type'])
class TestUserSubscription(ApiTestCase):
def getSubscription(self):
return self.getJsonResponse(UserPlan)
def test_updateplan(self):
self.login(ADMIN_ACCESS_USER)
# Change the plan.
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-2018'))
# Verify
sub = self.getSubscription()
self.assertEquals('bus-large-2018', sub['plan'])
class TestOrgSubscription(ApiTestCase):
def getSubscription(self):
return self.getJsonResponse(OrganizationPlan, params=dict(orgname=ORGANIZATION))
def test_updateplan(self):
self.login(ADMIN_ACCESS_USER)
# Change the plan.
self.putJsonResponse(OrganizationPlan, params=dict(orgname=ORGANIZATION),
data=dict(plan='free'))
# Verify
sub = self.getSubscription()
self.assertEquals('free', sub['plan'])
# Change the plan.
self.putJsonResponse(OrganizationPlan, params=dict(orgname=ORGANIZATION),
data=dict(plan='bus-large-2018'))
# Verify
sub = self.getSubscription()
self.assertEquals('bus-large-2018', sub['plan'])
class TestUserRobots(ApiTestCase):
def getRobotNames(self):
return [r['name'] for r in self.getJsonResponse(UserRobotList)['robots']]
def test_robot_list(self):
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='goldy'), 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):
self.getJsonResponse(UserRobotList)
# Queries: Base + the lookup query
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'),
expected_code=201)
self.assertEquals(NO_ACCESS_USER + '+bender', json['name'])
# Verify.
robots = self.getRobotNames()
assert NO_ACCESS_USER + '+bender' in robots
# Delete the robot.
self.deleteEmptyResponse(UserRobot, params=dict(robot_shortname='bender'))
# Verify.
robots = self.getRobotNames()
assert not NO_ACCESS_USER + '+bender' in robots
def test_regenerate(self):
self.login(NO_ACCESS_USER)
# Create a robot.
json = self.putJsonResponse(UserRobot, params=dict(robot_shortname='bender'),
expected_code=201)
token = json['token']
# Regenerate the robot.
json = self.postJsonResponse(RegenerateUserRobot, params=dict(robot_shortname='bender'),
expected_code=200)
# Verify the token changed.
self.assertNotEquals(token, json['token'])
json2 = self.getJsonResponse(UserRobot, params=dict(robot_shortname='bender'),
expected_code=200)
self.assertEquals(json['token'], json2['token'])
class TestOrgRobots(ApiTestCase):
def getRobotNames(self, include_permissions=False):
params = dict(orgname=ORGANIZATION, permissions=include_permissions)
return [
r['name']
for r in self.getJsonResponse(OrgRobotList, params=params)['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'),
expected_code=201)
# Add the robot to a team.
membername = ORGANIZATION + '+mr_bender'
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'),
expected_code=200)
# Make sure the robot shows up in the org robots list.
self.assertTrue(membername in self.getRobotNames(include_permissions=True))
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'),
expected_code=201)
# Add the robot to a team.
membername = ORGANIZATION + '+bender'
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'))
# 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},
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}))
# Add a build trigger with the robot as the pull robot.
database.BuildTriggerService.create(name='fakeservice')
# Add a new fake trigger.
repo = model.repository.get_repository(ORGANIZATION, ORG_REPO)
user = model.user.get_user(ADMIN_ACCESS_USER)
pull_robot = model.user.get_user(membername)
trigger = model.build.create_build_trigger(repo, 'fakeservice', 'sometoken', user,
pull_robot=pull_robot)
# Add a fake build of the fake build trigger.
token = model.token.create_access_token(repo, 'write', kind='build-worker',
friendly_name='Repository Build Token')
build = model.build.create_repository_build(repo, token, {}, 'fake-dockerfile', 'fake-name',
trigger, pull_robot_name=membername)
# Add some log entries for the robot.
logs_model.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'))
# Verify the build is still present.
self.assertIsNotNone(model.build.get_repository_build(build.uuid))
# All the above records should now be deleted, along with the robot. We verify a few of the
# critical ones below.
# 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)
]
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)
self.assertEquals(ORGANIZATION + '+bender', json['name'])
# Verify.
robots = self.getRobotNames()
assert ORGANIZATION + '+bender' in robots
# Delete the robot.
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)
token = json['token']
# Regenerate the robot.
json = self.postJsonResponse(RegenerateOrgRobot,
params=dict(orgname=ORGANIZATION, robot_shortname='bender'),
expected_code=200)
# Verify the token changed.
self.assertNotEquals(token, json['token'])
json2 = self.getJsonResponse(
OrgRobot, params=dict(orgname=ORGANIZATION, robot_shortname='bender'), expected_code=200)
self.assertEquals(json['token'], json2['token'])
class TestLogs(ApiTestCase):
def test_repo_logs(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(RepositoryLogs, params=dict(repository='devtable/simple'))
assert 'logs' in json
assert 'start_time' in json
assert 'end_time' in json
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'))
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'])
def test_repo_aggregate_logs(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(RepositoryAggregateLogs, params=dict(repository='devtable/simple'))
assert 'aggregated' in json
assert len(json['aggregated']) > 0
def test_user_logs(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(UserLogs)
assert 'logs' in json
assert 'start_time' in json
assert 'end_time' in json
def test_org_logs(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(OrgLogs, params=dict(orgname=ORGANIZATION))
assert 'logs' in json
assert 'start_time' in json
assert 'end_time' in json
def test_user_aggregate_logs(self):
self.login(ADMIN_ACCESS_USER)
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)
json = self.getJsonResponse(OrgLogs, params=dict(orgname=ORGANIZATION))
all_logs = json['logs']
json = self.getJsonResponse(OrgLogs,
params=dict(performer=READ_ACCESS_USER, orgname=ORGANIZATION))
assert len(json['logs']) < len(all_logs)
for log in json['logs']:
self.assertEquals(READ_ACCESS_USER, log['performer']['name'])
class TestApplicationInformation(ApiTestCase):
def test_get_info(self):
json = self.getJsonResponse(ApplicationInformation,
params=dict(client_id=FAKE_APPLICATION_CLIENT_ID))
assert 'name' in json
assert 'uri' in json
assert 'organization' in json
def test_get_invalid_info(self):
self.getJsonResponse(ApplicationInformation, params=dict(client_id='invalid-code'),
expected_code=404)
class TestOrganizationApplications(ApiTestCase):
def test_list_create_applications(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(OrganizationApplications, params=dict(orgname=ORGANIZATION))
self.assertEquals(2, len(json['applications']))
found = False
for application in json['applications']:
if application['client_id'] == FAKE_APPLICATION_CLIENT_ID:
found = True
break
self.assertTrue(found)
# Add a new application.
json = self.postJsonResponse(OrganizationApplications, params=dict(orgname=ORGANIZATION),
data=dict(name="Some cool app", description="foo"))
self.assertEquals("Some cool app", json['name'])
self.assertEquals("foo", json['description'])
# Retrieve the apps list again
list_json = self.getJsonResponse(OrganizationApplications, params=dict(orgname=ORGANIZATION))
self.assertEquals(3, len(list_json['applications']))
class TestOrganizationApplicationResource(ApiTestCase):
def test_get_edit_delete_application(self):
self.login(ADMIN_ACCESS_USER)
# Retrieve the application.
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"))
self.assertEquals(FAKE_APPLICATION_CLIENT_ID, edit_json['client_id'])
self.assertEquals("Some App", edit_json['name'])
self.assertEquals("foo", edit_json['description'])
self.assertEquals("bar", edit_json['application_uri'])
self.assertEquals("baz", edit_json['redirect_uri'])
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))
self.assertEquals(json, edit_json)
# Delete the application.
self.deleteEmptyResponse(
OrganizationApplicationResource,
params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID))
# Make sure the application is gone.
self.getJsonResponse(OrganizationApplicationResource,
params=dict(orgname=ORGANIZATION, client_id=FAKE_APPLICATION_CLIENT_ID),
expected_code=404)
class TestOrganization(ApiTestCase):
def test_change_send_billing_invoice(self):
self.login(ADMIN_ACCESS_USER)
self.putJsonResponse(Organization, params=dict(orgname=ORGANIZATION),
data=dict(invoice_email=False, invoice_email_address=None))
class TestOrganizationApplicationResetClientSecret(ApiTestCase):
def test_reset_client_secret(self):
self.login(ADMIN_ACCESS_USER)
# Retrieve the application.
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))
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))
self.assertEquals(reset_json['client_secret'], json['client_secret'])
class FakeBuildTrigger(BuildTriggerHandler):
@classmethod
def service_name(cls):
return 'fakeservice'
def list_build_source_namespaces(self):
return [
{
'name': 'first',
'id': 'first'
},
{
'name': 'second',
'id': 'second'
},
]
def list_build_sources_for_namespace(self, namespace):
if namespace == "first":
return [{'name': 'source',}]
elif namespace == "second":
return [{'name': self.auth_token,}]
else:
return []
def list_build_subdirs(self):
return [self.auth_token, 'foo', 'bar', self.config['somevalue']]
def handle_trigger_request(self, request):
prepared = PreparedBuild(self.trigger)
prepared.build_name = 'build-name'
prepared.tags = ['bar']
prepared.dockerfile_id = 'foo'
prepared.subdirectory = 'subdir'
prepared.metadata = {'foo': 'bar'}
prepared.is_manual = False
return prepared
def is_active(self):
return 'active' in self.config and self.config['active']
def activate(self, standard_webhook_url):
self.config['active'] = True
return self.config, {}
def deactivate(self):
self.config['active'] = False
return self.config
def manual_start(self, run_parameters=None):
prepared = PreparedBuild(self.trigger)
prepared.build_name = 'build-name'
prepared.tags = ['bar']
prepared.dockerfile_id = 'foo'
prepared.subdirectory = 'subdir'
prepared.metadata = {'foo': 'bar'}
prepared.is_manual = True
prepared.context = '/'
return prepared
def get_repository_url(self):
return 'http://foo/' + self.config['build_source']
def load_dockerfile_contents(self):
if not 'dockerfile' in self.config:
return None
return self.config['dockerfile']
def list_field_values(self, field_name, limit=None):
if field_name == 'test_field':
return [1, 2, 3]
return None
class TestBuildTriggers(ApiTestCase):
def test_list_build_triggers(self):
self.login(ADMIN_ACCESS_USER)
# Check a repo with no known triggers.
json = self.getJsonResponse(BuildTriggerList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
self.assertEquals(0, len(json['triggers']))
# Check a repo with one known trigger.
json = self.getJsonResponse(BuildTriggerList,
params=dict(repository=ADMIN_ACCESS_USER + '/building'))
self.assertEquals(1, len(json['triggers']))
trigger = json['triggers'][0]
assert 'id' in trigger
assert 'is_active' in trigger
assert 'config' in trigger
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']))
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']))
assert 'builds' in builds_json
def test_delete_build_trigger(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(BuildTriggerList,
params=dict(repository=ADMIN_ACCESS_USER + '/building'))
self.assertEquals(1, len(json['triggers']))
trigger = json['triggers'][0]
# Delete the trigger.
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)
database.BuildTriggerService.create(name='fakeservice')
# Add a new fake trigger.
repo = model.repository.get_repository(ADMIN_ACCESS_USER, 'simple')
user = model.user.get_user(ADMIN_ACCESS_USER)
trigger = model.build.create_build_trigger(repo, 'fakeservice', 'sometoken', user)
# 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),
data={'config': trigger_config})
self.assertEquals('warning', analyze_json['status'])
self.assertEquals('Specified Dockerfile path for the trigger was not ' +
'found on the main branch. This trigger may fail.', analyze_json['message'])
# 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),
data={'config': trigger_config})
self.assertEquals('warning', analyze_json['status'])
self.assertEquals('No FROM line found in the Dockerfile', analyze_json['message'])
# 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),
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),
data={'config': trigger_config})
self.assertEquals('warning', analyze_json['status'])
self.assertEquals('"localhost:5000/somepath" is not a valid Quay repository path',
analyze_json['message'])
# 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),
data={'config': trigger_config})
self.assertEquals('error', analyze_json['status'])
nofound = 'Repository "localhost:5000/%s/randomrepo" referenced by the Dockerfile was not found'
self.assertEquals(nofound % 'nothere', analyze_json['message'])
# 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),
data={'config': trigger_config})
self.assertEquals('error', analyze_json['status'])
self.assertEquals(nofound % 'randomuser', analyze_json['message'])
# 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),
data={'config': trigger_config})
self.assertEquals('requiresrobot', analyze_json['status'])
self.assertEquals('devtable', analyze_json['namespace'])
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)
database.BuildTriggerService.create(name='fakeservice')
# Add a new fake trigger.
repo = model.repository.get_repository(ADMIN_ACCESS_USER, 'simple')
user = model.user.get_user(ADMIN_ACCESS_USER)
trigger = model.build.create_build_trigger(repo, 'fakeservice', 'sometoken', user)
# Verify the trigger.
json = self.getJsonResponse(BuildTriggerList,
params=dict(repository=ADMIN_ACCESS_USER + '/simple'))
self.assertEquals(1, len(json['triggers']))
self.assertEquals(trigger.uuid, json['triggers'][0]['id'])
self.assertEquals(trigger.service.name, json['triggers'][0]['service'])
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'])
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),
data={'somevalue': 'meh'})
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),
data={'config': trigger_config})
self.assertEquals(True, activate_json['is_active'])
# Make sure the trigger has a write token.
trigger = model.build.get_build_trigger(trigger.uuid)
self.assertNotEquals(None, trigger.write_token)
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)
# Retrieve values for a field.
result = self.postJsonResponse(BuildTriggerFieldValues,
params=dict(repository=ADMIN_ACCESS_USER + '/simple',
trigger_uuid=trigger.uuid, field_name="test_field"))
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)
# Start a manual build.
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
self.assertEquals("build-name", start_json['display_name'])
self.assertEquals(['bar'], start_json['tags'])
self.assertEquals('subdir', start_json['subdirectory'])
self.assertEquals('somesource', start_json['trigger']['build_source'])
# Verify the metadata was added.
build_obj = database.RepositoryBuild.get(database.RepositoryBuild.uuid == start_json['id'])
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)
# 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),
expected_code=201)
def test_invalid_robot_account(self):
self.login(ADMIN_ACCESS_USER)
database.BuildTriggerService.create(name='fakeservice')
# Add a new fake trigger.
repo = model.repository.get_repository(ADMIN_ACCESS_USER, 'simple')
user = model.user.get_user(ADMIN_ACCESS_USER)
trigger = model.build.create_build_trigger(repo, 'fakeservice', 'sometoken', user)
# 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)
def test_unauthorized_robot_account(self):
self.login(ADMIN_ACCESS_USER)
database.BuildTriggerService.create(name='fakeservice')
# Add a new fake trigger.
repo = model.repository.get_repository(ADMIN_ACCESS_USER, 'simple')
user = model.user.get_user(ADMIN_ACCESS_USER)
trigger = model.build.create_build_trigger(repo, 'fakeservice', 'sometoken', user)
# 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)
def test_robot_account(self):
self.login(ADMIN_ACCESS_USER)
database.BuildTriggerService.create(name='fakeservice')
# Add a new fake trigger.
repo = model.repository.get_repository(ADMIN_ACCESS_USER, 'simple')
user = model.user.get_user(ADMIN_ACCESS_USER)
trigger = model.build.create_build_trigger(repo, 'fakeservice', 'sometoken', user)
# 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'})
# 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),
data=dict(refs=dict(kind='branch', name='foobar')),
expected_code=201)
assert 'id' in start_json
self.assertEquals("build-name", start_json['display_name'])
self.assertEquals(['bar'], start_json['tags'])
class TestUserAuthorizations(ApiTestCase):
def test_list_get_delete_user_authorizations(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(UserAuthorizationList)
self.assertEquals(1, len(json['authorizations']))
authorization = json['authorizations'][0]
assert 'uuid' in authorization
assert 'scopes' in authorization
assert 'application' in authorization
# Retrieve the authorization.
get_json = self.getJsonResponse(UserAuthorization,
params=dict(access_token_uuid=authorization['uuid']))
self.assertEquals(authorization, get_json)
# Delete the authorization.
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']),
expected_code=404)
class TestSuperUserLogs(ApiTestCase):
def test_get_logs(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(SuperUserLogs)
assert 'logs' in json
assert len(json['logs']) > 0
class TestSuperUserTakeOwnership(ApiTestCase):
def test_take_ownership_superuser(self):
self.login(ADMIN_ACCESS_USER)
# Should fail to take ownership of a superuser.
self.postResponse(SuperUserTakeOwnership, params=dict(namespace=ADMIN_ACCESS_USER),
expected_code=400)
def test_take_ownership_invalid_namespace(self):
self.login(ADMIN_ACCESS_USER)
self.postResponse(SuperUserTakeOwnership, params=dict(namespace='invalid'), expected_code=404)
def test_take_ownership_non_superuser(self):
self.login(READ_ACCESS_USER)
self.postResponse(SuperUserTakeOwnership, params=dict(namespace='freshuser'),
expected_code=403)
def test_take_ownership_user(self):
self.login(ADMIN_ACCESS_USER)
with assert_action_logged('take_ownership'):
# Take ownership of the read user.
self.postResponse(SuperUserTakeOwnership, params=dict(namespace=READ_ACCESS_USER))
# Ensure that the read access user is now an org, with the superuser as the owner.
reader = model.user.get_user_or_org(READ_ACCESS_USER)
self.assertTrue(reader.organization)
usernames = [admin.username for admin in model.organization.get_admin_users(reader)]
self.assertIn(ADMIN_ACCESS_USER, usernames)
def test_take_ownership_org(self):
# Create a new org with another user as owner.
public_user = model.user.get_user(PUBLIC_USER)
org = model.organization.create_organization('someorg', 'some@example.com', public_user)
# Ensure that the admin is not yet owner of the org.
usernames = [admin.username for admin in model.organization.get_admin_users(org)]
self.assertNotIn(ADMIN_ACCESS_USER, usernames)
with assert_action_logged('take_ownership'):
# Take ownership.
self.login(ADMIN_ACCESS_USER)
self.postResponse(SuperUserTakeOwnership, params=dict(namespace='someorg'))
# Ensure now in the admin users.
usernames = [admin.username for admin in model.organization.get_admin_users(org)]
self.assertIn(ADMIN_ACCESS_USER, usernames)
class TestSuperUserKeyManagement(ApiTestCase):
def test_get_update_keys(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(SuperUserServiceKeyManagement)
key_count = len(json['keys'])
key = json['keys'][0]
self.assertTrue('name' in key)
self.assertTrue('service' in key)
self.assertTrue('kid' in key)
self.assertTrue('created_date' in key)
self.assertTrue('expiration_date' in key)
self.assertTrue('jwk' in key)
self.assertTrue('approval' in key)
self.assertTrue('metadata' in key)
with assert_action_logged('service_key_modify'):
# Update the key's name.
self.putJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']),
data=dict(name='somenewname'))
# Ensure the key's name has been changed.
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']))
self.assertEquals('somenewname', json['name'])
with assert_action_logged('service_key_modify'):
# Update the key's metadata.
self.putJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']),
data=dict(metadata=dict(foo='bar')))
# Ensure the key's metadata has been changed.
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']))
self.assertEquals('bar', json['metadata']['foo'])
with assert_action_logged('service_key_extend'):
# Change the key's expiration.
self.putJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']),
data=dict(expiration=None))
# Ensure the key's expiration has been changed.
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']))
self.assertIsNone(json['expiration_date'])
with assert_action_logged('service_key_delete'):
# Delete the key.
self.deleteEmptyResponse(SuperUserServiceKey, params=dict(kid=key['kid']))
# Ensure the key no longer exists.
self.getResponse(SuperUserServiceKey, params=dict(kid=key['kid']), expected_code=404)
json = self.getJsonResponse(SuperUserServiceKeyManagement)
self.assertEquals(key_count - 1, len(json['keys']))
def test_approve_key(self):
self.login(ADMIN_ACCESS_USER)
# Ensure the key is not yet approved.
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid='kid3'))
self.assertEquals('unapprovedkey', json['name'])
self.assertIsNone(json['approval'])
# Approve the key.
with assert_action_logged('service_key_approve'):
self.postResponse(SuperUserServiceKeyApproval, params=dict(kid='kid3'),
data=dict(notes='testapprove'), expected_code=201)
# Ensure the key is approved.
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid='kid3'))
self.assertEquals('unapprovedkey', json['name'])
self.assertIsNotNone(json['approval'])
self.assertEquals('ServiceKeyApprovalType.SUPERUSER', json['approval']['approval_type'])
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)
new_key = {
'service': 'coolservice',
'name': 'mynewkey',
'metadata': dict(foo='baz'),
'notes': 'whazzup!?',
'expiration': timegm((datetime.datetime.now() + datetime.timedelta(days=1)).utctimetuple()),
}
# Create the key (preapproved automatically)
json = self.postJsonResponse(SuperUserServiceKeyManagement, data=new_key)
# Try to approve again.
self.postResponse(SuperUserServiceKeyApproval, params=dict(kid=json['kid']), expected_code=201)
def test_create_key(self):
self.login(ADMIN_ACCESS_USER)
new_key = {
'service': 'coolservice',
'name': 'mynewkey',
'metadata': dict(foo='baz'),
'notes': 'whazzup!?',
'expiration': timegm((datetime.datetime.now() + datetime.timedelta(days=1)).utctimetuple()),
}
with assert_action_logged('service_key_create'):
# Create the key.
json = self.postJsonResponse(SuperUserServiceKeyManagement, data=new_key)
self.assertEquals('mynewkey', json['name'])
self.assertTrue('kid' in json)
self.assertTrue('public_key' in json)
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())
# Verify the key.
kid = json['kid']
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=kid))
self.assertEquals('mynewkey', json['name'])
self.assertEquals('coolservice', json['service'])
self.assertEquals('baz', json['metadata']['foo'])
self.assertEquals(kid, json['kid'])
self.assertIsNotNone(json['approval'])
self.assertEquals('ServiceKeyApprovalType.SUPERUSER', json['approval']['approval_type'])
self.assertEquals(ADMIN_ACCESS_USER, json['approval']['approver']['username'])
self.assertEquals('whazzup!?', json['approval']['notes'])
class TestRepositoryManifestLabels(ApiTestCase):
def test_basic_labels(self):
self.login(ADMIN_ACCESS_USER)
repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, 'complex')
tag = registry_model.get_repo_tag(repo_ref, 'prod')
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))
self.assertEquals(0, len(json['labels']))
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)
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)
# 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)
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)
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)
# Ensure we have *3* labels
json = self.getJsonResponse(RepositoryManifestLabels, params=dict(
repository=repository, manifestref=tag.manifest_digest))
self.assertEquals(3, len(json['labels']))
self.assertNotEquals(label2['label']['id'], label1['label']['id'])
self.assertNotEquals(label3['label']['id'], label1['label']['id'])
self.assertNotEquals(label2['label']['id'], label3['label']['id'])
self.assertEquals('text/plain', label1['label']['media_type'])
self.assertEquals('text/plain', label2['label']['media_type'])
self.assertEquals('application/json', label3['label']['media_type'])
# 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']))
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']))
# Ensure the label is gone.
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'))
self.assertEquals(1, len(json['labels']))
def test_prefixed_labels(self):
self.login(ADMIN_ACCESS_USER)
repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, 'complex')
tag = registry_model.get_repo_tag(repo_ref, 'prod')
repository = ADMIN_ACCESS_USER + '/complex'
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)
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)
def test_add_invalid_media_type(self):
self.login(ADMIN_ACCESS_USER)
repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, 'complex')
tag = registry_model.get_repo_tag(repo_ref, 'prod')
repository = ADMIN_ACCESS_USER + '/complex'
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)
repo_ref = registry_model.lookup_repository(ADMIN_ACCESS_USER, 'complex')
tag = registry_model.get_repo_tag(repo_ref, 'prod')
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)
# 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)
# 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)
class TestSuperUserManagement(ApiTestCase):
def test_get_user(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(SuperUserManagement, params=dict(username='freshuser'))
self.assertEquals('freshuser', json['username'])
self.assertEquals('jschorr+test@devtable.com', json['email'])
self.assertEquals(False, json['super_user'])
def test_delete_user(self):
self.login(ADMIN_ACCESS_USER)
# Verify the user exists.
json = self.getJsonResponse(SuperUserManagement, params=dict(username='freshuser'))
self.assertEquals('freshuser', json['username'])
# Delete the user.
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)
def test_change_user_password(self):
self.login(ADMIN_ACCESS_USER)
# Verify the user exists.
json = self.getJsonResponse(SuperUserManagement, params=dict(username='freshuser'))
self.assertEquals('freshuser', json['username'])
self.assertEquals('jschorr+test@devtable.com', json['email'])
# Update the user.
json = self.putJsonResponse(SuperUserManagement, params=dict(username='freshuser'),
data=dict(password='somepassword'))
self.assertTrue('encrypted_password' in json)
def test_update_user(self):
self.login(ADMIN_ACCESS_USER)
# Verify the user exists.
json = self.getJsonResponse(SuperUserManagement, params=dict(username='freshuser'))
self.assertEquals('freshuser', json['username'])
self.assertEquals('jschorr+test@devtable.com', json['email'])
# Update the user.
json = self.putJsonResponse(SuperUserManagement, params=dict(username='freshuser'),
data=dict(email='foo@bar.com'))
self.assertFalse('encrypted_password' in json)
# Verify the user was updated.
json = self.getJsonResponse(SuperUserManagement, params=dict(username='freshuser'))
self.assertEquals('freshuser', json['username'])
self.assertEquals('foo@bar.com', json['email'])
def test_set_message(self):
self.login(ADMIN_ACCESS_USER)
# Create a message
message = {"content": "new message", "severity": "info", "media_type": "text/plain"}
self.postResponse(GlobalUserMessages, data=dict(message=message), expected_code=201)
json = self.getJsonResponse(GlobalUserMessages)
self.assertEquals(len(json['messages']), 3)
has_matching_message = False
for message in json["messages"]:
new_message_match = message["content"] == "new message"
severity_match = message["severity"] == "info"
media_type_match = message["media_type"] == "text/plain"
if new_message_match and severity_match and media_type_match:
has_matching_message = True
break
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"])
def test_delete_message(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(GlobalUserMessages)
self.deleteEmptyResponse(GlobalUserMessage, {"uuid": json['messages'][0]['uuid']}, 204)
json = self.getJsonResponse(GlobalUserMessages)
self.assertEquals(len(json['messages']), 1)
if __name__ == '__main__':
unittest.main()