#!/usr/bin/python

# Copyright (C) 2014, ProfitBricks GmbH
# Authors: Benjamin Drung <benjamin.drung@profitbricks.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

"""Unit tests for the profitbricks_client Python module."""

from __future__ import print_function

import datetime
import io
import os
import re
import unittest
import xml.dom.minidom

import httpretty

from suds.sax.date import UtcTimezone

import profitbricks_client

ADDED_FIREWALL_RULE = u"""<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:addFirewallRulesToNicResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <active>false</active>
    <firewallId>35ee3ae6-5bed-4059-889e-a1b930b36cb7</firewallId>
    <firewallRules>
      <firewallRuleId>57b4c449-3d45-4214-9ac3-344a2001fc44</firewallRuleId>
      <name>test</name>
      <portRangeEnd>55555</portRangeEnd>
      <portRangeStart>44444</portRangeStart>
      <protocol>TCP</protocol>
      <sourceIp>10.1.1.1</sourceIp>
    </firewallRules>
    <nicId>55585aaa-fca3-41f0-b8d1-6d5fc231d5cd</nicId>
    <provisioningState>INPROCESS</provisioningState>
  </return>
</ns2:addFirewallRulesToNicResponse>
</S:Body>
</S:Envelope>"""

ALL_DATACENTERS_API_1_2 = u"""<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getAllDataCentersResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
    <dataCenterName>profitbricks-client test datacenter</dataCenterName>
    <dataCenterVersion>7</dataCenterVersion>
  </return>
</ns2:getAllDataCentersResponse>
</S:Body>
</S:Envelope>"""

ALL_DATACENTERS = u"""<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getAllDataCentersResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
    <dataCenterName>profitbricks-client test datacenter</dataCenterName>
    <dataCenterVersion>7</dataCenterVersion>
    <provisioningState>AVAILABLE</provisioningState>
  </return>
</ns2:getAllDataCentersResponse>
</S:Body>
</S:Envelope>"""

CREATED_DATACENTER_API_1_2 = u"""<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:CreateDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <requestId>2360771</requestId>
    <dataCenterId>4649b926-5989-4911-8d43-6114e2ae1f49</dataCenterId>
    <dataCenterVersion>1</dataCenterVersion>
    <region>EUROPE</region>
  </return>
</ns2:CreateDataCenterResponse>
</S:Body>
</S:Envelope>"""

CREATED_DATACENTER = u"""<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:CreateDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <requestId>2360771</requestId>
    <dataCenterId>4649b926-5989-4911-8d43-6114e2ae1f49</dataCenterId>
    <dataCenterVersion>1</dataCenterVersion>
    <location>de/fra</location>
  </return>
</ns2:CreateDataCenterResponse>
</S:Body>
</S:Envelope>"""

CREATED_LOAD_BALANCER = """<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:createLoadBalancerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <requestId>15266171</requestId>
    <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
    <dataCenterVersion>4</dataCenterVersion>
    <loadBalancerId>4d56832c-44dc-46a2-9e98-43efb3598f83</loadBalancerId>
  </return>
</ns2:createLoadBalancerResponse>
</S:Body>
</S:Envelope>"""

CREATED_SERVER = u"""<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:CreateServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <requestId>2360771</requestId>
    <dataCenterId>7724e95d-c446-4d7f-bede-3d2b0f1d56af</dataCenterId>
    <dataCenterVersion>1</dataCenterVersion>
    <serverId>35c34b7e-e212-46af-91a6-4dd50bafbe5c</serverId>
  </return>
</ns2:CreateServerResponse>
</S:Body>
</S:Envelope>"""

DATACENTER_API_1_2 = u"""<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <requestId>2524736</requestId>
    <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
    <dataCenterVersion>7</dataCenterVersion>
    <dataCenterName>profitbricks-client test datacenter</dataCenterName>
    <servers>
      <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
      <dataCenterVersion>7</dataCenterVersion>
      <serverId>a6376253-0c1b-4949-9722-b471e696b616</serverId>
      <serverName>Server 42</serverName>
      <cores>1</cores>
      <ram>256</ram>
      <internetAccess>true</internetAccess>
      <ips>192.0.2.7</ips>
      <nics>
        <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
        <dataCenterVersion>7</dataCenterVersion>
        <nicId>17949987-30c1-4f43-b6ae-e006d27c99bc</nicId>
        <lanId>2</lanId>
        <internetAccess>true</internetAccess>
        <serverId>a6376253-0c1b-4949-9722-b471e696b616</serverId>
        <ips>192.0.2.7</ips>
        <macAddress>00:16:3e:1f:fd:0f</macAddress>
        <firewall>
          <active>false</active>
          <firewallId>341bff23-baec-4bcf-a766-547ca7e5a975</firewallId>
          <nicId>17949987-30c1-4f43-b6ae-e006d27c99bc</nicId>
          <provisioningState>AVAILABLE</provisioningState>
        </firewall>
        <dhcpActive>true</dhcpActive>
        <gatewayIp>192.0.2.1</gatewayIp>
        <provisioningState>AVAILABLE</provisioningState>
      </nics>
      <provisioningState>AVAILABLE</provisioningState>
      <virtualMachineState>RUNNING</virtualMachineState>
      <creationTime>2014-03-12T09:36:58.554Z</creationTime>
      <lastModificationTime>2014-03-12T15:34:22.661Z</lastModificationTime>
      <osType>UNKNOWN</osType>
      <availabilityZone>AUTO</availabilityZone>
      </servers>
    <provisioningState>AVAILABLE</provisioningState>
    <region>EUROPE</region>
  </return>
</ns2:getDataCenterResponse>
</S:Body>
</S:Envelope>
"""

