Added a lot of unittests and fixed some small issues
authorSybren A. Stüvel <sybren@stuvel.eu>
Wed, 8 Nov 2017 14:05:08 +0000 (15:05 +0100)
committerSybren A. Stüvel <sybren@stuvel.eu>
Wed, 8 Nov 2017 14:05:08 +0000 (15:05 +0100)
svnman/__init__.py
svnman/exceptions.py
tests/test_api.py
tests/test_pillar_extension.py

index 1ae4e75..400aa9a 100644 (file)
@@ -136,11 +136,9 @@ class SVNManExtension(PillarExtension):
     def is_svnman_project(self, project: pillarsdk.Project) -> bool:
         """Checks whether the project is correctly set up for SVNman."""
 
-        if not project.extension_props:
-            return False
-
         try:
-            pprops = project.extension_props[EXTENSION_NAME]
+            if not project.extension_props:
+                return False
         except AttributeError:
             self._log.warning("is_svnman_project: Project url=%r doesn't have"
                               " any extension properties.", project['url'])
@@ -148,12 +146,15 @@ class SVNManExtension(PillarExtension):
                 import pprint
                 self._log.debug('Project: %s', pprint.pformat(project.to_dict()))
             return False
+
+        try:
+            pprops = project.extension_props[EXTENSION_NAME]
         except KeyError:
             return False
 
         if pprops is None:
             self._log.warning("is_svnman_project: Project url=%r doesn't have"
-                              " Flamenco extension properties.", project['url'])
+                              " %s extension properties.", EXTENSION_NAME, project['url'])
             return False
 
         return bool(pprops.repo_id)
index c98e7ca..419744c 100644 (file)
@@ -44,4 +44,5 @@ http_error_map = collections.defaultdict(lambda: RemoteError)
 http_error_map.update({
     400: BadAPIRequest,
     500: InternalAPIServerError,
+    404: RepoNotFound,
 })
index c4483a6..a227a06 100644 (file)
@@ -1,3 +1,6 @@
+import json
+
+import requests
 import responses
 
 from abstract_svnman_test import AbstractSVNManTest
@@ -16,11 +19,23 @@ class TestAPI(AbstractSVNManTest):
         resp = self.remote.create_repo(cr)
         self.assertEqual('something-completely-different', resp)
 
+    @responses.activate
+    def test_create_repo_already_exists(self):
+        from svnman.remote import CreateRepo
+        from svnman.exceptions import RepoAlreadyExists
+
+        responses.add(responses.POST, 'http://svnman_api_url/api/repo',
+                      status=requests.codes.conflict)
+
+        cr = CreateRepo(repo_id='UPPERCASE', project_id='someproject', creator='me <here@there>')
+        with self.assertRaises(RepoAlreadyExists):
+            self.remote.create_repo(cr)
+
     @responses.activate
     def test_delete_repo(self):
         responses.add(responses.DELETE,
                       'http://svnman_api_url/api/repo/repo-id',
-                      status=204)
+                      status=requests.codes.no_content)
         self.remote.delete_repo('repo-id')
 
     @responses.activate
@@ -40,3 +55,53 @@ class TestAPI(AbstractSVNManTest):
             access=['someuser', 'otheruser'],
         )
         self.assertEqual(expect, resp)
+
+    @responses.activate
+    def test_fetch_repo_internal_server_error(self):
+        from svnman.exceptions import InternalAPIServerError
+
+        responses.add(responses.GET, 'http://svnman_api_url/api/repo/repo-id', status=500)
+
+        with self.assertRaises(InternalAPIServerError):
+            self.remote.fetch_repo('repo-id')
+
+    @responses.activate
+    def test_fetch_repo_not_found(self):
+        from svnman.exceptions import RepoNotFound
+
+        responses.add(responses.GET, 'http://svnman_api_url/api/repo/repo-id', status=404)
+
+        with self.assertRaises(RepoNotFound):
+            self.remote.fetch_repo('repo-id')
+
+    @responses.activate
+    def test_fetch_repo_not_found(self):
+        from svnman.exceptions import RemoteError
+
+        responses.add(responses.GET, 'http://svnman_api_url/api/repo/repo-id',
+                      status=requests.codes.im_a_teapot)
+
+        with self.assertRaises(RemoteError):
+            self.remote.fetch_repo('repo-id')
+
+    @responses.activate
+    def test_modify_access(self):
+        def request_callback(request):
+            payload = json.loads(request.body)
+            # Both password hashes should use $2y$ type indication.
+            self.assertEqual({
+                'grant': [{'username': 'username', 'password': '$2y$1234'},
+                          {'username': 'username2', 'password': '$2y$5555'},
+                          ],
+                'revoke': ['someone-else']
+            }, payload)
+
+            # Returns status code, headers, body
+            return 204, {}, ''
+
+        responses.add_callback(responses.POST, 'http://svnman_api_url/api/repo/repo-id/access',
+                               callback=request_callback)
+
+        self.remote.modify_access('repo-id',
+                                  grant=[('username', '$2a$1234'), ('username2', '$2y$5555')],
+                                  revoke=['someone-else'])
index 5d37f05..f44a126 100644 (file)
@@ -37,6 +37,8 @@ class TestPillarExtension(AbstractSVNManTest):
         conv()
         self.assertTrue(svn.is_svnman_project(sdk_project))
 
