##############################################################################
#
# Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

import logging
import os
import unittest
import urlparse
import tempfile

import httmock

import slapos.slap
import xml_marshaller


class UndefinedYetException(Exception):
  """To catch exceptions which are not yet defined"""


class SlapMixin(unittest.TestCase):
  """
  Useful methods for slap tests
  """
  def setUp(self):
    self._server_url = os.environ.get('TEST_SLAP_SERVER_URL', None)
    if self._server_url is None:
      self.server_url = 'http://localhost/'
    else:
      self.server_url = self._server_url
    print 'Testing against SLAP server %r' % self.server_url
    self.slap = slapos.slap.slap()
    self.partition_id = 'PARTITION_01'
    if os.environ.has_key('SLAPGRID_INSTANCE_ROOT'):
      del os.environ['SLAPGRID_INSTANCE_ROOT']

  def tearDown(self):
    pass

  def _getTestComputerId(self):
    """
    Returns the computer id used by the test
    """
    return self.id()


class TestSlap(SlapMixin):
  """
  Test slap against slap server
  """

  def test_slap_initialisation(self):
    """
    Asserts that slap initialisation works properly in case of
    passing correct url
    """
    slap_instance = slapos.slap.slap()
    slap_instance.initializeConnection(self.server_url)
    self.assertEquals(slap_instance._connection_helper.slapgrid_uri, self.server_url)

  def test_slap_initialisation_ipv6_and_port(self):
    slap_instance = slapos.slap.slap()
    slap_instance.initializeConnection("http://fe80:1234:1234:1234:1:1:1:1:5000/foo/")
    self.assertEqual(
        slap_instance._connection_helper.slapgrid_uri,
        "http://[fe80:1234:1234:1234:1:1:1:1]:5000/foo/"
    )

  def test_slap_initialisation_ipv6_without_port(self):
    slap_instance = slapos.slap.slap()
    slap_instance.initializeConnection("http://fe80:1234:1234:1234:1:1:1:1/foo/")
    self.assertEqual(
        slap_instance._connection_helper.slapgrid_uri,
        "http://[fe80:1234:1234:1234:1:1:1:1]/foo/"
    )

  def test_slap_initialisation_ipv6_with_bracket(self):
    slap_instance = slapos.slap.slap()
    slap_instance.initializeConnection("http://[fe80:1234:1234:1234:1:1:1:1]:5000/foo/")
    self.assertEqual(
        slap_instance._connection_helper.slapgrid_uri,
        "http://[fe80:1234:1234:1234:1:1:1:1]:5000/foo/"
    )

  def test_slap_initialisation_ipv4(self):
    slap_instance = slapos.slap.slap()
    slap_instance.initializeConnection("http://127.0.0.1:5000/foo/")
    self.assertEqual(
        slap_instance._connection_helper.slapgrid_uri,
        "http://127.0.0.1:5000/foo/"
    )

  def test_slap_initialisation_hostname(self):
    # XXX this really opens a connection !
    slap_instance = slapos.slap.slap()
    slap_instance.initializeConnection("http://example.com:80/foo/")
    self.assertEqual(
        slap_instance._connection_helper.slapgrid_uri,
        "http://example.com:80/foo/"
    )

  def test_registerComputer_with_new_guid(self):
    """
    Asserts that calling slap.registerComputer with new guid returns
    Computer object
    """
    computer_guid = self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    computer = self.slap.registerComputer(computer_guid)
    self.assertIsInstance(computer, slapos.slap.Computer)

  def test_registerComputer_with_existing_guid(self):
    """
    Asserts that calling slap.registerComputer with already used guid
    returns Computer object
    """
    computer_guid = self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    computer = self.slap.registerComputer(computer_guid)
    self.assertIsInstance(computer, slapos.slap.Computer)

    computer2 = self.slap.registerComputer(computer_guid)
    self.assertIsInstance(computer2, slapos.slap.Computer)

  # XXX: There is naming conflict in slap library.
  # SoftwareRelease is currently used as suboject of Slap transmission object
  def test_registerSoftwareRelease_with_new_uri(self):
    """
    Asserts that calling slap.registerSoftwareRelease with new guid
    returns SoftwareRelease object
    """
    software_release_uri = 'http://server/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    software_release = self.slap.registerSoftwareRelease(software_release_uri)
    self.assertIsInstance(software_release, slapos.slap.SoftwareRelease)

  def test_registerSoftwareRelease_with_existing_uri(self):
    """
    Asserts that calling slap.registerSoftwareRelease with already
    used guid returns SoftwareRelease object
    """
    software_release_uri = 'http://server/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    software_release = self.slap.registerSoftwareRelease(software_release_uri)
    self.assertIsInstance(software_release, slapos.slap.SoftwareRelease)

    software_release2 = self.slap.registerSoftwareRelease(software_release_uri)
    self.assertIsInstance(software_release2, slapos.slap.SoftwareRelease)

  def test_registerComputerPartition_new_partition_id_known_computer_guid(self):
    """
    Asserts that calling slap.registerComputerPartition on known computer
    returns ComputerPartition object
    """
    computer_guid = self._getTestComputerId()
    partition_id = self.partition_id
    self.slap.initializeConnection(self.server_url)
    self.slap.registerComputer(computer_guid)

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition'
            and qs == {
                'computer_reference': [computer_guid],
                'computer_partition_reference': [partition_id]
                }):
        partition = slapos.slap.ComputerPartition(computer_guid, partition_id)
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(partition)
                }
      else:
        return {'status_code': 400}

    self._handler = handler

    with httmock.HTTMock(handler):
      partition = self.slap.registerComputerPartition(computer_guid, partition_id)
      self.assertIsInstance(partition, slapos.slap.ComputerPartition)

  def test_registerComputerPartition_existing_partition_id_known_computer_guid(self):
    """
    Asserts that calling slap.registerComputerPartition on known computer
    returns ComputerPartition object
    """
    self.test_registerComputerPartition_new_partition_id_known_computer_guid()
    with httmock.HTTMock(self._handler):
      partition = self.slap.registerComputerPartition(self._getTestComputerId(),
                                                      self.partition_id)
      self.assertIsInstance(partition, slapos.slap.ComputerPartition)

  def test_registerComputerPartition_unknown_computer_guid(self):
    """
    Asserts that calling slap.registerComputerPartition on unknown
    computer raises NotFoundError exception
    """
    computer_guid = self._getTestComputerId()
    self.slap.initializeConnection(self.server_url)
    partition_id = 'PARTITION_01'

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition'
            and qs == {
                'computer_reference': [computer_guid],
                'computer_partition_reference': [partition_id]
                }):
        return {'status_code': 404}
      else:
        return {'status_code': 0}

    with httmock.HTTMock(handler):
      self.assertRaises(slapos.slap.NotFoundError,
                        self.slap.registerComputerPartition,
                        computer_guid, partition_id)


  def test_getFullComputerInformation_empty_computer_guid(self):
    """
    Asserts that calling getFullComputerInformation with empty computer_id
    raises early, before calling master.
    """
    self.slap.initializeConnection(self.server_url)

    def handler(url, req):
      # Shouldn't even be called
      self.assertFalse(True)

    with httmock.HTTMock(handler):
      self.assertRaises(slapos.slap.NotFoundError,
                        self.slap._connection_helper.getFullComputerInformation,
                        None)

  def test_registerComputerPartition_empty_computer_guid(self):
    """
    Asserts that calling registerComputerPartition with empty computer_id
    raises early, before calling master.
    """
    self.slap.initializeConnection(self.server_url)

    def handler(url, req):
      # Shouldn't even be called
      self.assertFalse(True)

    with httmock.HTTMock(handler):
      self.assertRaises(slapos.slap.NotFoundError,
                        self.slap.registerComputerPartition,
                        None, 'PARTITION_01')

  def test_registerComputerPartition_empty_computer_partition_id(self):
    """
    Asserts that calling registerComputerPartition with empty
    computer_partition_id raises early, before calling master.
    """
    self.slap.initializeConnection(self.server_url)

    def handler(url, req):
      # Shouldn't even be called
      self.assertFalse(True)

    with httmock.HTTMock(handler):
      self.assertRaises(slapos.slap.NotFoundError,
                        self.slap.registerComputerPartition,
                        self._getTestComputerId(), None)

  def test_registerComputerPartition_empty_computer_guid_empty_computer_partition_id(self):
    """
    Asserts that calling registerComputerPartition with empty
    computer_partition_id raises early, before calling master.
    """
    self.slap.initializeConnection(self.server_url)

    def handler(url, req):
      # Shouldn't even be called
      self.assertFalse(True)

    with httmock.HTTMock(handler):
      self.assertRaises(slapos.slap.NotFoundError,
                        self.slap.registerComputerPartition,
                        None, None)


  def test_getSoftwareReleaseListFromSoftwareProduct_software_product_reference(self):
    """
    Check that slap.getSoftwareReleaseListFromSoftwareProduct calls
    "/getSoftwareReleaseListFromSoftwareProduct" URL with correct parameters,
    with software_product_reference parameter being specified.
    """
    self.slap.initializeConnection(self.server_url)
    software_product_reference = 'random_reference'
    software_release_url_list = ['1', '2']

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/getSoftwareReleaseListFromSoftwareProduct'
            and qs == {'software_product_reference': [software_product_reference]}):
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(software_release_url_list)
                }

    with httmock.HTTMock(handler):
      self.assertEqual(
        self.slap.getSoftwareReleaseListFromSoftwareProduct(
          software_product_reference=software_product_reference),
        software_release_url_list
      )

  def test_getSoftwareReleaseListFromSoftwareProduct_software_release_url(self):
    """
    Check that slap.getSoftwareReleaseListFromSoftwareProduct calls
    "/getSoftwareReleaseListFromSoftwareProduct" URL with correct parameters,
    with software_release_url parameter being specified.
    """
    self.slap.initializeConnection(self.server_url)
    software_release_url = 'random_url'
    software_release_url_list = ['1', '2']

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/getSoftwareReleaseListFromSoftwareProduct'
         and qs == {'software_release_url': [software_release_url]}):
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(software_release_url_list)
                }

    with httmock.HTTMock(handler):
      self.assertEqual(
        self.slap.getSoftwareReleaseListFromSoftwareProduct(
            software_release_url=software_release_url),
        software_release_url_list
      )

  def test_getSoftwareReleaseListFromSoftwareProduct_too_many_parameters(self):
    """
    Check that slap.getSoftwareReleaseListFromSoftwareProduct raises if
    both parameters are set.
    """
    self.assertRaises(
      AttributeError,
      self.slap.getSoftwareReleaseListFromSoftwareProduct, 'foo', 'bar'
    )

  def test_getSoftwareReleaseListFromSoftwareProduct_no_parameter(self):
    """
    Check that slap.getSoftwareReleaseListFromSoftwareProduct raises if
    both parameters are either not set or None.
    """
    self.assertRaises(
      AttributeError,
      self.slap.getSoftwareReleaseListFromSoftwareProduct
    )

  def test_initializeConnection_getHateoasUrl(self):
    """
    Test that by default, slap will try to fetch Hateoas URL from XML/RPC URL.
    """
    hateoas_url = 'foo'
    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/getHateoasUrl'):
        return {
                'status_code': 200,
                'content': hateoas_url
                }

    with httmock.HTTMock(handler):
      self.slap.initializeConnection('http://%s' % self.id())
    self.assertEqual(
        self.slap._hateoas_navigator.slapos_master_hateoas_uri,
        hateoas_url
    )

  def test_initializeConnection_specifiedHateoasUrl(self):
    """
    Test that if rest URL is specified, slap will NOT try to fetch
    Hateoas URL from XML/RPC URL.
    """
    hateoas_url = 'foo'
    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/getHateoasUrl'):
        self.fail('slap should not have contacted master to get Hateoas URL.')

    with httmock.HTTMock(handler):
      self.slap.initializeConnection('http://%s' % self.id(), slapgrid_rest_uri=hateoas_url)
    self.assertEqual(
        self.slap._hateoas_navigator.slapos_master_hateoas_uri,
        hateoas_url
    )

  def test_initializeConnection_noHateoasUrl(self):
    """
    Test that if no rest URL is specified and master does not know about rest,
    it still work.
    """
    hateoas_url = 'foo'
    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/getHateoasUrl'):
        return {
                'status_code': 404,
                }

    with httmock.HTTMock(handler):
      self.slap.initializeConnection('http://%s' % self.id())
    self.assertEqual(None, getattr(self.slap, '_hateoas_navigator', None))