DATACENTER = u"""<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <requestId>2524736</requestId>
    <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
    <dataCenterVersion>7</dataCenterVersion>
    <dataCenterName>profitbricks-client test datacenter</dataCenterName>
    <servers>
      <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
      <dataCenterVersion>7</dataCenterVersion>
      <serverId>a6376253-0c1b-4949-9722-b471e696b616</serverId>
      <serverName>Server 42</serverName>
      <cores>1</cores>
      <ram>256</ram>
      <internetAccess>true</internetAccess>
      <ips>192.0.2.7</ips>
      <nics>
        <dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>
        <dataCenterVersion>7</dataCenterVersion>
        <nicId>17949987-30c1-4f43-b6ae-e006d27c99bc</nicId>
        <lanId>2</lanId>
        <internetAccess>true</internetAccess>
        <serverId>a6376253-0c1b-4949-9722-b471e696b616</serverId>
        <ips>192.0.2.7</ips>
        <macAddress>00:16:3e:1f:fd:0f</macAddress>
        <firewall>
          <active>false</active>
          <firewallId>341bff23-baec-4bcf-a766-547ca7e5a975</firewallId>
          <nicId>17949987-30c1-4f43-b6ae-e006d27c99bc</nicId>
          <provisioningState>AVAILABLE</provisioningState>
        </firewall>
        <dhcpActive>true</dhcpActive>
        <gatewayIp>192.0.2.1</gatewayIp>
        <provisioningState>AVAILABLE</provisioningState>
      </nics>
      <provisioningState>AVAILABLE</provisioningState>
      <virtualMachineState>RUNNING</virtualMachineState>
      <creationTime>2014-03-12T09:36:58.554Z</creationTime>
      <lastModificationTime>2014-03-12T15:34:22.661Z</lastModificationTime>
      <osType>UNKNOWN</osType>
      <availabilityZone>AUTO</availabilityZone>
      <cpuHotPlug>true</cpuHotPlug>
      <ramHotPlug>true</ramHotPlug>
      <nicHotPlug>true</nicHotPlug>
      <nicHotUnPlug>true</nicHotUnPlug>
      <discVirtioHotPlug>true</discVirtioHotPlug>
      <discVirtioHotUnPlug>true</discVirtioHotUnPlug>
      </servers>
    <provisioningState>AVAILABLE</provisioningState>
    <location>de/fra</location>
  </return>
</ns2:getDataCenterResponse>
</S:Body>
</S:Envelope>
"""

UPDATED_SERVER = """<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:updateServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
  <return>
    <requestId>15553233</requestId>
    <dataCenterId>7724e95d-c446-4d7f-bede-3d2b0f1d56af</dataCenterId>
    <dataCenterVersion>2</dataCenterVersion>
  </return>
</ns2:updateServerResponse>
</S:Body>
</S:Envelope>"""

SUPPORT_MATRIX = u"""
[2.0]
1.2=https://api.profitbricks.com/1.2/wsdl
1.3=https://api.profitbricks.com/1.3/wsdl

[2.1]
1.4=https://api.profitbricks.com/1.4/wsdl

[3.0]
1.5=https://api.profitbricks.com/1.5/wsdl
1.6=https://api.profitbricks.com/1.6/wsdl
"""


def soap_request(body):
    """Return a full SOAP request XML document with the given body."""
    return ('<?xml version="1.0" encoding="UTF-8"?>'
            '<SOAP-ENV:Envelope xmlns:ns0="http://ws.api.profitbricks.com/"'
            ' xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/"'
            ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
            ' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">'
            '<SOAP-ENV:Header/>'
            '<ns1:Body>{body}</ns1:Body>'
            '</SOAP-ENV:Envelope>'.format(body=body))


def switch_namespaces(doc):
    """Switch the first with the second namespace in the given XML document"""
    child = doc.firstChild
    first_ns = child.getAttribute('xmlns:ns0')
    second_ns = child.getAttribute('xmlns:ns1')
    child.setAttribute('xmlns:ns0', second_ns)
    child.setAttribute('xmlns:ns1', first_ns)
    # Use reqular expression to replace the namespaces (as a quick and dirty solution)
    xml_string = re.sub('(</?ns)([01]):',
                        lambda x: x.group(1) + {'0': '1', '1': '0'}[x.group(2)] + ':',
                        doc.toxml())
    return xml.dom.minidom.parseString(xml_string)


