import mock
import unittest

import manager


def create_mock_load_builder_fn(mock_rings):
    """To avoid the need for swift.common.ring library, mock a basic rings
    dictionary, keyed by path. Each ring has enough logic to hold a dictionary
    with a single 'devs' key, which stores the list of passed dev(s) by
    add_dev().

    If swift (actual) ring representation diverges (see _load_builder),
    this mock will need to be adapted.

    :param mock_rings: a dict containing the dict form of the rings
    """
    def mock_load_builder_fn(path):
        class mock_ring(object):
            def __init__(self, path):
                self.path = path

            def to_dict(self):
                return mock_rings[self.path]

            def add_dev(self, dev):
                mock_rings[self.path]['devs'].append(dev)

        return mock_ring(path)
    return mock_load_builder_fn


MOCK_SWIFT_RINGS = {
    'account': 'account.builder',
    'container': 'container.builder',
    'object': 'object.builder'
}


class TestSwiftManager(unittest.TestCase):

    @mock.patch('os.path.isfile')
    @mock.patch.object(manager, '_load_builder')
    def test_has_minimum_zones(self, mock_load_builder, mock_is_file):
        mock_rings = {}

        mock_load_builder.side_effect = create_mock_load_builder_fn(mock_rings)
        for ring in MOCK_SWIFT_RINGS:
            mock_rings[ring] = {
                'replicas': 3,
                'devs': [{'region': 1, 'zone': 1},
                         {'region': 1, 'zone': 2},
                         None,
                         {'region': 1, 'zone': 3}],
            }
        ret = manager.has_minimum_zones(MOCK_SWIFT_RINGS)
        self.assertTrue(ret['result'])

        # Increase the replicas to make sure that it returns false
        for ring in MOCK_SWIFT_RINGS:
            mock_rings[ring]['replicas'] = 4

        ret = manager.has_minimum_zones(MOCK_SWIFT_RINGS)
        self.assertFalse(ret['result'])

    @mock.patch.object(manager, '_load_builder')
    def test_exists_in_ring(self, mock_load_builder):
        mock_rings = {}

        mock_load_builder.side_effect = create_mock_load_builder_fn(mock_rings)
        ring = 'account'
        mock_rings[ring] = {
            'devs': [
                {'replication_port': 6000, 'zone': 1, 'weight': 100.0,
                 'ip': '172.16.0.2', 'region': 1, 'port': 6000,
                 'replication_ip': '172.16.0.2', 'parts': 2, 'meta': '',
                 'device': u'bcache10', 'parts_wanted': 0, 'id': 199},
                None,  # Ring can have holes, so add None to simulate
                {'replication_port': 6000, 'zone': 1, 'weight': 100.0,
                 'ip': '172.16.0.2', 'region': 1, 'id': 198,
                 'replication_ip': '172.16.0.2', 'parts': 2, 'meta': '',
                 'device': u'bcache13', 'parts_wanted': 0, 'port': 6000},
            ]
        }

        node = {
            'ip': '172.16.0.2',
            'region': 1,
            'account_port': 6000,
            'zone': 1,
            'replication_port': 6000,
            'weight': 100.0,
            'device': u'bcache10',
        }

        ret = manager.exists_in_ring(ring, node)
        self.assertTrue(ret)

        node['region'] = 2
        ret = manager.exists_in_ring(ring, node)
        self.assertFalse(ret)

    @mock.patch.object(manager, '_write_ring')
    @mock.patch.object(manager, '_load_builder')
    def test_add_dev(self, mock_load_builder, mock_write_ring):
        mock_rings = {}
        mock_load_builder.side_effect = create_mock_load_builder_fn(mock_rings)
        ring = 'account'
        mock_rings[ring] = {
            'devs': []
        }

        new_dev = {
            'meta': '',
            'zone': 1,
            'ip': '172.16.0.2',
            'device': '/dev/sdb',
            'port': 6000,
            'weight': 100
        }
        manager.add_dev(ring, new_dev)
        mock_write_ring.assert_called_once()
        self.assertTrue('id' not in mock_rings[ring]['devs'][0])