# 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 configparser
import contextlib
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,
        make_unique_slug,
        patch_os_isatty,
        patch_os_path_exists,
        patch_system_interfaces,
        setup_file_double_behaviour,
        )
from .test_dputhelper import (
        patch_get_username_from_system,
        )


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',
                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',
                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, 'login',
                    getattr(testcase, 'config_login', "username"))
            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',
                    getattr(testcase, 'config_passive_ftp', False))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'run_lintian',
                    getattr(testcase, 'config_run_lintian', False))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'run_dinstall',
                    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',
                    getattr(testcase, 'config_progress_indicator', 0))
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'allow_dcut',
                    getattr(testcase, 'config_allow_dcut', True))
            config_incoming = getattr(
                    testcase, 'config_incoming', NotImplemented)
            maybe_set_option(
                    testcase.runtime_config_parser,
                    section_name, 'incoming', config_incoming,
                    default=tempfile.mktemp())
            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
        with contextlib.suppress(dput.configfile.ConfigurationError):
            all(dput.configfile.active_config_files(**self.test_args))
        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
        with contextlib.suppress(dput.configfile.ConfigurationError):
            all(dput.configfile.active_config_files(**self.test_args))
        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)


class print_default_upload_method_TestCase(testtools.TestCase):
    """ Test cases for `print_default_upload_method` function. """

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

        set_config(self, 'exist-simple')
        patch_runtime_config_options(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                )

    def test_emits_default_method(self):
        """ Should emit default upload method name to stdout. """
        dput.dput.print_default_upload_method(**self.test_args)
        expected_output = textwrap.dedent("""\

                Default Method: {method}

                """).format(
                    method=self.runtime_config_parser.get(
                        'DEFAULT', 'method'))
        self.assertThat(
                sys.stdout.getvalue(),
                testtools.matchers.DocTestMatches(
                    expected_output, doctest.ELLIPSIS))


def patch_print_default_upload_method(testcase):
    """ Patch `print_default_upload_method` function for the `testcase`. """
    func_patcher = unittest.mock.patch.object(
            dput.dput, "print_default_upload_method", autospec=True,
            return_value=testcase.config_login)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)

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

    scenarios = [
            ('default', {}),
            ('config-distribution-one', {
                'config_scenario_name': "exist-distribution-one",
                }),
            ('config-distribution-three', {
                'config_scenario_name': "exist-distribution-three",
                }),
            ]

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

        config_scenario_name = getattr(
                self, 'config_scenario_name',
                'exist-simple')
        set_config(self, config_scenario_name)
        patch_runtime_config_options(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                )

    def test_emits_all_config_sections(self):
        """ Should iteratively emit all config sections to stdout. """
        dput.dput.print_host_list(**self.test_args)
        expected_output_lines = [
                "{section} => {fqdn}  (Upload method: {method}...)".format(
                    section=section_name,
                    fqdn=self.runtime_config_parser.get(section_name, 'fqdn'),
                    method=self.runtime_config_parser.get(
                        section_name, 'method'),
                    )
                for section_name in self.runtime_config_parser.sections()
                ] + ["..."]
        expected_output = "\n".join(expected_output_lines)
        self.assertThat(
                sys.stdout.getvalue(),
                testtools.matchers.DocTestMatches(
                    expected_output, doctest.ELLIPSIS))


def patch_print_host_list(testcase):
    """ Patch the `print_host_list` function for the `testcase`. """
    func_patcher = unittest.mock.patch.object(
            dput.dput, "print_host_list", autospec=True)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)

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

    scenarios = [
            ('config-default-method', {
                'config_default_method': "method-from-config-default",
                'config_method': None,
                'expected_result': "method-from-config-default",
                }),
            ('config-host-method', {
                'config_method': "method-from-config-host",
                'expected_result': "method-from-config-host",
                }),
            ('config-default-and-host-method', {
                'config_default_method': "method-from-config-default",
                'config_method': "method-from-config-host",
                'expected_result': "method-from-config-host",
                }),
            ]

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

        set_config(self, 'exist-simple')
        patch_runtime_config_options(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                host=self.test_host,
                )

    def test_returns_expected_result(self):
        """ Should return expected method name for host configuration. """
        result = dput.dput.get_upload_method_name_for_host(**self.test_args)
        self.assertEqual(self.expected_result, result)


