# test/test_configfile.py
# Part of ‘dput’, a Debian package upload toolkit.
#
# This is free software, and you are welcome to redistribute it under
# certain conditions; see the end of this file for copyright
# information, grant of license, and disclaimer of warranty.

""" Unit tests for config file behaviour. """

import builtins
import configparser
import doctest
import io
import os
import os.path
import sys
import tempfile
import textwrap
import unittest.mock

import testscenarios
import testtools
import testtools.matchers
import xdg

import dput.configfile
import dput.dput

from .helper import (
        EXIT_STATUS_FAILURE,
        FakeSystemExit,
        FileDouble,
        patch_os_path_exists,
        patch_system_interfaces,
        setup_file_double_behaviour,
        )


def make_config_from_stream(stream):
    """ Make a ConfigParser parsed configuration from the stream content.

        :param stream: Text stream content of a config file.
        :return: The resulting config if the content parses correctly,
            or ``None``.
        """
    config = configparser.ConfigParser(
            defaults={
                'allow_unsigned_uploads': "false",
                },
            )

    config_file = io.StringIO(stream)
    try:
        config.read_file(config_file)
    except configparser.ParsingError:
        config = None

    return config


def make_config_file_scenarios(config_file_paths=None):
    """ Make a collection of scenarios for testing with config files.

        :return: A collection of scenarios for tests involving config files.

        The collection is a mapping from scenario name to a dictionary of
        scenario attributes.
        """
    if (config_file_paths is None):
        config_file_paths = {}
    runtime_config_file_path = config_file_paths.get(
            'runtime', tempfile.mktemp())
    global_config_file_path = config_file_paths.get(
            'global', os.path.join(os.path.sep, "etc", "dput.cf"))
    user_xdg_config_file_path = config_file_paths.get(
            'user_xdg', os.path.join(xdg.xdg_config_home(), "dput", "dput.cf"))
    user_home_config_file_path = config_file_paths.get(
            'user_home', os.path.join(os.path.expanduser("~"), ".dput.cf"))

    fake_file_empty = io.StringIO()
    fake_file_bogus = io.StringIO("b0gUs")
    fake_file_minimal = io.StringIO(textwrap.dedent("""\
            [DEFAULT]
            """))
    fake_file_comments = io.StringIO(textwrap.dedent("""\
            # Lorem ipsum, dolor sit amet.
            # Lȏrėm îpsüm, dølòr sĭt ámέt.
            [DEFAULT]
            """))
    fake_file_simple = io.StringIO(textwrap.dedent("""\
            [DEFAULT]
            hash = md5
            [foo]
            method = ftp
            fqdn = quux.example.com
            incoming = quux
            check_version = false
            allow_unsigned_uploads = false
            allowed_distributions =
            run_dinstall = false
            """))
    fake_file_simple_host_three = io.StringIO(textwrap.dedent("""\
            [DEFAULT]
            hash = md5
            [foo]
            method = ftp
            fqdn = quux.example.com
            incoming = quux
            check_version = false
            allow_unsigned_uploads = false
            allowed_distributions =
            run_dinstall = false
            [bar]
            fqdn = xyzzy.example.com
            incoming = xyzzy
            [baz]
            fqdn = chmrr.example.com
            incoming = chmrr
            """))
    fake_file_method_local = io.StringIO(textwrap.dedent("""\
            [foo]
            method = local
            incoming = quux
            """))
    fake_file_missing_fqdn = io.StringIO(textwrap.dedent("""\
            [foo]
            method = ftp
            incoming = quux
            """))
    fake_file_missing_incoming = io.StringIO(textwrap.dedent("""\
            [foo]
            method = ftp
            fqdn = quux.example.com
            """))
    fake_file_default_not_unsigned = io.StringIO(textwrap.dedent("""\
            [DEFAULT]
            allow_unsigned_uploads = false
            [foo]
            method = ftp
            fqdn = quux.example.com
            """))
    fake_file_default_distribution_only = io.StringIO(textwrap.dedent("""\
            [DEFAULT]
            default_host_main = consecteur
            [ftp-master]
            method = ftp
            fqdn = quux.example.com
            """))
    fake_file_distribution_none = io.StringIO(textwrap.dedent("""\
            [foo]
            method = ftp
            fqdn = quux.example.com
            distributions =
            """))
    fake_file_distribution_one = io.StringIO(textwrap.dedent("""\
            [foo]
            method = ftp
            fqdn = quux.example.com
            distributions = spam
            """))
    fake_file_distribution_three = io.StringIO(textwrap.dedent("""\
            [foo]
            method = ftp
            fqdn = quux.example.com
            distributions = spam,eggs,beans
            """))

    default_scenario_params = {
            'runtime': {
                'file_double_params': dict(
                    path=runtime_config_file_path,
                    fake_file=fake_file_comments),
                'os_path_exists_scenario_name': 'exist',
                'open_scenario_name': 'okay',
                },
            'global': {
                'file_double_params': dict(
                    path=global_config_file_path,
                    fake_file=fake_file_comments),
                'os_path_exists_scenario_name': 'exist',
                'open_scenario_name': 'okay',
                },
            'user_xdg': {
                'file_double_params': dict(
                    path=user_xdg_config_file_path,
                    fake_file=fake_file_comments),
                'os_path_exists_scenario_name': 'exist',
                'open_scenario_name': 'okay',
                },
            'user_home': {
                'file_double_params': dict(
                    path=user_home_config_file_path,
                    fake_file=fake_file_comments),
                'os_path_exists_scenario_name': 'exist',
                'open_scenario_name': 'okay',
                },
            }

    scenarios = {
            'default': {
                'configs_by_name': {
                    'runtime': None,
                    },
                'expected_config_names': ['global', 'user_xdg'],
                },
            'not-exist': {
                'configs_by_name': {
                    'runtime': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    },
                'expected_config_names': [],
                },
            'exist-read-denied': {
                'configs_by_name': {
                    'runtime': {
                        'open_scenario_name': 'read_denied',
                        },
                    },
                'expected_config_names': [],
                },
            'exist-empty': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_empty),
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-invalid': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_bogus),
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-minimal': {
                'expected_config_names': ['runtime'],
                },
            'exist-simple': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_simple),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-simple-host-three': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_simple_host_three),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-method-local': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_method_local),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-missing-fqdn': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_missing_fqdn),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-missing-incoming': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_missing_incoming),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-default-not-unsigned': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_default_not_unsigned),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-default-distribution-only': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_default_distribution_only),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-distribution-none': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_distribution_none),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-distribution-one': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_distribution_one),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'exist-distribution-three': {
                'configs_by_name': {
                    'runtime': {
                        'file_double_params': dict(
                            path=runtime_config_file_path,
                            fake_file=fake_file_distribution_three),
                        'test_section': "foo",
                        },
                    },
                'expected_config_names': ['runtime'],
                },
            'global-config-not-exist': {
                'configs_by_name': {
                    'global': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    'runtime': None,
                    },
                'expected_config_names': ['user_xdg'],
                },
            'global-config-read-denied': {
                'configs_by_name': {
                    'global': {
                        'open_scenario_name': 'read_denied',
                        },
                    'runtime': None,
                    },
                'expected_config_names': ['user_xdg'],
                },
            'user-xdg-config-not-exist': {
                'configs_by_name': {
                    'user_xdg': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    'user_home': {
                        'open_scenario_name': 'okay',
                        },
                    'runtime': None,
                    },
                'expected_config_names': ['global', 'user_home'],
                },
            'user-xdg-config-read-denied': {
                'configs_by_name': {
                    'user_xdg': {
                        'open_scenario_name': 'read_denied',
                        },
                    'user_home': {
                        'open_scenario_name': 'okay',
                        },
                    'runtime': None,
                    },
                'expected_config_names': ['global', 'user_home'],
                },
            'user-xdg-config-not-exist-user-home-config-not-exist': {
                'configs_by_name': {
                    'user_xdg': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    'user_home': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    'runtime': None,
                    },
                'expected_config_names': ['global'],
                },
            'user-xdg-config-not-exist-user-home-config-read-denied': {
                'configs_by_name': {
                    'user_xdg': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    'user_home': {
                        'open_scenario_name': 'read_denied',
                        },
                    'runtime': None,
                    },
                'expected_config_names': ['global'],
                },
            'all-not-exist': {
                'configs_by_name': {
                    'global': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    'user_xdg': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    'user_home': {
                        'os_path_exists_scenario_name': 'not_exist',
                        'open_scenario_name': 'nonexist',
                        },
                    'runtime': None,
                    },
                'expected_config_names': [],
                },
            'all-read-denied': {
                'configs_by_name': {
                    'global': {
                        'open_scenario_name': 'read_denied',
                        },
                    'user_xdg': {
                        'open_scenario_name': 'read_denied',
                        },
                    'user_home': {
                        'open_scenario_name': 'read_denied',
                        },
                    'runtime': None,
                    },
                'expected_config_names': [],
                },
            }

    for scenario in scenarios.values():
        scenario['empty_file'] = fake_file_empty
        if 'configs_by_name' not in scenario:
            scenario['configs_by_name'] = {}
        for (config_name, default_params) in default_scenario_params.items():
            if config_name not in scenario['configs_by_name']:
                params = default_params
            elif scenario['configs_by_name'][config_name] is None:
                continue
            else:
                params = default_params.copy()
                params.update(scenario['configs_by_name'][config_name])
            params['file_double'] = FileDouble(**params['file_double_params'])
            params['file_double'].set_os_path_exists_scenario(
                    params['os_path_exists_scenario_name'])
            params['file_double'].set_open_scenario(
                    params['open_scenario_name'])
            params['config'] = make_config_from_stream(
                    params['file_double'].fake_file.getvalue())
            scenario['configs_by_name'][config_name] = params

    return scenarios