class TestCase(unittest.TestCase):  # pylint: disable=too-many-public-methods
    """Abstraction layer to backport features from newer Python 3 versinos"""
    def __init__(self, *args, **kwargs):
        super(TestCase, self).__init__(*args, **kwargs)
        if not hasattr(self, "assertRaisesRegex"):
            self.assertRaisesRegex = self.assertRaisesRegexp  # pylint: disable=invalid-name


class XmlTestCase(TestCase):  # pylint: disable=R0904
    """Extended TestCase with XML comparisions"""

    maxDiff = None

    def assert_sudsobject_equal(self, first, second):
        """Fail if the two objects are unequal as determined by their string representation."""
        self.assertMultiLineEqual(str(first), str(second))

    def assert_xml_equal(self, first, second):
        """Prettify XML strings before comparing them to make the exception easier to read."""
        try:
            first_xml = xml.dom.minidom.parseString(first)
            second_xml = xml.dom.minidom.parseString(second)
            first_child = first_xml.firstChild  # pylint: disable=maybe-no-member
            second_child = second_xml.firstChild  # pylint: disable=maybe-no-member
            if first_child.hasAttribute('xmlns:ns1') and second_child.hasAttribute('xmlns:ns0') \
                    and first_child.getAttribute('xmlns:ns1') == \
                    second_child.getAttribute('xmlns:ns0'):
                first_xml = switch_namespaces(first_xml)
            first = first_xml.toprettyxml()
            second = second_xml.toprettyxml()
        except xml.parsers.expat.ExpatError:
            pass
        self.assertEqual(first, second)