def patch_get_upload_method_name_for_host(testcase):
    """ Patch `get_upload_method_name_for_host` function for `testcase`. """
    if not hasattr(testcase, 'get_upload_method_name_for_host_return_value'):
        method_name = testcase.runtime_config_parser.get(
                testcase.test_host, 'method')
        testcase.get_upload_method_name_for_host_return_value = method_name
    func_patcher = unittest.mock.patch.object(
            dput.dput, "get_upload_method_name_for_host", autospec=True,
            return_value=testcase.get_upload_method_name_for_host_return_value)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)

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

    scenarios = [
            ('config-default-login', {
                'config_extras': {
                    'default': {
                        'login': "login-from-config-default",
                        },
                    },
                'system_username': "login-from-system",
                'expected_result': "login-from-config-default",
                'expected_output_template':
                    "D: Default login {login} used",
                }),
            ('config-host-login', {
                'config_extras': {
                    'host': {
                        'login': "login-from-config-host",
                        },
                    },
                'system_username': "login-from-system",
                'expected_result': "login-from-config-host",
                'expected_output_template':
                    "D: Login {login} from section {host} used",
                }),
            ('config-default-login sentinel', {
                'config_extras': {
                    'default': {
                        'login': "username",
                        },
                    },
                'system_username': "login-from-system",
                'expected_result': "login-from-system",
                'expected_output_template':
                    "D: Neither host {host} nor default login used.",
                }),
            ('config-host-login sentinel', {
                'config_extras': {
                    'host': {
                        'login': "username",
                        },
                    },
                'system_username': "login-from-system",
                'expected_result': "login-from-system",
                'expected_output_template':
                    "D: Neither host {host} nor default login used.",
                }),
            ]

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

        set_config(
                self,
                getattr(self, 'config_scenario_name', 'exist-simple'))
        patch_runtime_config_options(self)

        patch_get_username_from_system(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                host=self.test_host,
                debug=False,
                )

    def test_emits_debug_message_for_discovered_values(self):
        """ Should emit debug message for values discovered. """
        self.test_args['debug'] = True
        dput.dput.get_login_for_host(**self.test_args)
        expected_output = self.expected_output_template.format(
                host=self.test_host, login=self.expected_result)
        self.assertIn(expected_output, sys.stdout.getvalue())

    def test_returns_expected_result(self):
        """ Should return expected login value for host configuration. """
        result = dput.dput.get_login_for_host(**self.test_args)
        self.assertEqual(self.expected_result, result)


def patch_get_login_for_host(testcase):
    """ Patch the `get_login_for_host` function for the `testcase`. """
    if not hasattr(testcase, 'config_login'):
        testcase.config_login = make_unique_slug(testcase)
    func_patcher = unittest.mock.patch.object(
            dput.dput, "get_login_for_host", autospec=True,
            return_value=testcase.config_login)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)

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

    scenarios = [
            ('config-method-http', {
                'config_extras': {
                    'host': {
                        'method': "http",
                        'fqdn': "fqdn-from-config-host",
                        },
                    },
                'expected_result': "fqdn-from-config-host",
                }),
            ('config-method-http port-443', {
                'config_extras': {
                    'host': {
                        'method': "http",
                        'fqdn': "fqdn-from-config-host:443",
                        },
                    },
                'expected_result': "fqdn-from-config-host",
                }),
            ('config-method-ftp', {
                'config_extras': {
                    'host': {
                        'method': "ftp",
                        'fqdn': "fqdn-from-config-host",
                        },
                    },
                'expected_result': "fqdn-from-config-host",
                }),
            ('config-method-ftp port-21', {
                'config_extras': {
                    'host': {
                        'method': "ftp",
                        'fqdn': "fqdn-from-config-host:21",
                        },
                    },
                'expected_result': "fqdn-from-config-host",
                }),
            ('config-method-ftp port-42', {
                'config_extras': {
                    'host': {
                        'method': "ftp",
                        'fqdn': "fqdn-from-config-host:42",
                        },
                    },
                'expected_result': "fqdn-from-config-host",
                }),
            ('config-method-local', {
                'config_extras': {
                    'host': {
                        'method': "local",
                        'fqdn': "fqdn-from-config-host",
                        },
                    },
                'expected_result': "localhost",
                }),
            ]

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

        set_config(self, 'exist-simple')
        patch_runtime_config_options(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                host=self.test_host,
                )

    def test_returns_expected_result(self):
        """ Should return expected FQDN value for host configuration. """
        result = dput.dput.get_fqdn_for_host(**self.test_args)
        self.assertEqual(self.expected_result, result)


