class TestNumberRangeValidator(unittest.TestCase):
    """
    Number range validator uses the data, which is already known as integer
    """
    def setUp(self):
        self.store = AnswerStore()

        answer1 = Answer(
            answer_id='set-minimum',
            answer_instance=1,
            group_instance=1,
            value=10,
        )
        answer2 = Answer(
            answer_id='set-maximum',
            answer_instance=1,
            group_instance=1,
            value=20,
        )
        answer3 = Answer(
            answer_id='set-maximum-cat',
            answer_instance=1,
            group_instance=1,
            value='cat',
        )

        self.store.add(answer1)
        self.store.add(answer2)
        self.store.add(answer3)

    def tearDown(self):
        self.store.clear()

    def test_too_small_when_min_set_is_invalid(self):
        validator = NumberRange(minimum=0)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = -10

        with self.assertRaises(ValidationError) as ite:
            validator(mock_form, mock_field)

        self.assertEqual(error_messages['NUMBER_TOO_SMALL'] % dict(min=0),
                         str(ite.exception))

    def test_too_big_when_max_set_is_invalid(self):
        validator = NumberRange(maximum=9999999999)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = 10000000000

        with self.assertRaises(ValidationError) as ite:
            validator(mock_form, mock_field)

        self.assertEqual(
            error_messages['NUMBER_TOO_LARGE'] %
            dict(max=format_number(9999999999)), str(ite.exception))

    def test_within_range(self):
        validator = NumberRange(minimum=0, maximum=10)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = 10

        try:
            validator(mock_form, mock_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_within_range_at_min(self):
        validator = NumberRange(minimum=0, maximum=9999999999)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = 0

        try:
            validator(mock_form, mock_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_within_range_at_max(self):
        validator = NumberRange(minimum=0, maximum=9999999999)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = 9999999999

        try:
            validator(mock_form, mock_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_manual_min(self):
        answer = {
            'min_value': {
                'value': 10
            },
            'label': 'Min Test',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL':
                    'The minimum value allowed is 10. Please correct your answer.'
                }
            },
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        integer_field = get_number_field(answer, label, '',
                                         returned_error_messages, self.store)

        self.assertTrue(integer_field.field_class == CustomIntegerField)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        mock_form = Mock()
        integer_field.data = 9

        with self.assertRaises(ValidationError) as ite:
            test_validator(mock_form, integer_field)

        self.assertEqual(str(ite.exception),
                         returned_error_messages['NUMBER_TOO_SMALL'])

        try:
            integer_field.data = 10
            test_validator(mock_form, integer_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_manual_max(self):
        answer = {
            'max_value': {
                'value': 20
            },
            'label': 'Max Test',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_LARGE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        integer_field = get_number_field(answer, label, '',
                                         returned_error_messages, self.store)

        self.assertTrue(integer_field.field_class == CustomIntegerField)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        mock_form = Mock()
        integer_field.data = 21

        with self.assertRaises(ValidationError) as ite:
            test_validator(mock_form, integer_field)

        self.assertEqual(str(ite.exception),
                         returned_error_messages['NUMBER_TOO_LARGE'])

        try:
            integer_field.data = 20
            test_validator(mock_form, integer_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_zero_max(self):
        max_value = 0
        answer = {
            'max_value': {
                'value': max_value
            },
            'label': 'Max Test',
            'mandatory': False,
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        error_message = error_messages['NUMBER_TOO_LARGE'] % dict(
            max=max_value)

        integer_field = get_number_field(answer, label, '', error_messages,
                                         self.store)

        self.assertTrue(integer_field.field_class == CustomIntegerField)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        mock_form = Mock()
        integer_field.data = 1

        with self.assertRaises(ValidationError) as ite:
            test_validator(mock_form, integer_field)

        self.assertEqual(str(ite.exception), error_message)

        try:
            integer_field.data = 0
            test_validator(mock_form, integer_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_zero_min(self):
        min_value = 0
        answer = {
            'min_value': {
                'value': min_value
            },
            'label': 'Min Test',
            'mandatory': False,
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        error_message = error_messages['NUMBER_TOO_SMALL'] % dict(
            min=min_value)

        integer_field = get_number_field(answer, label, '', error_messages,
                                         self.store)

        self.assertTrue(integer_field.field_class == CustomIntegerField)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        mock_form = Mock()
        integer_field.data = -1

        with self.assertRaises(ValidationError) as ite:
            test_validator(mock_form, integer_field)

        self.assertEqual(str(ite.exception), error_message)

        try:
            integer_field.data = 0
            test_validator(mock_form, integer_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_value_range(self):
        answer = {
            'min_value': {
                'value': 10
            },
            'max_value': {
                'value': 20
            },
            'label': 'Range Test 10 to 20',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL':
                    'The minimum value allowed is 10. Please correct your answer.',
                    'NUMBER_TOO_LARGE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        integer_field = get_number_field(answer, label, '', error_messages,
                                         self.store)

        self.assertTrue(integer_field.field_class == CustomIntegerField)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        mock_form = Mock()
        integer_field.data = 9

        with self.assertRaises(ValidationError) as ite:
            test_validator(mock_form, integer_field)

        self.assertEqual(str(ite.exception),
                         returned_error_messages['NUMBER_TOO_SMALL'])

        try:
            integer_field.data = 20
            test_validator(mock_form, integer_field)
            integer_field.data = 10
            test_validator(mock_form, integer_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_answer_id_range(self):

        answer = {
            'min_value': {
                'answer_id': 'set-minimum'
            },
            'max_value': {
                'answer_id': 'set-maximum'
            },
            'label': 'Range Test 10 to 20',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL':
                    'The minimum value allowed is 10. Please correct your answer.',
                    'NUMBER_TOO_LARGE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        integer_field = get_number_field(answer, label, '',
                                         returned_error_messages, self.store)

        self.assertTrue(integer_field.field_class == CustomIntegerField)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        mock_form = Mock()
        integer_field.data = 9

        with self.assertRaises(ValidationError) as ite:
            test_validator(mock_form, integer_field)

        self.assertEqual(str(ite.exception),
                         returned_error_messages['NUMBER_TOO_SMALL'])

        try:
            integer_field.data = 20
            test_validator(mock_form, integer_field)
            integer_field.data = 10
            test_validator(mock_form, integer_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_default_range(self):
        answer = {
            'decimal_places': 2,
            'label': 'Range Test 10 to 20',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL':
                    'The minimum value allowed is 10. Please correct your answer.',
                    'NUMBER_TOO_LARGE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        integer_field = get_number_field(answer, label, '',
                                         returned_error_messages, self.store)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        self.assertEqual(test_validator.maximum, 9999999999)
        self.assertEqual(test_validator.minimum, 0)

    def test_min_less_than_system_limits(self):
        answer = {
            'min_value': {
                'value': -1000000000
            },
            'id': 'test-range',
            'label': 'Range Test 10 to 20',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL':
                    'The minimum value allowed is 10. Please correct your answer.',
                    'NUMBER_TOO_LARGE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        with self.assertRaises(Exception) as ite:
            get_number_field(answer, label, '', returned_error_messages,
                             self.store)

        self.assertEqual(
            str(ite.exception),
            'min_value: -1000000000 < system minimum: -999999999 for answer id: test-range'
        )

    def test_max_greater_than_system_limits(self):
        answer = {
            'max_value': {
                'value': 10000000000
            },
            'id': 'test-range',
            'label': 'Range Test 10 to 20',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL':
                    'The minimum value allowed is 10. Please correct your answer.',
                    'NUMBER_TOO_LARGE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        with self.assertRaises(Exception) as ite:
            get_number_field(answer, label, '', returned_error_messages,
                             self.store)

        self.assertEqual(
            str(ite.exception),
            'max_value: 10000000000 > system maximum: 9999999999 for answer id: test-range'
        )

    def test_min_greater_than_max(self):
        answer = {
            'min_value': {
                'value': 20
            },
            'max_value': {
                'value': 10
            },
            'id': 'test-range',
            'label': 'Range Test 10 to 20',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL':
                    'The minimum value allowed is 10. Please correct your answer.',
                    'NUMBER_TOO_LARGE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        with self.assertRaises(Exception) as ite:
            get_number_field(answer, label, '', returned_error_messages,
                             self.store)

        self.assertEqual(
            str(ite.exception),
            'min_value: 20 > max_value: 10 for answer id: test-range')

    def test_answer_id_invalid_type(self):

        answer = {
            'max_value': {
                'answer_id': 'set-maximum-cat'
            },
            'label': 'Range Test 10 to 20',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL':
                    'The minimum value allowed is 10. Please correct your answer.',
                    'NUMBER_TOO_LARGE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        with self.assertRaises(Exception) as ite:
            get_number_field(answer, label, '', returned_error_messages,
                             self.store)

        self.assertEqual(
            str(ite.exception),
            'answer: set-maximum-cat value: cat for answer id: test-range is not a valid number'
        )

    def test_manual_min_exclusive(self):
        answer = {
            'min_value': {
                'value': 10,
                'exclusive': True
            },
            'label': 'Min Test',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_SMALL_EXCLUSIVE':
                    'The minimum value allowed is 10. Please correct your answer.'
                }
            },
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        integer_field = get_number_field(answer, label, '', error_messages,
                                         self.store)

        self.assertTrue(integer_field.field_class == CustomIntegerField)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        mock_form = Mock()
        integer_field.data = 10

        with self.assertRaises(ValidationError) as ite:
            test_validator(mock_form, integer_field)

        self.assertEqual(str(ite.exception),
                         returned_error_messages['NUMBER_TOO_SMALL_EXCLUSIVE'])

        try:
            integer_field.data = 11
            test_validator(mock_form, integer_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')

    def test_manual_max_exclusive(self):
        answer = {
            'max_value': {
                'value': 20,
                'exclusive': True
            },
            'label': 'Max Test',
            'mandatory': False,
            'validation': {
                'messages': {
                    'INVALID_NUMBER':
                    'Please only enter whole numbers into the field.',
                    'NUMBER_TOO_LARGE_EXCLUSIVE':
                    'The maximum value allowed is 20. Please correct your answer.'
                }
            },
            'id': 'test-range',
            'type': 'Currency'
        }
        label = answer['label']
        returned_error_messages = answer['validation']['messages']

        integer_field = get_number_field(answer, label, '',
                                         returned_error_messages, self.store)

        self.assertTrue(integer_field.field_class == CustomIntegerField)

        for validator in integer_field.kwargs['validators']:
            if isinstance(validator, NumberRange):
                test_validator = validator

        mock_form = Mock()
        integer_field.data = 20

        with self.assertRaises(ValidationError) as ite:
            test_validator(mock_form, integer_field)

        self.assertEqual(str(ite.exception),
                         returned_error_messages['NUMBER_TOO_LARGE_EXCLUSIVE'])

        try:
            integer_field.data = 19
            test_validator(mock_form, integer_field)
        except ValidationError:
            self.fail('Valid integer raised ValidationError')
class TestFields(AppContextTestCase):

    def setUp(self):
        super().setUp()
        self.answer_store = AnswerStore()
        self.metadata = {
            'user_id': '789473423',
            'form_type': '0205',
            'collection_exercise_sid': 'test-sid',
            'eq_id': '1',
            'period_id': '2016-02-01',
            'period_str': '2016-01-01',
            'ref_p_start_date': '2016-02-02',
            'ref_p_end_date': '2016-03-03',
            'ru_ref': '432423423423',
            'ru_name': 'Apple',
            'return_by': '2016-07-07',
            'case_id': '1234567890',
            'case_ref': '1000000000000001'
        }

    def tearDown(self):
        super().tearDown()
        self.answer_store.clear()
        self.metadata.clear()

    def test_get_mandatory_validator_optional(self):
        answer = {
            'mandatory': False
        }
        validate_with = get_mandatory_validator(answer, None, 'MANDATORY_TEXTFIELD')

        self.assertIsInstance(validate_with[0], validators.Optional)

    def test_get_mandatory_validator_mandatory(self):
        answer = {
            'mandatory': True
        }
        validate_with = get_mandatory_validator(answer, {
            'MANDATORY_TEXTFIELD': 'This is the default mandatory message'
        }, 'MANDATORY_TEXTFIELD')

        self.assertIsInstance(validate_with[0], ResponseRequired)
        self.assertEqual(validate_with[0].message, 'This is the default mandatory message')

    def test_get_mandatory_validator_mandatory_with_error(self):
        answer = {
            'mandatory': True,
            'validation': {
                'messages': {
                    'MANDATORY_TEXTFIELD': 'This is the mandatory message for an answer'
                }
            }
        }
        validate_with = get_mandatory_validator(answer, {
            'MANDATORY_TEXTFIELD': 'This is the default mandatory message'
        }, 'MANDATORY_TEXTFIELD')

        self.assertIsInstance(validate_with[0], ResponseRequired)
        self.assertEqual(validate_with[0].message, 'This is the mandatory message for an answer')

    def test_get_length_validator(self):
        validate_with = get_length_validator({}, {
            'MAX_LENGTH_EXCEEDED': 'This is the default max length of %(max)d message'
        })

        self.assertEqual(validate_with[0].message, 'This is the default max length of %(max)d message')

    def test_get_length_validator_with_message_override(self):
        answer = {
            'validation': {
                'messages': {
                    'MAX_LENGTH_EXCEEDED': 'A message with characters %(max)d placeholder'
                }
            }
        }

        validate_with = get_length_validator(answer, {
            'MAX_LENGTH_EXCEEDED': 'This is the default max length message'
        })

        self.assertEqual(validate_with[0].message, 'A message with characters %(max)d placeholder')

    def test_get_length_validator_with_max_length_override(self):
        answer = {
            'max_length': 30
        }

        validate_with = get_length_validator(answer, {
            'MAX_LENGTH_EXCEEDED': '%(max)d characters'
        })

        self.assertEqual(validate_with[0].max, 30)

    def test_string_field(self):
        textfield_json = {
            'id': 'job-title-answer',
            'label': 'Job title',
            'mandatory': False,
            'guidance': '<p>Please enter your job title in the space provided.</p>',
            'type': 'TextField'
        }
        unbound_field = get_field(textfield_json, textfield_json['label'], error_messages, self.answer_store,
                                  self.metadata)

        self.assertEqual(unbound_field.field_class, StringField)
        self.assertEqual(unbound_field.kwargs['label'], textfield_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], textfield_json['guidance'])

    def test_text_area_field(self):
        textarea_json = {
            'guidance': '',
            'id': 'answer',
            'label': 'Enter your comments',
            'mandatory': False,
            'q_code': '0',
            'type': 'TextArea'
        }

        unbound_field = get_field(textarea_json, textarea_json['label'], error_messages, self.answer_store,
                                  self.metadata)

        self.assertEqual(unbound_field.field_class, MaxTextAreaField)
        self.assertEqual(unbound_field.kwargs['label'], textarea_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], textarea_json['guidance'])

    def test_date_field(self):
        date_json = {
            'guidance': 'Please enter a date',
            'id': 'period-to',
            'label': 'Period to',
            'mandatory': True,
            'type': 'Date',
            'validation': {
                'messages': {
                    'INVALID_DATE': 'The date entered is not valid.  Please correct your answer.',
                    'MANDATORY': 'Please provide an answer to continue.'
                }
            }
        }

        with self.app_request_context('/'):
            unbound_field = get_field(date_json, date_json['label'], error_messages, self.answer_store,
                                      self.metadata)

        self.assertEqual(unbound_field.field_class, DateField)
        self.assertEqual(unbound_field.kwargs['label'], date_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], date_json['guidance'])

    def test_month_year_date_field(self):
        date_json = {
            'guidance': '',
            'id': 'month-year-answer',
            'label': 'Date',
            'mandatory': True,
            'options': [],
            'q_code': '11',
            'type': 'MonthYearDate',
            'validation': {
                'messages': {
                    'INVALID_DATE': 'The date entered is not valid.  Please correct your answer.',
                    'MANDATORY': 'Please provide an answer to continue.'
                }
            }
        }

        with self.app_request_context('/'):
            unbound_field = get_field(date_json, date_json['label'], error_messages, self.answer_store,
                                      self.metadata)

        self.assertEqual(unbound_field.field_class, MonthYearField)
        self.assertEqual(unbound_field.kwargs['label'], date_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], date_json['guidance'])

    def test_year_date_field(self):
        date_json = {
            'guidance': '',
            'id': 'month-year-answer',
            'label': 'Date',
            'mandatory': True,
            'options': [],
            'q_code': '11',
            'type': 'YearDate',
            'validation': {
                'messages': {
                    'INVALID_DATE': 'The date entered is not valid.  Please correct your answer.',
                    'MANDATORY': 'Please provide an answer to continue.'
                }
            }
        }

        with self.app_request_context('/'):
            unbound_field = get_field(date_json, date_json['label'], error_messages, self.answer_store,
                                      self.metadata)

        self.assertEqual(unbound_field.field_class, YearField)
        self.assertEqual(unbound_field.kwargs['label'], date_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], date_json['guidance'])

    def test_duration_field(self):
        date_json = {
            'guidance': '',
            'id': 'year-month-answer',
            'label': 'Duration',
            'mandatory': True,
            'options': [],
            'q_code': '11',
            'type': 'Duration',
            'units': ['years', 'months'],
            'validation': {
                'messages': {
                    'INVALID_DURATION': 'The duration entered is not valid.  Please correct your answer.',
                    'MANDATORY_DURATION': 'Please provide a duration to continue.'
                }
            }
        }

        with self.app_request_context('/'):
            unbound_field = get_field(date_json, date_json['label'], error_messages, self.answer_store,
                                      self.metadata)

        self.assertEqual(unbound_field.field_class, FormField)
        self.assertEqual(unbound_field.kwargs['label'], date_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], date_json['guidance'])

    def test_radio_field(self):
        radio_json = {
            'guidance': '',
            'id': 'choose-your-side-answer',
            'label': 'Choose a side',
            'mandatory': True,
            'options': [
                {
                    'label': 'Light Side',
                    'value': 'Light Side',
                    'description': 'The light side of the Force'
                },
                {
                    'label': 'Dark Side',
                    'value': 'Dark Side',
                    'description': 'The dark side of the Force'
                },
                {
                    'label': 'I prefer Star Trek',
                    'value': 'I prefer Star Trek'
                },
                {
                    'label': 'Other',
                    'value': 'Other'
                }
            ],
            'q_code': '20',
            'type': 'Radio'
        }

        unbound_field = get_field(radio_json, radio_json['label'], error_messages, self.answer_store,
                                  self.metadata)

        expected_choices = [(option['label'], option['value']) for option in radio_json['options']]

        self.assertEqual(unbound_field.field_class, SelectField)
        self.assertTrue(unbound_field.kwargs['coerce'], _coerce_str_unless_none)
        self.assertEqual(unbound_field.kwargs['label'], radio_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], radio_json['guidance'])
        self.assertEqual(unbound_field.kwargs['choices'], expected_choices)

    def test_dropdown_field(self):
        dropdown_json = {
            'type': 'Dropdown',
            'id': 'dropdown-mandatory-with-label-answer',
            'mandatory': True,
            'label': 'Please choose an option',
            'description': 'This is a mandatory dropdown, therefore you must select a value!.',
            'options': [
                {
                    'label': 'Liverpool',
                    'value': 'Liverpool'
                },
                {
                    'label': 'Chelsea',
                    'value': 'Chelsea'
                },
                {
                    'label': 'Rugby is better!',
                    'value': 'Rugby is better!'
                }
            ]
        }

        unbound_field = get_field(dropdown_json, dropdown_json['label'], error_messages, self.answer_store,
                                  self.metadata)

        expected_choices = [('', 'Select an answer')] + \
                           [(option['label'], option['value']) for option in dropdown_json['options']]

        self.assertEqual(unbound_field.field_class, SelectField)
        self.assertEqual(unbound_field.kwargs['label'], dropdown_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], '')
        self.assertEqual(unbound_field.kwargs['default'], '')
        self.assertEqual(unbound_field.kwargs['choices'], expected_choices)

    def test__coerce_str_unless_none(self):
        # pylint: disable=protected-access
        self.assertEqual(_coerce_str_unless_none(1), '1')
        self.assertEqual(_coerce_str_unless_none('bob'), 'bob')
        self.assertEqual(_coerce_str_unless_none(12323245), '12323245')
        self.assertEqual(_coerce_str_unless_none('9887766'), '9887766')
        self.assertEqual(_coerce_str_unless_none('None'), 'None')
        self.assertEqual(_coerce_str_unless_none(None), None)

    def test_checkbox_field(self):
        checkbox_json = {
            'guidance': '',
            'id': 'opening-crawler-answer',
            'label': '',
            'mandatory': False,
            'options': [
                {
                    'label': 'Luke Skywalker',
                    'value': 'Luke Skywalker'
                },
                {
                    'label': 'Han Solo',
                    'value': 'Han Solo'
                },
                {
                    'label': 'The Emperor',
                    'value': 'The Emperor'
                },
                {
                    'label': 'R2D2',
                    'value': 'R2D2'
                },
                {
                    'label': 'Senator Amidala',
                    'value': 'Senator Amidala'
                },
                {
                    'label': 'Yoda',
                    'value': 'Yoda'
                }
            ],
            'q_code': '7',
            'type': 'Checkbox'
        }

        unbound_field = get_field(checkbox_json, checkbox_json['label'], error_messages, self.answer_store,
                                  self.metadata)

        expected_choices = [(option['value'], option['label']) for option in checkbox_json['options']]

        self.assertEqual(unbound_field.field_class, SelectMultipleField)
        self.assertEqual(unbound_field.kwargs['label'], checkbox_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], checkbox_json['guidance'])
        self.assertEqual(unbound_field.kwargs['choices'], expected_choices)
        self.assertEqual(len(unbound_field.kwargs['validators']), 1)


    def test_mutually_exclusive_checkbox_field(self):
        checkbox_json = {
            'guidance': '',
            'id': 'opening-crawler-answer',
            'label': '',
            'mandatory': False,
            'options': [
                {
                    'label': 'Luke Skywalker',
                    'value': 'Luke Skywalker'
                },
                {
                    'label': 'Han Solo',
                    'value': 'Han Solo'
                },
                {
                    'label': 'The Emperor',
                    'value': 'The Emperor'
                },
                {
                    'label': 'R2D2',
                    'value': 'R2D2'
                },
                {
                    'label': 'Senator Amidala',
                    'value': 'Senator Amidala'
                },
                {
                    'label': 'I prefer star trek',
                    'value': 'None'
                }
            ],
            'type': 'MutuallyExclusiveCheckbox'
        }

        unbound_field = get_field(checkbox_json, checkbox_json['label'], error_messages, self.answer_store,
                                  self.metadata)

        expected_choices = [(option['value'], option['label']) for option in checkbox_json['options']]

        self.assertEqual(unbound_field.field_class, SelectMultipleField)
        self.assertEqual(unbound_field.kwargs['label'], checkbox_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], checkbox_json['guidance'])
        self.assertEqual(unbound_field.kwargs['choices'], expected_choices)
        self.assertEqual(type(unbound_field.kwargs['validators'][1]), MutuallyExclusive)


    def test_integer_field(self):
        integer_json = {
            'alias': 'chewies_age',
            'guidance': '',
            'id': 'chewies-age-answer',
            'label': 'How old is Chewy?',
            'mandatory': True,
            'q_code': '1',
            'type': 'Number',
            'validation': {
                'messages': {
                    'NUMBER_TOO_LARGE': 'No one lives that long, not even Yoda',
                    'NUMBER_TOO_SMALL': 'Negative age you can not be.',
                    'INVALID_NUMBER': 'Please enter your age.'
                }
            }
        }

        unbound_field = get_field(integer_json, integer_json['label'], error_messages, self.answer_store, self.metadata)

        self.assertEqual(unbound_field.field_class, CustomIntegerField)
        self.assertEqual(unbound_field.kwargs['label'], integer_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], integer_json['guidance'])

    def test_decimal_field(self):
        decimal_json = {
            'guidance': '',
            'id': 'lightsaber-cost-answer',
            'label': 'How hot is a lightsaber in degrees C?',
            'mandatory': False,
            'type': 'Number',
            'decimal_places': 2,
            'validation': {
                'messages': {
                    'NUMBER_TOO_LARGE': 'Thats hotter then the sun, Jar Jar Binks you must be',
                    'NUMBER_TOO_SMALL': 'How can it be negative?',
                    'INVALID_NUMBER': 'Please only enter whole numbers into the field.'
                }
            }
        }

        unbound_field = get_field(decimal_json, decimal_json['label'], error_messages, self.answer_store, self.metadata)

        self.assertEqual(unbound_field.field_class, CustomDecimalField)
        self.assertEqual(unbound_field.kwargs['label'], decimal_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], decimal_json['guidance'])

    def test_currency_field(self):
        currency_json = {
            'guidance': '',
            'id': 'a04a516d-502d-4068-bbed-a43427c68cd9',
            'label': '',
            'mandatory': True,
            'q_code': '2',
            'type': 'Currency',
            'validation': {
                'messages': {
                    'NUMBER_TOO_LARGE': 'How much, fool you must be',
                    'NUMBER_TOO_SMALL': 'How can it be negative?',
                    'INVALID_NUMBER': 'Please only enter whole numbers into the field.'
                }
            }
        }

        unbound_field = get_field(currency_json, currency_json['label'], error_messages, self.answer_store,
                                  self.metadata)

        self.assertEqual(unbound_field.field_class, CustomIntegerField)
        self.assertEqual(unbound_field.kwargs['label'], currency_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], currency_json['guidance'])

    def test_percentage_field(self):
        percentage_json = {
            'description': '',
            'id': 'percentage-turnover-2016-market-new-answer',
            'label': 'New to the market in 2014-2016',
            'mandatory': False,
            'q_code': '0810',
            'type': 'Percentage',
            'max_value': {
                'value': 100
            },
            'validation': {
                'messages': {
                    'NUMBER_TOO_LARGE': 'How much, fool you must be',
                    'NUMBER_TOO_SMALL': 'How can it be negative?',
                    'INVALID_NUMBER': 'Please only enter whole numbers into the field.'
                }
            }
        }

        unbound_field = get_field(percentage_json, percentage_json['label'], error_messages, self.answer_store,
                                  self.metadata)

        self.assertEqual(unbound_field.field_class, CustomIntegerField)
        self.assertEqual(unbound_field.kwargs['label'], percentage_json['label'])
        self.assertEqual(unbound_field.kwargs['description'], percentage_json['description'])

    def test_invalid_field_type_raises_on_invalid(self):
        # Given
        invalid_field_type = 'Football'
        # When / Then
        with self.assertRaises(KeyError):
            get_field({'type': invalid_field_type}, 'Football Field', error_messages, self.answer_store, self.metadata)
Esempio n. 3
0
class QuestionnaireStore:
    LATEST_VERSION = 1

    def __init__(self, storage, version=None):
        self._storage = storage
        if version is None:
            version = self.get_latest_version_number()
        self.version = version
        self.metadata = {}
        self.answer_store = AnswerStore()
        self.completed_blocks = []

        raw_data, version = self._storage.get_user_data()
        if raw_data:
            self._deserialise(raw_data)
        if version is not None:
            self.version = version

    def get_latest_version_number(self):
        return self.LATEST_VERSION

    def _deserialise(self, data):
        json_data = json.loads(data, use_decimal=True)
        # pylint: disable=maybe-no-member
        completed_blocks = [
            Location.from_dict(location_dict=completed_block)
            for completed_block in json_data.get('COMPLETED_BLOCKS', [])
        ]
        self.metadata = json_data.get('METADATA', {})
        self.answer_store.answers = json_data.get('ANSWERS', [])
        self.completed_blocks = completed_blocks

    def _serialise(self):
        data = {
            'METADATA': self.metadata,
            'ANSWERS': self.answer_store.answers,
            'COMPLETED_BLOCKS': self.completed_blocks,
        }
        return json.dumps(data, default=self._encode_questionnaire_store)

    def delete(self):
        self._storage.delete()
        self.metadata = {}
        self.answer_store.clear()
        self.completed_blocks = []

    def add_or_update(self):
        data = self._serialise()
        self._storage.add_or_update(data=data, version=self.version)

    def remove_completed_blocks(self,
                                location=None,
                                group_id=None,
                                block_id=None):
        """Removes completed blocks from store either by specific location
           or all group instances within a group and block.

            e.g.
            ```
            # By location
            question_store.remove_completed_blocks(location=Location(...))

            # By group_id/block_id (i.e. for all group instances for that group/block)
            question_store.remove_completed_blocks(group_id='a-test-group', block_id='a-test-block')
            ```
        """

        if location:
            if not isinstance(location, Location):
                raise TypeError('location needs to be a Location instance')
            self.completed_blocks.remove(location)
        else:
            if None in (group_id, block_id):
                raise KeyError('Both group_id and block_id required')

            self.completed_blocks = [
                completed_block for completed_block in self.completed_blocks
                if completed_block.group_id != group_id
                or completed_block.block_id != block_id
            ]

    def _encode_questionnaire_store(self, o):
        if hasattr(o, 'to_dict'):
            return o.to_dict()

        return json.JSONEncoder.default(self, o)
Esempio n. 4
0
class QuestionnaireStore:
    LATEST_VERSION = 2

    def __init__(self, storage, version=None):
        self._storage = storage
        if version is None:
            version = self.get_latest_version_number()
        self.version = version
        self._metadata = {}
        # metadata is a read-only view over self._metadata
        self.metadata = MappingProxyType(self._metadata)
        self.collection_metadata = {}
        self.answer_store = AnswerStore()
        self.completed_blocks = []

        raw_data, version = self._storage.get_user_data()
        if raw_data:
            self._deserialise(raw_data)
        if version is not None:
            self.version = version

    def get_latest_version_number(self):
        return self.LATEST_VERSION

    def set_metadata(self, to_set):
        """
        Set metadata. This should only be used where absolutely necessary.
        Metadata should normally be read only.
        """
        self._metadata = to_set
        self.metadata = MappingProxyType(self._metadata)

    def ensure_latest_version(self, schema):
        """ If the code has been updated, the data being loaded may need
        transforming to match the latest code. """
        new_version = self.get_latest_version_number()
        if self.version < new_version:
            self.answer_store.upgrade(self.version, schema)
            self.version = new_version

        return self

    def _deserialise(self, data):
        json_data = json.loads(data, use_decimal=True)
        # pylint: disable=maybe-no-member
        completed_blocks = [
            Location.from_dict(location_dict=completed_block)
            for completed_block in json_data.get('COMPLETED_BLOCKS', [])
        ]
        self.set_metadata(json_data.get('METADATA', {}))
        self.answer_store.answers = json_data.get('ANSWERS', [])
        self.completed_blocks = completed_blocks
        self.collection_metadata = json_data.get('COLLECTION_METADATA', {})

    def _serialise(self):
        data = {
            'METADATA': self._metadata,
            'ANSWERS': self.answer_store.answers,
            'COMPLETED_BLOCKS': self.completed_blocks,
            'COLLECTION_METADATA': self.collection_metadata,
        }
        return json.dumps(data, default=self._encode_questionnaire_store)

    def delete(self):
        self._storage.delete()
        self._metadata.clear()
        self.collection_metadata = {}
        self.answer_store.clear()
        self.completed_blocks = []

    def add_or_update(self):
        data = self._serialise()
        self._storage.add_or_update(data=data, version=self.version)

    def remove_completed_blocks(self,
                                location=None,
                                group_id=None,
                                block_id=None):
        """Removes completed blocks from store either by specific location
           or all group instances within a group and block.

            e.g.
            ```
            # By location
            question_store.remove_completed_blocks(location=Location(...))

            # By group_id/block_id (i.e. for all group instances for that group/block)
            question_store.remove_completed_blocks(group_id='a-test-group', block_id='a-test-block')
            ```
        """

        if location:
            if not isinstance(location, Location):
                raise TypeError('location needs to be a Location instance')
            self.completed_blocks.remove(location)
        else:
            if None in (group_id, block_id):
                raise KeyError('Both group_id and block_id required')

            self.completed_blocks = [
                completed_block for completed_block in self.completed_blocks
                if completed_block.group_id != group_id
                or completed_block.block_id != block_id
            ]

    def _encode_questionnaire_store(self, o):
        if hasattr(o, 'to_dict'):
            return o.to_dict()

        return json.JSONEncoder.default(self, o)
Esempio n. 5
0
class TestGetMappedAnswers(unittest.TestCase):
    def setUp(self):
        self.store = AnswerStore(None)

    def tearDown(self):
        self.store.clear()

    def test_maps_and_filters_answers(self):
        questionnaire = {
            'sections': [{
                'id':
                'section1',
                'groups': [{
                    'id':
                    'group1',
                    'blocks': [{
                        'id':
                        'block1',
                        'questions': [{
                            'id':
                            'question1',
                            'answers': [{
                                'id': 'answer1',
                                'type': 'TextArea'
                            }]
                        }]
                    }, {
                        'id':
                        'block2',
                        'questions': [{
                            'id':
                            'question2',
                            'answers': [{
                                'id': 'answer2',
                                'type': 'TextArea'
                            }]
                        }]
                    }]
                }]
            }]
        }
        schema = QuestionnaireSchema(questionnaire)

        answer_1 = Answer(
            answer_id='answer2',
            answer_instance=1,
            group_instance_id='group-1',
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            answer_id='answer1',
            answer_instance=1,
            group_instance_id='group-1',
            group_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        expected_answers = {'answer1_1': 65}

        self.assertEqual(
            get_mapped_answers(schema,
                               self.store,
                               block_id='block1',
                               group_instance=1,
                               group_instance_id='group-1'), expected_answers)

    def test_returns_ordered_map(self):

        questionnaire = {
            'sections': [{
                'id':
                'section1',
                'groups': [{
                    'id':
                    'group1',
                    'blocks': [{
                        'id':
                        'block1',
                        'questions': [{
                            'id':
                            'question1',
                            'answers': [{
                                'id': 'answer1',
                                'type': 'TextArea'
                            }]
                        }]
                    }]
                }]
            }]
        }
        schema = QuestionnaireSchema(questionnaire)

        answer = Answer(
            answer_id='answer1',
            group_instance_id='group-1',
            group_instance=1,
            value=25,
        )

        for i in range(0, 100):
            answer.answer_instance = i

            self.store.add(answer)

        last_instance = -1

        self.assertEqual(len(self.store.answers), 100)

        mapped = get_mapped_answers(schema,
                                    self.store,
                                    block_id='block1',
                                    group_instance=1,
                                    group_instance_id='group-1')

        for key, _ in mapped.items():
            pos = key.find('_')

            instance = 0 if pos == -1 else int(key[pos + 1:])

            self.assertGreater(instance, last_instance)

            last_instance = instance
class TestAnswerStore(unittest.TestCase):  # pylint: disable=too-many-public-methods
    def setUp(self):
        self.store = AnswerStore()

    def tearDown(self):
        self.store.clear()

    def test_adds_answer(self):
        answer = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=25,
        )

        self.store.add(answer)

        self.assertEqual(len(self.store.answers), 1)

    def test_raises_error_on_invalid(self):

        with self.assertRaises(TypeError) as ite:
            self.store.add({
                'answer_id': '4',
                'answer_instance': 1,
                'group_instance': 1,
                'value': 25,
            })

        self.assertIn('Method only supports Answer argument type', str(ite.exception))

    def test_raises_error_on_add_existing(self):
        answer_1 = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=25,
        )
        self.store.add(answer_1)

        with self.assertRaises(ValueError) as ite:
            self.store.add(answer_1)

        self.assertIn('Answer instance already exists in store', str(ite.exception))

    def test_raises_error_on_update_nonexisting(self):
        answer_1 = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=25,
        )

        with self.assertRaises(ValueError) as ite:
            self.store.update(answer_1)

        self.assertIn('Answer instance does not exist in store', str(ite.exception))

    def test_add_inserts_instances(self):
        answer_1 = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=25,
        )
        self.store.add(answer_1)

        answer_1.answer_instance = 2

        self.store.add(answer_1)

        answer_1.answer_instance = 3

        self.store.add(answer_1)

        self.assertEqual(len(self.store.answers), 3)

    def test_updates_answer(self):
        answer_1 = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.update(answer_2)

        self.assertEqual(len(self.store.answers), 1)

        store_match = self.store.filter(
            answer_ids=['4'],
            answer_instance=1,
            group_instance=1,
        )

        self.assertEqual(store_match.answers, [answer_2.__dict__])

    def test_filters_answers(self):

        answer_1 = Answer(
            answer_id='2',
            answer_instance=1,
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            answer_id='5',
            answer_instance=1,
            group_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        filtered = self.store.filter(answer_ids=['5'])

        self.assertEqual(len(filtered.answers), 1)

    def test_filters_answers_with_limit(self):

        for i in range(1, 50):
            self.store.add(Answer(
                answer_id='2',
                answer_instance=i,
                group_instance=1,
                value=25,
            ))

        filtered = self.store.filter(answer_ids=['2'], limit=True)

        self.assertEqual(len(filtered.answers), 25)

    def test_escaped(self):

        self.store.add(Answer(
            answer_id='1',
            answer_instance=0,
            group_instance=1,
            value=25,
        ))

        self.store.add(Answer(
            answer_id='2',
            answer_instance=0,
            group_instance=1,
            value="'Twenty Five'",
        ))

        escaped = self.store.escaped()

        self.assertEqual(len(escaped.answers), 2)
        self.assertEqual(escaped[0]['value'], 25)
        self.assertEqual(escaped[1]['value'], '&#39;Twenty Five&#39;')

        # answers in the store have not been escaped
        self.assertEqual(self.store.answers[0]['value'], 25)
        self.assertEqual(self.store.answers[1]['value'], "'Twenty Five'")

    def test_filter_answers_does_not_escapes_values(self):

        self.store.add(Answer(
            answer_id='1',
            answer_instance=0,
            group_instance=1,
            value=25,
        ))

        self.store.add(Answer(
            answer_id='2',
            answer_instance=0,
            group_instance=1,
            value="'Twenty Five'",
        ))

        filtered = self.store.filter(['1', '2'])

        self.assertEqual(len(filtered.answers), 2)
        self.assertEqual(filtered[0]['value'], 25)
        self.assertEqual(filtered[1]['value'], "'Twenty Five'")

    def test_filter_chaining_escaped(self):

        self.store.add(Answer(
            answer_id='1',
            answer_instance=0,
            group_instance=1,
            value=25,
        ))

        self.store.add(Answer(
            answer_id='2',
            answer_instance=0,
            group_instance=1,
            value="'Twenty Five'",
        ))

        escaped = self.store.filter(answer_ids=['2']).escaped()

        self.assertEqual(len(escaped.answers), 1)
        self.assertEqual(escaped[0]['value'], '&#39;Twenty Five&#39;')

        # answers in the store have not been escaped
        self.assertEqual(self.store[0]['value'], 25)
        self.assertEqual(self.store[1]['value'], "'Twenty Five'")

        values = self.store.filter(answer_ids=['2']).escaped().values()

        self.assertEqual(len(values), 1)
        self.assertEqual(values[0], '&#39;Twenty Five&#39;')

    def test_filter_chaining_count(self):

        self.store.add(Answer(
            answer_id='1',
            answer_instance=0,
            group_instance=1,
            value=25,
        ))

        self.store.add(Answer(
            answer_id='2',
            answer_instance=0,
            group_instance=1,
            value="'Twenty Five'",
        ))

        self.assertEqual(self.store.count(), 2)
        self.assertEqual(self.store.filter(answer_ids=['2']).count(), 1)
        self.assertEqual(self.store.filter(answer_ids=['1', '2']).count(), 2)

    def tests_upgrade_reformats_date(self):
        questionnaire = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'secetion1',
                'groups': [{
                    'id': 'group1',
                    'blocks': [{
                        'id': 'block1',
                        'questions': [{
                            'id': 'question1',
                            'answers': [
                                {
                                    'id': 'answer1',
                                    'type': 'Date'
                                }
                            ]
                        }]
                    }]
                }]
            }]
        }

        answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '25/12/2017'
            }
        ]

        self.store = AnswerStore(existing_answers=answers)

        self.store.upgrade(current_version=0, schema=QuestionnaireSchema(questionnaire))

        self.assertEqual(self.store.answers[0]['value'], '2017-12-25')

    def tests_upgrade_reformats_month_year_date(self):
        questionnaire = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'section1',
                'groups': [{
                    'id': 'group1',
                    'blocks': [{
                        'id': 'block1',
                        'questions': [{
                            'id': 'question1',
                            'answers': [
                                {
                                    'id': 'answer1',
                                    'type': 'MonthYearDate'
                                }
                            ]
                        }]
                    }]
                }]
            }]
        }

        answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '12/2017'
            }
        ]

        self.store = AnswerStore(existing_answers=answers)

        self.store.upgrade(current_version=0, schema=QuestionnaireSchema(questionnaire))

        self.assertEqual(self.store.answers[0]['value'], '2017-12')

    def tests_upgrade_when_answer_no_longer_in_schema_does_not_reformat(self):
        questionnaire = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'section1',
                'groups': [{
                    'id': 'group1',
                    'blocks': [{
                        'id': 'block1',
                        'questions': [{
                            'id': 'question1',
                            'answers': [
                            ]
                        }]
                    }]
                }]
            }]
        }

        answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '12/2017'
            }
        ]

        self.store = AnswerStore(existing_answers=answers)

        self.store.upgrade(current_version=0, schema=QuestionnaireSchema(questionnaire))

        self.assertEqual(self.store.answers[0]['value'], '12/2017')

    def tests_upgrade_when_block_no_longer_in_schema_does_not_reformat(self):
        questionnaire = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'section1',
                'groups': [{
                    'id': 'group1',
                    'blocks': []
                }]
            }]
        }

        answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '12/2017'
            }
        ]

        self.store = AnswerStore(existing_answers=answers)

        self.store.upgrade(current_version=0, schema=QuestionnaireSchema(questionnaire))

        self.assertEqual(self.store.answers[0]['value'], '12/2017')

    def test_remove_all_answers(self):
        answer_1 = Answer(
            answer_id='answer1',
            value=10,
        )
        answer_2 = Answer(
            answer_id='answer2',
            value=20,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        self.store.remove()
        self.assertEqual(len(self.store.answers), 0)
class TestAnswerStore(unittest.TestCase):  # pylint: disable=too-many-public-methods
    def setUp(self):
        self.store = AnswerStore()

    def tearDown(self):
        self.store.clear()

    def test_adds_answer(self):
        answer = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=25,
        )

        self.store.add_or_update(answer)

        self.assertEqual(len(self.store), 1)

    def test_raises_error_on_invalid(self):

        with self.assertRaises(TypeError) as ite:
            self.store.add_or_update({
                'answer_id': '4',
                'answer_instance': 1,
                'group_instance': 1,
                'value': 25,
            })

        self.assertIn('Method only supports Answer argument type', str(ite.exception))

    def test_add_inserts_instances(self):
        answer_1 = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=25,
        )
        self.store.add_or_update(answer_1)

        answer_1.answer_instance = 2

        self.store.add_or_update(answer_1)

        answer_1.answer_instance = 3

        self.store.add_or_update(answer_1)

        self.assertEqual(len(self.store), 3)

    def test_updates_answer(self):
        answer_1 = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            answer_id='4',
            answer_instance=1,
            group_instance=1,
            value=65,
        )

        self.store.add_or_update(answer_1)
        self.store.add_or_update(answer_2)

        self.assertEqual(len(self.store), 1)

        store_match = self.store.filter(
            answer_ids=['4'],
            answer_instance=1,
            group_instance=1,
        )

        self.assertEqual(store_match, AnswerStore([answer_2.__dict__]))

    def test_filters_answers(self):

        answer_1 = Answer(
            answer_id='2',
            answer_instance=1,
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            answer_id='5',
            answer_instance=1,
            group_instance=1,
            value=65,
        )

        self.store.add_or_update(answer_1)
        self.store.add_or_update(answer_2)

        filtered = self.store.filter(answer_ids=['5'])

        self.assertEqual(len(filtered), 1)

    def test_filters_answers_with_limit(self):

        for i in range(1, 50):
            self.store.add_or_update(Answer(
                answer_id='2',
                answer_instance=i,
                group_instance=1,
                value=25,
            ))

        filtered = self.store.filter(answer_ids=['2'], limit=True)

        self.assertEqual(len(filtered), 25)

    def test_escaped(self):

        self.store.add_or_update(Answer(
            answer_id='1',
            answer_instance=0,
            group_instance=1,
            value=25,
        ))

        self.store.add_or_update(Answer(
            answer_id='2',
            answer_instance=0,
            group_instance=1,
            value="'Twenty Five'",
        ))

        escaped = self.store.escaped()

        self.assertEqual(len(escaped), 2)
        self.assertEqual(escaped.filter(answer_ids=['1']).values()[0], 25)
        self.assertEqual(escaped.filter(answer_ids=['2']).values()[0], '&#39;Twenty Five&#39;')

        # answers in the store have not been escaped
        self.assertEqual(self.store.filter(answer_ids=['1']).values()[0], 25)
        self.assertEqual(self.store.filter(answer_ids=['2']).values()[0], "'Twenty Five'")

    def test_filter_answers_does_not_escapes_values(self):

        self.store.add_or_update(Answer(
            answer_id='1',
            answer_instance=0,
            group_instance=1,
            value=25,
        ))

        self.store.add_or_update(Answer(
            answer_id='2',
            answer_instance=0,
            group_instance=1,
            value="'Twenty Five'",
        ))

        filtered = self.store.filter(['1', '2'])

        self.assertEqual(len(filtered), 2)
        self.assertEqual(filtered.filter(answer_ids=['1']).values()[0], 25)
        self.assertEqual(filtered.filter(answer_ids=['2']).values()[0], "'Twenty Five'")

    def test_filter_chaining_escaped(self):

        self.store.add_or_update(Answer(
            answer_id='1',
            answer_instance=0,
            group_instance=1,
            value=25,
        ))

        self.store.add_or_update(Answer(
            answer_id='2',
            answer_instance=0,
            group_instance=1,
            value="'Twenty Five'",
        ))

        escaped = self.store.filter(answer_ids=['2']).escaped()

        self.assertEqual(len(escaped), 1)
        self.assertEqual(escaped.values()[0], '&#39;Twenty Five&#39;')

        # answers in the store have not been escaped
        self.assertEqual(self.store.filter(answer_ids=['1']).values()[0], 25)
        self.assertEqual(self.store.filter(answer_ids=['2']).values()[0], "'Twenty Five'")

        values = self.store.filter(answer_ids=['2']).escaped().values()

        self.assertEqual(len(values), 1)
        self.assertEqual(values[0], '&#39;Twenty Five&#39;')

    def test_filter_chaining_count(self):

        self.store.add_or_update(Answer(
            answer_id='1',
            answer_instance=0,
            group_instance=1,
            value=25,
        ))

        self.store.add_or_update(Answer(
            answer_id='2',
            answer_instance=0,
            group_instance=1,
            value="'Twenty Five'",
        ))

        self.assertEqual(self.store.count(), 2)
        self.assertEqual(self.store.filter(answer_ids=['2']).count(), 1)
        self.assertEqual(self.store.filter(answer_ids=['1', '2']).count(), 2)

    def tests_upgrade_reformats_date(self):
        questionnaire = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'secetion1',
                'groups': [{
                    'id': 'group1',
                    'blocks': [{
                        'id': 'block1',
                        'questions': [{
                            'id': 'question1',
                            'answers': [
                                {
                                    'id': 'answer1',
                                    'type': 'Date'
                                }
                            ]
                        }]
                    }]
                }]
            }]
        }

        answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '25/12/2017'
            }
        ]

        self.store = AnswerStore(existing_answers=answers)

        upgrade_to_1_update_date_formats(self.store, QuestionnaireSchema(questionnaire))

        self.assertEqual(self.store.values()[0], '2017-12-25')

    def tests_upgrade_reformats_month_year_date(self):
        questionnaire = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'section1',
                'groups': [{
                    'id': 'group1',
                    'blocks': [{
                        'id': 'block1',
                        'questions': [{
                            'id': 'question1',
                            'answers': [
                                {
                                    'id': 'answer1',
                                    'type': 'MonthYearDate'
                                }
                            ]
                        }]
                    }]
                }]
            }]
        }

        answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '12/2017'
            }
        ]

        self.store = AnswerStore(existing_answers=answers)

        upgrade_to_1_update_date_formats(self.store, QuestionnaireSchema(questionnaire))

        self.assertEqual(self.store.values()[0], '2017-12')

    def tests_upgrade_when_answer_no_longer_in_schema_does_not_reformat(self):
        questionnaire = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'section1',
                'groups': [{
                    'id': 'group1',
                    'blocks': [{
                        'id': 'block1',
                        'questions': [{
                            'id': 'question1',
                            'answers': [
                            ]
                        }]
                    }]
                }]
            }]
        }

        answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '12/2017'
            }
        ]

        self.store = AnswerStore(existing_answers=answers)

        upgrade_to_1_update_date_formats(self.store, QuestionnaireSchema(questionnaire))

        self.assertEqual(self.store.values()[0], '12/2017')

    def tests_upgrade_when_block_no_longer_in_schema_does_not_reformat(self):
        questionnaire = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'section1',
                'groups': [{
                    'id': 'group1',
                    'blocks': []
                }]
            }]
        }

        answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '12/2017'
            }
        ]

        self.store = AnswerStore(existing_answers=answers)

        upgrade_to_1_update_date_formats(self.store, QuestionnaireSchema(questionnaire))

        self.assertEqual(self.store.values()[0], '12/2017')


    def test_upgrade_add_group_instance_id(self):

        survey = {
            'survey_id': '021',
            'data_version': '0.0.2',
            'sections': [{
                'id': 'section1',
                'groups': [{
                    'id': 'group1',
                    'blocks': [{
                        'id': 'block1',
                        'type': 'Question',
                        'questions': [{
                            'id': 'question1',
                            'answers': [
                                {
                                    'id': 'answer1',
                                    'type': 'TextArea'
                                }
                            ]
                        }]
                    }]
                }, {
                    'id': 'group-2',
                    'blocks': [{
                        'id': 'block-2',
                        'type': 'Question'
                    }],
                    'routing_rules':[{
                        'repeat': {
                            'type': 'group',
                            'group_ids': ['group1']
                        }
                    }]
                }]
            }]
        }

        existing_answers = [
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 0,
                'value': '12/2017'
            },
            {
                'answer_id': 'answer1',
                'answer_instance': 1,
                'group_instance': 0,
                'value': '12/2017'
            },
            {
                'answer_id': 'answer1',
                'answer_instance': 0,
                'group_instance': 1,
                'value': '12/2017'
            },
        ]

        answer_store = AnswerStore(existing_answers)

        upgrade_to_2_add_group_instance_id(answer_store, QuestionnaireSchema(survey))

        filtered = iter(answer_store.filter(answer_ids=['answer1']))
        first_group_instance_id = next(filtered)['group_instance_id']

        self.assertEqual(first_group_instance_id, next(filtered)['group_instance_id'])
        self.assertNotEqual(first_group_instance_id, next(filtered)['group_instance_id'])


    def test_upgrade_multiple_versions(self):

        answer_store = AnswerStore()

        upgrade_0 = MagicMock()
        upgrade_0.__name__ = 'upgrade_0'
        upgrade_1 = MagicMock()
        upgrade_1.__name__ = 'upgrade_1'
        upgrade_2 = MagicMock()
        upgrade_2.__name__ = 'upgrade_2'

        UPGRADE_TRANSFORMS = {
            1: upgrade_0,
            2: upgrade_1,
            3: upgrade_2
        }

        schema = MagicMock()

        with patch('app.data_model.answer_store.UPGRADE_TRANSFORMS', UPGRADE_TRANSFORMS):
            answer_store.upgrade(0, schema)

        upgrade_0.assert_called_once_with(answer_store, schema)
        upgrade_1.assert_called_once_with(answer_store, schema)
        upgrade_2.assert_called_once_with(answer_store, schema)

    def test_upgrade_multiple_versions_skipping_already_run(self):

        answer_store = AnswerStore()

        upgrade_0 = MagicMock()
        upgrade_0.__name__ = 'upgrade_0'
        upgrade_1 = MagicMock()
        upgrade_1.__name__ = 'upgrade_1'
        upgrade_2 = MagicMock()
        upgrade_2.__name__ = 'upgrade_2'

        UPGRADE_TRANSFORMS = {
            1: upgrade_0,
            2: upgrade_1,
            3: upgrade_2
        }

        schema = MagicMock()

        with patch('app.data_model.answer_store.UPGRADE_TRANSFORMS', UPGRADE_TRANSFORMS):
            answer_store.upgrade(1, schema)

        upgrade_0.assert_not_called()
        upgrade_1.assert_called_once_with(answer_store, schema)
        upgrade_2.assert_called_once_with(answer_store, schema)

    def test_upgrade_skipped_version(self):
        """ Ensure skipping an answer store version does not affect the upgrade path.
        """
        answer_store = AnswerStore()

        upgrade_0 = MagicMock()
        upgrade_0.__name__ = 'upgrade_0'
        upgrade_2 = MagicMock()
        upgrade_2.__name__ = 'upgrade_2'
        upgrade_3 = MagicMock()
        upgrade_3.__name__ = 'upgrade_3'

        UPGRADE_TRANSFORMS = {
            1: upgrade_0,
            3: upgrade_2,
            4: upgrade_3
        }

        schema = MagicMock()

        with patch('app.data_model.answer_store.UPGRADE_TRANSFORMS', UPGRADE_TRANSFORMS):
            answer_store.upgrade(0, schema)

        upgrade_0.assert_called_once_with(answer_store, schema)
        upgrade_2.assert_called_once_with(answer_store, schema)
        upgrade_3.assert_called_once_with(answer_store, schema)

    def test_remove_all_answers(self):
        answer_1 = Answer(
            answer_id='answer1',
            value=10,
        )
        answer_2 = Answer(
            answer_id='answer2',
            value=20,
        )

        self.store.add_or_update(answer_1)
        self.store.add_or_update(answer_2)

        self.store.clear()
        self.assertEqual(len(self.store), 0)

    def test_remove_answer(self):
        answer_1 = Answer(
            answer_id='answer1',
            value=10,
        )
        answer_2 = Answer(
            answer_id='answer2',
            value=20,
        )

        self.store.add_or_update(answer_1)
        self.store.add_or_update(answer_2)

        self.store.remove_answer(vars(answer_1))
        self.assertEqual(len(self.store), 1)