# pylint: disable=W0212
class CallTestsApi12(XmlTestCase):  # pylint: disable=R0904
    """Test API 1.2 function call."""

    port = 'https://api.profitbricks.com/1.2'

    @httpretty.activate
    def setUp(self):  # pylint: disable=C0103
        wsdl_filename = os.path.join(os.path.abspath(os.path.dirname(__name__)),
                                     "tests", "api-1.2-wsdl.xml")
        endpoint = "https://api.test.profitbricks.test.com/1.2/wsdl"
        with open(wsdl_filename) as wsdl_file:
            wsdl_content = wsdl_file.read()
        httpretty.register_uri(httpretty.GET, endpoint, body=wsdl_content)
        self.client = profitbricks_client.get_profitbricks_client(
            "profitbricks-client test user",
            "very secret password",
            endpoint=endpoint,
            store_endpoint=False
        )
        self.factory = self.client._soap_client.factory

    @httpretty.activate
    def test_create_datacenter_1arg(self):
        """Test API 1.2 call client.createDataCenter(dataCenterName='Test')"""
        httpretty.register_uri(httpretty.POST, self.port, body=CREATED_DATACENTER_API_1_2)
        created_datacenter = self.client.createDataCenter(dataCenterName='Test')
        request = httpretty.last_request()
        expected_request = ('<ns0:createDataCenter><dataCenterName>Test</dataCenterName>'
                            '</ns0:createDataCenter>')
        self.assert_xml_equal(soap_request(expected_request), request.body)

        expected_datacenter = self.factory.create('createDcResponse')
        expected_datacenter.requestId = '2360771'
        expected_datacenter.dataCenterId = '4649b926-5989-4911-8d43-6114e2ae1f49'
        expected_datacenter.dataCenterVersion = 1
        expected_datacenter.region = 'EUROPE'
        self.assert_sudsobject_equal(expected_datacenter, created_datacenter)

    @httpretty.activate
    def test_create_server(self):
        """Test API 1.2 call client.createServer(dataCenterId='<id>', cores=1, ram=256)"""
        httpretty.register_uri(httpretty.POST, self.port, body=CREATED_SERVER)
        datacenter_id = "7724e95d-c446-4d7f-bede-3d2b0f1d56af"
        created_server = self.client.createServer(dataCenterId=datacenter_id, cores=1, ram=256)
        request = httpretty.last_request()
        expected_request = ('<ns0:createServer><request>'
                            '<dataCenterId>7724e95d-c446-4d7f-bede-3d2b0f1d56af</dataCenterId>'
                            '<cores>1</cores><ram>256</ram><internetAccess/><osType/>'
                            '<availabilityZone/></request></ns0:createServer>')
        self.assert_xml_equal(soap_request(expected_request), request.body)

        expected_response = self.factory.create('createServerResponse')
        expected_response.requestId = "2360771"
        expected_response.dataCenterId = datacenter_id
        expected_response.dataCenterVersion = 1
        expected_response.serverId = "35c34b7e-e212-46af-91a6-4dd50bafbe5c"
        self.assert_sudsobject_equal(expected_response, created_server)

    @httpretty.activate
    def test_create_datacenter_2args(self):
        """Test API 1.2 call client.createDataCenter(dataCenterName='Test', region='EUROPE')"""
        httpretty.register_uri(httpretty.POST, self.port, body=CREATED_DATACENTER_API_1_2)
        created_datacenter = self.client.createDataCenter(dataCenterName='Test', region='EUROPE')
        request = httpretty.last_request()
        expected_request = ('<ns0:createDataCenter><dataCenterName>Test</dataCenterName>'
                            '<region>EUROPE</region></ns0:createDataCenter>')
        self.assert_xml_equal(soap_request(expected_request), request.body)

        expected_datacenter = self.factory.create('createDcResponse')
        expected_datacenter.requestId = '2360771'
        expected_datacenter.dataCenterId = '4649b926-5989-4911-8d43-6114e2ae1f49'
        expected_datacenter.dataCenterVersion = 1
        expected_datacenter.region = 'EUROPE'
        self.assert_sudsobject_equal(expected_datacenter, created_datacenter)

    @httpretty.activate
    def test_get_all_datacenters(self):
        """Test API 1.2 call client.getAllDataCenters()"""
        httpretty.register_uri(httpretty.POST, self.port, body=ALL_DATACENTERS_API_1_2)
        datacenters = self.client.getAllDataCenters()
        request = httpretty.last_request()
        self.assert_xml_equal(soap_request("<ns0:getAllDataCenters/>"), request.body)

        expected_datacenter = self.factory.create('dataCenterIdentifier')
        expected_datacenter.dataCenterId = "7cf8012b-b834-4e31-aa70-2c67e808e271"
        expected_datacenter.dataCenterName = "profitbricks-client test datacenter"
        expected_datacenter.dataCenterVersion = 7
        self.assert_sudsobject_equal([expected_datacenter], datacenters)

    @httpretty.activate
    def test_get_datacenter(self):  # pylint: disable=R0915
        """Test API 1.2 call client.getDataCenter(dataCenterId=<id>)"""
        httpretty.register_uri(httpretty.POST, self.port, body=DATACENTER_API_1_2)
        datacenter_id = "7cf8012b-b834-4e31-aa70-2c67e808e271"
        datacenter = self.client.getDataCenter(dataCenterId=datacenter_id)
        request = httpretty.last_request()
        expected_body = ("<ns0:getDataCenter><dataCenterId>" + datacenter_id +
                         "</dataCenterId></ns0:getDataCenter>")
        self.assert_xml_equal(soap_request(expected_body), request.body)

        expected_datacenter = self.factory.create('dataCenter')
        expected_datacenter.requestId = "2524736"
        expected_datacenter.dataCenterId = datacenter_id
        expected_datacenter.dataCenterVersion = 7
        expected_datacenter.dataCenterName = "profitbricks-client test datacenter"
        server1 = self.factory.create('server')
        expected_datacenter.servers = [server1]
        del expected_datacenter.storages
        del expected_datacenter.loadBalancers
        expected_datacenter.provisioningState = "AVAILABLE"
        expected_datacenter.region = "EUROPE"

        del server1.requestId
        server1.dataCenterId = datacenter_id
        server1.dataCenterVersion = 7
        server1.serverId = "a6376253-0c1b-4949-9722-b471e696b616"
        server1.serverName = "Server 42"
        server1.cores = 1
        server1.ram = 256
        server1.internetAccess = True
        server1.ips = ["192.0.2.7"]
        del server1.connectedStorages
        del server1.romDrives
        nic1 = self.factory.create('nic')
        server1.nics = [nic1]
        server1.provisioningState = "AVAILABLE"
        server1.virtualMachineState = "RUNNING"
        server1.creationTime = datetime.datetime(2014, 3, 12, 9, 36, 58, 554000, UtcTimezone())
        server1.lastModificationTime = datetime.datetime(2014, 3, 12, 15, 34, 22, 661000,
                                                         UtcTimezone())
        server1.osType = "UNKNOWN"
        server1.availabilityZone = "AUTO"

        del nic1.requestId
        nic1.dataCenterId = datacenter_id
        nic1.dataCenterVersion = 7
        nic1.nicId = "17949987-30c1-4f43-b6ae-e006d27c99bc"
        del nic1.nicName
        nic1.lanId = 2
        nic1.internetAccess = True
        nic1.serverId = "a6376253-0c1b-4949-9722-b471e696b616"
        nic1.ips = ["192.0.2.7"]
        nic1.macAddress = "00:16:3e:1f:fd:0f"
        nic1.firewall = self.factory.create('firewall')
        nic1.firewall.active = False
        nic1.firewall.firewallId = "341bff23-baec-4bcf-a766-547ca7e5a975"
        nic1.firewall.nicId = nic1.nicId
        del nic1.firewall.firewallRules
        nic1.firewall.provisioningState = "AVAILABLE"
        nic1.dhcpActive = True
        nic1.gatewayIp = "192.0.2.1"
        nic1.provisioningState = "AVAILABLE"

        self.assert_sudsobject_equal(expected_datacenter, datacenter)


