Merge remote-tracking branch 'upstream/master' into python-registry-v2
This commit is contained in:
commit
26cea9a07c
96 changed files with 2044 additions and 626 deletions
Binary file not shown.
|
@ -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 {}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
49
test/test_queries.py
Normal 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()
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
Reference in a new issue