Merge remote-tracking branch 'upstream/master' into python-registry-v2

This commit is contained in:
Jake Moshenko 2015-09-17 16:16:27 -04:00
commit 26cea9a07c
96 changed files with 2044 additions and 626 deletions

Binary file not shown.

View file

@ -7,12 +7,14 @@ from flask.blueprints import Blueprint
from flask.ext.testing import LiveServerTestCase
from app import app
from data.database import close_db_filter, configure
from endpoints.v1 import v1_bp
from endpoints.v2 import v2_bp
from endpoints.v2.manifest import SignedManifestBuilder
from endpoints.api import api_bp
from initdb import wipe_database, initialize_database, populate_database
from endpoints.csrf import generate_csrf_token
from tempfile import NamedTemporaryFile
import endpoints.decorated
import json
@ -20,6 +22,7 @@ import features
import hashlib
import tarfile
import shutil
from jwkest.jws import SIGNER_ALGS
from jwkest.jwk import RSAKey
@ -37,7 +40,9 @@ except ValueError:
pass
# Add a test blueprint for generating CSRF tokens and setting feature flags.
# Add a test blueprint for generating CSRF tokens, setting feature flags and reloading the
# DB connection.
testbp = Blueprint('testbp', __name__)
@testbp.route('/csrf', methods=['GET'])
@ -51,6 +56,15 @@ def set_feature(feature_name):
features._FEATURES[feature_name].value = request.get_json()['value']
return jsonify({'old_value': old_value})
@testbp.route('/reloaddb', methods=['POST'])
def reload_db():
# Close any existing connection.
close_db_filter(None)
# Reload the database config.
configure(app.config)
return 'OK'
app.register_blueprint(testbp, url_prefix='/__test')
@ -78,6 +92,62 @@ class TestFeature(object):
headers={'Content-Type': 'application/json'})
_PORT_NUMBER = 5001
_CLEAN_DATABASE_PATH = None
def get_new_database_uri():
# If a clean copy of the database has not yet been created, create one now.
global _CLEAN_DATABASE_PATH
if not _CLEAN_DATABASE_PATH:
wipe_database()
initialize_database()
populate_database()
close_db_filter(None)
# Save the path of the clean database.
_CLEAN_DATABASE_PATH = app.config['TEST_DB_FILE'].name
# Create a new temp file to be used as the actual backing database for the test.
# Note that we have the close() the file to ensure we can copy to it via shutil.
local_db_file = NamedTemporaryFile(delete=True)
local_db_file.close()
# Copy the clean database to the path.
shutil.copy2(_CLEAN_DATABASE_PATH, local_db_file.name)
return 'sqlite:///{0}'.format(local_db_file.name)
class RegistryTestCase(LiveServerTestCase):
maxDiff = None
def create_app(self):
global _PORT_NUMBER
_PORT_NUMBER = _PORT_NUMBER + 1
app.config['TESTING'] = True
app.config['LIVESERVER_PORT'] = _PORT_NUMBER
app.config['DB_URI'] = get_new_database_uri()
return app
def setUp(self):
self.clearSession()
# Tell the remote running app to reload the database. By default, the app forks from the
# current context and has already loaded the DB config with the *original* DB URL. We call
# the remote reload method to force it to pick up the changes to DB_URI set in the create_app
# method.
self.conduct('POST', '/__test/reloaddb')
def clearSession(self):
self.session = requests.Session()
self.signature = None
self.docker_token = 'true'
# Load the CSRF token.
self.csrf_token = ''
self.csrf_token = self.conduct('GET', '/__test/csrf').text
class BaseRegistryMixin(object):
def conduct(self, method, url, headers=None, data=None, auth=None, params=None, expected_code=200):
params = params or {}

View file

@ -47,7 +47,8 @@ from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPe
RepositoryUserTransitivePermission)
from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement,
SuperUserSendRecoveryEmail, ChangeLog,
SuperUserOrganizationManagement, SuperUserOrganizationList)
SuperUserOrganizationManagement, SuperUserOrganizationList,
SuperUserAggregateLogs)
try:
@ -3928,6 +3929,24 @@ class TestUserAuthorization(ApiTestCase):
self._run_test('DELETE', 404, 'devtable', None)
class TestSuperAggregateUserLogs(ApiTestCase):
def setUp(self):
ApiTestCase.setUp(self)
self._set_url(SuperUserAggregateLogs)
def test_get_anonymous(self):
self._run_test('GET', 401, None, None)
def test_get_freshuser(self):
self._run_test('GET', 403, 'freshuser', None)
def test_get_reader(self):
self._run_test('GET', 403, 'reader', None)
def test_get_devtable(self):
self._run_test('GET', 200, 'devtable', None)
class TestSuperUserLogs(ApiTestCase):
def setUp(self):
ApiTestCase.setUp(self)