# pylint: disable=W0212
class CallTests(XmlTestCase):  # pylint: disable=R0904
    """Test API function calls."""

    port = 'https://api.profitbricks.com/1.3'

    @httpretty.activate
    def setUp(self):  # pylint: disable=C0103
        wsdl_filename = os.path.join(os.path.abspath(os.path.dirname(__name__)),
                                     "tests", "api-1.3-wsdl.xml")
        endpoint = "https://api.test.profitbricks.test.com/1.3/wsdl"
        with open(wsdl_filename) as wsdl_file:
            wsdl_content = wsdl_file.read()
        httpretty.register_uri(httpretty.GET, endpoint, body=wsdl_content)
        self.client = profitbricks_client.get_profitbricks_client(
            "profitbricks-client test user",
            "very secret password",
            endpoint=endpoint,
            store_endpoint=False
        )
        self.factory = self.client._soap_client.factory

    @property
    def updated_server_response(self):
        """Return the expected SUDS object corresponding to the UPDATED_SERVER SOAP response"""
        response = self.factory.create('versionResponse')
        response.requestId = "15553233"
        response.dataCenterId = "7724e95d-c446-4d7f-bede-3d2b0f1d56af"
        response.dataCenterVersion = 2
        return response

    @httpretty.activate
    def test_boolean(self):
        """Test boolean with updateServer(serverId='<id>', cpuHotPlug=True)"""
        httpretty.register_uri(httpretty.POST, self.port, body=UPDATED_SERVER)
        updated_server = self.client.updateServer(serverId="35c34b7e-e212-46af-91a6-4dd50bafbe5c",
                                                  cpuHotPlug=True)
        request = httpretty.last_request()
        expected_request = ('<ns0:updateServer><request><serverId>35c34b7e-e212-46af-91a6-'
                            '4dd50bafbe5c</serverId><osType/><availabilityZone/>'
                            '<cpuHotPlug>true</cpuHotPlug></request></ns0:updateServer>')
        self.assert_xml_equal(soap_request(expected_request), request.body)
        self.assert_sudsobject_equal(self.updated_server_response, updated_server)

    @httpretty.activate
    def test_boolean_ivalid_string(self):
        """Test invalid boolean string with updateServer(serverId='<id>', cpuHotPlug='xyz')"""
        msg = "invalid string 'xyz' for boolean argument 'cpuHotPlug'"
        self.assertRaisesRegex(ValueError, msg, self.client.updateServer,
                               serverId="35c34b7e-e212-46af-91a6-4dd50bafbe5c", cpuHotPlug='xyz')

    @httpretty.activate
    def test_boolean_string_all_caps(self):
        """Test boolean string with updateServer(serverId='<id>', cpuHotPlug='TRUE')"""
        httpretty.register_uri(httpretty.POST, self.port, body=UPDATED_SERVER)
        updated_server = self.client.updateServer(serverId="35c34b7e-e212-46af-91a6-4dd50bafbe5c",
                                                  cpuHotPlug='TRUE')
        request = httpretty.last_request()
        expected_request = ('<ns0:updateServer><request><serverId>35c34b7e-e212-46af-91a6-'
                            '4dd50bafbe5c</serverId><osType/><availabilityZone/>'
                            '<cpuHotPlug>true</cpuHotPlug></request></ns0:updateServer>')
        self.assert_xml_equal(soap_request(expected_request), request.body)
        self.assert_sudsobject_equal(self.updated_server_response, updated_server)

    @httpretty.activate
    def test_boolean_string_lowercase(self):
        """Test boolean string with updateServer(serverId='<id>', cpuHotPlug='true')"""
        httpretty.register_uri(httpretty.POST, self.port, body=UPDATED_SERVER)
        updated_server = self.client.updateServer(serverId="35c34b7e-e212-46af-91a6-4dd50bafbe5c",
                                                  cpuHotPlug='true')
        request = httpretty.last_request()
        expected_request = ('<ns0:updateServer><request><serverId>35c34b7e-e212-46af-91a6-'
                            '4dd50bafbe5c</serverId><osType/><availabilityZone/>'
                            '<cpuHotPlug>true</cpuHotPlug></request></ns0:updateServer>')
        self.assert_xml_equal(soap_request(expected_request), request.body)
        self.assert_sudsobject_equal(self.updated_server_response, updated_server)

    @httpretty.activate
    def test_boolean_string_start_case(self):
        """Test boolean string with updateServer(serverId='<id>', cpuHotPlug='True')"""
        httpretty.register_uri(httpretty.POST, self.port, body=UPDATED_SERVER)
        updated_server = self.client.updateServer(serverId="35c34b7e-e212-46af-91a6-4dd50bafbe5c",
                                                  cpuHotPlug='True')
        request = httpretty.last_request()
        expected_request = ('<ns0:updateServer><request><serverId>35c34b7e-e212-46af-91a6-'
                            '4dd50bafbe5c</serverId><osType/><availabilityZone/>'
                            '<cpuHotPlug>true</cpuHotPlug></request></ns0:updateServer>')
        self.assert_xml_equal(soap_request(expected_request), request.body)
        self.assert_sudsobject_equal(self.updated_server_response, updated_server)

    @httpretty.activate
    def test_complex_input_parameter(self):
        """Test complex input parameter by calling addFirewallRulesToNic([...])"""
        httpretty.register_uri(httpretty.POST, self.port, body=ADDED_FIREWALL_RULE)
        nic_id = "55585aaa-fca3-41f0-b8d1-6d5fc231d5cd"
        added_rule = self.client.addFirewallRulesToNic(nicId=nic_id, portRangeStart=44444,
                                                       portRangeEnd=55555, protocol="TCP",
                                                       sourceIp="10.1.1.1")
        request = httpretty.last_request()
        expected_request = ('<ns0:addFirewallRulesToNic><request><portRangeEnd>55555'
                            '</portRangeEnd><portRangeStart>44444</portRangeStart>'
                            '<protocol>TCP</protocol><sourceIp>10.1.1.1</sourceIp></request>'
                            '<nicId>55585aaa-fca3-41f0-b8d1-6d5fc231d5cd</nicId>'
                            '</ns0:addFirewallRulesToNic>')
        self.assert_xml_equal(soap_request(expected_request), request.body)

        rule1 = self.factory.create('firewallRule')
        rule1.firewallRuleId = '57b4c449-3d45-4214-9ac3-344a2001fc44'
        del rule1.icmpCode
        del rule1.icmpType
        rule1.name = 'test'
        rule1.portRangeEnd = 55555
        rule1.portRangeStart = 44444
        rule1.protocol = 'TCP'
        rule1.sourceIp = '10.1.1.1'
        del rule1.sourceMac
        del rule1.targetIp
        expected_response = self.factory.create('firewall')
        expected_response.active = False
        expected_response.firewallId = '35ee3ae6-5bed-4059-889e-a1b930b36cb7'
        expected_response.firewallRules = [rule1]
        expected_response.nicId = nic_id
        expected_response.provisioningState = 'INPROCESS'
        self.assert_sudsobject_equal(expected_response, added_rule)

    @httpretty.activate
    def test_create_server(self):
        """Test calling client.createServer(dataCenterId='<id>', cores=1, ram=256)"""
        datacenter_id = '7724e95d-c446-4d7f-bede-3d2b0f1d56af'
        httpretty.register_uri(httpretty.POST, self.port, body=CREATED_SERVER)
        created_server = self.client.createServer(dataCenterId=datacenter_id, cores=1, ram=256)
        request = httpretty.last_request()
        expected_request = ('<ns0:createServer><request>'
                            '<dataCenterId>7724e95d-c446-4d7f-bede-3d2b0f1d56af</dataCenterId>'
                            '<cores>1</cores><ram>256</ram><internetAccess/><osType/>'
                            '<availabilityZone/></request></ns0:createServer>')
        self.assert_xml_equal(soap_request(expected_request), request.body)

        expected_response = self.factory.create('createServerResponse')
        expected_response.requestId = "2360771"
        expected_response.dataCenterId = datacenter_id
        expected_response.dataCenterVersion = 1
        expected_response.serverId = "35c34b7e-e212-46af-91a6-4dd50bafbe5c"
        self.assert_sudsobject_equal(expected_response, created_server)

    @httpretty.activate
    def test_create_datacenter(self):
        """Test calling client.createDataCenter(dataCenterName='Test', location='de/fra')"""
        httpretty.register_uri(httpretty.POST, self.port, body=CREATED_DATACENTER)
        created_datacenter = self.client.createDataCenter(dataCenterName='Test', location='de/fra')
        request = httpretty.last_request()
        expected_request = ('<ns0:createDataCenter><request><dataCenterName>Test</dataCenterName>'
                            '<location>de/fra</location></request></ns0:createDataCenter>')
        self.assert_xml_equal(soap_request(expected_request), request.body)

        expected_datacenter = self.factory.create('createDcResponse')
        expected_datacenter.requestId = '2360771'
        expected_datacenter.dataCenterId = '4649b926-5989-4911-8d43-6114e2ae1f49'
        expected_datacenter.dataCenterVersion = 1
        expected_datacenter.location = 'de/fra'
        self.assert_sudsobject_equal(expected_datacenter, created_datacenter)

    @httpretty.activate
    def test_get_all_datacenters(self):
        """Test calling client.getAllDataCenters()"""
        httpretty.register_uri(httpretty.POST, self.port, body=ALL_DATACENTERS)
        datacenters = self.client.getAllDataCenters()
        request = httpretty.last_request()
        self.assert_xml_equal(soap_request("<ns0:getAllDataCenters/>"), request.body)

        expected_datacenter = self.factory.create('dataCenterIdentifier')
        expected_datacenter.dataCenterId = "7cf8012b-b834-4e31-aa70-2c67e808e271"
        expected_datacenter.dataCenterName = "profitbricks-client test datacenter"
        expected_datacenter.dataCenterVersion = 7
        expected_datacenter.provisioningState = "AVAILABLE"
        self.assert_sudsobject_equal([expected_datacenter], datacenters)

    @httpretty.activate
    def test_get_datacenter(self):  # pylint: disable=R0915
        """Test calling client.getDataCenter(dataCenterId=<id>)"""
        httpretty.register_uri(httpretty.POST, self.port, body=DATACENTER)
        datacenter_id = "7cf8012b-b834-4e31-aa70-2c67e808e271"
        datacenter = self.client.getDataCenter(dataCenterId=datacenter_id)
        request = httpretty.last_request()
        expected_body = ("<ns0:getDataCenter><dataCenterId>" + datacenter_id +
                         "</dataCenterId></ns0:getDataCenter>")
        self.assert_xml_equal(soap_request(expected_body), request.body)

        expected_datacenter = self.factory.create('dataCenter')
        expected_datacenter.requestId = "2524736"
        expected_datacenter.dataCenterId = datacenter_id
        expected_datacenter.dataCenterVersion = 7
        expected_datacenter.dataCenterName = "profitbricks-client test datacenter"
        server1 = self.factory.create('server')
        expected_datacenter.servers = [server1]
        del expected_datacenter.storages
        del expected_datacenter.loadBalancers
        expected_datacenter.provisioningState = "AVAILABLE"
        expected_datacenter.location = "de/fra"

        del server1.requestId
        server1.dataCenterId = datacenter_id
        server1.dataCenterVersion = 7
        server1.serverId = "a6376253-0c1b-4949-9722-b471e696b616"
        server1.serverName = "Server 42"
        server1.cores = 1
        server1.ram = 256
        server1.internetAccess = True
        server1.ips = ["192.0.2.7"]
        del server1.connectedStorages
        del server1.romDrives
        nic1 = self.factory.create('nic')
        server1.nics = [nic1]
        server1.provisioningState = "AVAILABLE"
        server1.virtualMachineState = "RUNNING"
        server1.creationTime = datetime.datetime(2014, 3, 12, 9, 36, 58, 554000, UtcTimezone())
        server1.lastModificationTime = datetime.datetime(2014, 3, 12, 15, 34, 22, 661000,
                                                         UtcTimezone())
        server1.osType = "UNKNOWN"
        server1.availabilityZone = "AUTO"
        server1.cpuHotPlug = True
        server1.ramHotPlug = True
        server1.nicHotPlug = True
        server1.nicHotUnPlug = True
        server1.discVirtioHotPlug = True
        server1.discVirtioHotUnPlug = True

        del nic1.requestId
        nic1.dataCenterId = datacenter_id
        nic1.dataCenterVersion = 7
        nic1.nicId = "17949987-30c1-4f43-b6ae-e006d27c99bc"
        del nic1.nicName
        nic1.lanId = 2
        nic1.internetAccess = True
        nic1.serverId = "a6376253-0c1b-4949-9722-b471e696b616"
        nic1.ips = ["192.0.2.7"]
        nic1.macAddress = "00:16:3e:1f:fd:0f"
        nic1.firewall = self.factory.create('firewall')
        nic1.firewall.active = False
        nic1.firewall.firewallId = "341bff23-baec-4bcf-a766-547ca7e5a975"
        nic1.firewall.nicId = nic1.nicId
        del nic1.firewall.firewallRules
        nic1.firewall.provisioningState = "AVAILABLE"
        nic1.dhcpActive = True
        nic1.gatewayIp = "192.0.2.1"
        nic1.provisioningState = "AVAILABLE"

        self.assert_sudsobject_equal(expected_datacenter, datacenter)

    @httpretty.activate
    def test_complex_parameter_1arg(self):
        """Test calling client.createLoadBalancer(dataCenterId='<id>')

        Test passing one simple paramater to an API function that expects exactly
        one complex parameter (containing simple parameters).
        """
        datacenter_id = "7cf8012b-b834-4e31-aa70-2c67e808e271"
        httpretty.register_uri(httpretty.POST, self.port, body=CREATED_LOAD_BALANCER)
        created_load_balancer = self.client.createLoadBalancer(dataCenterId=datacenter_id)
        request = httpretty.last_request()
        expected_request = ('<ns0:createLoadBalancer><request>'
                            '<dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>'
                            '<loadBalancerAlgorithm/></request></ns0:createLoadBalancer>')
        self.assert_xml_equal(soap_request(expected_request), request.body)

        expected_load_balancer = self.factory.create('createLbResponse')
        expected_load_balancer.requestId = '15266171'
        expected_load_balancer.dataCenterId = datacenter_id
        expected_load_balancer.dataCenterVersion = 4
        expected_load_balancer.loadBalancerId = '4d56832c-44dc-46a2-9e98-43efb3598f83'
        self.assert_sudsobject_equal(expected_load_balancer, created_load_balancer)

    @httpretty.activate
    def test_complex_parameter_2args(self):
        """Test calling client.createLoadBalancer(dataCenterId='<id>', lanId=1)

        Test passing two simple paramaters to an API function that expects exactly
        one complex parameter (containing simple parameters).
        """
        datacenter_id = "7cf8012b-b834-4e31-aa70-2c67e808e271"
        httpretty.register_uri(httpretty.POST, self.port, body=CREATED_LOAD_BALANCER)
        created_load_balancer = self.client.createLoadBalancer(dataCenterId=datacenter_id, lanId=1)
        request = httpretty.last_request()
        expected_request = ('<ns0:createLoadBalancer><request>'
                            '<dataCenterId>7cf8012b-b834-4e31-aa70-2c67e808e271</dataCenterId>'
                            '<loadBalancerAlgorithm/><lanId>1</lanId>'
                            '</request></ns0:createLoadBalancer>')
        self.assert_xml_equal(soap_request(expected_request), request.body)

        expected_load_balancer = self.factory.create('createLbResponse')
        expected_load_balancer.requestId = '15266171'
        expected_load_balancer.dataCenterId = datacenter_id
        expected_load_balancer.dataCenterVersion = 4
        expected_load_balancer.loadBalancerId = '4d56832c-44dc-46a2-9e98-43efb3598f83'
        self.assert_sudsobject_equal(expected_load_balancer, created_load_balancer)


