diff --git a/nexup/repo.py b/nexup/repo.py index e66cb11..4e80c0d 100644 --- a/nexup/repo.py +++ b/nexup/repo.py @@ -34,18 +34,21 @@ def push_zip(session, repo_key, zip_file, delete_first=False): delete_param = '?delete=true' url = COMPRESSED_CONTENT_PATH.format(key=repo_key, delete=delete_param) - session.post(url, f) + if session.debug is True: + print "POSTing: %s" % url + session.post(url, f, expect_status=201) def repo_exists(session, repo_key): return session.exists( NAMED_REPO_PATH.format(key=repo_key) ) def load(session, key, ignore_missing=True): response, xml = session.get(NAMED_REPO_PATH.format(key=key), ignore_404=ignore_missing) - if ignore_missing and response.status == 404: + if ignore_missing and response.status_code == 404: return None doc = objectify.fromstring(xml) - return Repository(doc.data.id, doc.data.name)._set_xml_obj(doc) + # return Repository(doc.data.id, doc.data.name)._set_xml_obj(doc) + return Repository(doc) def load_all(session, name_pattern=None): response, xml = session.get(REPOS_PATH) @@ -72,7 +75,7 @@ def load_all(session, name_pattern=None): if name_re is not None: match = name_re.match(name) - if match is not None: + if name_re is None or match is not None: rid=child.xpath('id/text()') if len(rid) < 1: if session.debug is True: @@ -87,7 +90,8 @@ def load_all(session, name_pattern=None): child = r doc = objectify.fromstring(etree.tostring(child)) - repos.append(Repository(rid, name)._set_xml_obj(doc)) + # repos.append(Repository(rid, name)._set_xml_obj(doc)) + repos.append(Repository(doc)) if session.debug is True: print "+ %s" % name @@ -111,23 +115,35 @@ class Repository(object): methods for accessing repository configuration without knowing all of the xml structure. """ - def __init__(self, key, name): - self.new = True - self.xml = objectify.Element('repository') - self.data = etree.SubElement(self.xml, 'data') - self.data.id=key - self.data.name=name - self.data.repoType = 'hosted' - self.data.writePolicy = WRITE_POLICIES.read_write - self.data.exposed = 'true' - self.data.browseable = 'true' - self.data.indexable = 'true' - self.data.downloadRemoteIndexes = 'false' - self.data.provider = 'maven2' - self.data.format = 'maven2' - self.data.providerRole='org.sonatype.nexus.proxy.repository.Repository' - self.data.checksumPolicy = CHECKSUM_POLICIES.warn - self.data.repoPolicy = REPO_POLICIES.release + def __init__(self, key_or_doc, name=None): + if type(key_or_doc) is objectify.ObjectifiedElement: + self.new=False + self._set_xml_obj(key_or_doc) + elif name is None: + raise Exception('Invalid new repository; must supply key AND name (name is missing)') + else: + self.new = True + self.xml = objectify.Element('repository') + self.data = etree.SubElement(self.xml, 'data') + self.data.id=key_or_doc + self.data.name=name + self.data.repoType = 'hosted' + self.data.writePolicy = WRITE_POLICIES.read_write + self.data.exposed = 'true' + self.data.browseable = 'true' + self.data.indexable = 'true' + self.data.downloadRemoteIndexes = 'false' + self.data.provider = 'maven2' + self.data.format = 'maven2' + self.data.providerRole='org.sonatype.nexus.proxy.repository.Repository' + self.data.checksumPolicy = CHECKSUM_POLICIES.warn + self.data.repoPolicy = REPO_POLICIES.release + + def __str__(self): + return "Repository: %s" % self.data.id + + def __repr__(self): + return self.__str__(); def _set_xml_string(self, xml): self.xml = objectify.fromstring(xml) @@ -146,7 +162,7 @@ def _set_xml_obj(self, xml_obj): return self def set_hosted(self, storage_location=None): - self.data.repoType = 'hosted' + self.data.repoType = REPO_TYPES.hosted if hasattr(self.data, 'remoteStorage'): self.data.remove(self.data.remoteStorage) @@ -161,32 +177,34 @@ def set_hosted(self, storage_location=None): return self def set_remote(self, url): + if hasattr(self.data, 'remoteStorage'): + self.data.remove(self.data.remoteStorage) + self.set('remoteStorage/remoteStorageUrl', url) - self.data.repoType = 'remote' + self.data.repoType = REPO_TYPES.remote - if hasattr(self.data, 'writePolicy'): - self.data.remove(self.data.writePolicy) - - self.data.downloadRemoteIndexes = 'true' + if hasattr(self.data, 'overrideLocalStorageUrl'): + self.data.remove(self.data.overrideLocalStorageUrl) + return self def set_exposed(self, exposed): - exposed = nexus_boolean(exposed) + # exposed = nexus_boolean(exposed) self.data.exposed = exposed return self def set_browseable(self, browse): - browse = nexus_boolean(browse) + # browse = nexus_boolean(browse) self.data.browseable = browse return self def set_indexable(self, index): - index = nexus_boolean(index) + # index = nexus_boolean(index) self.data.indexable = index return self def set_download_remote_indexes(self, download): - download = self._get_nexus_boolean(download) + # download = nexus_boolean(download) self.data.downloadRemoteIndexes = download return self @@ -234,6 +252,7 @@ def set(self, path, value=None): def render(self, pretty_print=True): objectify.deannotate(self.xml, xsi_nil=True) + etree.cleanup_namespaces(self.xml) return etree.tostring(self.xml, pretty_print=pretty_print) def content_uri(self): diff --git a/test-input/all-repos.xml b/test-input/all-repos.xml new file mode 100644 index 0000000..d4b61c4 --- /dev/null +++ b/test-input/all-repos.xml @@ -0,0 +1,90 @@ + + + + http://localhost:8081/nexus/service/local/repositories/snapshots + http://localhost:8081/nexus/content/repositories/snapshots + snapshots + Snapshots + hosted + SNAPSHOT + maven2 + org.sonatype.nexus.proxy.repository.Repository + maven2 + true + true + file:/sonatype-work/storage/snapshots + + + http://localhost:8081/nexus/service/local/repositories/central + http://localhost:8081/nexus/content/repositories/central + central + Central + proxy + RELEASE + maven2 + org.sonatype.nexus.proxy.repository.Repository + maven2 + true + true + file:/sonatype-work/storage/central + https://repo1.maven.org/maven2/ + + + http://localhost:8081/nexus/service/local/repositories/apache-snapshots + http://localhost:8081/nexus/content/repositories/apache-snapshots + apache-snapshots + Apache Snapshots + proxy + SNAPSHOT + maven2 + org.sonatype.nexus.proxy.repository.Repository + maven2 + true + true + file:/sonatype-work/storage/apache-snapshots + https://repository.apache.org/snapshots/ + + + http://localhost:8081/nexus/service/local/repositories/central-m1 + http://localhost:8081/nexus/content/shadows/central-m1 + central-m1 + Central M1 shadow + virtual + RELEASE + m2-m1-shadow + org.sonatype.nexus.proxy.repository.ShadowRepository + maven1 + true + true + file:/sonatype-work/storage/central-m1 + + + http://localhost:8081/nexus/service/local/repositories/thirdparty + http://localhost:8081/nexus/content/repositories/thirdparty + thirdparty + 3rd party + hosted + RELEASE + maven2 + org.sonatype.nexus.proxy.repository.Repository + maven2 + true + true + file:/sonatype-work/storage/thirdparty + + + http://localhost:8081/nexus/service/local/repositories/releases + http://localhost:8081/nexus/content/repositories/releases + releases + Releases + hosted + RELEASE + maven2 + org.sonatype.nexus.proxy.repository.Repository + maven2 + true + true + file:/sonatype-work/storage/releases + + + \ No newline at end of file diff --git a/test-input/central-repo.xml b/test-input/central-repo.xml new file mode 100644 index 0000000..288c2fe --- /dev/null +++ b/test-input/central-repo.xml @@ -0,0 +1,28 @@ + + + http://localhost:8081/nexus/content/repositories/central + central + Central + maven2 + org.sonatype.nexus.proxy.repository.Repository + maven2 + proxy + true + READ_ONLY + true + true + 1440 + RELEASE + WARN + false + file:/sonatype-work/storage/central + + https://repo1.maven.org/maven2/ + + true + -1 + 1440 + 1440 + true + + \ No newline at end of file diff --git a/tests/base.py b/tests/base.py index ad96a2a..7b2baac 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,12 +1,20 @@ +import nexup +import traceback import os import tempfile import shutil import unittest import yaml from nexup import config +import zipfile +from random import randint + WORDS = ['/usr/share/dict/words', '/usr/dict/words'] +TEST_BASEURL='http://localhost:8080/nexus' +TEST_INPUT_DIR='test-input' + class NexupBaseTest(unittest.TestCase): """ Creates config files, configures the environment, and cleans up afterwards. @@ -39,6 +47,29 @@ def load_words(self): with open(w) as f: self.words.extend([line.rstrip() for line in f.readlines()]) + def write_zip(self, src_zip, paths, content=None): + zf = zipfile.ZipFile(src_zip, mode='w') + for path in paths: + if content is None: + content = '' + for i in range(randint(1,10)): + content += self.words[randint(1,len(self.words))] + content += ' ' + zf.writestr(path, content) + zf.close() + + def write_dir(self, srcdir, paths, content=None): + for fname in paths: + path = os.path.join(srcdir, fname) + os.makedirs(os.path.dirname(path)) + with open(path, 'w') as f: + if content is None: + for i in range(randint(1,10)): + f.write(self.words[randint(1,len(self.words))]) + f.write(' ') + else: + f.write(content) + def write_config(self, conf): """ Create an empty file in the temporary directory, return the full path. @@ -59,3 +90,8 @@ def write_config(self, conf): return fpath + def create_and_load_conf(self, conf={'test':{nexup.config.URL: TEST_BASEURL}}): + fpath = self.write_config(conf) + return nexup.config.load('test') + + diff --git a/tests/test_archive_dir.py b/tests/test_archive_dir.py index 5f7e175..73face7 100644 --- a/tests/test_archive_dir.py +++ b/tests/test_archive_dir.py @@ -1,23 +1,11 @@ -from base import NexupBaseTest from nexup import archive +from base import NexupBaseTest import tempfile import os from random import randint import zipfile -class ArchiveTest(NexupBaseTest): - - def write_dir(self, srcdir, paths, content=None): - for fname in paths: - path = os.path.join(srcdir, fname) - os.makedirs(os.path.dirname(path)) - with open(path, 'w') as f: - if content is None: - for i in range(randint(1,10)): - f.write(self.words[randint(1,len(self.words))]) - f.write(' ') - else: - f.write(content) +class ArchiveZipest(NexupBaseTest): def test_small(self): self.load_words() diff --git a/tests/test_archive_zip.py b/tests/test_archive_zip.py index 16babba..32e3b60 100644 --- a/tests/test_archive_zip.py +++ b/tests/test_archive_zip.py @@ -1,22 +1,11 @@ -from base import NexupBaseTest from nexup import archive +from base import NexupBaseTest import tempfile import os from random import randint import zipfile -class ArchiveTest(NexupBaseTest): - - def write_zip(self, src_zip, paths, content=None): - zf = zipfile.ZipFile(src_zip, mode='w') - for path in paths: - if content is None: - content = '' - for i in range(randint(1,10)): - content += self.words[randint(1,len(self.words))] - content += ' ' - zf.writestr(path, content) - zf.close() +class ArchiveZipest(NexupBaseTest): def test_small(self): self.load_words() diff --git a/tests/test_repo.py b/tests/test_repo.py new file mode 100644 index 0000000..a4c6489 --- /dev/null +++ b/tests/test_repo.py @@ -0,0 +1,240 @@ +from base import (TEST_INPUT_DIR, NexupBaseTest) +import nexup +import responses +import os +import yaml +import traceback +import tempfile + +class TestRepo(NexupBaseTest): + + @responses.activate + def test_push_zip_with_delete(self): + conf = self.create_and_load_conf() + key='central' + path = nexup.repo.COMPRESSED_CONTENT_PATH.format(key=key, delete='?delete=true') + # print "\n\n\n\nPOST: %s%s\n\n\n\n" % (conf.url, path) + + responses.add(responses.POST, conf.url + path, match_querystring=True, status=201) + + self.load_words() + + paths = ['path/one.txt', 'path/to/two.txt', 'path/to/stuff/three.txt'] + + (_f,src_zip) = tempfile.mkstemp(suffix='.zip') + + self.write_zip(src_zip, paths) + + sess = nexup.session.Session(conf) + nexup.repo.push_zip(sess, key, src_zip, True) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_push_zip_default_no_delete(self): + conf = self.create_and_load_conf() + key='central' + path = nexup.repo.COMPRESSED_CONTENT_PATH.format(key=key, delete='') + # print "\n\n\n\nPOST: %s%s\n\n\n\n" % (conf.url, path) + + responses.add(responses.POST, conf.url + path, status=201) + + self.load_words() + + paths = ['path/one.txt', 'path/to/two.txt', 'path/to/stuff/three.txt'] + + (_f,src_zip) = tempfile.mkstemp(suffix='.zip') + + self.write_zip(src_zip, paths) + + sess = nexup.session.Session(conf) + nexup.repo.push_zip(sess, key, src_zip) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_exists(self): + conf = self.create_and_load_conf() + key='central' + path = nexup.repo.NAMED_REPO_PATH.format(key=key) + + responses.add(responses.HEAD, conf.url + path, status=200) + + sess = nexup.session.Session(conf) + self.assertEqual(nexup.repo.repo_exists(sess, key), True) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_delete(self): + conf = self.create_and_load_conf() + key='central' + path = nexup.repo.NAMED_REPO_PATH.format(key=key) + + responses.add(responses.DELETE, conf.url + path, status=204) + + sess = nexup.session.Session(conf) + nexup.repo.delete(sess, key) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_save_new(self): + conf = self.create_and_load_conf() + key='foo' + path = nexup.repo.REPOS_PATH + + def callbk(req): + print "RECV body: '%s'" % req.body + return (201,req.headers,req.body) + + responses.add_callback(responses.POST, conf.url + path, callback=callbk) + + sess = nexup.session.Session(conf) + repo = nexup.repo.Repository(key, 'Foo Repo') + + repo.save(sess) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_load_change_save(self): + conf = self.create_and_load_conf() + key='central' + central_path = nexup.repo.NAMED_REPO_PATH.format(key=key) + + def callbk(req): + print "RECV body: '%s'" % req.body + return (200,req.headers,req.body) + + responses.add_callback(responses.PUT, conf.url + central_path, callback=callbk) + + body = None + with open(os.path.join(TEST_INPUT_DIR, 'central-repo.xml')) as f: + body=f.read() + + responses.add(responses.GET, conf.url + central_path, body=body, status=200) + + sess = nexup.session.Session(conf) + repo = nexup.repo.load(sess, key) + repo.set_exposed(False) + + repo.save(sess) + self.assertEqual(len(responses.calls), 2) + + @responses.activate + def test_load_central(self): + conf = self.create_and_load_conf() + key='central' + path = nexup.repo.NAMED_REPO_PATH.format(key=key) + body = None + with open(os.path.join(TEST_INPUT_DIR, 'central-repo.xml')) as f: + body=f.read() + + responses.add(responses.GET, conf.url + path, body=body, status=200) + + sess = nexup.session.Session(conf) + repo = nexup.repo.load(sess, key) + + self.assertEqual(repo.id(), key) + self.assertEqual(repo.name(), 'Central') + self.assertEqual(repo.content_uri(), 'http://localhost:8081/nexus/content/repositories/central') + + body_lines = body.split('\n') + rendered_lines = repo.render().split('\n') + for i in range(0,len(body_lines)): + self.assertEqual(rendered_lines[i], body_lines[i]) + + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_set_properties(self): + conf = self.create_and_load_conf() + key='central' + path = nexup.repo.NAMED_REPO_PATH.format(key=key) + body = None + with open(os.path.join(TEST_INPUT_DIR, 'central-repo.xml')) as f: + body=f.read() + + responses.add(responses.GET, conf.url + path, body=body, status=200) + + sess = nexup.session.Session(conf) + repo = nexup.repo.load(sess, key) + + self.assertEqual(repo.id(), key) + self.assertEqual(len(responses.calls), 1) + + repo.set_nfc_ttl(12) + self.assertEqual(repo.data.notFoundCacheTTL, '12') + repo.set_nfc_ttl(1440) + + repo.set_checksum_policy(nexup.repo.CHECKSUM_POLICIES.fail) + self.assertEqual(repo.data.checksumPolicy, 'FAIL') + repo.set_checksum_policy(nexup.repo.CHECKSUM_POLICIES.warn) + + repo.set_repo_policy(nexup.repo.REPO_POLICIES.snapshot) + self.assertEqual(repo.data.repoPolicy, 'SNAPSHOT') + repo.set_repo_policy(nexup.repo.REPO_POLICIES.release) + + self.assertEqual(repo.data.downloadRemoteIndexes, False) + repo.set_download_remote_indexes(True) + self.assertEqual(repo.data.downloadRemoteIndexes, True) + repo.set_download_remote_indexes(False) + + repo.set_hosted('/path/to/storage') + self.assertEqual(repo.data.repoType, nexup.repo.REPO_TYPES.hosted) + self.assertEqual(repo.data.overrideLocalStorageUrl, 'file:/path/to/storage') + self.assertEqual(repo.data.get('remoteStorage'), None) + + repo.set_remote('https://repo1.maven.org/maven456/') + self.assertEqual(repo.data.remoteStorage.remoteStorageUrl, 'https://repo1.maven.org/maven456/') + self.assertEqual(repo.data.repoType, nexup.repo.REPO_TYPES.remote) + self.assertEqual(repo.data.get('overrideLocalStorageUrl'), None) + + repo.set_remote('https://repo1.maven.org/maven789/') + self.assertEqual(repo.data.remoteStorage.remoteStorageUrl, 'https://repo1.maven.org/maven789/') + self.assertEqual(repo.data.repoType, nexup.repo.REPO_TYPES.remote) + self.assertEqual(repo.data.get('overrideLocalStorageUrl'), None) + + repo.set_remote('https://repo1.maven.org/maven2/') + + self.assertEqual(repo.data.exposed, True) + repo.set_exposed(False) + self.assertEqual(repo.data.exposed, False) + repo.set_exposed(True) + + self.assertEqual(repo.data.browseable, True) + repo.set_browseable(False) + self.assertEqual(repo.data.browseable, False) + repo.set_browseable(True) + + self.assertEqual(repo.data.indexable, True) + repo.set_indexable(False) + self.assertEqual(repo.data.indexable, False) + repo.set_indexable(True) + + repo.set_write_policy(nexup.repo.WRITE_POLICIES.write_once) + self.assertEqual(repo.data.writePolicy, 'ALLOW_WRITE_ONCE') + repo.set_write_policy(nexup.repo.WRITE_POLICIES.read_only) + + body_lines = body.split('\n') + rendered_lines = repo.render().split('\n') + for i in range(0,len(body_lines)): + print "Searching: %s" % rendered_lines[i] + self.assertEqual(rendered_lines[i] in body_lines, True) + + + @responses.activate + def test_load_all(self): + conf = self.create_and_load_conf() + path = nexup.repo.REPOS_PATH + + body = None + with open(os.path.join(TEST_INPUT_DIR, 'all-repos.xml')) as f: + body=f.read() + + responses.add(responses.GET, conf.url + path, body=body, status=200) + + sess = nexup.session.Session(conf) + repos = nexup.repo.load_all(sess) + + print "Loaded all repositories: %s" % repos + self.assertEqual(len(repos), 6) + self.assertEqual(len(responses.calls), 1) + + diff --git a/tests/test_session.py b/tests/test_session.py deleted file mode 100644 index eb04aa6..0000000 --- a/tests/test_session.py +++ /dev/null @@ -1,17 +0,0 @@ - -from base import NexupBaseTest -from unittest import TestCase -import nexup -import responses -import os -import yaml -import traceback - -TEST_BASEURL='http://localhost:8080/nexus' - -class TestSession(NexupBaseTest): - - def create_and_load_conf(self, conf={'test':{nexup.config.URL: TEST_BASEURL}}): - fpath = self.write_config(conf) - return nexup.config.load('test') - diff --git a/tests/test_session_delete.py b/tests/test_session_delete.py index 3f2f5ba..511acca 100644 --- a/tests/test_session_delete.py +++ b/tests/test_session_delete.py @@ -1,12 +1,12 @@ -from test_session import TestSession +from base import NexupBaseTest import nexup import responses import os import yaml import traceback -class TestSessionDelete(TestSession): +class TestSessionDelete(NexupBaseTest): @responses.activate def test_default(self): diff --git a/tests/test_session_exists.py b/tests/test_session_exists.py index 188496b..3cb433a 100644 --- a/tests/test_session_exists.py +++ b/tests/test_session_exists.py @@ -1,12 +1,12 @@ -from test_session import TestSession +from base import NexupBaseTest import nexup import responses import os import yaml import traceback -class TestSessionExists(TestSession): +class TestSessionExists(NexupBaseTest): @responses.activate def test_default(self): diff --git a/tests/test_session_get.py b/tests/test_session_get.py index 3a70caf..8c7846c 100644 --- a/tests/test_session_get.py +++ b/tests/test_session_get.py @@ -1,12 +1,12 @@ -from test_session import TestSession +from base import NexupBaseTest import nexup import responses import os import yaml import traceback -class TestSessionGet(TestSession): +class TestSessionGet(NexupBaseTest): @responses.activate def test_default(self): diff --git a/tests/test_session_head.py b/tests/test_session_head.py index 4d3ffcb..1e6730c 100644 --- a/tests/test_session_head.py +++ b/tests/test_session_head.py @@ -1,12 +1,12 @@ -from test_session import TestSession +from base import NexupBaseTest import nexup import responses import os import yaml import traceback -class TestSessionHead(TestSession): +class TestSessionHead(NexupBaseTest): @responses.activate def test_default(self): diff --git a/tests/test_session_post.py b/tests/test_session_post.py index f46066f..f6b14ef 100644 --- a/tests/test_session_post.py +++ b/tests/test_session_post.py @@ -1,12 +1,12 @@ -from test_session import TestSession +from base import NexupBaseTest import nexup import responses import os import yaml import traceback -class TestSessionPost(TestSession): +class TestSessionPost(NexupBaseTest): @responses.activate def test_default(self): diff --git a/tests/test_session_put.py b/tests/test_session_put.py index 08e99c0..3f68c2d 100644 --- a/tests/test_session_put.py +++ b/tests/test_session_put.py @@ -1,12 +1,12 @@ -from test_session import TestSession +from base import NexupBaseTest import nexup import responses import os import yaml import traceback -class TestSessionPut(TestSession): +class TestSessionPut(NexupBaseTest): @responses.activate def test_default(self):