class TestNumberRangeValidator(unittest.TestCase):
    """
    Number range validator uses the data, which is already known as integer
    """
    def setUp(self):
        self.store = AnswerStore()

        answer1 = Answer(answer_id="set-minimum", value=10)
        answer2 = Answer(answer_id="set-maximum", value=20)
        answer3 = Answer(answer_id="set-maximum-cat", value="cat")

        self.store.add_or_update(answer1)
        self.store.add_or_update(answer2)
        self.store.add_or_update(answer3)

    def tearDown(self):
        self.store.clear()

    def test_too_small_when_min_set_is_invalid(self):
        validator = NumberRange(minimum=0)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = -10

        with self.assertRaises(ValidationError) as ite:
            validator(mock_form, mock_field)

        self.assertEqual(error_messages["NUMBER_TOO_SMALL"] % dict(min=0),
                         str(ite.exception))

    def test_too_big_when_max_set_is_invalid(self):
        validator = NumberRange(maximum=9999999999)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = 10000000000

        with self.assertRaises(ValidationError) as ite:
            validator(mock_form, mock_field)

        self.assertEqual(
            error_messages["NUMBER_TOO_LARGE"] %
            dict(max=format_number(9999999999)),
            str(ite.exception),
        )

    def test_within_range(self):
        validator = NumberRange(minimum=0, maximum=10)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = 10

        try:
            validator(mock_form, mock_field)
        except ValidationError:
            self.fail("Valid integer raised ValidationError")

    def test_within_range_at_min(self):
        validator = NumberRange(minimum=0, maximum=9999999999)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = 0

        try:
            validator(mock_form, mock_field)
        except ValidationError:
            self.fail("Valid integer raised ValidationError")

    def test_within_range_at_max(self):
        validator = NumberRange(minimum=0, maximum=9999999999)

        mock_form = Mock()
        mock_field = Mock()
        mock_field.data = 9999999999

        try:
            validator(mock_form, mock_field)
        except ValidationError:
            self.fail("Valid integer raised ValidationError")