class TestComputer(SlapMixin):
  """
  Tests slapos.slap.slap.Computer class functionality
  """

  def test_computer_getComputerPartitionList_no_partition(self):
    """
    Asserts that calling Computer.getComputerPartitionList without Computer
    Partitions returns empty list
    """
    computer_guid = self._getTestComputerId()
    slap = self.slap
    slap.initializeConnection(self.server_url)

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition'
              and 'computer_reference' in qs
              and 'computer_partition_reference' in qs):
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_reference'][0],
            qs['computer_partition_reference'][0])
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
                }
      elif (url.path == '/getFullComputerInformation'
              and 'computer_id' in qs):
        slap_computer = slapos.slap.Computer(qs['computer_id'][0])
        slap_computer._software_release_list = []
        slap_computer._computer_partition_list = []
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_computer)
                }
      elif url.path == '/requestComputerPartition':
        return {'status_code': 408}
      else:
        return {'status_code': 404}

    with httmock.HTTMock(handler):
      computer = self.slap.registerComputer(computer_guid)
      self.assertEqual(computer.getComputerPartitionList(), [])

  def _test_computer_empty_computer_guid(self, computer_method):
    """
    Helper method checking if calling Computer method with empty id raises
    early.
    """
    self.slap.initializeConnection(self.server_url)

    def handler(url, req):
      # Shouldn't even be called
      self.assertFalse(True)

    with httmock.HTTMock(handler):
      computer = self.slap.registerComputer(None)
      self.assertRaises(slapos.slap.NotFoundError,
                        getattr(computer, computer_method))

  def test_computer_getComputerPartitionList_empty_computer_guid(self):
    """
    Asserts that calling getComputerPartitionList with empty
    computer_guid raises early, before calling master.
    """
    self._test_computer_empty_computer_guid('getComputerPartitionList')

  def test_computer_getSoftwareReleaseList_empty_computer_guid(self):
    """
    Asserts that calling getSoftwareReleaseList with empty
    computer_guid raises early, before calling master.
    """
    self._test_computer_empty_computer_guid('getSoftwareReleaseList')

  def test_computer_getComputerPartitionList_only_partition(self):
    """
    Asserts that calling Computer.getComputerPartitionList with only
    Computer Partitions returns empty list
    """
    self.computer_guid = self._getTestComputerId()
    partition_id = 'PARTITION_01'
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition'
            and qs == {
                'computer_reference': [self.computer_guid],
                'computer_partition_reference': [partition_id]
                }):
        partition = slapos.slap.ComputerPartition(self.computer_guid, partition_id)
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(partition)
                }
      elif (url.path == '/getFullComputerInformation'
              and 'computer_id' in qs):
        slap_computer = slapos.slap.Computer(qs['computer_id'][0])
        slap_computer._computer_partition_list = []
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_computer)
                }
      else:
        return {'status_code': 400}

    with httmock.HTTMock(handler):
      self.computer = self.slap.registerComputer(self.computer_guid)
      self.partition = self.slap.registerComputerPartition(self.computer_guid,
                                                           partition_id)
      self.assertEqual(self.computer.getComputerPartitionList(), [])

  @unittest.skip("Not implemented")
  def test_computer_reportUsage_non_valid_xml_raises(self):
    """
    Asserts that calling Computer.reportUsage with non DTD
    (not defined yet) XML raises (not defined yet) exception
    """

    self.computer_guid = self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    self.computer = self.slap.registerComputer(self.computer_guid)
    non_dtd_xml = """<xml>
<non-dtd-parameter name="xerxes">value<non-dtd-parameter name="xerxes">
</xml>"""
    self.assertRaises(UndefinedYetException,
                      self.computer.reportUsage,
                      non_dtd_xml)

  @unittest.skip("Not implemented")
  def test_computer_reportUsage_valid_xml_invalid_partition_raises(self):
    """
    Asserts that calling Computer.reportUsage with DTD (not defined
    yet) XML which refers to invalid partition raises (not defined yet)
    exception
    """
    self.computer_guid = self._getTestComputerId()
    partition_id = 'PARTITION_01'
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    self.computer = self.slap.registerComputer(self.computer_guid)
    self.partition = self.slap.registerComputerPartition(self.computer_guid,
                                                         partition_id)
    # XXX: As DTD is not defined currently proper XML is not known
    bad_partition_dtd_xml = """<xml>
<computer-partition id='ANOTHER_PARTITION>96.5% CPU</computer-partition>
</xml>"""
    self.assertRaises(UndefinedYetException,
                      self.computer.reportUsage,
                      bad_partition_dtd_xml)


