Merge pull request #74 from petevg/bug/fix-invalid-annotations
authorPete Vander Giessen <petevg@gmail.com>
Tue, 7 Mar 2017 21:40:58 +0000 (15:40 -0600)
committerGitHub <noreply@github.com>
Tue, 7 Mar 2017 21:40:58 +0000 (15:40 -0600)
Raise errors in a centralized fashion.

juju/application.py
juju/client/connection.py
juju/model.py
tests/integration/test_errors.py [new file with mode: 0644]

index 9863d88..6b1f8ab 100644 (file)
@@ -253,7 +253,7 @@ class Application(model.ModelEntity):
         self.ann_facade.connect(self.connection)
 
         ann = client.EntityAnnotations(
-            entity=self.name,
+            entity=self.tag,
             annotations=annotations,
         )
         return await self.ann_facade.Set([ann])
index 7119292..b508a1a 100644 (file)
@@ -96,8 +96,30 @@ class Connection:
         outgoing = json.dumps(msg, indent=2, cls=encoder)
         await self.ws.send(outgoing)
         result = await self.recv()
-        if result and 'error' in result:
+
+        if not result:
+            return result
+
+        if 'error' in result:
+            # API Error Response
             raise JujuAPIError(result)
+
+        if not 'response' in result:
+            # This may never happen
+            return result
+
+        if 'results' in result['response']:
+            # Check for errors in a result list.
+            errors = []
+            for res in result['response']['results']:
+                if res.get('error', {}).get('message'):
+                    errors.append(res['error']['message'])
+            if errors:
+                raise JujuError(errors)
+
+        elif result['response'].get('error', {}).get('message'):
+            raise JujuError(result['response']['error']['message'])
+
         return result
 
     def http_headers(self):
index 04fb2d4..55ad086 100644 (file)
@@ -1565,9 +1565,6 @@ class BundleHandler(object):
         self.plan = await self.client_facade.GetBundleChanges(
             yaml.dump(self.bundle))
 
-        if self.plan.errors:
-            raise JujuError('\n'.join(self.plan.errors))
-
     async def execute_plan(self):
         for step in self.plan.changes:
             method = getattr(self, step.method)
diff --git a/tests/integration/test_errors.py b/tests/integration/test_errors.py
new file mode 100644 (file)
index 0000000..d173bce
--- /dev/null
@@ -0,0 +1,70 @@
+import pytest
+
+from .. import base
+
+MB = 1
+GB = 1024
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_juju_api_error(event_loop):
+    '''
+    Verify that we raise a JujuAPIError for responses with an error in
+    a top level key (for completely invalid requests).
+
+    '''
+    from juju.errors import JujuAPIError
+
+    async with base.CleanModel() as model:
+        with pytest.raises(JujuAPIError):
+            await model.add_machine(constraints={'mem': 'foo'})
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_juju_error_in_results_list(event_loop):
+    '''
+    Replicate the code that caused
+    https://github.com/juju/python-libjuju/issues/67, and verify that
+    we get a JujuError instead of passing silently by the failure.
+
+    (We don't raise a JujuAPIError, because the request isn't
+    completely invalid -- it's just passing a tag that doesn't exist.)
+
+    This also verifies that we will raise a JujuError any time there
+    is an error in one of a list of results.
+
+    '''
+    from juju.errors import JujuError
+    from juju.client import client
+
+    async with base.CleanModel() as model:
+        ann_facade = client.AnnotationsFacade()
+        ann_facade.connect(model.connection)
+
+        ann = client.EntityAnnotations(
+            entity='badtag',
+            annotations={'gui-x': '1', 'gui-y': '1'},
+        )
+        with pytest.raises(JujuError):
+            return await ann_facade.Set([ann])
+
+
+@base.bootstrapped
+@pytest.mark.asyncio
+async def test_juju_error_in_result(event_loop):
+    '''
+    Verify that we raise a JujuError when appropraite when we are
+    looking at a single result coming back.
+
+    '''
+    from juju.errors import JujuError    
+    from juju.client import client
+
+    async with base.CleanModel() as model:
+        app_facade = client.ApplicationFacade()
+        app_facade.connect(model.connection)
+
+        with pytest.raises(JujuError):
+            return await app_facade.GetCharmURL('foo')