View file

@ -1,6 +1,7 @@
# coding=utf-8
import unittest
import datetime
import json as py_json
from urllib import urlencode
@ -15,6 +16,7 @@ from endpoints.trigger import BuildTriggerHandler
from app import app
from initdb import setup_database_for_testing, finished_database_for_testing
from data import database, model
from data.database import RepositoryActionCount
from endpoints.api.team import TeamMember, TeamMemberList, TeamMemberInvite, OrganizationTeam
from endpoints.api.tag import RepositoryTagImages, RepositoryTag, RevertTag, ListRepositoryTags
@ -315,8 +317,18 @@ class TestGetUserPrivateAllowed(ApiTestCase):
def test_allowed(self):
self.login(ADMIN_ACCESS_USER)
# Change the subscription of the namespace.
self.putJsonResponse(UserPlan, data=dict(plan='personal-30'))
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-30'))
json = self.getJsonResponse(PrivateRepositories)
assert json['privateAllowed']
@ -1435,6 +1447,36 @@ class TestUpdateRepo(ApiTestCase):
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-30'))
# 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)
@ -1468,6 +1510,10 @@ class TestDeleteRepository(ApiTestCase):
def test_deleterepo(self):
self.login(ADMIN_ACCESS_USER)
# Verify the repo exists.
self.getResponse(Repository,
params=dict(repository=self.SIMPLE_REPO))
self.deleteResponse(Repository, params=dict(repository=self.SIMPLE_REPO))
# Verify the repo was deleted.
@ -1478,6 +1524,10 @@ class TestDeleteRepository(ApiTestCase):
def test_deleterepo2(self):
self.login(ADMIN_ACCESS_USER)
# Verify the repo exists.
self.getResponse(Repository,
params=dict(repository=self.COMPLEX_REPO))
self.deleteResponse(Repository, params=dict(repository=self.COMPLEX_REPO))
# Verify the repo was deleted.
@ -1488,7 +1538,11 @@ class TestDeleteRepository(ApiTestCase):
def test_populate_and_delete_repo(self):
self.login(ADMIN_ACCESS_USER)
# Make sure the repository has come images and tags.
# Verify the repo exists.
self.getResponse(Repository,
params=dict(repository=self.COMPLEX_REPO))
# Make sure the repository has some images and tags.
self.assertTrue(len(list(model.image.get_repository_images(ADMIN_ACCESS_USER, 'complex'))) > 0)
self.assertTrue(len(list(model.tag.list_repository_tags(ADMIN_ACCESS_USER, 'complex'))) > 0)
@ -1524,6 +1578,13 @@ class TestDeleteRepository(ApiTestCase):
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=1), count=2)
RepositoryActionCount.create(repository=repository,
date=datetime.datetime.now() - datetime.timedelta(days=3), count=6)
# Delete the repository.
self.deleteResponse(Repository, params=dict(repository=self.COMPLEX_REPO))
@ -2079,6 +2140,7 @@ class TestRevertTag(ApiTestCase):
self.assertEquals(previous_image_id, json['tags'][0]['docker_image_id'])
class TestListAndDeleteTag(ApiTestCase):
def test_listdeletecreateandmovetag(self):
self.login(ADMIN_ACCESS_USER)
@ -2166,6 +2228,19 @@ class TestListAndDeleteTag(ApiTestCase):
self.assertEquals(prod_images, json['images'])
def test_listtagpagination(self):
self.login(ADMIN_ACCESS_USER)
for i in xrange(1, 100):
model.tag.create_or_update_tag(ADMIN_ACCESS_USER, "complex", "tag" + str(i),
"1d8cbff4e0363d1826c6a0b64ef0bc501d8cbff4e0363d1826c6a0b64ef0bc50")
json = self.getJsonResponse(ListRepositoryTags,
params=dict(repository=ADMIN_ACCESS_USER + '/complex', page=2))
# Make sure that we're able to see the second page of results.
assert json['page'] == 2
assert len(json['tags']) == 50
class TestRepoPermissions(ApiTestCase):
def listUserPermissions(self, namespace=ADMIN_ACCESS_USER, repo='simple'):
@ -2512,6 +2587,26 @@ class TestOrgRobots(ApiTestCase):
return [r['name'] for r in self.getJsonResponse(OrgRobotList,
params=dict(orgname=ORGANIZATION))['robots']]
def test_create_robot_with_underscores(self):
self.login(ADMIN_ACCESS_USER)
# Create the robot.
self.putJsonResponse(OrgRobot,
params=dict(orgname=ORGANIZATION, robot_shortname='mr_bender'),
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)
def test_delete_robot_after_use(self):
self.login(ADMIN_ACCESS_USER)

