def post_household_composition(routing_path, schema, metadata, answer_store, **kwargs): group_id = kwargs['group_id'] if _household_answers_changed(answer_store, schema): _remove_repeating_on_household_answers(answer_store, schema) disable_mandatory = any(x in request.form for x in [ 'action[add_answer]', 'action[remove_answer]', 'action[save_sign_out]' ]) current_location = Location(group_id, 0, 'household-composition') block = _get_block_json(current_location, schema, answer_store, metadata) form = post_form_for_location(schema, block, current_location, answer_store, metadata, request.form, disable_mandatory=disable_mandatory) form.validate( ) # call validate here to keep errors in the form object on the context context = _get_context(routing_path, block, current_location, schema, form) if 'action[add_answer]' in request.form: form.household.append_entry() return _render_page(block['type'], context, current_location, schema, answer_store, metadata, routing_path) if 'action[remove_answer]' in request.form: index_to_remove = int(request.form.get('action[remove_answer]')) form.remove_person(index_to_remove) return _render_page(block['type'], context, current_location, schema, answer_store, metadata, routing_path) if 'action[save_sign_out]' in request.form: response = _save_sign_out(routing_path, current_location, form, schema, answer_store, metadata) remove_empty_household_members_from_answer_store(answer_store, schema) return response if form.validate(): questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) update_questionnaire_store_with_answer_data(questionnaire_store, current_location, form.serialise(), schema) metadata = get_metadata(current_user) next_location = path_finder.get_next_location( current_location=current_location) return redirect(next_location.url(metadata)) return _render_page(block['type'], context, current_location, schema, answer_store, metadata, routing_path)
def test_get_first_incomplete_location_in_survey_started_with_repeat(self): schema = load_schema_from_params('test', 'navigation') completed_blocks = [ Location('property-details', 0, 'insurance-type'), Location('property-details', 0, 'insurance-address'), Location('multiple-questions-group', 0, 'household-composition'), Location('extra-cover', 1, 'extra-cover-block'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 1, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('repeating-group', 1, 'repeating-block-2'), Location('extra-cover', 0, 'extra-cover-block'), Location('extra-cover-items-group', 0, 'extra-cover-items'), Location('extra-cover-items-group', 0, 'extra-cover-items-radio'), ] expected_location = Location('extra-cover-items-group', 1, 'extra-cover-items') routing_path = [ Location('multiple-questions-group', 0, 'household-composition'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 1, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('repeating-group', 1, 'repeating-block-2'), Location('extra-cover', 0, 'extra-cover-block'), Location('extra-cover-items-group', 0, 'extra-cover-items'), Location('extra-cover-items-group', 0, 'extra-cover-items-radio'), expected_location, ] answer_store = AnswerStore() answer_store.add_or_update(Answer( answer_instance=0, answer_id='first-name', value='Person1' )) answer_store.add_or_update(Answer( answer_instance=1, answer_id='first-name', value='Person2' )) answer_store.add_or_update(Answer( answer_instance=0, answer_id='extra-cover-answer', value=2 )) progress = Completeness( schema, answer_store, completed_blocks, routing_path, metadata={}) with patch('app.questionnaire.path_finder.evaluate_goto', return_value=True): invalid_location = progress.get_first_incomplete_location_in_survey() self.assertEqual(expected_location, invalid_location)
def test_build_view_context_for_calculation_summary(self): # Given schema = load_schema_from_params('test', 'calculated_summary') block = schema.get_block('currency-total-playback') metadata = { 'tx_id': '12345678-1234-5678-1234-567812345678', 'collection_exercise_sid': '789', 'form_type': 'calculated_summary', 'eq_id': 'test', } answers = [ { 'answer_instance': 0, 'group_instance': 0, 'answer_id': 'first-number-answer', 'value': 1 }, { 'answer_instance': 0, 'group_instance': 0, 'answer_id': 'second-number-answer', 'value': 2 }, { 'answer_instance': 0, 'group_instance': 0, 'answer_id': 'third-number-answer', 'value': 4 }, { 'answer_instance': 0, 'group_instance': 0, 'answer_id': 'fourth-number-answer', 'value': 6 }, ] schema_context = { 'answers': answers, 'group_instance': 0, 'metadata': metadata } current_location = Location('group', 0, 'currency-total-playback') # When with self._application.test_request_context(): view_context = build_view_context(block['type'], metadata, schema, AnswerStore(answers), schema_context, block, current_location, form=None) # Then self.assertTrue('summary' in view_context) self.assertTrue('calculated_question' in view_context['summary']) self.assertEqual( view_context['summary']['title'], 'We calculate the total of currency values entered to be £13.00. Is this correct?' )
def build_path(self): """ Visits all the blocks from a location forwards and returns path taken given a list of answers. :param blocks: A list containing all block content in the survey :param this_location: The location to visit, represented as a dict :return: A list of locations followed through the survey """ this_location = None blocks = [] path = [] block_index = 0 first_groups = self._get_first_group_in_section() for group in self.schema.groups: first_block_in_group = self.schema.get_first_block_id_for_group( group['id']) if not this_location: this_location = Location(group['id'], 0, first_block_in_group) no_of_repeats = get_number_of_repeats(group, self.schema, path, self.answer_store) first_group_instance_index = None for group_instance in range(0, no_of_repeats): if 'skip_conditions' in group: group_instance_id = get_group_instance_id( self.schema, self.answer_store, Location(group['id'], group_instance, first_block_in_group)) if evaluate_skip_conditions(group['skip_conditions'], self.schema, self.metadata, self.answer_store, group_instance, group_instance_id, routing_path=path): continue group_blocks = list( self._build_blocks_for_group(group, group_instance)) blocks += group_blocks if group_blocks and first_group_instance_index is None: # get the group instance of the first instance of a block in this group that has not been skipped first_group_instance_index = group_blocks[0][ 'group_instance'] all_blocks_skipped = first_group_instance_index is None if not all_blocks_skipped: if group['id'] in first_groups: this_location = Location(group['id'], first_group_instance_index, first_block_in_group) if blocks: path, block_index = self._build_path_within_group( blocks, block_index, this_location, path) return RoutingPath(path)
def test_repeating_skipped_section(self): schema = load_schema_from_params('test', 'navigation_confirmation') completed_blocks = [ Location('property-details', 0, 'insurance-type'), Location('property-details', 0, 'insurance-address'), Location('property-details', 0, 'property-interstitial'), Location('house-details', 0, 'house-type'), Location('multiple-questions-group', 0, 'household-composition'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('extra-cover', 0, 'extra-cover-block'), Location('extra-cover', 0, 'extra-cover-interstitial'), Location('payment-details', 0, 'credit-card'), Location('payment-details', 0, 'expiry-date'), Location('payment-details', 0, 'security-code'), Location('payment-details', 0, 'security-code-interstitial'), Location('extra-cover-items-group', 0, 'extra-cover-items'), ] routing_path = [ Location('property-details', 0, 'insurance-type'), Location('property-details', 0, 'insurance-address'), Location('property-details', 0, 'property-interstitial'), Location('house-details', 0, 'house-type'), Location('multiple-questions-group', 0, 'household-composition'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('extra-cover', 0, 'extra-cover-block'), Location('extra-cover', 0, 'extra-cover-interstitial'), Location('payment-details', 0, 'credit-card'), Location('payment-details', 0, 'expiry-date'), Location('payment-details', 0, 'security-code'), Location('payment-details', 0, 'security-code-interstitial'), Location('extra-cover-items-group', 0, 'extra-cover-items'), Location('confirmation-group', 0, 'confirmation'), ] progress = Completeness( schema, AnswerStore(), completed_blocks, routing_path, metadata={}) progress_value = progress.get_state_for_section( 'household-full-names-section') self.assertEqual(Completeness.SKIPPED, progress_value)
def test_serialise_answers(self): form = generate_household_composition_form(self.block_json, { 'household-0-first-name': 'Joe', 'household-0-last-name': 'Bloggs', 'household-1-first-name': 'Bob', 'household-1-last-name': 'Seymour', }, error_messages=self.error_messages) location = Location('who-lives-here', 0, 'household-composition') answers = form.serialise(location) expected_answers = [ { 'group_id': 'who-lives-here', 'group_instance': 0, 'block_id': 'household-composition', 'answer_id': 'first-name', 'answer_instance': 0, 'value': 'Joe' }, { 'group_id': 'who-lives-here', 'group_instance': 0, 'block_id': 'household-composition', 'answer_id': 'middle-names', 'answer_instance': 0, 'value': '' }, { 'group_id': 'who-lives-here', 'group_instance': 0, 'block_id': 'household-composition', 'answer_id': 'last-name', 'answer_instance': 0, 'value': 'Bloggs' }, { 'group_id': 'who-lives-here', 'group_instance': 0, 'block_id': 'household-composition', 'answer_id': 'first-name', 'answer_instance': 1, 'value': 'Bob' }, { 'group_id': 'who-lives-here', 'group_instance': 0, 'block_id': 'household-composition', 'answer_id': 'middle-names', 'answer_instance': 1, 'value': '' }, { 'group_id': 'who-lives-here', 'group_instance': 0, 'block_id': 'household-composition', 'answer_id': 'last-name', 'answer_instance': 1, 'value': 'Seymour' } ] for i, answer in enumerate(answers): self.assertIn(answer.__dict__, expected_answers)
def parent_location(self): parent_id = self._schema.parent_id_map[self.rendered_block["id"]] return Location(section_id=self._current_location.section_id, block_id=parent_id)
def post_block( routing_path, schema, metadata, collection_metadata, answer_store, eq_id, form_type, collection_id, group_id, # pylint: disable=too-many-locals group_instance, block_id): current_location = Location(group_id, group_instance, block_id) completeness = get_completeness(current_user) router = Router(schema, routing_path, completeness, current_location) if not router.can_access_location(): next_location = router.get_next_location() return _redirect_to_location(collection_id, eq_id, form_type, next_location) block = _get_block_json(current_location, schema, answer_store, metadata) schema_context = _get_schema_context(routing_path, current_location, metadata, collection_metadata, answer_store, schema) rendered_block = renderer.render(block, **schema_context) form = _generate_wtf_form(request.form, rendered_block, current_location, schema) if 'action[save_sign_out]' in request.form: return _save_sign_out(routing_path, current_location, form, schema, answer_store, metadata) if 'action[sign_out]' in request.form: return redirect(url_for('session.get_sign_out')) if form.validate(): _set_started_at_metadata_if_required(form, collection_metadata) questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) answer_store_updater = AnswerStoreUpdater(current_location, schema, questionnaire_store) answer_store_updater.save_answers(form) next_location = path_finder.get_next_location( current_location=current_location) if _is_end_of_questionnaire(block, next_location): return submit_answers(routing_path, eq_id, form_type, schema) return redirect(_next_location_url(next_location)) context = build_view_context(block['type'], metadata, schema, answer_store, schema_context, rendered_block, current_location, form) return _render_page(block['type'], context, current_location, schema, answer_store, metadata, routing_path)
def post_household_composition(routing_path, schema, metadata, answer_store, **kwargs): # pylint: disable=too-many-locals group_id = kwargs['group_id'] current_location = Location(group_id, 0, 'household-composition') questionnaire_store = get_questionnaire_store(current_user.user_id, current_user.user_ik) answer_store_updater = AnswerStoreUpdater(current_location, schema, questionnaire_store) answer_store_updater.remove_repeats_for_changed_household_answers( request.form.copy()) disable_mandatory = any(x in request.form for x in [ 'action[add_answer]', 'action[remove_answer]', 'action[save_sign_out]' ]) block = _get_block_json(current_location, schema, answer_store, metadata) form = post_form_for_location(schema, block, current_location, answer_store, metadata, request.form, disable_mandatory=disable_mandatory) form.validate( ) # call validate here to keep errors in the form object on the context context = _get_context(routing_path, block, current_location, schema, form) if 'action[add_answer]' in request.form: form.household.append_entry() return _render_page(block['type'], context, current_location, schema, answer_store, metadata, routing_path) if 'action[remove_answer]' in request.form: index_to_remove = int(request.form.get('action[remove_answer]')) form.remove_person(index_to_remove) return _render_page(block['type'], context, current_location, schema, answer_store, metadata, routing_path) if 'action[save_sign_out]' in request.form: response = _save_sign_out(routing_path, current_location, form, schema, answer_store, metadata) answer_store_updater.remove_empty_household_members() return response if form.validate(): answer_store_updater.save_answers(form) metadata = get_metadata(current_user) next_location = path_finder.get_next_location( current_location=current_location) return redirect(next_location.url(metadata)) return _render_page(block['type'], context, current_location, schema, answer_store, metadata, routing_path)
def test_context_for_driving_question_summary(): schema = load_schema_from_name("test_list_collector_driving_question") summary_context = SectionSummaryContext( DEFAULT_LANGUAGE_CODE, schema, AnswerStore( [ {"answer_id": "anyone-usually-live-at-answer", "value": "Yes"}, {"answer_id": "first-name", "value": "Toni", "list_item_id": "PlwgoG"}, { "answer_id": "last-name", "value": "Morrison", "list_item_id": "PlwgoG", }, ] ), ListStore([{"items": ["PlwgoG"], "name": "people"}]), ProgressStore(), {}, {}, current_location=Location(section_id="section"), routing_path=RoutingPath( ["anyone-usually-live-at", "anyone-else-live-at"], section_id="section" ), ) context = summary_context() expected = { "summary": { "answers_are_editable": True, "collapsible": False, "custom_summary": [ { "add_link": "/questionnaire/people/add-person/?return_to=section-summary", "add_link_text": "Add someone to this household", "empty_list_text": "There are no householders", "list": { "editable": True, "list_items": [ { "item_title": "Toni Morrison", "primary_person": False, "edit_link": "/questionnaire/people/PlwgoG/edit-person/?return_to=section-summary", "remove_link": "/questionnaire/people/PlwgoG/remove-person/?return_to=section-summary", "list_item_id": "PlwgoG", } ], }, "list_name": "people", "title": "Household members", "type": "List", } ], "page_title": "List Collector Driving Question Summary", "summary_type": "SectionSummary", "title": "List Collector Driving Question Summary", } } assert context == expected
def parent_location(self): return Location( section_id=self._current_location.section_id, block_id=self.relationships_block["id"], )
def test_context_for_section_list_summary(people_answer_store): schema = load_schema_from_name("test_list_collector_section_summary") summary_context = SectionSummaryContext( language=DEFAULT_LANGUAGE_CODE, schema=schema, answer_store=people_answer_store, list_store=ListStore( [ {"items": ["PlwgoG", "UHPLbX"], "name": "people"}, {"items": ["gTrlio"], "name": "visitors"}, ] ), progress_store=ProgressStore(), metadata={"display_address": "70 Abingdon Road, Goathill"}, response_metadata={}, current_location=Location(section_id="section"), routing_path=RoutingPath( [ "primary-person-list-collector", "list-collector", "visitor-list-collector", ], section_id="section", ), ) context = summary_context() expected = { "summary": { "answers_are_editable": True, "collapsible": False, "custom_summary": [ { "add_link": "/questionnaire/people/add-person/?return_to=section-summary", "add_link_text": "Add someone to this household", "empty_list_text": "There are no householders", "list": { "editable": True, "list_items": [ { "edit_link": "/questionnaire/people/PlwgoG/edit-person/?return_to=section-summary", "item_title": "Toni Morrison", "primary_person": False, "remove_link": "/questionnaire/people/PlwgoG/remove-person/?return_to=section-summary", "list_item_id": "PlwgoG", }, { "edit_link": "/questionnaire/people/UHPLbX/edit-person/?return_to=section-summary", "item_title": "Barry Pheloung", "primary_person": False, "remove_link": "/questionnaire/people/UHPLbX/remove-person/?return_to=section-summary", "list_item_id": "UHPLbX", }, ], }, "list_name": "people", "title": "Household members staying overnight on 13 October 2019 at 70 Abingdon Road, Goathill", "type": "List", }, { "add_link": "/questionnaire/visitors/add-visitor/?return_to=section-summary", "add_link_text": "Add another visitor to this household", "empty_list_text": "There are no visitors", "list": { "editable": True, "list_items": [ { "edit_link": "/questionnaire/visitors/gTrlio/edit-visitor-person/?return_to=section-summary", "item_title": "", "primary_person": False, "remove_link": "/questionnaire/visitors/gTrlio/remove-visitor/?return_to=section-summary", "list_item_id": "gTrlio", } ], }, "list_name": "visitors", "title": "Visitors staying overnight on 13 October 2019 at 70 Abingdon Road, Goathill", "type": "List", }, ], "page_title": "People who live here and overnight visitors", "summary_type": "SectionSummary", "title": "People who live here and overnight visitors", } } assert context == expected
def get_introduction(eq_id, form_type, collection_id): # pylint: disable=unused-argument group_id = SchemaHelper.get_first_group_id(g.schema_json) current_location = Location(group_id, group_instance=0, block_id='introduction') return _build_template(current_location, get_introduction_context(g.schema_json))
def build_path(self, blocks, this_location): """ Visits all the blocks from a location forwards and returns path taken given a list of answers. :param blocks: A list containing all block content in the survey :param this_location: The location to visit, represented as a dict :return: A list of locations followed through the survey """ path = [] block_index = 0 blocks_len = len(blocks) # Keep going unless we've hit the last block while block_index < blocks_len: if this_location.block_id in self.CLOSING_INTERSTITIAL_PATH: return path block_index = PathFinder._block_index_for_location( blocks, this_location) if block_index is None: logger.error( 'build_path: _block_index_for_location %s is None (invalid location)', this_location) return path path.append(this_location) block = blocks[block_index]["block"] # If routing rules exist then a rule must match (i.e. default goto) if 'routing_rules' in block and len(block['routing_rules']) > 0: original_this_location = copy.copy(this_location) for rule in filter(is_goto_rule, block['routing_rules']): should_goto = evaluate_goto(rule['goto'], self.metadata, self.answer_store, this_location.group_instance) if should_goto: next_location = copy.copy(this_location) next_location.block_id = rule['goto']['id'] next_block_index = PathFinder._block_index_for_location( blocks, next_location) next_precedes_current = next_block_index is not None and next_block_index < block_index if next_precedes_current: self._remove_rule_answers(rule['goto'], this_location) this_location = next_location break # If we haven't changed location based on routing rules then we can't progress any further if this_location == original_this_location: break # No routing rules, so if this isn't the last block, step forward a block elif block_index < len(blocks) - 1: this_location = Location( blocks[block_index + 1]['group_id'], blocks[block_index + 1]['group_instance'], blocks[block_index + 1]['block']['id']) # If we've reached last block stop evaluating the path else: break return path
def test_post_form_for_household_relationship_driven_by_multiple_answers( self): with self.app_request_context(): schema = load_schema_from_params( 'test', 'routing_on_answer_from_driving_repeating_group') block_json = schema.get_block('relationships') location = Location('household-relationships', 0, 'relationships') primary_group_instance_id = str(uuid.uuid4()) repeating_group_1_instance_id = str(uuid.uuid4()) repeating_group_2_instance_id = str(uuid.uuid4()) answer_store = AnswerStore({}) answer_store.add( Answer(answer_id='primary-name', value='Jon', group_instance=0, group_instance_id=primary_group_instance_id)) answer_store.add( Answer(answer_id='primary-live-here', value='No', group_instance=0, group_instance_id=primary_group_instance_id)) answer_store.add( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='Yes', group_instance=0, )) answer_store.add( Answer(answer_id='repeating-name', answer_instance=0, value='Adam', group_instance=0, group_instance_id=repeating_group_1_instance_id)) answer_store.add( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='Yes', group_instance=1, )) answer_store.add( Answer(answer_id='repeating-name', answer_instance=0, value='Ben', group_instance=1, group_instance_id=repeating_group_2_instance_id)) answer_store.add( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='No', group_instance=2, )) answer = schema.get_answers_for_block('relationships')[0] form = post_form_for_location( schema, block_json, location, answer_store, metadata=None, request_form=MultiDict( {'{answer_id}-0'.format(answer_id=answer['id']): '3'})) self.assertTrue(hasattr(form, answer['id'])) field_list = getattr(form, answer['id']) # With three people, we need to define 2 relationships self.assertEqual(len(field_list.entries), 2) # Check the data matches what was passed from request self.assertEqual(field_list.entries[0].data, '3')
def _is_answer_on_path(schema, answer, routing_path): answer_schema = schema.get_answer(answer['answer_id']) question_schema = schema.get_question(answer_schema['parent_id']) block_schema = schema.get_block(question_schema['parent_id']) location = Location(block_schema['parent_id'], answer['group_instance'], block_schema['id']) return location in routing_path
def parent_location(self): return Location( section_id=self._current_location.section_id, block_id=self.rendered_block["parent_id"], )
def test_converter_checkboxes_with_q_codes_and_other_value(self): with self.application.test_request_context(): routing_path = [Location(group_id='favourite food', group_instance=0, block_id='crisps')] answers = [create_answer('crisps-answer', ['Ready salted', 'other', 'Bacon'], group_id='favourite food', block_id='crisps')] questionnaire = { "survey_id": "999", "data_version": "0.0.1", "groups": [ { "id": "favourite food", "blocks": [ { "id": "crisps", "sections": [ { "questions": [{ "id": 'crisps-question', "answers": [ { "id": "crisps-answer", "type": "Checkbox", "options": [ { "label": "Ready salted", "value": "Ready salted", "q_code": "1" }, { "label": "Sweet chilli", "value": "Sweet chilli", "q_code": "2" }, { "label": "Cheese and onion", "value": "Cheese and onion", "q_code": "3" }, { "label": "Other", "value": "other", "q_code": "4", "description": "Choose any other flavour", "other": { "label": "Please specify other" } } ] } ] }] } ] } ] } ] } # When answer_object = convert_answers(metadata, questionnaire, AnswerStore(answers), routing_path) # Then self.assertEqual(len(answer_object['data']), 2) self.assertEqual(answer_object['data']['1'], 'Ready salted') self.assertEqual(answer_object['data']['4'], 'Bacon')
def test_when_rule_comparing_answer_values(self): answers = { "low": Answer(answer_id="low", value=1), "medium": Answer(answer_id="medium", value=5), "high": Answer(answer_id="high", value=10), "list_answer": Answer(answer_id="list_answer", value=["a", "abc", "cba"]), "other_list_answer": Answer(answer_id="other_list_answer", value=["x", "y", "z"]), "other_list_answer_2": Answer(answer_id="other_list_answer_2", value=["a", "abc", "cba"]), "text_answer": Answer(answer_id="small_string", value="abc"), "other_text_answer": Answer(answer_id="other_string", value="xyz"), } # An answer that won't be added to the answer store. missing_answer = Answer(answer_id="missing", value=1) param_list = [ (answers["medium"], "equals", answers["medium"], True), (answers["medium"], "equals", answers["low"], False), (answers["medium"], "greater than", answers["low"], True), (answers["medium"], "greater than", answers["high"], False), (answers["medium"], "less than", answers["high"], True), (answers["medium"], "less than", answers["low"], False), (answers["medium"], "equals", missing_answer, False), (answers["list_answer"], "contains", answers["text_answer"], True), (answers["list_answer"], "contains", answers["other_text_answer"], False), ( answers["list_answer"], "not contains", answers["other_text_answer"], True, ), (answers["list_answer"], "not contains", answers["text_answer"], False), ( answers["list_answer"], "contains any", answers["other_list_answer_2"], True, ), ( answers["list_answer"], "contains any", answers["other_list_answer"], False, ), ( answers["list_answer"], "contains all", answers["other_list_answer"], False, ), ( answers["list_answer"], "contains all", answers["other_list_answer_2"], True, ), (answers["text_answer"], "equals any", answers["list_answer"], True), (answers["text_answer"], "equals any", answers["other_list_answer"], False), ( answers["text_answer"], "not equals any", answers["other_list_answer"], True, ), (answers["text_answer"], "not equals any", answers["list_answer"], False), ] for lhs, comparison, rhs, expected_result in param_list: # Given with self.subTest(lhs=lhs, comparison=comparison, rhs=rhs, expected_result=expected_result): answer_store = AnswerStore() for answer in answers.values(): answer_store.add_or_update(answer) when = [{ "id": lhs.answer_id, "condition": comparison, "comparison": { "id": rhs.answer_id, "source": "answers" }, }] current_location = Location(section_id="some-section", block_id="some-block") self.assertEqual( evaluate_when_rules( when_rules=when, schema=get_schema(), metadata={}, answer_store=answer_store, list_store=ListStore(), current_location=current_location, routing_path_block_ids=None, ), expected_result, )
def _deserialise(raw_data): data = json.loads(raw_data) data['COMPLETED_BLOCKS'] = [Location.from_dict(location_dict=completed_block) for completed_block in data['COMPLETED_BLOCKS']] return data
def get_last_location_in_survey(self): last_section_id = self._schema.get_section_ids()[-1] last_block_id = self._schema.get_last_block_id_for_section( last_section_id) return Location(section_id=last_section_id, block_id=last_block_id)
def test_convert_answers(self): with self._app.test_request_context(): user_answer = [ create_answer('ABC', '2016-01-01', group_id='group-1', block_id='block-1'), create_answer('DEF', '2016-03-30', group_id='group-1', block_id='block-1') ] questionnaire = { 'survey_id': '021', 'data_version': '0.0.1', 'sections': [{ 'id': 'section-1', 'groups': [{ 'id': 'group-1', 'blocks': [{ 'id': 'block-1', 'questions': [{ 'id': 'question-1', 'answers': [{ 'id': 'ABC', 'type': 'TextField', 'q_code': '001' }, { 'id': 'DEF', 'type': 'TextField', 'q_code': '002' }] }] }] }] }] } routing_path = [ Location(group_id='group-1', group_instance=0, block_id='block-1') ] answer_object = convert_answers(self.metadata, QuestionnaireSchema(questionnaire), AnswerStore(user_answer), routing_path) self.assertEqual(answer_object['type'], 'uk.gov.ons.edc.eq:surveyresponse') self.assertEqual(answer_object['version'], '0.0.1') self.assertEqual(answer_object['origin'], 'uk.gov.ons.edc.eq') self.assertEqual(answer_object['survey_id'], '021') self.assertEqual(answer_object['collection']['exercise_sid'], self.metadata['collection_exercise_sid']) self.assertEqual(answer_object['collection']['instrument_id'], self.metadata['form_type']) self.assertEqual(answer_object['collection']['period'], self.metadata['period_id']) self.assertEqual(answer_object['metadata']['user_id'], self.metadata['user_id']) self.assertEqual(answer_object['metadata']['ru_ref'], self.metadata['ru_ref']) self.assertEqual(answer_object['data']['001'], '2016-01-01') self.assertEqual(answer_object['data']['002'], '2016-03-30')
def location(): return Location("test-section", "test-block", "test-list", "list_item_id")
def test_repeat_on_answer_count_when_not_all_answers_on_path(self): schema = load_schema_from_params('test', 'routing_repeat_until') primary_group_instance_id = str(uuid.uuid4()) repeating_group_1_instance_id = str(uuid.uuid4()) repeating_group_2_instance_id = str(uuid.uuid4()) answer_store = AnswerStore({}) answer_store.add_or_update( Answer(answer_id='primary-name', value='Jon', group_instance=0, group_instance_id=primary_group_instance_id)) answer_store.add_or_update( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='Yes', group_instance=0, )) answer_store.add_or_update( Answer(answer_id='repeating-name', answer_instance=0, value='Adam', group_instance=0, group_instance_id=repeating_group_1_instance_id)) answer_store.add_or_update( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='No', group_instance=1, )) answer_store.add_or_update( Answer(answer_id='repeating-name', answer_instance=0, value='Ben', group_instance=1, group_instance_id=repeating_group_2_instance_id)) answer_store.add_or_update( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='No', group_instance=2, )) routing_path = [ Location('primary-group', 0, 'primary-name-block'), Location('repeating-group', 0, 'repeating-anyone-else-block'), Location('repeating-group', 0, 'repeating-name-block'), Location('repeating-group', 1, 'repeating-anyone-else-block'), ] repeat = { 'type': 'answer_count', 'answer_ids': ['primary-name', 'repeating-name'] } # When number_of_repeats = evaluate_repeat(repeat, answer_store, schema, routing_path) self.assertEqual(number_of_repeats, 2)
def test_skipped_group(self): schema = load_schema_from_params('test', 'navigation') completed_blocks = [ Location('property-details', 0, 'insurance-type'), Location('property-details', 0, 'insurance-address'), Location('property-details', 0, 'property-interstitial'), Location('house-details', 0, 'house-type'), Location('multiple-questions-group', 0, 'household-composition'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('extra-cover', 0, 'extra-cover-block'), Location('extra-cover', 0, 'extra-cover-interstitial'), Location('payment-details', 0, 'credit-card'), Location('payment-details', 0, 'expiry-date'), Location('payment-details', 0, 'security-code'), Location('payment-details', 0, 'security-code-interstitial'), Location('extra-cover-items-group', 0, 'extra-cover-items'), ] routing_path = [ Location('property-details', 0, 'insurance-type'), Location('property-details', 0, 'insurance-address'), Location('property-details', 0, 'property-interstitial'), Location('house-details', 0, 'house-type'), Location('multiple-questions-group', 0, 'household-composition'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('payment-details', 0, 'credit-card'), Location('payment-details', 0, 'expiry-date'), Location('payment-details', 0, 'security-code'), Location('payment-details', 0, 'security-code-interstitial'), Location('extra-cover', 0, 'extra-cover-block'), Location('extra-cover', 0, 'extra-cover-interstitial'), Location('extra-cover-items-group', 0, 'extra-cover-items'), Location('summary-group', 0, 'summary'), ] progress = Completeness( schema, AnswerStore(), completed_blocks, routing_path, metadata={}) with patch('app.questionnaire.path_finder.evaluate_skip_conditions', return_value=True): progress_value = progress.get_state_for_group('payment-details') self.assertEqual(Completeness.SKIPPED, progress_value)
def test_should_repeat_until(self): questionnaire = { 'survey_id': '021', 'data_version': '0.0.1', 'sections': [{ 'id': 'section1', 'groups': [{ 'id': 'group-1', 'blocks': [{ 'id': 'block-1', 'questions': [{ 'id': 'question-2', 'answers': [{ 'id': 'my_answer', 'type': 'TextField' }] }] }] }] }] } schema = QuestionnaireSchema(questionnaire) # Given repeat = { 'type': 'until', 'when': [{ 'id': 'my_answer', 'condition': 'equals', 'value': 'Done' }] } answer_store = AnswerStore({}) current_path = [Location('group-1', 0, 'block-1')] # The schema doesn't actually contain a repeat clause, so fake it. with patch.object(schema, 'answer_is_in_repeating_group', return_value=True): self.assertEqual( evaluate_repeat(repeat, answer_store, schema, current_path), 1) answer_store.add_or_update( Answer(answer_id='my_answer', value='Not Done', group_instance=0, group_instance_id=None)) self.assertEqual( evaluate_repeat(repeat, answer_store, schema, current_path), 2) answer_store.add_or_update( Answer(answer_id='my_answer', value='Done', group_instance=1, group_instance_id=None)) self.assertEqual( evaluate_repeat(repeat, answer_store, schema, current_path), 2)
def test_get_first_incomplete_location_in_survey_completed(self): schema = load_schema_from_params('test', 'navigation') # interstitial blocks have been removed completed_blocks = [ Location('property-details', 0, 'insurance-type'), Location('property-details', 0, 'insurance-address'), Location('house-details', 0, 'house-type'), Location('multiple-questions-group', 0, 'household-composition'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('extra-cover', 0, 'extra-cover-block'), Location('payment-details', 0, 'credit-card'), Location('payment-details', 0, 'expiry-date'), Location('payment-details', 0, 'security-code'), Location('extra-cover-items-group', 0, 'extra-cover-items'), Location('extra-cover-items-group', 0, 'extra-cover-items-radio'), Location('skip-payment-group', 0, 'skip-payment'), ] routing_path = [ Location('property-details', 0, 'insurance-type'), Location('property-details', 0, 'insurance-address'), Location('property-details', 0, 'property-interstitial'), Location('house-details', 0, 'house-type'), Location('multiple-questions-group', 0, 'household-composition'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('extra-cover', 0, 'extra-cover-block'), Location('extra-cover', 0, 'extra-cover-interstitial'), Location('payment-details', 0, 'credit-card'), Location('payment-details', 0, 'expiry-date'), Location('payment-details', 0, 'security-code'), Location('payment-details', 0, 'security-code-interstitial'), Location('extra-cover-items-group', 0, 'extra-cover-items'), Location('extra-cover-items-group', 0, 'extra-cover-items-radio'), Location('skip-payment-group', 0, 'skip-payment'), Location('confirmation-group', 0, 'confirmation'), ] progress = Completeness( schema, AnswerStore(), completed_blocks, routing_path, metadata={}) with patch('app.questionnaire.path_finder.evaluate_goto', return_value=False): self.assertFalse(progress.get_first_incomplete_location_in_survey())
def test_get_form_for_household_relationship_driven_by_multiple_answers( self): with self.app_request_context(): schema = load_schema_from_params( 'test', 'routing_on_answer_from_driving_repeating_group') block_json = schema.get_block('relationships') location = Location('household-relationships', 0, 'relationships') error_messages = schema.error_messages primary_group_instance_id = str(uuid.uuid4()) repeating_group_1_instance_id = str(uuid.uuid4()) repeating_group_2_instance_id = str(uuid.uuid4()) answer_store = AnswerStore({}) answer_store.add( Answer(answer_id='primary-name', value='Jon', group_instance=0, group_instance_id=primary_group_instance_id)) answer_store.add( Answer(answer_id='primary-live-here', value='No', group_instance=0, group_instance_id=primary_group_instance_id)) answer_store.add( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='Yes', group_instance=0, )) answer_store.add( Answer(answer_id='repeating-name', answer_instance=0, value='Adam', group_instance=0, group_instance_id=repeating_group_1_instance_id)) answer_store.add( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='Yes', group_instance=1, )) answer_store.add( Answer(answer_id='repeating-name', answer_instance=0, value='Ben', group_instance=1, group_instance_id=repeating_group_2_instance_id)) answer_store.add( Answer( answer_id='repeating-anyone-else', answer_instance=0, value='No', group_instance=2, )) form = get_form_for_location(schema, block_json, location, answer_store, error_messages) answer = schema.get_answers_for_block('relationships')[0] self.assertTrue(hasattr(form, answer['id'])) field_list = getattr(form, answer['id']) # With three people, we need to define 2 relationships self.assertEqual(len(field_list.entries), 2)
def get_first_location(cls, survey_json): return Location( group_id=cls.get_first_group_id(survey_json), group_instance=0, block_id=cls.get_first_block_id(survey_json), )
def parent_location(self): return Location(section_id=self._current_location.section_id, block_id=self.block["id"])