Пример #1
0
class TestAnonymizer__input_callback(TestCaseMixin, unittest.TestCase):
    def setUp(self):
        self.event_type = 'bl-update'
        self.event_data = {'some...': 'content...', 'id': 'some id...'}
        self.routing_key = self.event_type + '.filtered.*.*'
        self.body = json.dumps(self.event_data)
        self.resource_to_org_ids = {}

        self.mock = MagicMock(__class__=Anonymizer)
        self.meth = MethodProxy(Anonymizer, self.mock, '_process_input')

        self.mock._get_resource_to_org_ids.return_value = self.resource_to_org_ids
        self.mock._get_result_dicts_and_output_body.return_value = (
            sen.raw_result_dict,
            sen.cleaned_result_dict,
            sen.output_body,
        )
        self.force_exit_on_any_remaining_entered_contexts_mock = self.patch(
            'n6.utils.anonymizer.force_exit_on_any_remaining_entered_contexts')

    @foreach(
        param(resource_to_org_ids_items={
            'foo': [sen.o1, sen.o2],
        }),
        param(resource_to_org_ids_items={
            'foo': [sen.o1, sen.o2],
            'bar': [],
        }),
        param(resource_to_org_ids_items={
            'foo': [],
            'bar': [sen.o3, sen.o4, sen.o5],
        }),
        param(resource_to_org_ids_items={
            'foo': [sen.o1, sen.o2],
            'bar': [sen.o3, sen.o4, sen.o5],
        }),
    )
    def test_with_some_org_ids(self, resource_to_org_ids_items):
        self.resource_to_org_ids.update(resource_to_org_ids_items)

        self.meth.input_callback(self.routing_key, self.body, sen.properties)

        self.assertEqual(
            self.force_exit_on_any_remaining_entered_contexts_mock.mock_calls,
            [
                call(self.mock.auth_api),
            ])
        self.assertEqual(self.mock.mock_calls, [
            call.setting_error_event_info(self.event_data),
            call.setting_error_event_info().__enter__(),
            call._check_event_type(self.event_type, self.event_data),
            call.auth_api.__enter__(),
            call._get_resource_to_org_ids(self.event_type, self.event_data),
            call._get_result_dicts_and_output_body(
                self.event_type, self.event_data, self.resource_to_org_ids),
            call._publish_output_data(
                self.event_type, self.resource_to_org_ids, sen.raw_result_dict,
                sen.cleaned_result_dict, sen.output_body),
            call.auth_api.__exit__(None, None, None),
            call.setting_error_event_info().__exit__(None, None, None),
        ])

    @foreach(
        param(resource_to_org_ids_items={}),
        param(resource_to_org_ids_items={
            'foo': [],
        }),
        param(resource_to_org_ids_items={
            'foo': [],
            'bar': [],
        }),
    )
    def test_without_org_ids(self, resource_to_org_ids_items):
        self.resource_to_org_ids.update(resource_to_org_ids_items)

        self.meth.input_callback(self.routing_key, self.body, sen.properties)

        self.assertEqual(
            self.force_exit_on_any_remaining_entered_contexts_mock.mock_calls,
            [
                call(self.mock.auth_api),
            ])
        self.assertEqual(self.mock.mock_calls, [
            call.setting_error_event_info(self.event_data),
            call.setting_error_event_info().__enter__(),
            call._check_event_type(self.event_type, self.event_data),
            call.auth_api.__enter__(),
            call._get_resource_to_org_ids(self.event_type, self.event_data),
            call.auth_api.__exit__(None, None, None),
            call.setting_error_event_info().__exit__(None, None, None),
        ])

    def test_with_some_error(self):
        self.resource_to_org_ids.update({
            'foo': [sen.o1, sen.o2],
            'bar': [sen.o3, sen.o4, sen.o5],
        })
        exc_type = ZeroDivisionError  # (just an example exception class)
        self.mock._get_result_dicts_and_output_body.side_effect = exc_type

        with self.assertRaises(exc_type) as exc_context:
            self.meth.input_callback(self.routing_key, self.body,
                                     sen.properties)

        self.assertEqual(
            self.force_exit_on_any_remaining_entered_contexts_mock.mock_calls,
            [
                call(self.mock.auth_api),
            ])
        self.assertEqual(self.mock.mock_calls, [
            call.setting_error_event_info(self.event_data),
            call.setting_error_event_info().__enter__(),
            call._check_event_type(self.event_type, self.event_data),
            call.auth_api.__enter__(),
            call._get_resource_to_org_ids(self.event_type, self.event_data),
            call._get_result_dicts_and_output_body(
                self.event_type, self.event_data, self.resource_to_org_ids),
            call.auth_api.__exit__(exc_type, exc_context.exception, ANY),
            call.setting_error_event_info().__exit__(
                exc_type, exc_context.exception, ANY),
        ])