def get_file_doubles_from_config_file_scenarios(scenarios):
    """ Get the `FileDouble` instances from config file scenarios.

        :param scenarios: Collection of config file scenarios.
        :return: Collection of `FileDouble` instances.
        """
    doubles = set()
    for scenario in scenarios:
        configs_by_name = scenario['configs_by_name']
        doubles.update(
                configs_by_name[config_name]['file_double']
                for config_name in ['global', 'user_home', 'runtime']
                if configs_by_name[config_name] is not None)

    return doubles


def setup_config_file_fixtures(testcase, config_file_paths=None):
    """ Set up fixtures for config file doubles. """
    if (config_file_paths is None):
        config_file_paths = dput.configfile.default_config_paths
    scenarios = make_config_file_scenarios(
            config_file_paths=config_file_paths)
    testcase.config_file_scenarios = scenarios

    setup_file_double_behaviour(
            testcase,
            get_file_doubles_from_config_file_scenarios(scenarios.values()))
    patch_os_path_exists(testcase)


def set_config(testcase, name):
    """ Set the config scenario for a specific test case. """
    scenarios = make_config_file_scenarios()
    testcase.config_scenario = scenarios[name]


def patch_runtime_config_options(testcase):
    """ Patch specific options in the runtime config. """
    config_params_by_name = testcase.config_scenario['configs_by_name']
    runtime_config_params = config_params_by_name['runtime']
    testcase.runtime_config_parser = runtime_config_params['config']

    def maybe_set_option(
            parser, section_name, option_name, value, default=""):
        if value is not None:
            if value is NotImplemented:
                # No specified value. Set a default.
                value = default
            parser.set(section_name, option_name, str(value))
        else:
            # Specifically requested *no* value for the option.
            parser.remove_option(section_name, option_name)

    if testcase.runtime_config_parser is not None:
        testcase.test_host = runtime_config_params.get(
                'test_section', None)

        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'method',
                getattr(testcase, 'config_default_method', "ftp"))
        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'login',
                getattr(testcase, 'config_default_login', "username"))
        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'scp_compress',
                str(getattr(testcase, 'config_default_scp_compress', False)))
        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'ssh_config_options',
                getattr(testcase, 'config_default_ssh_config_options', ""))
        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'distributions',
                getattr(testcase, 'config_default_distributions', ""))
        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'incoming',
                getattr(testcase, 'config_default_incoming', "quux"))
        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'allow_dcut',
                str(getattr(testcase, 'config_default_allow_dcut', True)))

        config_default_default_host_main = getattr(
                testcase, 'config_default_default_host_main', NotImplemented)
        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'default_host_main',
                config_default_default_host_main,
                default="")
        config_default_delayed = getattr(
                testcase, 'config_default_delayed', NotImplemented)
        maybe_set_option(
                testcase.runtime_config_parser,
                'DEFAULT', 'delayed', config_default_delayed,
                default=7)

        for section_name in testcase.runtime_config_parser.sections():
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'method',
                    getattr(testcase, 'config_method', "ftp"))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'fqdn',
                    getattr(testcase, 'config_fqdn', "quux.example.com"))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'passive_ftp',
                    str(getattr(testcase, 'config_passive_ftp', False)))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'run_lintian',
                    str(getattr(testcase, 'config_run_lintian', False)))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'run_dinstall',
                    str(getattr(testcase, 'config_run_dinstall', False)))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'pre_upload_command',
                    getattr(testcase, 'config_pre_upload_command', ""))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'post_upload_command',
                    getattr(testcase, 'config_post_upload_command', ""))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'progress_indicator',
                    str(getattr(testcase, 'config_progress_indicator', 0)))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'allow_dcut',
                    str(getattr(testcase, 'config_allow_dcut', True)))
            if hasattr(testcase, 'config_incoming'):
                testcase.runtime_config_parser.set(
                        section_name, 'incoming', testcase.config_incoming)
            config_delayed = getattr(
                    testcase, 'config_delayed', NotImplemented)
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'delayed', config_delayed,
                    default=9)

        for (section_type, options) in (
                getattr(testcase, 'config_extras', {}).items()):
            section_name = {
                    'default': "DEFAULT",
                    'host': testcase.test_host,
                    }[section_type]
            for (option_name, option_value) in options.items():
                maybe_set_option(
                        testcase.runtime_config_parser,
                        section_name, option_name, option_value)