class RequestWasCalled(Exception):
  pass


class TestComputerPartition(SlapMixin):
  """
  Tests slapos.slap.slap.ComputerPartition class functionality
  """

  def test_request_sends_request(self):
    partition_id = 'PARTITION_01'

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition'
              and 'computer_reference' in qs
              and 'computer_partition_reference' in qs):
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_reference'][0],
            qs['computer_partition_reference'][0])
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
                }
      elif (url.path == '/getComputerInformation'
              and 'computer_id' in qs):
        slap_computer = slapos.slap.Computer(qs['computer_id'][0])
        slap_computer._software_release_list = []
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_id'][0],
            partition_id)
        slap_computer._computer_partition_list = [slap_partition]
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_computer)
                }
      elif url.path == '/requestComputerPartition':
        raise RequestWasCalled
      else:
        return {
                'status_code': 404
                }

    with httmock.HTTMock(handler):
      self.computer_guid = self._getTestComputerId()
      self.slap = slapos.slap.slap()
      self.slap.initializeConnection(self.server_url)
      computer_partition = self.slap.registerComputerPartition(
          self.computer_guid, partition_id)
      self.assertRaises(RequestWasCalled,
                        computer_partition.request,
                        'http://server/new/' + self._getTestComputerId(),
                        'software_type', 'myref')

  def test_request_not_raises(self):
    partition_id = 'PARTITION_01'

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition'
              and 'computer_reference' in qs
              and 'computer_partition_reference' in qs):
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_reference'][0],
            qs['computer_partition_reference'][0])
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
                }
      elif (url.path == '/getComputerInformation'
              and 'computer_id' in qs):
        slap_computer = slapos.slap.Computer(qs['computer_id'][0])
        slap_computer._software_release_list = []
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_id'][0],
            partition_id)
        slap_computer._computer_partition_list = [slap_partition]
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_computer)
                }
      elif url.path == '/requestComputerPartition':
        return {'status_code': 408}
      else:
        return {'status_code': 404}

    self.computer_guid = self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    with httmock.HTTMock(handler):
      computer_partition = self.slap.registerComputerPartition(
          self.computer_guid, partition_id)
      requested_partition = computer_partition.request(
          'http://server/new/' + self._getTestComputerId(),
          'software_type',
          'myref')
      self.assertIsInstance(requested_partition, slapos.slap.ComputerPartition)

  def test_request_raises_later(self):
    partition_id = 'PARTITION_01'

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition' and
              'computer_reference' in qs and
              'computer_partition_reference' in qs):
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_reference'][0],
            qs['computer_partition_reference'][0])
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
                }
      elif (url.path == '/getComputerInformation'
              and 'computer_id' in qs):
        slap_computer = slapos.slap.Computer(qs['computer_id'][0])
        slap_computer._software_release_list = []
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_id'][0],
            partition_id)
        slap_computer._computer_partition_list = [slap_partition]
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_computer)
                }
      elif url.path == '/requestComputerPartition':
        return {'status_code': 408}
      else:
        return {'status_code': 404}

    self.computer_guid = self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    with httmock.HTTMock(handler):
      computer_partition = self.slap.registerComputerPartition(
          self.computer_guid, partition_id)
      requested_partition = computer_partition.request(
          'http://server/new/' + self._getTestComputerId(),
          'software_type',
          'myref')
      self.assertIsInstance(requested_partition, slapos.slap.ComputerPartition)
      # as request method does not raise, accessing data raises
      self.assertRaises(slapos.slap.ResourceNotReady,
                        requested_partition.getId)

  def test_request_fullfilled_work(self):
    partition_id = 'PARTITION_01'
    requested_partition_id = 'PARTITION_02'
    computer_guid = self._getTestComputerId()

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition' and
              'computer_reference' in qs and
              'computer_partition_reference' in qs):
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_reference'][0],
            qs['computer_partition_reference'][0])
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
                }
      elif (url.path == '/getComputerInformation' and 'computer_id' in qs):
        slap_computer = slapos.slap.Computer(qs['computer_id'][0])
        slap_computer._software_release_list = []
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_id'][0],
            partition_id)
        slap_computer._computer_partition_list = [slap_partition]
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_computer)
                }
      elif url.path == '/requestComputerPartition':
        from slapos.slap.slap import SoftwareInstance
        slap_partition = SoftwareInstance(
            slap_computer_id=computer_guid,
            slap_computer_partition_id=requested_partition_id)
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
                }
      else:
        return {'status_code': 404}


    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)

    with httmock.HTTMock(handler):
      computer_partition = self.slap.registerComputerPartition(
          computer_guid, partition_id)
      requested_partition = computer_partition.request(
          'http://server/new/' + self._getTestComputerId(),
          'software_type',
          'myref')
      self.assertIsInstance(requested_partition, slapos.slap.ComputerPartition)
      # as request method does not raise, accessing data in case when
      # request was done works correctly
      self.assertEqual(requested_partition_id, requested_partition.getId())

  def test_request_with_slapgrid_request_transaction(self):
    from slapos.slap.slap import COMPUTER_PARTITION_REQUEST_LIST_TEMPLATE_FILENAME
    partition_id = 'PARTITION_01'
    instance_root = tempfile.mkdtemp()
    partition_root = os.path.join(instance_root, partition_id)
    os.mkdir(partition_root)
    os.environ['SLAPGRID_INSTANCE_ROOT'] = instance_root
    transaction_file_name = COMPUTER_PARTITION_REQUEST_LIST_TEMPLATE_FILENAME % partition_id
    transaction_file_path = os.path.join(partition_root, transaction_file_name)

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition'
              and 'computer_reference' in qs
              and 'computer_partition_reference' in qs):
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_reference'][0],
            qs['computer_partition_reference'][0])
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
                }
      elif (url.path == '/getComputerInformation'
              and 'computer_id' in qs):
        slap_computer = slapos.slap.Computer(qs['computer_id'][0])
        slap_computer._software_release_list = []
        slap_partition = slapos.slap.ComputerPartition(
            qs['computer_id'][0],
            partition_id)
        slap_computer._computer_partition_list = [slap_partition]
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(slap_computer)
                }
      elif url.path == '/requestComputerPartition':
        raise RequestWasCalled
      else:
        return {
                'status_code': 404
                }

    with httmock.HTTMock(handler):
      self.computer_guid = self._getTestComputerId()
      self.slap = slapos.slap.slap()
      self.slap.initializeConnection(self.server_url)
      computer_partition = self.slap.registerComputerPartition(
          self.computer_guid, partition_id)

      self.assertTrue(os.path.exists(transaction_file_path))
      with open(transaction_file_path, 'r') as f:
        content = f.read()
        self.assertEqual(content, '')
      self.assertRaises(RequestWasCalled,
                        computer_partition.request,
                        'http://server/new/' + self._getTestComputerId(),
                        'software_type', 'myref')
      self.assertTrue(os.path.exists(transaction_file_path))
      with open(transaction_file_path, 'r') as f:
        content_list = f.read().strip().split('\n')
        self.assertEqual(content_list, ['myref'])

      # Not override
      computer_partition = self.slap.registerComputerPartition(
          self.computer_guid, partition_id)
      self.assertTrue(os.path.exists(transaction_file_path))
      with open(transaction_file_path, 'r') as f:
        content_list = f.read().strip().split('\n')
        self.assertEqual(content_list, ['myref'])

      # Request a second instance
      self.assertRaises(RequestWasCalled,
                        computer_partition.request,
                        'http://server/new/' + self._getTestComputerId(),
                        'software_type', 'mysecondref')
      with open(transaction_file_path, 'r') as f:
        content_list = f.read().strip().split('\n')
        self.assertEquals(list(set(content_list)), ['myref', 'mysecondref'])

  def _test_new_computer_partition_state(self, state):
    """
    Helper method to automate assertions of failing states on new Computer
    Partition
    """
    computer_guid = self._getTestComputerId()
    partition_id = 'PARTITION_01'
    slap = self.slap
    slap.initializeConnection(self.server_url)

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition' and
              qs['computer_reference'][0] == computer_guid and
              qs['computer_partition_reference'][0] == partition_id):
        partition = slapos.slap.ComputerPartition(
            computer_guid, partition_id)
        return {
                'status_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(partition)
                }
      else:
        return {'status_code': 404}


    with httmock.HTTMock(handler):
      computer_partition = self.slap.registerComputerPartition(
          computer_guid, partition_id)
      self.assertRaises(slapos.slap.NotFoundError,
                        getattr(computer_partition, state))

  def test_available_new_ComputerPartition_raises(self):
    """
    Asserts that calling ComputerPartition.available on new partition
    raises (not defined yet) exception
    """
    self._test_new_computer_partition_state('available')

  def test_building_new_ComputerPartition_raises(self):
    """
    Asserts that calling ComputerPartition.building on new partition raises
    (not defined yet) exception
    """
    self._test_new_computer_partition_state('building')

  def test_started_new_ComputerPartition_raises(self):
    """
    Asserts that calling ComputerPartition.started on new partition raises
    (not defined yet) exception
    """
    self._test_new_computer_partition_state('started')

  def test_stopped_new_ComputerPartition_raises(self):
    """
    Asserts that calling ComputerPartition.stopped on new partition raises
    (not defined yet) exception
    """
    self._test_new_computer_partition_state('stopped')

  def test_error_new_ComputerPartition_works(self):
    """
    Asserts that calling ComputerPartition.error on new partition works
    """
    computer_guid = self._getTestComputerId()
    partition_id = 'PARTITION_01'
    slap = self.slap
    slap.initializeConnection(self.server_url)

    def handler(url, req):
      qs = urlparse.parse_qs(url.query)
      if (url.path == '/registerComputerPartition' and
              qs['computer_reference'][0] == computer_guid and
              qs['computer_partition_reference'][0] == partition_id):
        partition = slapos.slap.ComputerPartition(
            computer_guid, partition_id)
        return {
                'statu_code': 200,
                'content': xml_marshaller.xml_marshaller.dumps(partition)
                }
      elif url.path == '/softwareInstanceError':
        parsed_qs_body = urlparse.parse_qs(req.body)
        # XXX: why do we have computer_id and not computer_reference?
        # XXX: why do we have computer_partition_id and not
        # computer_partition_reference?
        if (parsed_qs_body['computer_id'][0] == computer_guid and
                parsed_qs_body['computer_partition_id'][0] == partition_id and
                parsed_qs_body['error_log'][0] == 'some error'):
          return {'status_code': 200}

      return {'status_code': 404}


    with httmock.HTTMock(handler):
      computer_partition = slap.registerComputerPartition(
          computer_guid, partition_id)
      # XXX: Interface does not define return value
      computer_partition.error('some error')


