def test_build_path_with_invalid_location(self): path_finder = PathFinder(survey_json={}) blocks = [ { "group_id": 'first-valid-group-id', "group_instance": 0, "block": {"id": 'first-valid-block-id', 'type': 'questionnaire'} }, { "group_id": 'second-valid-group-id', "group_instance": 0, "block": {"id": 'second-valid-block-id', 'type': 'questionnaire'} } ] invalid_group_location = Location( group_instance=0, group_id='this-group-id-doesnt-exist-in-the-list-of-blocks', block_id='first-valid-block-id' ) self.assertEqual([], path_finder.build_path(blocks, invalid_group_location)) invalid_block_location = Location( group_instance=0, group_id='second-valid-group-id', block_id='this-block-id-doesnt-exist-in-the-list-of-blocks' ) self.assertEqual([], path_finder.build_path(blocks, invalid_block_location))
def test_routing_path(self): schema = load_schema_from_name("test_summary") section_id = schema.get_section_id_for_block_id("dessert") expected_path = RoutingPath( ["radio", "dessert", "dessert-confirmation", "numbers", "summary"], section_id="default-section", ) progress_store = ProgressStore([{ "section_id": "default-section", "list_item_id": None, "status": CompletionStatus.COMPLETED, "block_ids": [ "radio", "dessert", "dessert-confirmation", "numbers", ], }]) path_finder = PathFinder(schema, self.answer_store, self.list_store, progress_store, self.metadata) routing_path = path_finder.routing_path(section_id=section_id) self.assertEqual(routing_path, expected_path)
def test_routing_path_should_not_skip_group(self): # Given schema = load_schema_from_name("test_skip_condition_group") section_id = schema.get_section_id_for_block_id("do-you-want-to-skip") answer_store = AnswerStore() answer_store.add_or_update( Answer(answer_id="do-you-want-to-skip-answer", value="No")) progress_store = ProgressStore([{ "section_id": "default-section", "list_item_id": None, "status": CompletionStatus.COMPLETED, "block_ids": ["do-you-want-to-skip"], }]) # When path_finder = PathFinder(schema, answer_store, self.list_store, progress_store, self.metadata) routing_path = path_finder.routing_path(section_id=section_id) # Then expected_routing_path = RoutingPath( [ "do-you-want-to-skip", "should-skip", "last-group-block", "summary" ], section_id="default-section", ) with patch("app.questionnaire.path_finder.evaluate_skip_conditions", return_value=False): self.assertEqual(routing_path, expected_routing_path)
def test_new_routing_path_should_skip_group(self): # Given schema = load_schema_from_name("test_new_skip_condition_group") section_id = schema.get_section_id_for_block_id("do-you-want-to-skip") answer_store = AnswerStore() answer_store.add_or_update( Answer(answer_id="do-you-want-to-skip-answer", value="Yes")) progress_store = ProgressStore([{ "section_id": "default-section", "list_item_id": None, "status": CompletionStatus.COMPLETED, "block_ids": ["do-you-want-to-skip"], }]) # When path_finder = PathFinder( schema, answer_store, self.list_store, progress_store, self.metadata, self.response_metadata, ) routing_path = path_finder.routing_path(section_id=section_id) # Then expected_routing_path = RoutingPath( ["do-you-want-to-skip"], section_id="default-section", ) self.assertEqual(routing_path, expected_routing_path)
def test_routing_path_with_conditional_path(self): schema = load_schema_from_name("test_new_routing_number_equals") section_id = schema.get_section_id_for_block_id("number-question") expected_path = RoutingPath( ["number-question", "correct-answer"], section_id="default-section", ) answer = Answer(answer_id="answer", value=123) answer_store = AnswerStore() answer_store.add_or_update(answer) progress_store = ProgressStore([{ "section_id": "default-section", "list_item_id": None, "status": CompletionStatus.COMPLETED, "block_ids": ["number-question"], }]) path_finder = PathFinder( schema, answer_store, self.list_store, progress_store, self.metadata, self.response_metadata, ) routing_path = path_finder.routing_path(section_id=section_id) self.assertEqual(routing_path, expected_path)
def test_repeating_group_block_skip_correct_if_independent_answer_is_not_repeating_but_causes_a_skip(self): """ tests blocks are correctly added to path in the case where the blocks in a repeating group have skip conditions and those skip conditions are dependant on a non repeating answer which causes a skip condition to return true. """ # Given independent_answer = '3' expected_path = [ Location('about-household-group', 0, 'household-composition'), Location('independent-answer-group', 0, 'non-repeating-question-block'), Location('questionnaire-completed', 0, 'confirmation') ] schema = load_schema_from_params('test', 'skip_conditions_from_repeating_group_based_on_non_repeating_answer') answer_store = AnswerStore() # Set up some first names answer_store.add(Answer(answer_id='first-name', value='aaa', group_instance=0)) answer_store.add(Answer(answer_id='first-name', value='bbb', group_instance=1)) answer_store.add(Answer(answer_id='first-name', value='ccc', group_instance=2)) # Set up the independent answer answer_store.add(Answer(answer_id='an-independent-answer', value=independent_answer, group_instance=0)) path_finder = PathFinder(schema, answer_store=answer_store, metadata={}, completed_blocks=[]) # When with patch('app.questionnaire.path_finder.evaluate_skip_conditions', return_value=True): new_path = path_finder.get_full_routing_path() # Then self.assertEqual(expected_path, new_path)
def test_new_routing_basic_and_conditional_path(self): # Given schema = load_schema_from_name("test_new_routing_number_equals") section_id = schema.get_section_id_for_block_id("number-question") expected_path = RoutingPath( ["number-question", "correct-answer"], section_id="default-section", ) answer_1 = Answer(answer_id="answer", value=123) answer_store = AnswerStore() answer_store.add_or_update(answer_1) # When path_finder = PathFinder( schema, answer_store, self.list_store, self.progress_store, self.metadata, self.response_metadata, ) routing_path = path_finder.routing_path(section_id=section_id) # Then self.assertEqual(routing_path, expected_path)
def build_summary_rendering_context(schema, sections, answer_store, metadata, schema_context): """ Build questionnaire summary context containing metadata and content from the answers of the questionnaire :param schema: schema of the current questionnaire :param sections: the sections of the current schema :param answer_store: all of the answers to the questionnaire :param metadata: all of the metadata :param schema_context: The schema context :return: questionnaire summary context """ navigator = PathFinder(schema, answer_store, metadata, []) path = navigator.get_full_routing_path() groups = [] group_lists = (section['groups'] for section in sections) group_ids_on_path = [location.group_id for location in path] for group in itertools.chain.from_iterable(group_lists): if (group['id'] in group_ids_on_path and schema.group_has_questions(group['id'])): no_of_repeats = _number_of_repeats(group, path) repeating_groups = [] for instance_idx in range(0, no_of_repeats): summary_group = Group(group, path, answer_store, metadata, schema, instance_idx, schema_context).serialize() repeating_groups.append(summary_group) groups.extend(repeating_groups) return groups
def test_next_location_goto_summary(self): survey = load_schema_file("0_star_wars.json") expected_path = [ Location("14ba4707-321d-441d-8d21-b8367366e766", 0, 'introduction'), Location("14ba4707-321d-441d-8d21-b8367366e766", 0, 'choose-your-side-block'), Location("14ba4707-321d-441d-8d21-b8367366e766", 0, 'summary'), ] answer = Answer( group_id="14ba4707-321d-441d-8d21-b8367366e766", group_instance=0, block_id="choose-your-side-block", answer_id="ca3ce3a3-ae44-4e30-8f85-5b6a7a2fb23c", value="I prefer Star Trek", ) answers = AnswerStore() answers.add(answer) path_finder = PathFinder(survey, answer_store=answers) current_location = expected_path[1] expected_next_location = expected_path[2] next_location = path_finder.get_next_location( current_location=current_location) self.assertEqual(next_location, expected_next_location)
def login(): """ Initial url processing - expects a token parameter and then will authenticate this token. Once authenticated it will be placed in the users session :return: a 302 redirect to the next location for the user """ logger.new() # logging in again clears any session state if session: session.clear() logger.debug("attempting token authentication") authenticator.jwt_login(request) logger.debug("token authenticated - linking to session") metadata = get_metadata(current_user) eq_id = metadata["eq_id"] form_type = metadata["form_type"] logger.bind(eq_id=eq_id, form_type=form_type) if not eq_id or not form_type: logger.error("missing eq id or form type in jwt") raise NotFound json = get_schema(metadata) navigator = PathFinder(json, get_answer_store(current_user), metadata) current_location = navigator.get_latest_location( get_completed_blocks(current_user)) return redirect(current_location.url(metadata))
def build_summary_rendering_context(schema, sections, answer_store, metadata): """ Build questionnaire summary context containing metadata and content from the answers of the questionnaire :param schema: schema of the current questionnaire :param sections: the sections of the current schema :param answer_store: all of the answers to the questionnaire :param metadata: all of the metadata :return: questionnaire summary context """ navigator = PathFinder(schema, answer_store, metadata, []) path = navigator.get_full_routing_path() groups = [] group_lists = (section['groups'] for section in sections) group_ids_on_path = [location.group_id for location in path] for group in itertools.chain.from_iterable(group_lists): if group['id'] in group_ids_on_path \ and schema.group_has_questions(group['id']): groups.extend([ Group(group, path, answer_store, metadata, schema).serialize() ]) return groups
def build_summary_rendering_context(schema_json, answer_store, metadata): """ Build questionnaire summary context containing metadata and content from the answers of the questionnaire :param schema_json: schema of the current questionnaire :param answer_store: all of the answers to the questionnaire :param metadata: all of the metadata :return: questionnaire summary context """ navigator = PathFinder(schema_json, answer_store, metadata) path = navigator.get_routing_path() sections = [] for group in schema_json['groups']: for block in group['blocks']: if block['id'] in [location.block_id for location in path]: if "type" not in block or block['type'] != "interstitial": link = url_for( 'questionnaire.get_block', eq_id=metadata['eq_id'], form_type=metadata['form_type'], collection_id=metadata['collection_exercise_sid'], group_id=group['id'], group_instance=0, block_id=block['id']) sections.extend([ Section(section, answer_store.map(), link) for section in block['sections'] ]) return sections
def test_evaluate_skip_condition_returns_false_when_no_skip_condition( self): # Given skip_conditions = None answer_store = AnswerStore() current_location = Location(section_id="some-section", block_id="some-block") path_finder = PathFinder( get_schema(), answer_store, list_store=ListStore(), metadata={}, progress_store=ProgressStore(), response_metadata={}, ) routing_path_block_ids = [] # When condition = path_finder.evaluate_skip_conditions( current_location, routing_path_block_ids, skip_conditions) # Then self.assertFalse(condition)
def get_block(eq_id, form_type, collection_id, group_id, group_instance, block_id): # pylint: disable=unused-argument # Filter answers down to those we may need to render answer_store = get_answer_store(current_user) path_finder = PathFinder(g.schema_json, answer_store, get_metadata(current_user)) current_location = Location(group_id, group_instance, block_id) valid_group = group_id in SchemaHelper.get_group_ids(g.schema_json) if not valid_group or current_location not in path_finder.get_routing_path(group_id, group_instance): raise NotFound block = _render_schema(current_location) error_messages = SchemaHelper.get_messages(g.schema_json) form, template_params = get_form_for_location(block, current_location, answer_store, error_messages) content = {'form': form, 'block': block} if template_params: content.update(template_params) template = block['type'] if block and 'type' in block and block['type'] else 'questionnaire' return _build_template(current_location, content, template)
def test_next_location_empty_routing_rules(self): schema = load_schema_from_params('test', 'checkbox') schema.answer_is_in_repeating_group = MagicMock(return_value=False) expected_path = [ Location('checkboxes', 0, 'mandatory-checkbox'), Location('checkboxes', 0, 'non-mandatory-checkbox'), Location('checkboxes', 0, 'summary') ] answer_1 = Answer( answer_id='mandatory-checkbox-answer', value='Cheese', ) answer_2 = Answer( answer_id='non-mandatory-checkbox-answer', value='deep pan', ) answers = AnswerStore() answers.add(answer_1) answers.add(answer_2) path_finder = PathFinder(schema, answer_store=answers, metadata={}, completed_blocks=[]) current_location = expected_path[0] expected_next_location = expected_path[1] next_location = path_finder.get_next_location(current_location=current_location) self.assertEqual(next_location, expected_next_location)
def test_repeating_groups_no_of_answers_minus_one(self): survey = load_schema_file("test_repeating_household.json") # Default is to count answers, so switch to using value survey['groups'][-1]['routing_rules'][0]['repeat'][ 'type'] = 'answer_count_minus_one' expected_path = [ Location("multiple-questions-group", 0, "household-composition"), Location("repeating-group", 0, "repeating-block-1"), Location("repeating-group", 0, "repeating-block-2"), ] answer = Answer(group_id="multiple-questions-group", group_instance=0, answer_instance=0, answer_id="first-name", block_id="household-composition", value="Joe Bloggs") answer_2 = Answer(group_id="multiple-questions-group", group_instance=0, answer_instance=1, answer_id="first-name", block_id="household-composition", value="Sophie Bloggs") answers = AnswerStore() answers.add(answer) answers.add(answer_2) path_finder = PathFinder(survey, answer_store=answers) self.assertEqual(expected_path, path_finder.get_routing_path())
def test_repeating_groups(self): schema = load_schema_from_params('test', 'repeating_household') schema.answer_is_in_repeating_group = MagicMock(return_value=False) # Default is to count answers, so switch to using value schema.json['sections'][-2]['groups'][0]['routing_rules'][0]['repeat']['type'] = 'answer_value' expected_path = [ Location('multiple-questions-group', 0, 'introduction'), Location('multiple-questions-group', 0, 'household-composition'), Location('repeating-group', 0, 'repeating-block-1'), Location('repeating-group', 0, 'repeating-block-2'), Location('repeating-group', 0, 'repeating-block-3'), Location('repeating-group', 1, 'repeating-block-1'), Location('repeating-group', 1, 'repeating-block-2'), Location('repeating-group', 1, 'repeating-block-3'), Location('summary-group', 0, 'summary'), ] answer = Answer( group_instance=0, answer_id='first-name', value='2' ) answers = AnswerStore() answers.add(answer) path_finder = PathFinder(schema, answer_store=answers, metadata={}, completed_blocks=[]) self.assertEqual(expected_path, path_finder.get_full_routing_path())
def test_get_routing_path_when_first_block_in_group_skipped(self): # Given survey = load_schema_file('test_skip_condition_group.json') answer_store = AnswerStore() answer_store.add( Answer(group_id='do-you-want-to-skip-group', block_id='do-you-want-to-skip', answer_id='do-you-want-to-skip-answer', value='Yes')) # When path_finder = PathFinder(survey, answer_store=answer_store) # Then expected_route = [{ 'block_id': 'do-you-want-to-skip-block', 'group_id': 'do-you-want-to-skip-group', 'group_instance': 0 }, { 'block_id': 'summary', 'group_id': 'should-skip-group', 'group_instance': 0 }] self.assertEqual(path_finder.get_routing_path('should-skip-group'), expected_route)
def test_get_routing_path_when_first_block_in_group_skipped(self): # Given schema = load_schema_from_params('test', 'skip_condition_group') answer_store = AnswerStore() answer_store.add(Answer(answer_id='do-you-want-to-skip-answer', value='Yes')) # When path_finder = PathFinder(schema, answer_store=answer_store, metadata={}, completed_blocks=[]) # Then expected_route = [ { 'block_id': 'do-you-want-to-skip-block', 'group_id': 'do-you-want-to-skip-group', 'group_instance': 0 }, { 'block_id': 'summary', 'group_id': 'should-skip-group', 'group_instance': 0 } ] pytest.xfail(reason='Known bug when skipping last group due to summary bundled into it') self.assertEqual(path_finder.get_routing_path('should-skip-group'), expected_route)
def test_route_based_on_answer_count(self): schema = load_schema_from_params('test', 'routing_answer_count') answer_group_id = 'first-name' answer_store = AnswerStore({}) answer_store.add(Answer( answer_id=answer_group_id, answer_instance=0, value='alice', )) answer_store.add(Answer( answer_id=answer_group_id, answer_instance=1, value='bob', )) completed_blocks = [ Location('multiple-questions-group', 0, 'household-composition') ] expected_next_location = Location('group-equal-2', 0, 'group-equal-2-block') should_not_be_present = Location('group-less-than-2', 0, 'group-less-than-2-block') path_finder = PathFinder(schema, answer_store, metadata={}, completed_blocks=completed_blocks) path = path_finder.build_path() self.assertNotIn(should_not_be_present, path) self.assertIn(expected_next_location, path)
def test_routing_path_with_complete_introduction(self): schema = load_schema_from_name("test_introduction") section_id = schema.get_section_id_for_block_id("introduction") progress_store = ProgressStore([{ "section_id": "introduction-section", "list_item_id": None, "status": CompletionStatus.COMPLETED, "block_ids": ["introduction"], }]) expected_routing_path = RoutingPath( ["introduction", "general-business-information-completed"], section_id="introduction-section", ) path_finder = PathFinder( schema, self.answer_store, self.list_store, progress_store, self.metadata, self.response_metadata, ) routing_path = path_finder.routing_path(section_id=section_id) self.assertEqual(routing_path, expected_routing_path)
def test_routing_basic_and_conditional_path(self): # Given schema = load_schema_from_params('0', 'star_wars') expected_path = [ Location('star-wars', 0, 'introduction'), Location('star-wars', 0, 'choose-your-side-block'), Location('star-wars', 0, 'dark-side-pick-character-ship'), Location('star-wars', 0, 'light-side-ship-type'), Location('star-wars', 0, 'star-wars-trivia'), Location('star-wars', 0, 'star-wars-trivia-part-2'), Location('star-wars', 0, 'star-wars-trivia-part-3'), Location('star-wars', 0, 'summary'), ] answer_1 = Answer( answer_id='choose-your-side-answer', value='Dark Side' ) answer_2 = Answer( answer_id='dark-side-pick-ship-answer', value='Can I be a pain and have a goodies ship', ) answers = AnswerStore() answers.add(answer_1) answers.add(answer_2) schema.answer_is_in_repeating_group = MagicMock(return_value=False) # When path_finder = PathFinder(schema, answer_store=answers, metadata={}, completed_blocks=[]) routing_path = path_finder.get_full_routing_path() # Then self.assertEqual(routing_path, expected_path)
def test_get_routing_path_when_first_block_in_group_skipped(self): # Given schema = load_schema_from_name("test_skip_condition_group") answer_store = AnswerStore() answer_store.add_or_update( Answer(answer_id="do-you-want-to-skip-answer", value="Yes")) # When path_finder = PathFinder( schema, answer_store, self.list_store, self.progress_store, self.metadata, self.response_metadata, ) # Then expected_route = RoutingPath( section_id="default-section", block_ids=["do-you-want-to-skip"], ) self.assertEqual( expected_route, path_finder.routing_path(section_id="default-section"), )
def test_get_next_location_summary(self): schema = load_schema_from_params('0', 'star_wars') schema.answer_is_in_repeating_group = MagicMock(return_value=False) answer_1 = Answer( answer_id='choose-your-side-answer', value='Light Side' ) answer_2 = Answer( answer_id='light-side-pick-ship-answer', value='No', ) answers = AnswerStore() answers.add(answer_1) answers.add(answer_2) path_finder = PathFinder(schema, answer_store=answers, metadata={}, completed_blocks=[]) current_location = Location('star-wars', 0, 'star-wars-trivia-part-2') next_location = path_finder.get_next_location(current_location=current_location) expected_next_location = Location('star-wars', 0, 'star-wars-trivia-part-3') self.assertEqual(expected_next_location, next_location) current_location = expected_next_location next_location = path_finder.get_next_location(current_location=current_location) expected_next_location = Location('star-wars', 0, 'summary') self.assertEqual(expected_next_location, next_location)
def test_routing_path_empty_routing_rules(self): schema = load_schema_from_name("test_checkbox") section_id = schema.get_section_id_for_block_id("mandatory-checkbox") expected_path = RoutingPath( [ "mandatory-checkbox", "non-mandatory-checkbox", "single-checkbox" ], section_id="default-section", ) answer_1 = Answer(answer_id="mandatory-checkbox-answer", value="Cheese") answer_2 = Answer(answer_id="non-mandatory-checkbox-answer", value="deep pan") answer_3 = Answer(answer_id="single-checkbox-answer", value="Estimate") answer_store = AnswerStore() answer_store.add_or_update(answer_1) answer_store.add_or_update(answer_2) answer_store.add_or_update(answer_3) progress_store = ProgressStore([{ "section_id": "default-section", "list_item_id": None, "status": CompletionStatus.COMPLETED, "block_ids": ["mandatory-checkbox"], }]) path_finder = PathFinder(schema, answer_store, self.list_store, progress_store, self.metadata) routing_path = path_finder.routing_path(section_id=section_id) self.assertEqual(routing_path, expected_path)
def test_previous_with_conditional_path_alternative(self): schema = load_schema_from_params('0', 'star_wars') schema.answer_is_in_repeating_group = MagicMock(return_value=False) expected_path = [ Location('star-wars', 0, 'choose-your-side-block'), Location('star-wars', 0, 'light-side-pick-character-ship'), Location('star-wars', 0, 'star-wars-trivia'), Location('star-wars', 0, 'star-wars-trivia-part-2'), Location('star-wars', 0, 'star-wars-trivia-part-3'), ] current_location = expected_path[2] expected_previous_location = expected_path[1] answer_1 = Answer( answer_id='choose-your-side-answer', value='Light Side' ) answer_2 = Answer( answer_id='light-side-pick-ship-answer', value='No', ) answers = AnswerStore() answers.add(answer_1) answers.add(answer_2) path_finder = PathFinder(schema, answer_store=answers, metadata={}, completed_blocks=[]) self.assertEqual(path_finder.get_previous_location(current_location=current_location), expected_previous_location)
def test_routing_path_with_repeating_sections(self): schema = load_schema_from_name( "test_repeating_sections_with_hub_and_spoke") progress_store = ProgressStore([{ "section_id": "section", "status": CompletionStatus.COMPLETED, "block_ids": [ "primary-person-list-collector", "list-collector", "next-interstitial", "another-list-collector-block", ], }]) path_finder = PathFinder(schema, self.answer_store, self.list_store, progress_store, self.metadata) repeating_section_id = "personal-details-section" routing_path = path_finder.routing_path( section_id=repeating_section_id, list_item_id="abc123") expected_path = RoutingPath( ["proxy", "date-of-birth", "confirm-dob", "sex"], section_id="personal-details-section", list_name="people", list_item_id="abc123", ) self.assertEqual(routing_path, expected_path)
def test_next_location_goto_summary(self): schema = load_schema_from_params('0', 'star_wars') schema.answer_is_in_repeating_group = MagicMock(return_value=False) expected_path = [ Location('star-wars', 0, 'introduction'), Location('star-wars', 0, 'choose-your-side-block'), Location('star-wars', 0, 'summary'), ] answer = Answer( group_instance=0, answer_id='choose-your-side-answer', value='I prefer Star Trek', ) answers = AnswerStore() answers.add(answer) path_finder = PathFinder(schema, answer_store=answers, metadata={}, completed_blocks=[]) current_location = expected_path[1] expected_next_location = expected_path[2] next_location = path_finder.get_next_location(current_location=current_location) self.assertEqual(next_location, expected_next_location)
def test_get_routing_path_when_first_block_in_group_skipped(self): # Given schema = load_schema_from_name("test_skip_condition_group") answer_store = AnswerStore() answer_store.add_or_update( Answer(answer_id="do-you-want-to-skip-answer", value="Yes")) # When path_finder = PathFinder(schema, answer_store, self.list_store, self.progress_store, self.metadata) # Then expected_route = [ { "block_id": "do-you-want-to-skip-block", "group_id": "do-you-want-to-skip-group", }, { "block_id": "summary", "group_id": "should-skip-group" }, ] section_id = schema.get_section_id_for_block_id("summary") pytest.xfail( reason= "Known bug when skipping last group due to summary bundled into it" ) self.assertEqual(path_finder.routing_path(section_id=section_id), expected_route)
def test_should_not_show_block_for_zero_repeats(self): survey = load_schema_file("test_repeating_household.json") # Default is to count answers, so switch to using value survey['groups'][-2]['routing_rules'][0]['repeat']['type'] = 'answer_value' expected_path = [ Location("multiple-questions-group", 0, "introduction"), Location("multiple-questions-group", 0, "household-composition"), Location("summary-group", 0, "summary") ] answer = Answer( group_id="multiple-questions-group", group_instance=0, answer_id="first-name", block_id="household-composition", value="0" ) answers = AnswerStore() answers.add(answer) path_finder = PathFinder(survey, answer_store=answers) self.assertEqual(expected_path, path_finder.get_routing_path())