def setup_method(self, method):
     self.mock_arg_parser = Mock()
     self.mock_qsettings = Mock()
     self.program_config = ProgramConfig(arg_parser=self.mock_arg_parser,
                                         qsettings=self.mock_qsettings)
class TestProgramConfig:
    def setup_method(self, method):
        self.mock_arg_parser = Mock()
        self.mock_qsettings = Mock()
        self.program_config = ProgramConfig(arg_parser=self.mock_arg_parser,
                                            qsettings=self.mock_qsettings)

    def test_required_configuration_specified_on_command_line(self,
                                                              test_config):
        self.require_no_fallback(test_config)
        real_config = self.validate_command_line_persistence(test_config)
        self.assert_config_available(test_config, real_config)

    def test_required_configuration_previously_saved(self, test_config):
        self.require_no_fallback(test_config)

        # validate no command line persistence
        namespace_dict = {}
        with self.mock_qsettings as mock_qsettings:
            for item in test_config:
                mock_qsettings.contains(item['key']) >> True
                mock_qsettings.value(item['key']) >> item['value']
                namespace_dict[item['key']] = None
            for item in test_config:
                if item['persistent']:
                    mock_qsettings.setValue(item['key'], item['value']) >> None
            mock_qsettings.sync() >> None

        namespace = Namespace(**namespace_dict)
        with self.mock_arg_parser as mock_arg_parser:
            mock_arg_parser.parse_args([]) >> namespace
        real_config = self.program_config.validate([])

        self.assert_config_available(test_config, real_config)

    def test_required_configuration_default_value(self, test_config):
        self.require_default(test_config)
        real_config = self.validate_no_command_line_no_persistence(test_config)
        self.assert_config_available(test_config, real_config)

    def test_required_configuration_callback(self):
        # IMPORTANT:
        # Because of the callback function, only use one config item
        item = {'key': 'verbosity',
                'value': 3,
                'help': 'how much output to print',
                'type': int,
                'persistent': False}
        self.add_argument(item)

        def callback(key, help, type):
            assert item['key'] == key
            assert item['help'] == help
            assert item['type'] == type
            return item['value']
        self.program_config.\
            add_required_with_callback(item['key'],
                                       callback,
                                       help=item['help'],
                                       type=item['type'],
                                       persistent=item['persistent'])

        test_config = [item]

        real_config = self.validate_no_command_line_no_persistence(test_config)
        self.assert_config_available(test_config, real_config)

    def test_required_configuration_fails_when_not_given(self, test_config):
        self.require_no_fallback(test_config)
        from pyside_program_config import RequiredKeyError
        namespace_dict = {}
        with self.mock_qsettings as mock_qsettings:
            for item in test_config:
                mock_qsettings.contains(item['key']) >> False
                namespace_dict[item['key']] = None
                # never persist anything upon failure
                # make sure to use a persistent key first

        namespace = Namespace(**namespace_dict)
        with self.mock_arg_parser as mock_arg_parser:
            mock_arg_parser.parse_args([]) >> namespace
        with pytest.raises(RequiredKeyError) as e:
            self.program_config.validate([])
        assert str(e).endswith('Required key not provided: {0}'.
                               format(test_config[0]['key']))

    def test_optional_configuration(self, test_config):
        for item in test_config:
            self.add_argument(item)
            self.program_config.add_optional(item['key'],
                                             help=item['help'],
                                             type=item['type'],
                                             persistent=item['persistent'])
        real_config = self.validate_no_command_line_no_persistence(test_config)
        assert real_config == {}

    def test_add_duplicate_configuration(self, test_config):
        self.require_no_fallback(test_config)
        from pyside_program_config import DuplicateKeyError
        for item in test_config:
            with pytest.raises(DuplicateKeyError) as e:
                self.program_config.add_required(item['key'])
            assert str(e).endswith('Attempt to define duplicate key: {0}'.
                                   format(item['key']))

    def test_default_configuration_never_persisted(self, test_config):
        self.require_default(test_config)
        real_config = self.validate_no_command_line_no_persistence(test_config)
        self.assert_config_available(test_config, real_config)

    def test_default_configuration_on_command_line_never_persisted(
            self, test_config):
        self.require_default(test_config)

        namespace_dict = {}
        args = []
        with self.mock_qsettings as mock_qsettings:
            for item in test_config:
                namespace_dict[item['key']] = item['value']
                args.append('--' + item['key'])
                args.append(str(item['value']))
            mock_qsettings.sync() >> None

        namespace = Namespace(**namespace_dict)
        with self.mock_arg_parser as mock_arg_parser:
            mock_arg_parser.parse_args(args) >> namespace
        real_config = self.program_config.validate(args)

        self.assert_config_available(test_config, real_config)

    def test_handles_hyphens_properly(self):
        # argparse converts hyphens to underscores in the Namespace object
        # since hyphens are not valid characters in Python identifiers
        # PySide Program Config should always use hyphens in its command-line
        # xargument names
        # however, this should be transparent to the user
        test_config = [{'key': 'key-with-hyphens',
                        'value': 10,
                        'type': int,
                        'help': 'just a test',
                        'persistent': False}]
        self.require_no_fallback(test_config)
        real_config = self.validate_command_line_persistence(test_config)
        self.assert_config_available(test_config, real_config)

    def test_handles_underscores_properly(self):
        # PySide Program Config should always use hyphens in its command-line
        # argument names
        # however, this should be transparent to the user
        test_config = [{'key': 'key_with_underscores',
                        'value': 10,
                        'type': int,
                        'help': 'just a test',
                        'persistent': False}]
        self.require_no_fallback(test_config)
        real_config = self.validate_command_line_persistence(test_config)
        self.assert_config_available(test_config, real_config)

    def test_default_persistence_is_not_persistent_no_fallback(
            self, test_config_default_persistence):
        for item in test_config_default_persistence:
            self.add_argument(item)
            self.program_config.add_required(item['key'],
                                             help=item['help'],
                                             type=item['type'])
        real_config = self.validate_command_line_no_persistence(
            test_config_default_persistence)
        self.assert_config_available(test_config_default_persistence,
                                     real_config)

    def test_default_persistence_is_not_persistent_optional(
            self, test_config_default_persistence):
        for item in test_config_default_persistence:
            self.add_argument(item)
            self.program_config.add_optional(item['key'],
                                             help=item['help'],
                                             type=item['type'])
        real_config = self.validate_command_line_no_persistence(
            test_config_default_persistence)
        self.assert_config_available(test_config_default_persistence,
                                     real_config)

    def test_default_persistence_is_not_persistent_callback(self):
        # IMPORTANT:
        # Because of the callback function, only use one config item
        item = {'key': 'verbosity',
                'value': 3,
                'help': 'how much output to print',
                'type': int}
        self.add_argument(item)

        def callback(key, help, type):
            assert item['key'] == key
            assert item['help'] == help
            assert item['type'] == type
            return item['value']
        self.program_config.add_required_with_callback(item['key'],
                                                       callback,
                                                       help=item['help'],
                                                       type=item['type'])
        test_config_default_persistence = [item]
        real_config = self.validate_command_line_no_persistence(
            test_config_default_persistence)
        self.assert_config_available(test_config_default_persistence,
                                     real_config)

    def test_default_persistence_is_not_persistent_default(
            self, test_config_default_persistence):
        for item in test_config_default_persistence:
            self.add_argument(item)
            self.program_config.add_required_with_default(item['key'],
                                                          item['value'],
                                                          help=item['help'],
                                                          type=item['type'])
        real_config = self.validate_command_line_no_persistence(
            test_config_default_persistence)
        self.assert_config_available(test_config_default_persistence,
                                     real_config)

    def test_default_argument_list_to_validate_is_sys_argv(self, test_config):
        self.require_no_fallback(test_config)
        
        namespace_dict = {}
        with self.mock_qsettings as mock_qsettings:
            for item in test_config:
                namespace_dict[self.key_from_argparse(item['key'])] = \
                    item['value']
                if item['persistent']:
                    mock_qsettings.setValue(item['key'], item['value']) >> None
            mock_qsettings.sync() >> None

        namespace = Namespace(**namespace_dict)
        with self.mock_arg_parser as mock_arg_parser:
            mock_arg_parser.parse_args(None) >> namespace
        real_config = self.program_config.validate()
        
        self.assert_config_available(test_config, real_config)

    def test_validate_does_not_discard_other_argparse_arguments(self,
                                                                test_config):
        ppc_key = test_config[0]
        argparse_key = test_config[1]
        self.require_no_fallback([ppc_key])
        namespace_dict = {}
        args = []
        with self.mock_qsettings as mock_qsettings:
            for item in [ppc_key, argparse_key]:
                namespace_dict[self.key_from_argparse(item['key'])] = \
                    item['value']
                args.append('--' + item['key'])
                args.append(str(item['value']))
            if ppc_key['persistent']:
                mock_qsettings.setValue(ppc_key['key'], ppc_key['value']) >> None
            mock_qsettings.sync() >> None

        namespace = Namespace(**namespace_dict)
        with self.mock_arg_parser as mock_arg_parser:
            mock_arg_parser.parse_args(args) >> namespace
        real_config = self.program_config.validate(args)

        self.assert_config_available([ppc_key, argparse_key], real_config)


    def key_from_argparse(self, key):
        return key.replace('-', '_')

    def key_to_argparse(self, key):
        return key.replace('_', '-')

    def add_argument(self, item):
        with self.mock_arg_parser as mock_arg_parser:
                mock_arg_parser.add_argument('--' +
                                             self.key_to_argparse(item['key']),
                                             metavar=item['key'].upper(),
                                             help=item['help'],
                                             type=item['type']) >> None

    def require_no_fallback(self, config):
        for item in config:
            self.add_argument(item)
            self.program_config.add_required(item['key'],
                                             help=item['help'],
                                             type=item['type'],
                                             persistent=item['persistent'])

    def require_default(self, config):
        for item in config:
            self.add_argument(item)
            self.program_config.\
                add_required_with_default(item['key'],
                                          item['value'],
                                          help=item['help'],
                                          type=item['type'],
                                          persistent=item['persistent'])

    def validate_no_command_line_no_persistence(self, config):
        namespace_dict = {}
        with self.mock_qsettings as mock_qsettings:
            for item in config:
                mock_qsettings.contains(item['key']) >> False
                namespace_dict[self.key_from_argparse(item['key'])] = None
            mock_qsettings.sync() >> None

        namespace = Namespace(**namespace_dict)
        with self.mock_arg_parser as mock_arg_parser:
            mock_arg_parser.parse_args([]) >> namespace
        return self.program_config.validate([])

    def validate_command_line_persistence(self, config):
        namespace_dict = {}
        args = []
        with self.mock_qsettings as mock_qsettings:
            for item in config:
                namespace_dict[self.key_from_argparse(item['key'])] = \
                    item['value']
                args.append('--' + item['key'])
                args.append(str(item['value']))
                if item['persistent']:
                    mock_qsettings.setValue(item['key'], item['value']) >> None
            mock_qsettings.sync() >> None

        namespace = Namespace(**namespace_dict)
        with self.mock_arg_parser as mock_arg_parser:
            mock_arg_parser.parse_args(args) >> namespace
        return self.program_config.validate(args)

    def validate_command_line_no_persistence(self, config):
        namespace_dict = {}
        args = []
        with self.mock_qsettings as mock_qsettings:
            for item in config:
                namespace_dict[self.key_from_argparse(item['key'])] = \
                    item['value']
                args.append('--' + item['key'])
                args.append(str(item['value']))
            mock_qsettings.sync() >> None

        namespace = Namespace(**namespace_dict)
        with self.mock_arg_parser as mock_arg_parser:
            mock_arg_parser.parse_args(args) >> namespace
        return self.program_config.validate(args)

    def assert_config_available(self, test, real):
        for item in test:
            assert item['value'] == real[item['key']]