class SupportMatrixTests(TestCase):  # pylint: disable=R0904
    """Test parsing and processing the support_matrix.ini file.

    Use a fictional support_matrix.ini file to test all possible cases
    of available and supported API versions.
    """

    def __init__(self, *args, **kwargs):
        super(SupportMatrixTests, self).__init__(*args, **kwargs)
        self.support_matrix = io.StringIO(SUPPORT_MATRIX)

    def test_client_too_old(self):
        """Test getting the latest API endpoint for profitbricks-client 1.0"""
        msg = ("profitbricks-client 1.0 is too old and not supported any more. "
               "Please upgrade to a newer version.")
        self.assertRaisesRegex(profitbricks_client.ClientTooOldException, msg,
                               profitbricks_client._endpoint_from_support_matrix, "1.0", "latest",
                               self.support_matrix)

    def test_latest_2_0(self):
        """Test getting the latest API endpoint for profitbricks-client 2.0"""
        endpoint = profitbricks_client._endpoint_from_support_matrix("2.0", "latest",
                                                                     self.support_matrix)
        self.assertEqual("https://api.profitbricks.com/1.3/wsdl", endpoint)

    def test_latest_2_1(self):
        """Test getting the latest API endpoint for profitbricks-client 2.1.3"""
        endpoint = profitbricks_client._endpoint_from_support_matrix("2.1.3", "latest",
                                                                     self.support_matrix)
        self.assertEqual("https://api.profitbricks.com/1.4/wsdl", endpoint)

    def test_latest_2_5(self):
        """Test getting the latest API endpoint for profitbricks-client 2.5"""
        endpoint = profitbricks_client._endpoint_from_support_matrix("2.5", "latest",
                                                                     self.support_matrix)
        self.assertEqual("https://api.profitbricks.com/1.4/wsdl", endpoint)

    def test_client_too_new(self):
        """Test getting the latest API endpoint for profitbricks-client 4.1"""
        msg = "profitbricks-client 4.1 is too new and not tested against the API."
        self.assertRaisesRegex(profitbricks_client.ClientTooNewException, msg,
                               profitbricks_client._endpoint_from_support_matrix, "4.1", "latest",
                               self.support_matrix)

    def test_api_version_2_0(self):
        """Test getting the endpoint for a specific API version for profitbricks-client 2.0"""
        endpoint = profitbricks_client._endpoint_from_support_matrix("2.0", "1.2",
                                                                     self.support_matrix)
        self.assertEqual("https://api.profitbricks.com/1.2/wsdl", endpoint)

    def test_api_version_2_3(self):
        """Test getting the endpoint for a specific API version for profitbricks-client 2.3"""
        endpoint = profitbricks_client._endpoint_from_support_matrix("2.3", "1.3",
                                                                     self.support_matrix)
        self.assertEqual("https://api.profitbricks.com/1.3/wsdl", endpoint)

    def test_unknown_api_version(self):
        """Test getting the endpoint for unknown API version"""
        msg = ("The specified API version 1.1 is not known. Supported API versions "
               "by profitbricks-client 2.0: 1.2, 1.3")
        self.assertRaisesRegex(profitbricks_client.UnknownAPIVersionException, msg,
                               profitbricks_client._endpoint_from_support_matrix, "2.0", "1.1",
                               self.support_matrix)

    def test_too_old_for_api_version(self):
        """Test getting the endpoint for a client that is too new for the given API version"""
        msg = ("profitbricks-client 2.0 is too old for API version 1.4. Please upgrade "
               "the client to version 2.1 or later.")
        self.assertRaisesRegex(profitbricks_client.ClientTooOldException, msg,
                               profitbricks_client._endpoint_from_support_matrix, "2.0", "1.4",
                               self.support_matrix)

    def test_too_new_for_api_version(self):
        """Test getting the endpoint for a client that is too new for the given API version"""
        msg = ("profitbricks-client 3.1 is too new for API version 1.3. "
               "Please downgrade the client to version 2.0 or any later 2.x version.")
        self.assertRaisesRegex(profitbricks_client.ClientTooNewException, msg,
                               profitbricks_client._endpoint_from_support_matrix, "3.1", "1.3",
                               self.support_matrix)

if __name__ == '__main__':
    unittest.main()