class TestSoftwareRelease(SlapMixin):
  """
  Tests slap.SoftwareRelease class functionality
  """

  def _test_new_software_release_state(self, state):
    """
    Helper method to automate assertions of failing states on new Software
    Release
    """
    self.software_release_uri = 'http://server/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    software_release = self.slap.registerSoftwareRelease(
        self.software_release_uri)
    method = getattr(software_release, state)
    self.assertRaises(NameError, method)

  def test_available_new_SoftwareRelease_raises(self):
    """
    Asserts that calling SoftwareRelease.available on new software release
    raises NameError exception
    """
    self._test_new_software_release_state('available')

  def test_building_new_SoftwareRelease_raises(self):
    """
    Asserts that calling SoftwareRelease.building on new software release
    raises NameError exception
    """
    self._test_new_software_release_state('building')

  def test_error_new_SoftwareRelease_works(self):
    """
    Asserts that calling SoftwareRelease.error on software release works
    """
    computer_guid = self._getTestComputerId()
    software_release_uri = 'http://server/' + self._getTestComputerId()
    slap = self.slap
    slap.initializeConnection(self.server_url)

    def handler(url, req):
      qs = urlparse.parse_qs(req.body)
      if (url.path == '/softwareReleaseError' and
              qs['computer_id'][0] == computer_guid and
              qs['url'][0] == software_release_uri and
              qs['error_log'][0] == 'some error'):
        return {
                'status_code': 200
                }
      return {'status_code': 404}


    with httmock.HTTMock(handler):
      software_release = self.slap.registerSoftwareRelease(software_release_uri)
      software_release._computer_guid = computer_guid
      software_release.error('some error')


