def parse_cluster_slots(response):
    res = dict()
    for slot_info in response:
        min_slot, max_slot = slot_info[:2]
        nodes = slot_info[2:]
        parse_node = lambda node: {
            'host': node[0],
            'port': node[1],
            'node_id': node[2] if len(node) > 2 else '',
            'server_type': 'slave'
        }
        res[(min_slot, max_slot)] = [parse_node(node) for node in nodes]
        res[(min_slot, max_slot)][0]['server_type'] = 'master'
    return res class RedisClusterException(Exception):
    pass class ClusterDownError(ClusterError, ResponseError):

    def __init__(self, resp):
        self.args = (resp,)
        self.message = resp def list_keys_to_dict(key_list, callback):
    return dict.fromkeys(key_list, callback) def b(x):
    return x.encode('latin-1') if not isinstance(x, bytes) else x def dict_merge(*dicts):
    merged = {}
    for d in dicts:
        merged.update(d)
    return merged def blocked_command(self, command):
    """
    Raises a `RedisClusterException` mentioning the command is blocked.
    """
    raise RedisClusterException("Command: {0} is blocked in redis cluster mode".format(command)) def merge_result(res):
    """
    Merges all items in `res` into a list.

    This command is used when sending a command to multiple nodes
    and they result from each node should be merged into a single list.
    """
    if not isinstance(res, dict):
        raise ValueError('Value should be of dict type')

    result = set([])

    for _, v in res.items():
        for value in v:
            result.add(value)

    return list(result) def first_key(res):
    """
    Returns the first result for the given command.

    If more then 1 result is returned then a `RedisClusterException` is raised.
    """
    if not isinstance(res, dict):
        raise ValueError('Value should be of dict type')

    if len(res.keys()) != 1:
        raise RedisClusterException("More then 1 result from command")

    return list(res.values())[0] def clusterdown_wrapper(func):
    """
    Wrapper for CLUSTERDOWN error handling.

    If the cluster reports it is down it is assumed that:
     - connection_pool was disconnected
     - connection_pool was reseted
     - refereh_table_asap set to True

    It will try 3 times to rerun the command and raises ClusterDownException if it continues to fail.
    """

    @wraps(func)
    async def inner(*args, **kwargs):
        for _ in range(0, 3):
            try:
                return await func(*args, **kwargs)
            except ClusterDownError:
                # Try again with the new cluster setup. All other errors
                # should be raised.
                pass

        # If it fails 3 times then raise exception back to caller
        raise ClusterDownError("CLUSTERDOWN error. Unable to rebuild the cluster")

    return inner
from aredis.commands.cluster import parse_cluster_slots
from aredis.exceptions import (
    RedisClusterException, ClusterDownError
)
from aredis.utils import (
    list_keys_to_dict,
    b, dict_merge,
    blocked_command,
    merge_result,
    first_key,
    clusterdown_wrapper,
)
import pytest
# -*- coding: utf-8 -*-

# python std lib
from __future__ import with_statement

# rediscluster imports

# 3rd party imports