class QuestionnaireStore:
    LATEST_VERSION = 1

    def __init__(self, storage, version=None):
        self._storage = storage
        if version is None:
            version = self.get_latest_version_number()
        self.version = version
        self._metadata = {}
        # self.metadata is a read-only view over self._metadata
        self.metadata = MappingProxyType(self._metadata)
        self.collection_metadata = {}
        self.list_store = ListStore()
        self.answer_store = AnswerStore()
        self.progress_store = ProgressStore()

        raw_data, version = self._storage.get_user_data()
        if raw_data:
            self._deserialise(raw_data)
        if version is not None:
            self.version = version

    def get_latest_version_number(self):
        return self.LATEST_VERSION

    def set_metadata(self, to_set):
        """
        Set metadata. This should only be used where absolutely necessary.
        Metadata should normally be read only.
        """
        self._metadata = to_set
        self.metadata = MappingProxyType(self._metadata)

        return self

    def _deserialise(self, data):
        json_data = json.loads(data, use_decimal=True)
        self.progress_store = ProgressStore(json_data.get("PROGRESS"))
        self.set_metadata(json_data.get("METADATA", {}))
        self.answer_store = AnswerStore(json_data.get("ANSWERS"))
        self.list_store = ListStore.deserialise(json_data.get("LISTS"))
        self.collection_metadata = json_data.get("COLLECTION_METADATA", {})

    def serialise(self):
        data = {
            "METADATA": self._metadata,
            "ANSWERS": list(self.answer_store),
            "LISTS": self.list_store.serialise(),
            "PROGRESS": self.progress_store.serialise(),
            "COLLECTION_METADATA": self.collection_metadata,
        }
        return json.dumps(data, for_json=True)

    def delete(self):
        self._storage.delete()
        self._metadata.clear()
        self.collection_metadata = {}
        self.answer_store.clear()
        self.progress_store.clear()

    def save(self):
        data = self.serialise()
        self._storage.save(data=data)