class TestOpenOrder(SlapMixin):
  def test_request_sends_request(self):
    software_release_uri = 'http://server/new/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    # XXX: Interface lack registerOpenOrder method declaration
    open_order = self.slap.registerOpenOrder()

    def handler(url, req):
      if url.path == '/requestComputerPartition':
        raise RequestWasCalled

    with httmock.HTTMock(handler):
      self.assertRaises(RequestWasCalled,
                        open_order.request,
                        software_release_uri, 'myrefe')

  @unittest.skip('unclear what should be returned')
  def test_request_not_raises(self):
    software_release_uri = 'http://server/new/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    # XXX: Interface lack registerOpenOrder method declaration

    def handler(url, req):
      if url.path == '/requestComputerPartition':
        pass
        # XXX what to do here?

    with httmock.HTTMock(handler):
      open_order = self.slap.registerOpenOrder()
      computer_partition = open_order.request(software_release_uri, 'myrefe')
      self.assertIsInstance(computer_partition, slapos.slap.ComputerPartition)

  def test_request_raises_later(self):
    software_release_uri = 'http://server/new/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    # XXX: Interface lack registerOpenOrder method declaration
    open_order = self.slap.registerOpenOrder()

    def handler(url, req):
      return {'status_code': 408}

    with httmock.HTTMock(handler):
      computer_partition = open_order.request(software_release_uri, 'myrefe')
      self.assertIsInstance(computer_partition, slapos.slap.ComputerPartition)

      self.assertRaises(slapos.slap.ResourceNotReady,
                        computer_partition.getId)

  def test_request_fullfilled_work(self):
    software_release_uri = 'http://server/new/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    # XXX: Interface lack registerOpenOrder method declaration
    open_order = self.slap.registerOpenOrder()
    computer_guid = self._getTestComputerId()
    requested_partition_id = 'PARTITION_01'

    def handler(url, req):
      from slapos.slap.slap import SoftwareInstance
      slap_partition = SoftwareInstance(
          slap_computer_id=computer_guid,
          slap_computer_partition_id=requested_partition_id)
      return {
              'status_code': 200,
              'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
              }

    with httmock.HTTMock(handler):
      computer_partition = open_order.request(software_release_uri, 'myrefe')
      self.assertIsInstance(computer_partition, slapos.slap.ComputerPartition)
      self.assertEqual(requested_partition_id, computer_partition.getId())


  def test_request_getConnectionParameter(self):
    """ Backward compatibility API for slapproxy older them 1.0.1 """
    software_release_uri = 'http://server/new/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    # XXX: Interface lack registerOpenOrder method declaration
    open_order = self.slap.registerOpenOrder()
    computer_guid = self._getTestComputerId()
    requested_partition_id = 'PARTITION_01'

    def handler(url, req):
      from slapos.slap.slap import SoftwareInstance
      slap_partition = SoftwareInstance(
          _connection_dict = {"url": 'URL_CONNECTION_PARAMETER'},
          slap_computer_id=computer_guid,
          slap_computer_partition_id=requested_partition_id)
      return {
              'status_code': 200,
              'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
              }


    with httmock.HTTMock(handler):
      computer_partition = open_order.request(software_release_uri, 'myrefe')
      self.assertIsInstance(computer_partition, slapos.slap.ComputerPartition)
      self.assertEqual(requested_partition_id, computer_partition.getId())
      self.assertEqual("URL_CONNECTION_PARAMETER",
                       computer_partition.getConnectionParameter('url'))


  def test_request_connection_dict_backward_compatibility(self):
    """ Backward compatibility API for slapproxy older them 1.0.1 """
    software_release_uri = 'http://server/new/' + self._getTestComputerId()
    self.slap = slapos.slap.slap()
    self.slap.initializeConnection(self.server_url)
    # XXX: Interface lack registerOpenOrder method declaration
    open_order = self.slap.registerOpenOrder()
    computer_guid = self._getTestComputerId()
    requested_partition_id = 'PARTITION_01'

    def handler(url, req):
      from slapos.slap.slap import SoftwareInstance
      slap_partition = SoftwareInstance(
          connection_xml="""<?xml version='1.0' encoding='utf-8'?>
<instance>
  <parameter id="url">URL_CONNECTION_PARAMETER</parameter>
</instance>""",
          slap_computer_id=computer_guid,
          slap_computer_partition_id=requested_partition_id)
      return {
              'status_code': 200,
              'content': xml_marshaller.xml_marshaller.dumps(slap_partition)
              }

    with httmock.HTTMock(handler):
      computer_partition = open_order.request(software_release_uri, 'myrefe')
      self.assertIsInstance(computer_partition, slapos.slap.ComputerPartition)
      self.assertEqual(requested_partition_id, computer_partition.getId())
      self.assertEqual("URL_CONNECTION_PARAMETER",
                       computer_partition.getConnectionParameter('url'))