def set_config_file_scenario(testcase, name):
    """ Set the configuration file scenario for `testcase`. """
    testcase.config_file_scenario = testcase.config_file_scenarios[name]
    testcase.configs_by_name = testcase.config_file_scenario['configs_by_name']
    for config_params in testcase.configs_by_name.values():
        if config_params is not None:
            config_params['file_double'].register_for_testcase(testcase)


def get_path_for_runtime_config_file(testcase):
    """ Get the path to specify for runtime config file. """
    path = None
    runtime_config_params = testcase.configs_by_name['runtime']
    if runtime_config_params is not None:
        runtime_config_file_double = runtime_config_params['file_double']
        path = runtime_config_file_double.path
    return path


def patch_default_config_paths(testcase, fake_paths=NotImplemented):
    """ Patch the `default_config_paths` object during `testcase`. """
    if (fake_paths is NotImplemented):
        fake_paths = {
                name: testcase.getUniqueString()
                for name in ['global', 'user_xdg', 'user_home', 'lorem_ipsum']
                }
    patcher = unittest.mock.patch.object(
            dput.configfile, "default_config_paths",
            new=fake_paths)
    patcher.start()
    testcase.addCleanup(patcher.stop)


class active_config_files_TestCase(
        testscenarios.WithScenarios,
        testtools.TestCase):
    """ Test cases for `active_config_files` function. """

    scenarios = [
            ('default', {
                'config_scenario_name': 'default',
                'expected_debug_output': None,
                }),
            ('exist-minimal', {
                'config_scenario_name': 'exist-minimal',
                'expected_debug_output': None,
                }),
            ('not-exist', {
                'config_scenario_name': 'not-exist',
                'expected_debug_output': "skipping",
                }),
            ('exist-read-denied', {
                'config_scenario_name': 'exist-read-denied',
                'expected_debug_output': "skipping",
                }),
            ('global-config-not-exist', {
                'config_scenario_name': 'global-config-not-exist',
                'expected_debug_output': "skipping",
                }),
            ('user-xdg-config-not-exist', {
                'config_scenario_name': 'user-xdg-config-not-exist',
                'expected_debug_output': "skipping",
                }),
            ('user-xdg-config-read-denied', {
                'config_scenario_name': 'user-xdg-config-read-denied',
                'expected_debug_output': "skipping",
                }),
            ('user-xdg-config-not-exist-user-home-config-not-exist', {
                'config_scenario_name':
                    'user-xdg-config-not-exist-user-home-config-not-exist',
                'expected_debug_output': "skipping",
                }),
            ('user-xdg-config-not-exist-user-home-config-read-denied', {
                'config_scenario_name':
                    'user-xdg-config-not-exist-user-home-config-read-denied',
                'expected_debug_output': "skipping",
                }),
            ('all-not-exist', {
                'config_scenario_name': 'all-not-exist',
                'expected_debug_output': "skipping",
                }),
            ('all-read-denied', {
                'config_scenario_name': 'all-read-denied',
                'expected_debug_output': "skipping",
                }),
            ]

    def setUp(self):
        """ Set up test fixtures. """
        super().setUp()
        patch_system_interfaces(self)
        patch_default_config_paths(self)
        setup_config_file_fixtures(self)

        set_config_file_scenario(self, self.config_scenario_name)
        self.expected_config_names = (
                self.config_file_scenario['expected_config_names'])
        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        runtime_config_file_path = get_path_for_runtime_config_file(self)
        self.test_args = dict(
                config_path=runtime_config_file_path,
                debug=False,
                )

    def test_emits_skip_message_when_debug_true(self):
        """ Should emit a “skipping” message when `debug` is true. """
        if (not self.expected_debug_output):
            self.skipTest("no skip message expected in this scenario")
        self.test_args['debug'] = True
        try:
            all(dput.configfile.active_config_files(**self.test_args))
        except dput.configfile.ConfigurationError:
            pass
        expected_output = self.expected_debug_output
        self.assertIn(expected_output, sys.stderr.getvalue())

    def test_emits_no_skip_message_when_debug_false(self):
        """ Should not emit a “skipping” message when `debug` is false. """
        self.test_args['debug'] = False
        try:
            all(dput.configfile.active_config_files(**self.test_args))
        except dput.configfile.ConfigurationError:
            pass
        unwanted_output = self.expected_debug_output
        self.assertNotIn(unwanted_output, sys.stderr.getvalue())

    def test_yields_expected_config_files(self):
        """ Should yield a sequence of the expected configuration files. """
        if not self.expected_config_names:
            self.skipTest("Normal return not expected in this scenario")
        expected_files_list = [
                open(
                    self.configs_by_name[config_name]['file_double'].path,
                    encoding='UTF-8')
                for config_name in self.expected_config_names]
        result_list = list(
                dput.configfile.active_config_files(**self.test_args))
        self.assertEqual(expected_files_list, result_list)

    def test_raise_error_if_all_config_files_fail(self):
        """
        Should raise `ConfigurationError` if unable to open any config files.
        """
        if self.expected_config_names:
            self.skipTest("No error expected for this scenario")
        expected_message_regex = (
                "^Could not open any configfile, tried .*")
        with testtools.ExpectedException(
                dput.configfile.ConfigurationError,
                value_re=expected_message_regex):
            all(dput.configfile.active_config_files(**self.test_args))