Esempio n. 10
0
class TestAnswerStore(unittest.TestCase):  # pylint: disable=too-many-public-methods
    def setUp(self):
        self.store = AnswerStore()

    def tearDown(self):
        self.store.clear()

    def test_adds_answer(self):
        answer = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )

        self.store.add(answer)

        self.assertEqual(self.store.count(answer), 1)

    def test_raises_error_on_invalid(self):

        with self.assertRaises(TypeError) as ite:
            self.store.add({
                "block_id": "3",
                "answer_id": "4",
                "answer_instance": 1,
                "group_id": "5",
                "group_instance": 1,
                "value": 25,
            })

        self.assertIn("Method only supports Answer argument type",
                      str(ite.exception))

    def test_raises_error_on_add_existing(self):
        answer_1 = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )
        self.store.add(answer_1)

        with self.assertRaises(ValueError) as ite:
            self.store.add(answer_1)

        self.assertIn("Answer instance already exists in store",
                      str(ite.exception))

    def test_raises_error_on_update_nonexisting(self):
        answer_1 = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )

        with self.assertRaises(ValueError) as ite:
            self.store.update(answer_1)

        self.assertIn("Answer instance does not exist in store",
                      str(ite.exception))

    def test_raises_error_on_get_nonexisting(self):
        answer_1 = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )

        with self.assertRaises(ValueError) as ite:
            self.store.get(answer_1)

        self.assertIn("Answer instance does not exist in store",
                      str(ite.exception))

    def test_gets_answer(self):
        answer_1 = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )

        answer_2 = Answer(
            block_id="4",
            answer_id="5",
            group_id="6",
            group_instance=1,
            value=56,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        self.assertEqual(self.store.get(answer_1), 25)
        self.assertEqual(self.store.get(answer_2), 56)

    def test_adds_multidict_answer(self):
        answer_1 = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=[23, 45, 67],
        )

        self.store.add(answer_1)
        value = self.store.get(answer_1)

        self.assertEqual(value, [23, 45, 67])

    def test_add_inserts_instances(self):
        answer_1 = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )
        self.store.add(answer_1)

        answer_1.answer_instance = 2

        self.store.add(answer_1)

        answer_1.answer_instance = 3

        self.store.add(answer_1)

        self.assertEqual(len(self.store.answers), 3)

    def test_updates_answer(self):
        answer_1 = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.update(answer_2)

        self.assertEqual(self.store.count(answer_2), 1)

        store_match = self.store.filter(
            block_id="3",
            answer_id="4",
            answer_instance=1,
            group_id="5",
            group_instance=1,
        )

        self.assertEqual(store_match, [answer_2.__dict__])

    def test_filters_answers(self):

        answer_1 = Answer(
            block_id="1",
            answer_id="2",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            block_id="1",
            answer_id="5",
            answer_instance=1,
            group_id="6",
            group_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        filtered = self.store.filter(block_id="1")

        self.assertEqual(len(filtered), 2)

        filtered = self.store.filter(answer_id="5")

        self.assertEqual(len(filtered), 1)

        filtered = self.store.filter(group_id="6")

        self.assertEqual(len(filtered), 1)

    def test_filters_answers_by_location(self):
        answer_1 = Answer(
            block_id="1",
            answer_id="2",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            block_id="1",
            answer_id="5",
            answer_instance=1,
            group_id="6",
            group_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        location = Location("6", 1, "1")

        filtered = self.store.filter(location=location)

        self.assertEqual(len(filtered), 1)

    def test_maps_answers(self):

        answer_1 = Answer(
            block_id="1",
            answer_id="2",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            block_id="1",
            answer_id="5",
            answer_instance=1,
            group_id="6",
            group_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        expected_answers = {"2_1": 25, "5_1": 65}

        self.assertEqual(self.store.map(), expected_answers)

    def test_maps_and_filters_answers(self):
        answer_1 = Answer(
            block_id="1",
            answer_id="2",
            answer_instance=1,
            group_id="5",
            group_instance=1,
            value=25,
        )
        answer_2 = Answer(
            block_id="1",
            answer_id="5",
            answer_instance=1,
            group_id="6",
            group_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        expected_answers = {"5_1": 65}

        self.assertEqual(self.store.map(answer_id="5"), expected_answers)

    def test_returns_ordered_map(self):
        answer = Answer(
            block_id="1",
            answer_id="2",
            group_id="5",
            group_instance=1,
            value=25,
        )

        for i in range(0, 100):
            answer.answer_instance = i

            self.store.add(answer)

        last_instance = -1

        self.assertEqual(len(self.store.answers), 100)

        mapped = self.store.map()

        for key, _ in mapped.items():
            pos = key.find('_')

            instance = 0 if pos == -1 else int(key[pos + 1:])

            self.assertGreater(instance, last_instance)

            last_instance = instance

    def test_remove_answer(self):
        answer_1 = Answer(
            group_id="1",
            block_id="1",
            answer_id="2",
            answer_instance=1,
            value=25,
        )
        answer_2 = Answer(
            group_id="1",
            block_id="1",
            answer_id="5",
            answer_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        expected_answers = {
            "2_1": 25,
            "5_1": 65,
        }
        self.assertEqual(self.store.map(), expected_answers)

        self.store.remove_answer(answer_2)
        expected_answers = {
            "2_1": 25,
        }
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_answer_that_does_not_exist(self):
        answer_1 = Answer(
            group_id="1",
            block_id="1",
            answer_id="1",
            answer_instance=1,
            value=25,
        )
        answer_2 = Answer(
            group_id="1",
            block_id="1",
            answer_id="2",
            answer_instance=1,
            value=65,
        )
        answer_3 = Answer(
            group_id="1",
            block_id="1",
            answer_id="3",
            answer_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        expected_answers = {
            "1_1": 25,
            "2_1": 65,
        }
        self.assertEqual(self.store.map(), expected_answers)

        self.store.remove_answer(answer_3)
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_first_answer(self):
        answer_1 = Answer(
            group_id="1",
            block_id="1",
            answer_id="2",
            answer_instance=1,
            value=25,
        )
        answer_2 = Answer(
            group_id="1",
            block_id="1",
            answer_id="5",
            answer_instance=1,
            value=65,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        self.store.remove_answer(answer_1)
        expected_answers = {
            "5_1": 65,
        }
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_single_answer_by_group_id(self):
        answer_1 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer1",
            value=10,
        )
        answer_2 = Answer(
            group_id="group2",
            block_id="block1",
            answer_id="answer2",
            value=20,
        )
        answer_3 = Answer(
            group_id="group3",
            block_id="block1",
            answer_id="answer3",
            value=30,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)
        self.store.add(answer_3)

        self.store.remove(group_id="group1")
        expected_answers = {"answer2": 20, "answer3": 30}
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_multiple_answers_by_group_id(self):
        answer_1 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer1",
            value=10,
        )
        answer_2 = Answer(
            group_id="group2",
            block_id="block1",
            answer_id="answer2",
            value=20,
        )
        answer_3 = Answer(
            group_id="group2",
            block_id="block1",
            answer_id="answer3",
            value=30,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)
        self.store.add(answer_3)

        self.store.remove(group_id="group2")
        expected_answers = {
            "answer1": 10,
        }
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_single_answer_by_block_id(self):
        answer_1 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer1",
            value=10,
        )
        answer_2 = Answer(
            group_id="group1",
            block_id="block2",
            answer_id="answer2",
            value=20,
        )
        answer_3 = Answer(
            group_id="group1",
            block_id="block3",
            answer_id="answer3",
            value=30,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)
        self.store.add(answer_3)

        self.store.remove(block_id="block1")
        expected_answers = {"answer2": 20, "answer3": 30}
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_multiple_answers_by_block_id(self):
        answer_1 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer1",
            value=10,
        )
        answer_2 = Answer(
            group_id="group1",
            block_id="block2",
            answer_id="answer2",
            value=20,
        )
        answer_3 = Answer(
            group_id="group1",
            block_id="block2",
            answer_id="answer3",
            value=30,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)
        self.store.add(answer_3)

        self.store.remove(block_id="block2")
        expected_answers = {
            "answer1": 10,
        }
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_multiple_answers_by_location(self):
        answer_1 = Answer(
            group_id="group1",
            group_instance=0,
            block_id="block1",
            answer_id="answer1",
            value=10,
        )
        answer_2 = Answer(
            group_id="group1",
            group_instance=0,
            block_id="block1",
            answer_id="answer2",
            value=20,
        )
        answer_3 = Answer(
            group_id="group1",
            group_instance=0,
            block_id="block2",
            answer_id="answer3",
            value=30,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)
        self.store.add(answer_3)

        location = Location("group1", 0, "block1")

        self.store.remove(location=location)

        expected_answers = {
            "answer3": 30,
        }

        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_answers_by_group_id_that_does_not_exist(self):
        answer_1 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer1",
            value=10,
        )
        answer_2 = Answer(
            group_id="group1",
            block_id="block2",
            answer_id="answer2",
            value=20,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        self.store.remove(group_id="group2")
        expected_answers = {"answer1": 10, "answer2": 20}
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_answers_by_block_id_that_does_not_exist(self):
        answer_1 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer1",
            value=10,
        )
        answer_2 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer2",
            value=20,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        self.store.remove(block_id="block2")
        expected_answers = {"answer1": 10, "answer2": 20}
        self.assertEqual(self.store.map(), expected_answers)

    def test_remove_all_answers(self):
        answer_1 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer1",
            value=10,
        )
        answer_2 = Answer(
            group_id="group1",
            block_id="block1",
            answer_id="answer2",
            value=20,
        )

        self.store.add(answer_1)
        self.store.add(answer_2)

        self.store.remove()
        self.assertEqual(self.store.map(), {})