class TestSoftwareProductCollection(SlapMixin):
  def setUp(self):
    SlapMixin.setUp(self)
    self.real_getSoftwareReleaseListFromSoftwareProduct =\
        slapos.slap.slap.getSoftwareReleaseListFromSoftwareProduct

    def fake_getSoftwareReleaseListFromSoftwareProduct(inside_self, software_product_reference):
      return self.getSoftwareReleaseListFromSoftwareProduct_response
    slapos.slap.slap.getSoftwareReleaseListFromSoftwareProduct =\
        fake_getSoftwareReleaseListFromSoftwareProduct

    self.product_collection = slapos.slap.SoftwareProductCollection(
        logging.getLogger(), slapos.slap.slap())

  def tearDown(self):
    slapos.slap.slap.getSoftwareReleaseListFromSoftwareProduct =\
        self.real_getSoftwareReleaseListFromSoftwareProduct

  def test_get_product(self):
    """
    Test that the get method (aliased to __getattr__) returns the first element
    of the list given by getSoftwareReleaseListFromSoftwareProduct (i.e the
    best one).
    """
    self.getSoftwareReleaseListFromSoftwareProduct_response = ['0', '1', '2']
    self.assertEqual(
      self.product_collection.get('random_reference'),
      self.getSoftwareReleaseListFromSoftwareProduct_response[0]
    )

  def test_get_product_empty_product(self):
    """
    Test that the get method (aliased to __getattr__) raises if no
    Software Release is related to the Software Product, or if the
    Software Product does not exist.
    """
    self.getSoftwareReleaseListFromSoftwareProduct_response = []
    self.assertRaises(
      AttributeError,
      self.product_collection.get, 'random_reference',
    )

  def test_get_product_getattr(self):
    """
    Test that __getattr__ method is bound to get() method.
    """
    self.getSoftwareReleaseListFromSoftwareProduct_response = ['0']
    self.product_collection.foo
    self.assertEqual(
      self.product_collection.__getattr__,
      self.product_collection.get
    )
    self.assertEqual(self.product_collection.foo, '0')

if __name__ == '__main__':
  print 'You can point to any SLAP server by setting TEST_SLAP_SERVER_URL '\
      'environment variable'
  unittest.main()

