# Karl O. Pinc <kop@karlpinc.com>


import pytest
import builtins
import logging
import logging.config
import yaml

from unittest import mock

import libtest

from psoft2ldap2 import conflib


# Helper functions

def assert_critical_error_logged(caplog):
    logs = caplog.record_tuples
    assert len(logs) > 0
    assert logging.CRITICAL in [log[1] for log in logs]


# _yaml_fd_to_dict()

@pytest.mark.unittest
def test__yaml_fd_to_dict_success(monkeypatch, caplog):
    '''When yaml.safe_load() succeeds the result is returned and nothing is
    logged
    '''
    test_result = 'some test\ndata\n'
    mock_safe_load = mock.Mock(return_value=test_result)
    monkeypatch.setattr(yaml, 'safe_load', mock_safe_load)

    result = conflib._yaml_fd_to_dict(None, None, '/foo')

    logs = caplog.record_tuples

    assert result == test_result
    assert len(logs) == 0


@pytest.mark.unittest
def test__yaml_fd_to_dict_fail(monkeypatch, caplog):
    '''When yaml.safe_load() fails a critical error is logged and
    the program exits with a non-zero exit code
    '''
    logger = logging.getLogger()

    mock_safe_load = mock.Mock(side_effect=yaml.YAMLError())
    monkeypatch.setattr(yaml, 'safe_load', mock_safe_load)

    with pytest.raises(SystemExit) as exinfo:
        conflib._yaml_fd_to_dict(logger, None, '/foo')

    assert exinfo.value.code != 0
    assert_critical_error_logged(caplog)


# dict_from_path()

@pytest.fixture
def make_open_fail(monkeypatch):
    '''Make open() calls fail
    '''
    def mock_open(*args):
        raise OSError()

    monkeypatch.setattr(builtins, 'open', mock_open)


@pytest.mark.unittest
def test_dict_from_path_success(monkeypatch):
    '''When logfile open succeeds, _yaml_fd_to_dict is called
    and the result returned
    '''
    test_data = 'some test\ndata\n'

    mock__yaml_fd_to_dict = mock.Mock(return_value=test_data)
    monkeypatch.setattr(conflib, '_yaml_fd_to_dict', mock__yaml_fd_to_dict)
    monkeypatch.setattr(builtins, 'open',
                        mock.mock_open(read_data=test_data))

    result = conflib.dict_from_path(None, '/foo', False)

    mock__yaml_fd_to_dict.assert_called_once()
    assert result == test_data


@pytest.mark.unittest
def test_dict_from_path_fail_expected(caplog, make_open_fail):
    '''When expected is True and can't open logs a critical error
    and exits with a non-zero return code
    '''
    logger = logging.getLogger()

    with pytest.raises(SystemExit) as exinfo:
        conflib.dict_from_path(logger, '/foo', True)

    assert exinfo.value.code != 0
    assert_critical_error_logged(caplog)


@pytest.mark.unittest
def test_dict_from_path_fail_unexpected(caplog, make_open_fail):
    '''When expected is False and can't open a non-True value is returned
    and there is no log of warning or above
    '''
    logger = logging.getLogger()
    result = conflib.dict_from_path(logger, '/foo', False)

    assert not(result)

    logs = caplog.record_tuples
    assert len([log for log in logs if log[1] >= logging.WARNING]) == 0


# getcfg()

@pytest.mark.unittest
def test_getcfg_success():
    '''Returns the desired dict entry
    '''
    test_value = 'val'
    test_key = 'key'
    result = conflib.getcfg({test_key: test_value}, test_key)
    assert result == test_value


@pytest.mark.unittest
def test_getcfg_fail():
    '''Raises a NoSuchKeyError when the dict entry does not exist
    '''
    with pytest.raises(conflib.NoSuchKeyError):
        conflib.getcfg({}, 'notkey')

        assert True


mock_getcfg = libtest.make_mock_fixture(
    conflib, 'getcfg')


# load_logging_config()

@pytest.mark.unittest
def test_load_logging_config_success_w_logger(monkeypatch):
    '''logging.config.dictConfig() is called once,
    so long as the supplied config has a 'logger'node
    '''
    mock_dictconfig = mock.Mock()
    monkeypatch.setattr(logging.config, 'dictConfig', mock_dictconfig)

    conflib.load_logging_config(None, {'logger': None}, '/foo')

    mock_dictconfig.assert_called_once()


@pytest.mark.unittest
def test_load_logging_config_success_w_no_conf(monkeypatch):
    '''logging.config.dictConfig() is not called when
    no configuration exists'''
    mock_dictconfig = mock.Mock()
    monkeypatch.setattr(logging.config, 'dictConfig', mock_dictconfig)

    conflib.load_logging_config(None, None, '/foo')

    mock_dictconfig.assert_not_called()


@pytest.mark.unittest
def test_load_logging_config_fail_wo_logger(monkeypatch, caplog):
    '''logging.config.dictConfig() is not called when
    the supplied config has no 'logger' node, a critical error
    is logged and the program exits with a non-zero exit code
    '''
    logger = logging.getLogger()

    mock_dictconfig = mock.Mock()
    monkeypatch.setattr(logging.config, 'dictConfig', mock_dictconfig)

    with pytest.raises(SystemExit) as exinfo:
        conflib.load_logging_config(logger, {}, '/foo')

    assert exinfo.value.code != 0
    mock_dictconfig.assert_not_called()
    assert_critical_error_logged(caplog)


@pytest.mark.unittest
@pytest.mark.parametrize(
    'raise_exception',
    [(ValueError), (TypeError), (AttributeError), (ImportError)])
def test_load_logging_config_fail(raise_exception, monkeypatch, caplog):
    '''When logging.config.dictConfig fails a critical error is logged
    and the program exits with a non-zero exit code
    '''
    logger = logging.getLogger()

    mock_dictconfig = mock.Mock(side_effect=raise_exception())
    monkeypatch.setattr(logging.config, 'dictConfig', mock_dictconfig)

    with pytest.raises(SystemExit) as exinfo:
        conflib.load_logging_config(logger, {'logger': None}, '/foo')

    assert exinfo.value.code != 0
    mock_dictconfig.assert_called_once()
    assert_critical_error_logged(caplog)


#
# Integration tests
#

@pytest.mark.integrationtest
def test_dict_from_path_success_integration(monkeypatch):
    '''When logfile open succeeds, _yaml_fd_to_dict is called
    and the result returned
    '''
    test_data = 'key: value\n'

    monkeypatch.setattr(builtins, 'open',
                        mock.mock_open(read_data=test_data))

    result = conflib.dict_from_path(None, '/foo', False)

    assert isinstance(result, dict)