def patch_active_config_files(testcase):
    """ Patch the `active_config_files` function for `testcase`. """
    if not hasattr(testcase, 'fake_config_files'):
        testcase.fake_config_files = [
                FileDouble(path)
                for path in testcase.getUniqueString() for __ in range(3)]

    def fake_active_config_files(*args, **kwargs):
        for config_file in testcase.fake_config_files:
            yield config_file

    func_patcher = unittest.mock.patch.object(
            dput.configfile, "active_config_files", autospec=True,
            side_effect=fake_active_config_files)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)


class read_configs_TestCase(testtools.TestCase):
    """ Test cases for `read_config` function. """

    def setUp(self):
        """ Set up test fixtures. """
        super().setUp()
        patch_system_interfaces(self)
        setup_config_file_fixtures(self)

        self.test_configparser = configparser.ConfigParser()
        self.mock_configparser_class = unittest.mock.Mock(
                'ConfigParser',
                return_value=self.test_configparser)

        patcher_class_configparser = unittest.mock.patch.object(
                configparser, "ConfigParser",
                new=self.mock_configparser_class)
        patcher_class_configparser.start()
        self.addCleanup(patcher_class_configparser.stop)

        set_config_file_scenario(self, 'exist-minimal')
        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        runtime_config_file_path = get_path_for_runtime_config_file(self)
        config_files = [
                config_params['file_double'].fake_file
                for config_params in self.configs_by_name.values()]
        self.test_args = dict(
                config_files=config_files,
                debug=False,
                )

    def test_creates_new_parser(self):
        """ Should invoke the `ConfigParser` constructor. """
        dput.dput.read_configs(**self.test_args)
        configparser.ConfigParser.assert_called_with()

    def test_returns_expected_configparser(self):
        """ Should return expected `ConfigParser` instance. """
        result = dput.dput.read_configs(**self.test_args)
        self.assertEqual(self.test_configparser, result)

    def test_sets_default_option_values(self):
        """ Should set values for options, in section 'DEFAULT'. """
        option_names = set([
                'login',
                'method',
                'hash',
                'allow_unsigned_uploads',
                'allow_dcut',
                'distributions',
                'allowed_distributions',
                'run_lintian',
                'run_dinstall',
                'check_version',
                'scp_compress',
                'default_host_main',
                'post_upload_command',
                'pre_upload_command',
                'ssh_config_options',
                'passive_ftp',
                'progress_indicator',
                'delayed',
                ])
        result = dput.dput.read_configs(**self.test_args)
        self.assertTrue(option_names.issubset(set(result.defaults().keys())))

    def test_emits_debug_message_on_parsing_config_file(self):
        """ Should emit a debug message when parsing each config file. """
        self.test_args['debug'] = True
        config_file_double = self.configs_by_name['runtime']['file_double']
        dput.dput.read_configs(**self.test_args)
        expected_output = textwrap.dedent("""\
                D: Parsing configuration file ‘{path}’
                """).format(path=config_file_double.path)
        self.assertIn(expected_output, sys.stdout.getvalue())

    def test_calls_sys_exit_if_config_parsing_error(self):
        """ Should call `sys.exit` if a parsing error occurs. """
        set_config_file_scenario(self, 'exist-invalid')
        self.set_test_args()
        self.test_args['debug'] = True
        with testtools.ExpectedException(FakeSystemExit):
            dput.dput.read_configs(**self.test_args)
        expected_output = textwrap.dedent("""\
                Error parsing configuration file:
                ...
                """)
        self.assertThat(
                sys.stderr.getvalue(),
                testtools.matchers.DocTestMatches(
                    expected_output, flags=doctest.ELLIPSIS))
        sys.exit.assert_called_with(EXIT_STATUS_FAILURE)

    def test_sets_fqdn_option_if_local_method(self):
        """ Should set “fqdn” option for “local” method. """
        set_config_file_scenario(self, 'exist-method-local')
        self.set_test_args()
        result = dput.dput.read_configs(**self.test_args)
        runtime_config_params = self.configs_by_name['runtime']
        test_section = runtime_config_params['test_section']
        self.assertTrue(result.has_option(test_section, "fqdn"))

    def test_exits_with_error_if_missing_fqdn(self):
        """ Should exit with error if config is missing 'fqdn'. """
        set_config_file_scenario(self, 'exist-missing-fqdn')
        self.set_test_args()
        with testtools.ExpectedException(FakeSystemExit):
            dput.dput.read_configs(**self.test_args)
        expected_output = textwrap.dedent("""\
                Config error: {host} must have a fqdn set
                """).format(host="foo")
        self.assertIn(expected_output, sys.stderr.getvalue())
        sys.exit.assert_called_with(EXIT_STATUS_FAILURE)

    def test_exits_with_error_if_missing_incoming(self):
        """ Should exit with error if config is missing 'incoming'. """
        set_config_file_scenario(self, 'exist-missing-incoming')
        self.set_test_args()
        with testtools.ExpectedException(FakeSystemExit):
            dput.dput.read_configs(**self.test_args)
        expected_output = textwrap.dedent("""\
                Config error: {host} must have an incoming directory set
                """).format(host="foo")
        self.assertIn(expected_output, sys.stderr.getvalue())
        sys.exit.assert_called_with(EXIT_STATUS_FAILURE)


class print_config_TestCase(testtools.TestCase):
    """ Test cases for `print_config` function. """

    def setUp(self):
        """ Set up test fixtures. """
        super().setUp()
        patch_system_interfaces(self)

    def test_invokes_config_write_to_stdout(self):
        """ Should invoke config's `write` method with `sys.stdout`. """
        test_config = make_config_from_stream("")
        mock_config = unittest.mock.Mock(test_config)
        dput.dput.print_config(mock_config, debug=False)
        mock_config.write.assert_called_with(sys.stdout)


# Copyright © 2015–2022 Ben Finney <bignose@debian.org>
#
# This is free software: you may copy, modify, and/or distribute this work
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; version 3 of that license or any later version.
# No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details.


# Local variables:
# coding: utf-8
# mode: python
# End:
# vim: fileencoding=utf-8 filetype=python :
