Repository: Azure/msrestazure-for-python

Issue:
If final GET on Location is empty, does not necessarily mean there is a problem
If Swagger explains to follow Location on final state, and there is not return type, it is expected that the final body is empty. However, msrestazure right now assume this final call must have a body.

Fix, update this code:
```python
        elif self._operation.should_do_final_get():
            if self._operation.method == 'POST' and self._operation.location_url:
                final_get_url = self._operation.location_url
            else:
                final_get_url = self._operation.initial_response.request.url
            self._response = self.request_status(final_get_url)
            self._operation.get_status_from_resource(self._response)
```
to NOT do a `get_status_from_resource` at the end (we don't care about the status, we know it's right) but something smarter that accept an empty body

Will fix https://github.com/Azure/azure-cli/issues/7671

Discussion:

Patch:
diff --git a/msrestazure/polling/arm_polling.py b/msrestazure/polling/arm_polling.py
index 2f3aa2a..d299e6f 100644
--- a/msrestazure/polling/arm_polling.py
+++ b/msrestazure/polling/arm_polling.py
@@ -262,7 +262,17 @@ class LongRunningOperation(object):
         status = self._get_provisioning_state(response)
         self.status = status or 'Succeeded'
 
-        self.resource = self._deserialize(response)
+        self.parse_resource(response)
+
+    def parse_resource(self, response):
+        """Assuming this response is a resource, use the deserialization callback to parse it.
+        If body is empty, assuming no resource to return.
+        """
+        self._raise_if_bad_http_status_and_method(response)
+        if not self._is_empty(response):
+            self.resource = self._deserialize(response)
+        else:
+            self.resource = None
 
     def get_status_from_async(self, response):
         """Process the latest status update retrieved from a
@@ -398,7 +408,7 @@ class ARMPolling(PollingMethod):
             else:
                 final_get_url = self._operation.initial_response.request.url
             self._response = self.request_status(final_get_url)
-            self._operation.get_status_from_resource(self._response)
+            self._operation.parse_resource(self._response)
 
     def _delay(self):
         """Check for a 'retry-after' header to set timeout,


Test patch:
diff --git a/tests/test_arm_polling.py b/tests/test_arm_polling.py
index affa1fc..94e71d8 100644
--- a/tests/test_arm_polling.py
+++ b/tests/test_arm_polling.py
@@ -38,7 +38,7 @@ import pytest
 
 from requests import Request, Response
 
-from msrest import Deserializer
+from msrest import Deserializer, Configuration
 from msrest.service_client import ServiceClient
 from msrest.exceptions import DeserializationError
 from msrest.polling import LROPoller
@@ -80,7 +80,7 @@ RESOURCE_URL = 'http://subscriptions/sub1/resourcegroups/g1/resourcetype1/resour
 ERROR = 'http://dummyurl_ReturnError'
 POLLING_STATUS = 200
 
-CLIENT = ServiceClient(None, None)
+CLIENT = ServiceClient(None, Configuration("http://example.org"))
 def mock_send(client_self, request, header_parameters, stream):
     return TestArmPolling.mock_update(request.url, header_parameters)
 CLIENT.send = types.MethodType(mock_send, CLIENT)
@@ -336,6 +336,9 @@ class TestArmPolling(object):
         )
 
         class TestServiceClient(ServiceClient):
+            def __init__(self):
+                ServiceClient.__init__(self, None, Configuration("http://example.org"))
+
             def send(self, request, headers=None, content=None, **config):
                 assert request.method == 'GET'
 
@@ -359,7 +362,7 @@ class TestArmPolling(object):
 
         # Test 1, LRO options with Location final state
         poll = LROPoller(
-            TestServiceClient(None, None),
+            TestServiceClient(),
             response,
             deserialization_cb,
             ARMPolling(0, lro_options={"final-state-via": "location"}))
@@ -368,7 +371,7 @@ class TestArmPolling(object):
 
         # Test 2, LRO options with Azure-AsyncOperation final state
         poll = LROPoller(
-            TestServiceClient(None, None),
+            TestServiceClient(),
             response,
             deserialization_cb,
             ARMPolling(0, lro_options={"final-state-via": "azure-async-operation"}))
@@ -377,13 +380,46 @@ class TestArmPolling(object):
 
         # Test 3, backward compat (no options, means "azure-async-operation")
         poll = LROPoller(
-            TestServiceClient(None, None),
+            TestServiceClient(),
             response,
             deserialization_cb,
             ARMPolling(0))
         result = poll.result()
         assert result['status'] == 'Succeeded'
 
+        # Test 4, location has no body
+
+        class TestServiceClientNoBody(ServiceClient):
+            def __init__(self):
+                ServiceClient.__init__(self, None, Configuration("http://example.org"))
+
+            def send(self, request, headers=None, content=None, **config):
+                assert request.method == 'GET'
+
+                if request.url == 'http://example.org/location':
+                    return TestArmPolling.mock_send(
+                        'GET',
+                        200,
+                        body=""
+                    )
+                elif request.url == 'http://example.org/async_monitor':
+                    return TestArmPolling.mock_send(
+                        'GET',
+                        200,
+                        body={'status': 'Succeeded'}
+                    )
+                else:
+                    pytest.fail("No other query allowed")
+
+        poll = LROPoller(
+            TestServiceClientNoBody(),
+            response,
+            deserialization_cb,
+            ARMPolling(0, lro_options={"final-state-via": "location"}))
+        result = poll.result()
+        assert result is None
+
+
         # Former oooooold tests to refactor one day to something more readble
 
         # Test throw on non LRO related status code