View file

@ -75,8 +75,8 @@ class TestGarbageCollection(unittest.TestCase):
v1_metadata['parent'] = parent.docker_image_id
# Set the ancestors for the image.
parent = model.image.set_image_metadata(image_id, namespace, name, '', '', '',
v1_metadata, parent=parent)
parent = model.image.set_image_metadata(image_id, namespace, name, '', '', '', v1_metadata,
parent=parent)
# Set the tag for the image.
model.tag.create_or_update_tag(namespace, name, tag_name, image_ids[-1])

49
test/test_queries.py Normal file
View file

@ -0,0 +1,49 @@
import unittest
from app import app
from initdb import setup_database_for_testing, finished_database_for_testing
from data import model
from data.database import RepositoryBuild
ADMIN_ACCESS_USER = 'devtable'
SIMPLE_REPO = 'simple'
class TestSpecificQueries(unittest.TestCase):
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)
self.ctx.__exit__(True, None, None)
def test_archivable_buildlogs(self):
# Make sure there are no archivable logs.
result = model.build.get_archivable_build()
self.assertIsNone(result)
# Add a build that cannot (yet) be archived.
repo = model.repository.get_repository(ADMIN_ACCESS_USER, SIMPLE_REPO)
token = model.token.create_access_token(repo, 'write')
created = RepositoryBuild.create(repository=repo, access_token=token,
phase=model.build.BUILD_PHASE.WAITING,
logs_archived=False, job_config='{}',
display_name='')
# Make sure there are no archivable logs.
result = model.build.get_archivable_build()
self.assertIsNone(result)
# Change the build to being complete.
created.phase = model.build.BUILD_PHASE.COMPLETE
created.save()
# Make sure we now find an archivable build.
result = model.build.get_archivable_build()
self.assertEquals(created.id, result.id)
self.assertEquals(created.uuid, result.uuid)
if __name__ == '__main__':
unittest.main()

View file

@ -1,7 +1,7 @@
from test.test_api_usage import ApiTestCase, READ_ACCESS_USER, ADMIN_ACCESS_USER
from endpoints.api.suconfig import (SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile,
SuperUserCreateInitialSuperUser, SuperUserConfigValidate)
from app import CONFIG_PROVIDER
from app import config_provider
from data.database import User
import unittest
@ -10,11 +10,11 @@ import unittest
class ConfigForTesting(object):
def __enter__(self):
CONFIG_PROVIDER.reset_for_test()
return CONFIG_PROVIDER
config_provider.reset_for_test()
return config_provider
def __exit__(self, type, value, traceback):
CONFIG_PROVIDER.reset_for_test()
config_provider.reset_for_test()
class TestSuperUserRegistryStatus(ApiTestCase):
@ -166,7 +166,7 @@ class TestSuperUserConfig(ApiTestCase):
self.assertTrue(json['exists'])
# Verify the config file exists.
self.assertTrue(config.yaml_exists())
self.assertTrue(config.config_exists())
# Try writing it again. This should now fail, since the config.yaml exists.
self.putResponse(SuperUserConfig, data=dict(config={}, hostname='barbaz'), expected_code=403)

View file

@ -20,7 +20,9 @@ TEST_DB_FILE = NamedTemporaryFile(delete=True)
class TestConfig(DefaultConfig):
TESTING = True
SECRET_KEY = 'a36c9d7d-25a9-4d3f-a586-3d2f8dc40a83'
BILLING_TYPE = 'FakeStripe'
TEST_DB_FILE = TEST_DB_FILE
DB_URI = os.environ.get('TEST_DATABASE_URI', 'sqlite:///{0}'.format(TEST_DB_FILE.name))
DB_CONNECTION_ARGS = {
'threadlocals': True,