def patch_get_fqdn_for_host(testcase):
    """ Patch the `get_fqdn_for_host` function for the `testcase`. """
    if hasattr(testcase, 'get_fqdn_for_host_return_value'):
        return_value = testcase.get_fqdn_for_host_return_value
    elif hasattr(testcase, 'config_fqdn'):
        return_value = testcase.config_fqdn
    else:
        return_value = make_unique_slug(testcase)
    func_patcher = unittest.mock.patch.object(
            dput.dput, "get_fqdn_for_host", autospec=True,
            return_value=return_value)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)

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

    scenarios = [
            ('config-method-http', {
                'config_extras': {
                    'host': {
                        'method': "http",
                        'fqdn': "fqdn-from-config-host",
                        },
                    },
                'expected_result': None,
                }),
            ('config-method-http port-443', {
                'config_extras': {
                    'host': {
                        'method': "http",
                        'fqdn': "fqdn-from-config-host:443",
                        },
                    },
                'expected_result': 443,
                }),
            ('config-method-ftp', {
                'config_extras': {
                    'host': {
                        'method': "ftp",
                        'fqdn': "fqdn-from-config-host",
                        },
                    },
                'expected_result': None,
                }),
            ('config-method-ftp port-21', {
                'config_extras': {
                    'host': {
                        'method': "ftp",
                        'fqdn': "fqdn-from-config-host:21",
                        },
                    },
                'expected_result': 21,
                }),
            ('config-method-ftp port-42', {
                'config_extras': {
                    'host': {
                        'method': "ftp",
                        'fqdn': "fqdn-from-config-host:42",
                        },
                    },
                'expected_result': 42,
                }),
            ('config-method-local', {
                'config_extras': {
                    'host': {
                        'method': "local",
                        'fqdn': "fqdn-from-config-host",
                        },
                    },
                'expected_result': None,
                }),
            ]

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

        set_config(self, 'exist-simple')
        patch_runtime_config_options(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                host=self.test_host,
                )

    def test_returns_expected_result(self):
        """ Should return expected port value for host configuration. """
        result = dput.dput.get_port_for_host(**self.test_args)
        self.assertEqual(self.expected_result, result)


def patch_get_port_for_host(testcase):
    """ Patch the `get_port_for_host` function for the `testcase`. """
    if hasattr(testcase, 'get_port_for_host_return_value'):
        return_value = testcase.get_port_for_host_return_value
    else:
        return_value = testcase.getUniqueInteger()
    func_patcher = unittest.mock.patch.object(
            dput.dput, "get_port_for_host", autospec=True,
            return_value=return_value)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)

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

    scenarios = [
            ('valid numerals', {
                'option_arg_text': "11",
                'expected_result': 11,
                }),
            ('invalid none', {
                'option_arg_text': None,
                'expected_exception': TypeError,
                }),
            ('invalid empty-string', {
                'option_arg_text': "",
                'expected_exception': TypeError,
                }),
            ('invalid not-text', {
                'option_arg_text': object(),
                'expected_exception': TypeError,
                }),
            ('invalid <0', {
                'option_arg_text': "-1",
                'expected_exception': ValueError,
                }),
            ('invalid >15', {
                'option_arg_text': "16",
                'expected_exception': ValueError,
                }),
            ('invalid malformed', {
                'option_arg_text': "b0gUs",
                'expected_exception': TypeError,
                }),
            ]

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

        set_config(self, 'exist-simple')
        patch_runtime_config_options(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                days_text=self.option_arg_text,
                )

    def test_returns_expected_result(self):
        """ Should give expected result for inputs. """
        if hasattr(self, 'expected_exception'):
            with testtools.ExpectedException(self.expected_exception):
                dput.dput.get_delayed_days(**self.test_args)
        else:
            result = dput.dput.get_delayed_days(**self.test_args)
            self.assertEqual(self.expected_result, result)


def patch_get_delayed_days(testcase):
    """ Patch the `get_delayed_days` function for the `testcase`. """
    if not hasattr(testcase, 'get_delayed_days_return_value'):
        testcase.get_delayed_days_return_value = None
    func_patcher = unittest.mock.patch.object(
            dput.dput, 'get_delayed_days', autospec=True,
            return_value=testcase.get_delayed_days_return_value)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)

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

    scenarios = [
            ('default-incoming', {
                'config_default_incoming': "incoming-from-config-default",
                'config_incoming': None,
                'expected_result': "incoming-from-config-default",
                }),
            ('host-incoming no-trailing-slash', {
                'config_default_incoming': "incoming-from-config-default",
                'config_incoming': "incoming-from-config",
                'expected_result': "incoming-from-config",
                }),
            ('host-incoming with-trailing-slash', {
                'config_default_incoming': "incoming-from-config-default",
                'config_incoming': "incoming-from-config/",
                'expected_result': "incoming-from-config",
                }),
            ]

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

        set_config(self, 'exist-simple')
        patch_runtime_config_options(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                host=self.test_host,
                )

    def test_returns_expected_result(self):
        """ Should return expected incoming path value. """
        result = dput.dput.get_incoming_for_host(**self.test_args)
        self.assertEqual(self.expected_result, result)