def test_parse_cluster_slots():
    """
    Example raw output from redis cluster. Output is form a redis 3.2.x node
    that includes the id in the reponse. The test below that do not include the id
    is to validate that the code is compatible with redis versions that do not contain
    that value in the response from the server.

    127.0.0.1:10000> cluster slots
    1) 1) (integer) 5461
       2) (integer) 10922
       3) 1) "10.0.0.1"
          2) (integer) 10000
          3) "3588b4cf9fc72d57bb262a024747797ead0cf7ea"
       4) 1) "10.0.0.4"
          2) (integer) 10000
          3) "a72c02c7d85f4ec3145ab2c411eefc0812aa96b0"
    2) 1) (integer) 10923
       2) (integer) 16383
       3) 1) "10.0.0.2"
          2) (integer) 10000
          3) "ffd36d8d7cb10d813f81f9662a835f6beea72677"
       4) 1) "10.0.0.5"
          2) (integer) 10000
          3) "5c15b69186017ddc25ebfac81e74694fc0c1a160"
    3) 1) (integer) 0
       2) (integer) 5460
       3) 1) "10.0.0.3"
          2) (integer) 10000
          3) "069cda388c7c41c62abe892d9e0a2d55fbf5ffd5"
       4) 1) "10.0.0.6"
          2) (integer) 10000
          3) "dc152a08b4cf1f2a0baf775fb86ad0938cb907dc"
    """

    extended_mock_response = [
        [0, 5460, ['172.17.0.2', 7000, 'ffd36d8d7cb10d813f81f9662a835f6beea72677'], ['172.17.0.2', 7003, '5c15b69186017ddc25ebfac81e74694fc0c1a160']],
        [5461, 10922, ['172.17.0.2', 7001, '069cda388c7c41c62abe892d9e0a2d55fbf5ffd5'], ['172.17.0.2', 7004, 'dc152a08b4cf1f2a0baf775fb86ad0938cb907dc']],
        [10923, 16383, ['172.17.0.2', 7002, '3588b4cf9fc72d57bb262a024747797ead0cf7ea'], ['172.17.0.2', 7005, 'a72c02c7d85f4ec3145ab2c411eefc0812aa96b0']]
    ]

    parse_cluster_slots(extended_mock_response)

    extended_mock_binary_response = [
        [0, 5460, [b('172.17.0.2'), 7000, b('ffd36d8d7cb10d813f81f9662a835f6beea72677')], [b('172.17.0.2'), 7003, b('5c15b69186017ddc25ebfac81e74694fc0c1a160')]],
        [5461, 10922, [b('172.17.0.2'), 7001, b('069cda388c7c41c62abe892d9e0a2d55fbf5ffd5')], [b('172.17.0.2'), 7004, b('dc152a08b4cf1f2a0baf775fb86ad0938cb907dc')]],
        [10923, 16383, [b('172.17.0.2'), 7002, b('3588b4cf9fc72d57bb262a024747797ead0cf7ea')], [b('172.17.0.2'), 7005, b('a72c02c7d85f4ec3145ab2c411eefc0812aa96b0')]]
    ]

    extended_mock_parsed = {
        (0, 5460): [
            {'host': b'172.17.0.2',
             'node_id': b'ffd36d8d7cb10d813f81f9662a835f6beea72677',
             'port': 7000,
             'server_type': 'master'},
            {'host': b'172.17.0.2',
             'node_id': b'5c15b69186017ddc25ebfac81e74694fc0c1a160',
             'port': 7003,
             'server_type': 'slave'}],
        (5461, 10922): [
            {'host': b'172.17.0.2',
             'node_id': b'069cda388c7c41c62abe892d9e0a2d55fbf5ffd5',
             'port': 7001,
             'server_type': 'master'},
            {'host': b'172.17.0.2',
             'node_id': b'dc152a08b4cf1f2a0baf775fb86ad0938cb907dc',
             'port': 7004,
             'server_type': 'slave'}],
        (10923, 16383): [
            {'host': b'172.17.0.2',
             'node_id': b'3588b4cf9fc72d57bb262a024747797ead0cf7ea',
             'port': 7002,
             'server_type': 'master'},
            {'host': b'172.17.0.2',
             'node_id': b'a72c02c7d85f4ec3145ab2c411eefc0812aa96b0',
             'port': 7005,
             'server_type': 'slave'}]
    }

    assert parse_cluster_slots(extended_mock_binary_response) == extended_mock_parsed


def test_list_keys_to_dict():
    def mock_true():
        return True
    assert list_keys_to_dict(["FOO", "BAR"], mock_true) == {"FOO": mock_true, "BAR": mock_true}


def test_dict_merge():
    a = {"a": 1}
    b = {"b": 2}
    c = {"c": 3}
    assert dict_merge(a, b, c) == {"a": 1, "b": 2, "c": 3}


def test_dict_merge_empty_list():
    assert dict_merge([]) == {}


def test_blocked_command():
    with pytest.raises(RedisClusterException) as ex:

        blocked_command(None, "SET")