Repository: softlayer/softlayer-python
Pull request #534 (2015-04-26 17:23:12)

Adds XML-RPC proxy server to be used for tests
This allows the bindings to be used without a special transport in order to run tests.

* Setting compression off would still send compression headers. That is now fixed.
* Fixes minor testing errors.


Patch:
diff --git a/SoftLayer/API.py b/SoftLayer/API.py
index 2e86aad4..09a6c63c 100644
--- a/SoftLayer/API.py
+++ b/SoftLayer/API.py
@@ -192,11 +192,12 @@ def call(self, service, method, *args, **kwargs):
         if self._prefix and not service.startswith(self._prefix):
             service = self._prefix + service
 
-        http_headers = {}
+        http_headers = {'Accept': '*/*'}
 
         if kwargs.get('compress', True):
-            http_headers['Accept'] = '*/*'
             http_headers['Accept-Encoding'] = 'gzip, deflate, compress'
+        else:
+            http_headers['Accept-Encoding'] = None
 
         if kwargs.get('raw_headers'):
             http_headers.update(kwargs.get('raw_headers'))


Test patch:
diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py
index 5f24b483..69323202 100644
--- a/SoftLayer/testing/__init__.py
+++ b/SoftLayer/testing/__init__.py
@@ -5,13 +5,14 @@
     :license: MIT, see LICENSE for more details.
 """
 # Disable pylint import error and too many methods error
-# pylint: disable=F0401,R0904
+# pylint: disable=invalid-name
 import logging
 import os.path
 
 import SoftLayer
 from SoftLayer.CLI import core
 from SoftLayer.CLI import environment
+from SoftLayer.testing import xmlrpc
 
 from click import testing
 import mock
@@ -49,6 +50,11 @@ def set_mock(self, service, method):
         self.mocked[_mock_key(service, method)] = _mock
         return _mock
 
+    def clear(self):
+        """Clear out mocks and call history."""
+        self.calls = []
+        self.mocked = {}
+
     def _record_call(self, call):
         """Record and log the API call (for later assertions)."""
         self.calls.append(call)
@@ -74,6 +80,17 @@ def _mock_key(service, method):
 class TestCase(testtools.TestCase):
     """Testcase class with PEP-8 compatable method names."""
 
+    @classmethod
+    def setUpClass(cls):
+        """Stand up fixtured/mockable XML-RPC server."""
+        cls.mocks = MockableTransport(SoftLayer.FixtureTransport())
+        cls.server = xmlrpc.create_test_server(cls.mocks)
+
+    @classmethod
+    def tearDownClass(cls):
+        """Clean up the http server."""
+        cls.server.shutdown()
+
     def set_up(self):
         """Aliased from setUp."""
         pass
@@ -85,18 +102,23 @@ def tear_down(self):
     def setUp(self):  # NOQA
         testtools.TestCase.setUp(self)
 
-        # Create a crazy mockable, fixture client
-        self.mocks = MockableTransport(SoftLayer.FixtureTransport())
-        self.transport = SoftLayer.TimingTransport(self.mocks)
-        self.client = SoftLayer.BaseClient(transport=self.transport)
+        self.mocks.clear()
+
+        host, port = self.server.socket.getsockname()[:2]
+        endpoint_url = "http://%s:%s" % (host, port)
+        transport = SoftLayer.XmlRpcTransport(endpoint_url=endpoint_url)
+        wrapped_transport = SoftLayer.TimingTransport(transport)
+
+        self.client = SoftLayer.BaseClient(transport=wrapped_transport)
 
         self.env = environment.Environment()
         self.env.client = self.client
-        return self.set_up()
+        self.set_up()
 
     def tearDown(self):  # NOQA
         testtools.TestCase.tearDown(self)
-        return self.tear_down()
+        self.tear_down()
+        self.mocks.clear()
 
     def calls(self, service=None, method=None):
         """Return all API calls made during the current test."""
diff --git a/SoftLayer/testing/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/testing/fixtures/SoftLayer_Network_Subnet.py
index 000842f7..e3c97cd4 100644
--- a/SoftLayer/testing/fixtures/SoftLayer_Network_Subnet.py
+++ b/SoftLayer/testing/fixtures/SoftLayer_Network_Subnet.py
@@ -1,1 +1,1 @@
-getObject = {'id': id, 'billingItem': {'id': 1056}}
+getObject = {'id': 1234, 'billingItem': {'id': 1056}}
diff --git a/SoftLayer/testing/xmlrpc.py b/SoftLayer/testing/xmlrpc.py
new file mode 100644
index 00000000..920f86e1
--- /dev/null
+++ b/SoftLayer/testing/xmlrpc.py
@@ -0,0 +1,97 @@
+"""
+    SoftLayer.testing.xmlrpc
+    ~~~~~~~~~~~~~~~~~~~~~~~~
+    XMP-RPC server which can use a transport to proxy requests for testing.
+
+    :license: MIT, see LICENSE for more details.
+"""
+import logging
+import threading
+
+import six
+
+import SoftLayer
+from SoftLayer import transports
+from SoftLayer import utils
+
+# pylint: disable=invalid-name, broad-except
+
+
+class TestServer(six.moves.BaseHTTPServer.HTTPServer):
+    """Test HTTP server which holds a given transport."""
+
+    def __init__(self, transport, *args, **kw):
+        six.moves.BaseHTTPServer.HTTPServer.__init__(self, *args, **kw)
+        self.transport = transport
+
+
+class TestHandler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler):
+    """Test XML-RPC Handler which converts XML-RPC to transport requests."""
+
+    def do_POST(self):
+        """Handle XML-RPC POSTs."""
+        try:
+            length = int(self.headers['Content-Length'])
+            data = self.rfile.read(length).decode('utf-8')
+            args, method = utils.xmlrpc_client.loads(data)
+            headers = args[0].get('headers', {})
+
+            # Form Request for the transport
+            req = transports.Request()
+            req.service = self.path.lstrip('/')
+            req.method = method
+            req.limit = utils.lookup(headers, 'resultLimit', 'limit')
+            req.offset = utils.lookup(headers, 'resultLimit', 'offset')
+            req.args = args[1:]
+            req.filter = _item_by_key_postfix(headers, 'ObjectFilter') or None
+            req.mask = _item_by_key_postfix(headers, 'ObjectMask').get('mask')
+            req.identifier = _item_by_key_postfix(headers,
+                                                  'InitParameters').get('id')
+            req.transport_headers = dict(((k.lower(), v)
+                                          for k, v in self.headers.items()))
+
+            # Get response
+            response = self.server.transport(req)
+
+            response_body = utils.xmlrpc_client.dumps((response,),
+                                                      allow_none=True,
+                                                      methodresponse=True)
+
+            self.send_response(200)
+            self.send_header("Content-type", "application/xml")
+            self.end_headers()
+            self.wfile.write(response_body.encode('utf-8'))
+
+        except SoftLayer.SoftLayerAPIError as ex:
+            self.send_response(200)
+            self.end_headers()
+            response = utils.xmlrpc_client.Fault(ex.faultCode, str(ex.reason))
+            response_body = utils.xmlrpc_client.dumps(response,
+                                                      allow_none=True,
+                                                      methodresponse=True)
+            self.wfile.write(response_body.encode('utf-8'))
+        except Exception as ex:
+            self.send_response(500)
+            logging.exception("Error while handling request")
+
+    def log_message(self, fmt, *args):
+        """Override log_message."""
+        pass
+
+
+def _item_by_key_postfix(dictionary, key_prefix):
+    """Get item from a dictionary which begins with the given prefix."""
+    for key, value in dictionary.items():
+        if key.endswith(key_prefix):
+            return value
+
+    return {}
+
+
+def create_test_server(transport, host='localhost', port=0):
+    """Create a test XML-RPC server in a new thread."""
+    server = TestServer(transport, (host, port), TestHandler)
+    thread = threading.Thread(target=server.serve_forever,
+                              kwargs={'poll_interval': 0.05})
+    thread.start()
+    return server
diff --git a/SoftLayer/tests/CLI/modules/call_api_tests.py b/SoftLayer/tests/CLI/modules/call_api_tests.py
index 2555b8e8..5a5048ca 100644
--- a/SoftLayer/tests/CLI/modules/call_api_tests.py
+++ b/SoftLayer/tests/CLI/modules/call_api_tests.py
@@ -26,7 +26,7 @@ def test_options(self):
         self.assertEqual(result.exit_code, 0)
         self.assertEqual(json.loads(result.output), 'test')
         self.assert_called_with('SoftLayer_Service', 'method',
-                                mask='some.mask',
+                                mask='mask[some.mask]',
                                 limit=20,
                                 offset=40,
                                 identifier='100')
diff --git a/SoftLayer/tests/api_tests.py b/SoftLayer/tests/api_tests.py
index f18e5a75..95272d0a 100644
--- a/SoftLayer/tests/api_tests.py
+++ b/SoftLayer/tests/api_tests.py
@@ -189,34 +189,41 @@ def test_call_invalid_arguments(self):
             self.client.call, 'SERVICE', 'METHOD', invalid_kwarg='invalid')
 
     def test_call_compression_disabled(self):
-        self.set_mock('SoftLayer_SERVICE', 'METHOD')
+        mocked = self.set_mock('SoftLayer_SERVICE', 'METHOD')
+        mocked.return_value = {}
+
         self.client['SERVICE'].METHOD(compress=False)
 
-        self.assert_called_with('SoftLayer_SERVICE', 'METHOD')
+        calls = self.calls('SoftLayer_SERVICE', 'METHOD')
+        self.assertEqual(len(calls), 1)
+        headers = calls[0].transport_headers
+        self.assertEqual(headers.get('accept-encoding'), 'identity')
 
     def test_call_compression_enabled(self):
-        self.set_mock('SoftLayer_SERVICE', 'METHOD')
+        mocked = self.set_mock('SoftLayer_SERVICE', 'METHOD')
+        mocked.return_value = {}
+
         self.client['SERVICE'].METHOD(compress=True)
 
-        expected_headers = {
-            'Accept-Encoding': 'gzip, deflate, compress',
-            'Accept': '*/*',
-        }
-        self.assert_called_with('SoftLayer_SERVICE', 'METHOD',
-                                transport_headers=expected_headers)
+        calls = self.calls('SoftLayer_SERVICE', 'METHOD')
+        self.assertEqual(len(calls), 1)
+        headers = calls[0].transport_headers
+        self.assertEqual(headers.get('accept-encoding'),
+                         'gzip, deflate, compress')
 
     def test_call_compression_override(self):
         # raw_headers should override compress=False
-        self.set_mock('SoftLayer_SERVICE', 'METHOD')
+        mocked = self.set_mock('SoftLayer_SERVICE', 'METHOD')
+        mocked.return_value = {}
+
         self.client['SERVICE'].METHOD(
             compress=False,
             raw_headers={'Accept-Encoding': 'gzip'})
 
-        expected_headers = {
-            'Accept-Encoding': 'gzip',
-        }
-        self.assert_called_with('SoftLayer_SERVICE', 'METHOD',
-                                transport_headers=expected_headers)
+        calls = self.calls('SoftLayer_SERVICE', 'METHOD')
+        self.assertEqual(len(calls), 1)
+        headers = calls[0].transport_headers
+        self.assertEqual(headers.get('accept-encoding'), 'gzip')
 
 
 class UnauthenticatedAPIClient(testing.TestCase):
diff --git a/SoftLayer/tests/managers/dns_tests.py b/SoftLayer/tests/managers/dns_tests.py
index 7e3337a7..b59517f9 100644
--- a/SoftLayer/tests/managers/dns_tests.py
+++ b/SoftLayer/tests/managers/dns_tests.py
@@ -27,7 +27,7 @@ def test_get_zone(self):
         self.assertEqual(res, fixtures.SoftLayer_Dns_Domain.getObject)
         self.assert_called_with('SoftLayer_Dns_Domain', 'getObject',
                                 identifier=12345,
-                                mask='resourceRecords')
+                                mask='mask[resourceRecords]')
 
     def test_get_zone_without_records(self):
         self.dns_client.get_zone(12345, records=False)
diff --git a/SoftLayer/tests/managers/firewall_tests.py b/SoftLayer/tests/managers/firewall_tests.py
index 55966d85..0d056306 100644
--- a/SoftLayer/tests/managers/firewall_tests.py
+++ b/SoftLayer/tests/managers/firewall_tests.py
@@ -59,7 +59,7 @@ def test_get_standard_package_virtual_server(self):
 
         self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject',
                                 identifier=1234,
-                                mask='primaryNetworkComponent[maxSpeed]')
+                                mask='mask[primaryNetworkComponent[maxSpeed]]')
 
         _filter = {
             'items': {
@@ -77,7 +77,7 @@ def test_get_standard_package_bare_metal(self):
 
         # we should ask for the frontEndNetworkComponents to get
         # the firewall port speed
-        mask = 'id,maxSpeed,networkComponentGroup.networkComponents'
+        mask = 'mask[id,maxSpeed,networkComponentGroup.networkComponents]'
         self.assert_called_with('SoftLayer_Hardware_Server',
                                 'getFrontendNetworkComponents',
                                 identifier=1234,
@@ -154,7 +154,7 @@ def test_add_standard_firewall_virtual_server(self):
         self.firewall.add_standard_firewall(6327, is_virt=True)
 
         self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject',
-                                mask='primaryNetworkComponent[maxSpeed]',
+                                mask='mask[primaryNetworkComponent[maxSpeed]]',
                                 identifier=6327)
 
         _filter = {
@@ -195,7 +195,7 @@ def test_add_standard_firewall_server(self):
 
         # we should ask for the frontEndNetworkComponents to get
         # the firewall port speed
-        mask = 'id,maxSpeed,networkComponentGroup.networkComponents'
+        mask = 'mask[id,maxSpeed,networkComponentGroup.networkComponents]'
         self.assert_called_with('SoftLayer_Hardware_Server',
                                 'getFrontendNetworkComponents',
                                 mask=mask,
diff --git a/SoftLayer/tests/managers/loadbal_tests.py b/SoftLayer/tests/managers/loadbal_tests.py
index 534f39e5..960fd241 100644
--- a/SoftLayer/tests/managers/loadbal_tests.py
+++ b/SoftLayer/tests/managers/loadbal_tests.py
@@ -87,7 +87,7 @@ def test_get_local_lbs(self):
         result = self.lb_mgr.get_local_lbs()
 
         self.assertEqual(len(result), 0)
-        mask = 'loadBalancerHardware[datacenter],ipAddress'
+        mask = 'mask[loadBalancerHardware[datacenter],ipAddress]'
         self.assert_called_with('SoftLayer_Account', 'getAdcLoadBalancers',
                                 mask=mask)
 
@@ -95,11 +95,12 @@ def test_get_local_lb(self):
         result = self.lb_mgr.get_local_lb(22348)
 
         self.assertEqual(result['id'], 22348)
-        mask = ('loadBalancerHardware[datacenter], '
+        mask = ('mask['
+                'loadBalancerHardware[datacenter], '
                 'ipAddress, virtualServers[serviceGroups'
                 '[routingMethod,routingType,services'
                 '[healthChecks[type], groupReferences,'
-                ' ipAddress]]]')
+                ' ipAddress]]]]')
         self.assert_called_with(VIRT_IP_SERVICE, 'getObject',
                                 identifier=22348,
                                 mask=mask)
@@ -142,7 +143,7 @@ def test_edit_service(self):
                 }
             }
         }
-        mask = 'serviceGroups[services[groupReferences,healthChecks]]'
+        mask = 'mask[serviceGroups[services[groupReferences,healthChecks]]]'
         self.assert_called_with(VIRT_IP_SERVICE, 'getVirtualServers',
                                 identifier=12345,
                                 filter=_filter,
@@ -153,7 +154,7 @@ def test_edit_service(self):
     def test_add_service(self):
         self.lb_mgr.add_service(12345, 50718, 123, 80, True, 21, 1)
 
-        mask = 'virtualServers[serviceGroups[services[groupReferences]]]'
+        mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]'
         self.assert_called_with(VIRT_IP_SERVICE, 'getObject',
                                 mask=mask,
                                 identifier=12345)
@@ -173,7 +174,7 @@ def test_edit_service_group(self):
                                        routing_type=2,
                                        routing_method=10)
 
-        mask = 'virtualServers[serviceGroups[services[groupReferences]]]'
+        mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]'
         self.assert_called_with(VIRT_IP_SERVICE, 'getObject',
                                 identifier=12345,
                                 mask=mask)
@@ -183,7 +184,7 @@ def test_edit_service_group(self):
     def test_add_service_group(self):
         self.lb_mgr.add_service_group(12345, 100, 80, 2, 10)
 
-        mask = 'virtualServers[serviceGroups[services[groupReferences]]]'
+        mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]'
         self.assert_called_with(VIRT_IP_SERVICE, 'getObject',
                                 mask=mask,
                                 identifier=12345)
@@ -201,7 +202,7 @@ def test_reset_service_group(self):
         self.assert_called_with(VIRT_IP_SERVICE, 'getVirtualServers',
                                 identifier=12345,
                                 filter=_filter,
-                                mask='serviceGroups')
+                                mask='mask[serviceGroups]')
 
         service = ('SoftLayer_Network_Application_Delivery_Controller_'
                    'LoadBalancer_Service_Group')
diff --git a/SoftLayer/tests/managers/network_tests.py b/SoftLayer/tests/managers/network_tests.py
index 17b01fc7..959bdc24 100644
--- a/SoftLayer/tests/managers/network_tests.py
+++ b/SoftLayer/tests/managers/network_tests.py
@@ -153,7 +153,7 @@ def test_list_global_ips_default(self):
 
         self.assertEqual(result,
                          fixtures.SoftLayer_Account.getGlobalIpRecords)
-        mask = 'destinationIpAddress[hardware, virtualGuest],ipAddress'
+        mask = 'mask[destinationIpAddress[hardware, virtualGuest],ipAddress]'
         self.assert_called_with('SoftLayer_Account', 'getGlobalIpRecords',
                                 mask=mask)
 
@@ -162,7 +162,7 @@ def test_list_global_ips_with_filter(self):
 
         self.assertEqual(result,
                          fixtures.SoftLayer_Account.getGlobalIpRecords)
-        mask = 'destinationIpAddress[hardware, virtualGuest],ipAddress'
+        mask = 'mask[destinationIpAddress[hardware, virtualGuest],ipAddress]'
         _filter = {
             'globalIpRecords': {
                 'ipAddress': {
@@ -172,7 +172,7 @@ def test_list_global_ips_with_filter(self):
                 }
             }
         }
-        mask = 'destinationIpAddress[hardware, virtualGuest],ipAddress'
+        mask = 'mask[destinationIpAddress[hardware, virtualGuest],ipAddress]'
         self.assert_called_with('SoftLayer_Account', 'getGlobalIpRecords',
                                 mask=mask,
                                 filter=_filter)
@@ -182,7 +182,7 @@ def test_list_subnets_default(self):
 
         self.assertEqual(result, fixtures.SoftLayer_Account.getSubnets)
         _filter = {'subnets': {'subnetType': {'operation': '!= GLOBAL_IP'}}}
-        mask = 'hardware,datacenter,ipAddressCount,virtualGuests'
+        mask = 'mask[hardware,datacenter,ipAddressCount,virtualGuests]'
         self.assert_called_with('SoftLayer_Account', 'getSubnets',
                                 mask=mask,
                                 filter=_filter)
@@ -206,7 +206,7 @@ def test_list_subnets_with_filters(self):
                 'networkIdentifier': {'operation': '_= 10.0.0.1'}
             }
         }
-        mask = 'hardware,datacenter,ipAddressCount,virtualGuests'
+        mask = 'mask[hardware,datacenter,ipAddressCount,virtualGuests]'
         self.assert_called_with('SoftLayer_Account', 'getSubnets',
                                 mask=mask,
                                 filter=_filter)