def patch_get_incoming_for_host(testcase):
    """ Patch the `get_incoming_for_host` function for the `testcase`. """
    if not hasattr(testcase, 'get_incoming_for_host_return_value'):
        testcase.get_incoming_for_host_return_value = tempfile.mktemp()
    func_patcher = unittest.mock.patch.object(
            dput.dput, "get_incoming_for_host", autospec=True,
            return_value=testcase.get_incoming_for_host_return_value)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)

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

    config_scenarios = [
            ('config-default', {
                'expected_result': 0,
                }),
            ('config-specifies-progress', {
                'config_progress_indicator': 23,
                'expected_result': 23,
                }),
            ]

    isatty_scenarios = [
            ('isatty-true', {
                'os_isatty_return_value': True,
                }),
            ('isatty-false', {
                'os_isatty_return_value': False,
                'progress_indicator_override': 0,
                }),
            ]

    scenarios = testscenarios.multiply_scenarios(
            config_scenarios, isatty_scenarios)

    for (scenario_name, scenario) in scenarios:
        if 'progress_indicator_override' in scenario:
            scenario['expected_result'] = scenario[
                    'progress_indicator_override']

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

        set_config(
                self,
                getattr(self, 'config_scenario_name', 'exist-simple'))
        patch_runtime_config_options(self)

        patch_os_isatty(self)

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                host=self.test_host,
                )

    def test_returns_expected_result(self):
        """ Should return expected progress indicator value. """
        result = dput.dput.get_progress_indicator_for_host(**self.test_args)
        self.assertEqual(self.expected_result, result)


def patch_get_progress_indicator_for_host(testcase):
    """ Patch `get_progress_indicator_for_host` function for `testcase`. """
    if not hasattr(testcase, 'get_progress_indicator_for_host_return_value'):
        testcase.get_progress_indicator_for_host_return_value = 0
    func_patcher = unittest.mock.patch.object(
            dput.dput, "get_progress_indicator_for_host", autospec=True,
            return_value=testcase.get_progress_indicator_for_host_return_value)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)


class verify_config_upload_methods_BaseTestCase(
        testtools.TestCase):
    """ Base for test cases for `verify_config_upload_methods` function. """

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

        set_config(
                self,
                getattr(self, 'config_scenario_name', 'exist-simple'))
        patch_runtime_config_options(self)

        self.upload_methods = {
                name: unittest.mock.MagicMock()
                for name in ["local", "ftp", "scp"]}

        self.set_test_args()

    def set_test_args(self):
        """ Set the arguments for the test call to the function. """
        self.test_args = dict(
                config=self.runtime_config_parser,
                host=self.test_host,
                upload_methods=self.upload_methods,
                debug=False,
                )


class verify_config_upload_methods_SuccessTestCase(
        verify_config_upload_methods_BaseTestCase):
    """ Test cases for `verify_config_upload_methods`, when successful. """

    def test_emits_debug_message_for_discovered_methods(self):
        """ Should emit debug message for discovered upload methods. """
        self.test_args['debug'] = True
        dput.dput.verify_config_upload_methods(**self.test_args)
        expected_output = textwrap.dedent("""\
                D: Default Method: {default_method}
                D: Host Method: {host_method}
                """).format(
                    default_method=self.runtime_config_parser.get(
                        'DEFAULT', 'method'),
                    host_method=self.runtime_config_parser.get(
                        self.test_host, 'method'))
        self.assertIn(expected_output, sys.stdout.getvalue())


class verify_config_upload_methods_UnknownUploadMethodTestCase(
        testscenarios.WithScenarios,
        verify_config_upload_methods_BaseTestCase):
    """ Test cases for `verify_config_upload_methods`, when unknown method. """

    scenarios = [
            ('bogus-default-method', {
                'config_extras': {
                    'default': {
                        'method': "b0gUs",
                        },
                    },
                'expected_output': "Unknown upload method: b0gUs",
                }),
            ('bogus-host-method', {
                'config_extras': {
                    'host': {
                        'method': "b0gUs",
                        },
                    },
                'expected_output': "Unknown upload method: b0gUs",
                }),
            ]

    def test_emits_error_message_when_unknown_method(self):
        """ Should emit error message when unknown upload method. """
        with contextlib.suppress(FakeSystemExit):
            dput.dput.verify_config_upload_methods(**self.test_args)
        self.assertIn(self.expected_output, sys.stdout.getvalue())

    def test_calls_sys_exit_when_unknown_method(self):
        """ Should call `sys.exit` when unknown upload method. """
        with testtools.ExpectedException(FakeSystemExit):
            dput.dput.verify_config_upload_methods(**self.test_args)
        sys.exit.assert_called_with(EXIT_STATUS_FAILURE)


def patch_verify_config_upload_methods(testcase):
    """ Patch `verify_config_upload_methods` function for the `testcase`. """
    func_patcher = unittest.mock.patch.object(
            dput.dput, "verify_config_upload_methods", autospec=True)
    func_patcher.start()
    testcase.addCleanup(func_patcher.stop)


# Copyright © 2015–2024 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 :