+        self.assertFalse(svn.is_svnman_project(pillarsdk.Project()))
+
     @mock.patch('svnman.remote.API.create_repo')
     @mock.patch('svnman._random_id')
     def test_create_repo_happy(self, mock_random_id, mock_create_repo):
@@ -69,3 +71,86 @@ class TestPillarExtension(AbstractSVNManTest):
 
         db_proj = self.fetch_project_from_db(self.proj_id)
         self.assertEqual('new-repo-id', db_proj['extension_props'][EXTENSION_NAME]['repo_id'])
+
+    @mock.patch('svnman.remote.API.create_repo')
+    def test_create_repo_already_exists(self, mock_create_repo):
+        from svnman import EXTENSION_NAME
+
+        self.enter_app_context()
+        self.login_api_as(24 * 'a', roles={'admin'})
+
+        mock_create_repo.return_value = 'new-repo-id'
+        self.sdk_project.extension_props = {EXTENSION_NAME: {'repo_id': 'existing-repo-id'}}
+
+        returned_repo_id = self.svnman.create_repo(
+            self.sdk_project,
+            'tester <tester@unittests.com>',
+        )
+        mock_create_repo.assert_not_called()
+
+        self.assertEqual('existing-repo-id', returned_repo_id)
+
+    @mock.patch('svnman.remote.API.create_repo')
+    @mock.patch('svnman._random_id')
+    def test_create_repo_never_unique(self, mock_random_id, mock_create_repo):
+        from svnman.exceptions import RepoAlreadyExists
+
+        self.enter_app_context()
+        self.login_api_as(24 * 'a', roles={'admin'})
+
+        mock_create_repo.side_effect = RepoAlreadyExists('always-same')
+        mock_random_id.return_value = 'always-same'
+
+        with self.assertRaises(ValueError):
+            for _ in range(1000):
+                self.svnman.create_repo(
+                    self.sdk_project,
+                    'tester <tester@unittests.com>',
+                )
+
+    @mock.patch('svnman.remote.API.delete_repo')
+    def test_delete_repo_happy(self, mock_delete_repo):
+        from svnman import EXTENSION_NAME
+        from pillar.api.projects.utils import put_project
+
+        self.enter_app_context()
+        self.login_api_as(24 * 'a', roles={'admin'})
+
+        self.project['extension_props'] = {EXTENSION_NAME: {'repo_id': 'existing-repo-id'}}
+        put_project(self.project)
+
+        self.svnman.delete_repo(self.project['url'], 'existing-repo-id')
+        mock_delete_repo.assert_called_with('existing-repo-id')
+
+        db_proj = self.fetch_project_from_db(self.proj_id)
+        self.assertEqual({EXTENSION_NAME: {}}, db_proj['extension_props'])
+
+    @mock.patch('svnman.remote.API.delete_repo')
+    def test_delete_repo_wrong_id(self, mock_delete_repo):
+        from svnman import EXTENSION_NAME
+        from pillar.api.projects.utils import put_project
+
+        self.enter_app_context()
+        self.login_api_as(24 * 'a', roles={'admin'})
+
+        self.project['extension_props'] = {EXTENSION_NAME: {'repo_id': 'existing-repo-id'}}
+        put_project(self.project)
+
+        with self.assertRaises(ValueError):
+            self.svnman.delete_repo(self.project['url'], 'other-repo-id')
+        mock_delete_repo.assert_not_called()
+
+        db_proj = self.fetch_project_from_db(self.proj_id)
+        self.assertEqual({EXTENSION_NAME: {'repo_id': 'existing-repo-id'}},
+                         db_proj['extension_props'])
+
+    def test_random_id(self):
+        from svnman import _random_id
+
+        rid = _random_id(alphabet='1')
+        self.assertEqual(22 * '1', rid[2:])
+        self.assertRegex(rid[:2], '^[a-z]{2}$')
+
+        for _ in range(10):  # just try a couple of different ones.
+            rid = _random_id()
+            self.assertRegex(rid, '^[a-z]{2}[a-zA-Z0-9]{22}$')