Пример #2
0
class TestBaseParser(unittest.TestCase):
    def setUp(self):
        self.mock = Mock(__class__=BaseParser, allow_empty_results=False)
        self.meth = MethodProxy(BaseParser, self.mock)

    def _asserts_of_proper__new__instance_adjustment(self, instance):
        # BaseQueued.__new__() ensures that
        self.assertIsNot(instance.input_queue, BaseParser.input_queue)

    def _asserts_of_proper_preinit_hook_instance_adjustment(
            self, instance, binding_key):
        # for classes with `default_binding_key`
        # BaseParser.preinit_hook() ensures that
        self.assertEqual(
            instance.input_queue, {
                'exchange': 'raw',
                'exchange_type': 'topic',
                'queue_name': binding_key,
                'binding_keys': [binding_key],
            })
        self.assertEqual(BaseParser.input_queue, {
            'exchange': 'raw',
            'exchange_type': 'topic',
        })

    def _basic_init_related_asserts(self, instance, subclass, super_mock,
                                    super_cls_mock, expected_config,
                                    expected_config_full):
        # assert that an instance of the proper type has been returned
        self.assertIsInstance(instance, subclass)
        # assert that super used properly
        super_mock.assert_called_once_with(BaseParser, instance)
        super_cls_mock.__init__.assert_called_once_with(a=sentinel.a,
                                                        bb=sentinel.bb)
        # assert that configuration stuff has been obtained properly
        self.assertEqual(instance.config, expected_config)
        self.assertIsInstance(instance.config, ConfigSection)
        self.assertEqual(instance.config_full, expected_config_full)
        self.assertIsInstance(instance.config_full, Config)

    def test_basics(self):
        self.assertTrue(issubclass(BaseParser, QueuedBase))
        self.assertTrue(hasattr(BaseParser, 'default_binding_key'))
        self.assertTrue(hasattr(BaseParser, 'config_spec_pattern'))
        self.assertTrue(hasattr(BaseParser, 'constant_items'))
        self.assertTrue(hasattr(BaseParser, 'record_dict_class'))
        self.assertTrue(hasattr(BaseParser, 'event_type'))

    def test_config_spec_pattern(self):
        config_spec = BaseParser.config_spec_pattern.format(
            parser_class_name='example_foo')
        config_spec_parsed = parse_config_spec(config_spec)
        prefetch_count_opt_spec = config_spec_parsed.get_opt_spec(
            'example_foo.prefetch_count')
        self.assertEqual(prefetch_count_opt_spec.name, 'prefetch_count')
        self.assertEqual(prefetch_count_opt_spec.converter_spec, 'int')

    def test_initialization_without_default_binding_key(self):
        class SomeParser(BaseParser):
            pass  # no `default_binding_key` defined => it's an abstract class

        with self.assertRaises(NotImplementedError):
            SomeParser()

        unready_instance = SomeParser.__new__(SomeParser)
        self._asserts_of_proper__new__instance_adjustment(unready_instance)
        # for classes without `default_binding_key`
        # `queue_name` and `binding_keys` items are *not* added...
        self.assertEqual(unready_instance.input_queue, BaseParser.input_queue)
        self.assertEqual(BaseParser.input_queue, {
            'exchange': 'raw',
            'exchange_type': 'topic',
        })

    @foreach(
        param(
            mocked_conf_from_files={},
            expected_config=ConfigSection('SomeParser', {'prefetch_count': 1}),
            expected_config_full=Config.make(
                {'SomeParser': {
                    'prefetch_count': 1
                }}),
        ),
        param(
            mocked_conf_from_files={
                'SomeParser': {
                    'prefetch_count': '42'
                },
                'another_section': {
                    'another_opt': '123.456'
                },
            },
            expected_config=ConfigSection('SomeParser',
                                          {'prefetch_count': 42}),
            expected_config_full=Config.make(
                {'SomeParser': {
                    'prefetch_count': 42
                }}),
        ),
        param(
            custom_config_spec_pattern=concat_reducing_indent(
                BaseParser.config_spec_pattern,
                '''
                    some_opt = [-3, null] :: json
                    [another_section]
                    another_opt :: float
                    yet_another_opt = Foo Bar Spam Ham
                ''',
            ),
            mocked_conf_from_files={
                'SomeParser': {
                    'prefetch_count': '42'
                },
                'another_section': {
                    'another_opt': '123.456'
                },
            },
            expected_config=ConfigSection('SomeParser', {
                'prefetch_count': 42,
                'some_opt': [-3, None],
            }),
            expected_config_full=Config.make({
                'SomeParser': {
                    'prefetch_count': 42,
                    'some_opt': [-3, None],
                },
                'another_section': {
                    'another_opt': 123.456,
                    'yet_another_opt': 'Foo Bar Spam Ham',
                },
            }),
        ),
    )
    @foreach(
        param(binding_key='foo.bar'),
        param(binding_key='foo.bar.33'),
    )
    def test_initialization_with_default_binding_key(
            self,
            binding_key,
            mocked_conf_from_files,
            expected_config,
            expected_config_full,
            custom_config_spec_pattern=None):
        class SomeParser(BaseParser):
            default_binding_key = binding_key  # => it's a concrete class

        if custom_config_spec_pattern is not None:
            SomeParser.config_spec_pattern = custom_config_spec_pattern

        unready_instance = SomeParser.__new__(SomeParser)
        self._asserts_of_proper__new__instance_adjustment(unready_instance)
        self._asserts_of_proper_preinit_hook_instance_adjustment(
            unready_instance, binding_key)

        super_cls_mock = SimpleNamespace(__init__=Mock())
        with patch_always('n6.parsers.generic.super',
                          return_value=super_cls_mock) as super_mock, \
             patch('n6.parsers.generic.Config._load_n6_config_files',
                   return_value=mocked_conf_from_files):
            # instantiation
            instance = SomeParser(a=sentinel.a, bb=sentinel.bb)
            self._asserts_of_proper__new__instance_adjustment(instance)
            self._asserts_of_proper_preinit_hook_instance_adjustment(
                instance, binding_key)
            self._basic_init_related_asserts(instance, SomeParser, super_mock,
                                             super_cls_mock, expected_config,
                                             expected_config_full)

    def test__make_binding_keys(self):
        self.mock.default_binding_key = 'fooo.barr'
        binding_keys = self.meth.make_binding_keys()
        self.assertEqual(binding_keys, ['fooo.barr'])
        self.assertEqual(self.mock.mock_calls, [])

    def test__make_binding_keys_with_raw_format_version_tag(self):
        self.mock.default_binding_key = 'fooo.barr.33'
        binding_keys = self.meth.make_binding_keys()
        self.assertEqual(binding_keys, ['fooo.barr.33'])
        self.assertEqual(self.mock.mock_calls, [])

    def test__get_script_init_kwargs(self):
        self.assertIsInstance(
            vars(BaseParser)['get_script_init_kwargs'], classmethod)
        init_kwargs = BaseParser.get_script_init_kwargs.__func__(self.mock)
        self.assertEqual(init_kwargs, {})
        self.assertEqual(self.mock.mock_calls, [])

    def test__run_handling__interrupted(self):
        self.mock.configure_mock(**{'run.side_effect': KeyboardInterrupt})
        self.meth.run_handling()
        self.mock.run.assert_called_once_with()
        self.mock.stop.assert_called_once_with()

    def test__run_handling__not_interrupted(self):
        self.meth.run_handling()
        self.mock.run.assert_called_once_with()
        self.assertEqual(self.mock.stop.mock_calls, [])

    @patch('n6.parsers.generic.FilePagedSequence')
    def test__input_callback(self, FilePagedSequence_mock):
        FilePagedSequence_mock.return_value = MagicMock()
        FilePagedSequence_mock.return_value.__enter__.return_value = sentinel.working_seq
        data = MagicMock(**{'get.return_value': sentinel.rid})
        self.mock.configure_mock(
            **{
                '_fix_body.return_value':
                sentinel.body,
                'prepare_data.return_value':
                data,
                'setting_error_event_info':
                MagicMock(),
                'get_output_rk.return_value':
                sentinel.output_rk,
                'get_output_bodies.return_value':
                [sentinel.output_body1, sentinel.output_body2],
            })
        self.meth.input_callback(sentinel.routing_key, sentinel.body,
                                 sentinel.properties)
        self.assertEqual(self.mock.mock_calls, [
            call._fix_body(sentinel.body),
            call.prepare_data(sentinel.routing_key, sentinel.body,
                              sentinel.properties),
            call.prepare_data().get('properties.message_id'),
            call.setting_error_event_info(sentinel.rid),
            call.setting_error_event_info().__enter__(),
            call.get_output_rk(data),
            call.get_output_bodies(data, sentinel.working_seq),
            call.publish_output(routing_key=sentinel.output_rk,
                                body=sentinel.output_body1),
            call.publish_output(routing_key=sentinel.output_rk,
                                body=sentinel.output_body2),
            call.setting_error_event_info().__exit__(None, None, None),
        ])
        self.assertEqual(FilePagedSequence_mock.mock_calls, [
            call(page_size=1000),
            call().__enter__(),
            call().__exit__(None, None, None),
        ])

    def test__prepare_data(self):
        data = self.meth.prepare_data(routing_key='ham.spam',
                                      body=sentinel.body,
                                      properties=SimpleNamespace(
                                          foo=sentinel.foo,
                                          bar=sentinel.bar,
                                          timestamp=1389348840,
                                          headers={'a': sentinel.a}))
        self.assertEqual(
            data, {
                'a': sentinel.a,
                'properties.foo': sentinel.foo,
                'properties.bar': sentinel.bar,
                'source': 'ham.spam',
                'properties.timestamp': '2014-01-10 10:14:00',
                'raw_format_version_tag': None,
                'raw': sentinel.body,
            })

    def test__prepare_data__rk__with_raw_format_version_tag(self):
        data = self.meth.prepare_data(routing_key='ham.spam.33',
                                      body=sentinel.body,
                                      properties=SimpleNamespace(
                                          foo=sentinel.foo,
                                          bar=sentinel.bar,
                                          timestamp=1389348840,
                                          headers={'a': sentinel.a}))
        self.assertEqual(
            data, {
                'a': sentinel.a,
                'properties.foo': sentinel.foo,
                'properties.bar': sentinel.bar,
                'source': 'ham.spam',
                'properties.timestamp': '2014-01-10 10:14:00',
                'raw_format_version_tag': '33',
                'raw': sentinel.body,
            })

    def test__get_output_rk(self):
        self.mock.configure_mock(**{
            'event_type': 'foobar',
        })
        data = {'source': 'ham.spam'}
        output_rk = self.meth.get_output_rk(data)
        self.assertEqual(output_rk, 'foobar.parsed.ham.spam')

    def test__get_output_bodies(self):
        parsed = [
            MagicMock(
                **{
                    '__class__':
                    RecordDict,
                    'used_as_context_manager':
                    True,
                    'get_ready_json.return_value':
                    getattr(sentinel, 'output_body{}'.format(i))
                }) for i in (1, 2)
        ]
        self.mock.configure_mock(
            **{
                'parse.return_value':
                parsed,
                'get_output_message_id.side_effect': [
                    sentinel.msg_A,
                    sentinel.msg_B,
                ],
                'setting_error_event_info':
                MagicMock(),
                'postprocess_parsed.side_effect': (
                    lambda data, parsed, total, item_no: parsed),
            })
        seq_mock = FilePagedSequence._instance_mock()
        output_bodies = self.meth.get_output_bodies(sentinel.data, seq_mock)
        self.assertIs(output_bodies, seq_mock)
        self.assertEqual(seq_mock._list, [
            sentinel.output_body1,
            sentinel.output_body2,
        ])
        self.assertEqual(parsed[0].mock_calls, [
            call.__setitem__('id', sentinel.msg_A),
            call.get_ready_json(),
        ])
        self.assertEqual(parsed[1].mock_calls, [
            call.__setitem__('id', sentinel.msg_B),
            call.get_ready_json(),
        ])
        self.assertEqual(self.mock.mock_calls, [
            call.parse(sentinel.data),
            call.get_output_message_id(parsed[0]),
            call.delete_too_long_address(parsed[0]),
            call.get_output_message_id(parsed[1]),
            call.delete_too_long_address(parsed[1]),
            call.setting_error_event_info(parsed[0]),
            call.setting_error_event_info().__enter__(),
            call.postprocess_parsed(sentinel.data, parsed[0], 2, item_no=1),
            call.setting_error_event_info().__exit__(None, None, None),
            call.setting_error_event_info(parsed[1]),
            call.setting_error_event_info().__enter__(),
            call.postprocess_parsed(sentinel.data, parsed[1], 2, item_no=2),
            call.setting_error_event_info().__exit__(None, None, None),
        ])

    def test__get_output_bodies__record_dict_not_used_as_context_manager(self):
        parsed = [
            MagicMock(**{
                '__class__': RecordDict,
                'used_as_context_manager': False
            }) for i in (1, 2)
        ]
        self.mock.configure_mock(**{'parse.return_value': parsed})
        with self.assertRaises(AssertionError):
            self.meth.get_output_bodies(sentinel.data,
                                        FilePagedSequence._instance_mock())
        self.assertEqual(self.mock.method_calls, [
            call.parse(sentinel.data),
        ])

    def test__get_output_bodies__parse_yielded_no_items(self):
        self.mock.configure_mock(**{'parse.return_value': iter([])})
        with self.assertRaises(ValueError):
            self.meth.get_output_bodies(sentinel.data,
                                        FilePagedSequence._instance_mock())
        self.assertEqual(self.mock.method_calls, [
            call.parse(sentinel.data),
        ])

    def test__get_output_bodies__parse_yielded_no_items__allow_empty_results(
            self):
        self.mock.configure_mock(**{
            'parse.return_value': iter([]),
            'allow_empty_results': True
        })
        seq_mock = FilePagedSequence._instance_mock()
        output_bodies = self.meth.get_output_bodies(sentinel.data, seq_mock)
        self.assertIs(output_bodies, seq_mock)
        self.assertEqual(seq_mock._list, [])  # just empty
        self.assertEqual(self.mock.mock_calls, [
            call.parse(sentinel.data),
        ])

    def test__delete_too_long_address__address_is_ok(self):
        parsed = RecordDict()
        parsed['address'] = [{'ip': i + 1} for i in xrange(MAX_IPS_IN_ADDRESS)]
        expected = RecordDict()
        expected['address'] = [{
            'ip': i + 1
        } for i in xrange(MAX_IPS_IN_ADDRESS)]
        self.meth.delete_too_long_address(parsed)
        self.assertEqual(parsed, expected)

    def test__delete_too_long_address__address_is_too_long(self):
        ips = MAX_IPS_IN_ADDRESS + 1
        parsed = RecordDict()
        parsed['id'] = '0123456789abcdef0123456789abcdef'
        parsed['address'] = [{'ip': i + 1} for i in xrange(ips)]
        expected = RecordDict()
        expected['id'] = '0123456789abcdef0123456789abcdef'
        self.meth.delete_too_long_address(parsed)
        self.assertEqual(parsed, expected)

    def test__delete_too_long_address__address_is_empty(self):
        parsed = RecordDict()
        parsed.update({'source': 'foo.bar'})
        expected = RecordDict()
        expected.update({'source': 'foo.bar'})
        self.meth.delete_too_long_address(parsed)
        self.assertEqual(parsed, expected)

    def test__get_output_message_id(self):
        inputs_and_resultant_hash_bases = [
            # basics
            ({
                'source': 'foo.bar'
            }, 'source,foo.bar'),
            ({
                u'source': u'foo.bar'
            }, 'source,foo.bar'),
            # proper sorting of multiple values
            ({
                'key1': 2,
                'key2': ['value2', 'value3', 'value1']
            }, 'key1,2\nkey2,value1,value2,value3'),
            # ...and of keys + proper encoding of unicode keys/values
            ({
                u'key2': [u'value3', u'value1', u'value2'],
                u'key1': 2L
            }, 'key1,2\nkey2,value1,value2,value3'),
            # ...as well as proper int/long normalization/representation
            ({
                u'key2': [30, 10, 20L],
                u'key1': 9000111222333444555666777888999000L
            }, 'key1,9000111222333444555666777888999000\nkey2,10,20,30'),
            # non-ascii values
            ({
                'target': 'zażółć',
                u'client': [u'jaźń', u'gęślą']
            }, 'client,gęślą,jaźń\ntarget,zażółć'),
            ({
                u'target': u'zażółć',
                'client': ['jaźń', 'gęślą']
            }, 'client,gęślą,jaźń\ntarget,zażółć'),
            # subdicts
            ({
                'dip': u'3.3.3.3',
                u'address': [{
                    'ip': '255.255.255.0'
                }, {
                    'ip': '127.0.0.1'
                }]
            },
             "address,{'ip': '127.0.0.1'},{'ip': '255.255.255.0'}\ndip,3.3.3.3"
             ),
            # non-ascii subdict keys/values
            ({
                u'key2': [{
                    'ką2': 'vą2'
                }, {
                    'ką1': 'vą1'
                }],
                'key1': {
                    'ką': 'vą'
                }
            }, "key1,{'k\\xc4\\x85': 'v\\xc4\\x85'}\n" +
             "key2,{'k\\xc4\\x851': 'v\\xc4\\x851'},{'k\\xc4\\x852': 'v\\xc4\\x852'}"
             ),
            # proper encoding of unicode keys/values + proper sorting of whole subdicts
            ({
                'key1': {
                    u'ką': u'vą'
                },
                'key2': [{
                    u'ką2': 'vą2'
                }, {
                    'ką1': u'vą1'
                }]
            }, "key1,{'k\\xc4\\x85': 'v\\xc4\\x85'}\n" +
             "key2,{'k\\xc4\\x851': 'v\\xc4\\x851'},{'k\\xc4\\x852': 'v\\xc4\\x852'}"
             ),
            # ...as well as proper int/long normalization/representation
            ({
                'key1': {
                    u'k': 2L
                },
                'key2': [{
                    'k2': 2L
                }, {
                    u'k1': 1
                }]
            }, "key1,{'k': 2}\nkey2,{'k1': 1},{'k2': 2}"),
            ({
                u'key2': [{
                    'k2': 2
                }, {
                    'k1': 1
                }],
                'key1': {
                    'k': 3
                }
            }, "key1,{'k': 3}\nkey2,{'k1': 1},{'k2': 2}"),
            ({
                u'key2': [{
                    'k2': 2L
                }, {
                    'k1': 1L
                }],
                'key1': {
                    'k': 9000111222333444555666777888999000L
                }
            },
             "key1,{'k': 9000111222333444555666777888999000}\nkey2,{'k1': 1},{'k2': 2}"
             ),
            # proper sorting of multiple items in subdicts
            ({
                'key1': {
                    'c': 2,
                    u'a': 3L,
                    u'b': 1L
                },
                'key2': [{
                    'c': 2,
                    u'a': 3L,
                    u'b': 1L
                }, {
                    'd': 3,
                    u'a': 2L,
                    u'b': 1L
                }]
            }, "key1,{'a': 3, 'b': 1, 'c': 2}\n" +
             "key2,{'a': 2, 'b': 1, 'd': 3},{'a': 3, 'b': 1, 'c': 2}"),
        ]

        class _RecordDict(RecordDict):
            adjust_key1 = adjust_key2 = None
            optional_keys = RecordDict.optional_keys | {'key1', 'key2'}

        parser = BaseParser.__new__(BaseParser)
        for input_dict, expected_base in inputs_and_resultant_hash_bases:
            record_dict = _RecordDict(input_dict)
            expected_result = hashlib.md5(expected_base).hexdigest()
            result = parser.get_output_message_id(record_dict)
            self.assertIsInstance(result, str)
            self.assertEqual(result, expected_result)

    def test__get_output_message_id__errors(self):
        inputs_and_exc_classes = [
            # bad subdict key type
            (
                {
                    'key1': {
                        32: 2
                    }
                },
                TypeError,
            ),
            (
                {
                    'key1': [{
                        32: 2
                    }]
                },
                TypeError,
            ),
            # bad subdict value type
            (
                {
                    'key1': {
                        'k': 2.3
                    }
                },
                TypeError,
            ),
            (
                {
                    'key1': [{
                        'k': 2.3
                    }]
                },
                TypeError,
            ),
            (
                {
                    'key1': {
                        'k': {
                            'k': 2
                        }
                    }
                },  # nesting is illegal
                TypeError,
            ),
            (
                {
                    'key1': [{
                        'k': {
                            'k': 2
                        }
                    }]
                },  # nesting is illegal
                TypeError,
            ),
            # bad value type
            (
                {
                    'key1': 2.3
                },
                TypeError,
            ),
            (
                {
                    'key1': [2.3]
                },
                TypeError,
            ),
            (
                {
                    'key1': [[2]]
                },  # nesting is illegal
                TypeError,
            ),
        ]

        class _RecordDict(RecordDict):
            adjust_key1 = adjust_key2 = None
            optional_keys = RecordDict.optional_keys | {'key1', 'key2'}

        parser = BaseParser.__new__(BaseParser)
        for input_dict, exc_class in inputs_and_exc_classes:
            record_dict = _RecordDict(input_dict)
            with self.assertRaises(exc_class):
                parser.get_output_message_id(record_dict)

    def test__postprocess_parsed__without__do_not_resolve_fqdn_to_ip(self):
        data = {}
        parsed = RecordDict()
        self.meth.postprocess_parsed(data, parsed, 1, item_no=1)
        self.assertEqual(parsed, {})

    def test__postprocess_parsed__with__do_not_resolve_fqdn_to_ip__False(self):
        data = {'_do_not_resolve_fqdn_to_ip': False}
        parsed = RecordDict()
        self.meth.postprocess_parsed(data, parsed, 1, item_no=1)
        self.assertEqual(parsed, {})

    def test__postprocess_parsed__with__do_not_resolve_fqdn_to_ip__True(self):
        data = {'_do_not_resolve_fqdn_to_ip': True}
        parsed = RecordDict()
        self.meth.postprocess_parsed(data, parsed, 1, item_no=1)
        self.assertEqual(parsed, {'_do_not_resolve_fqdn_to_ip': True})