def test_should_go_to_next_question_when_condition_is_meta_and_answer_type( self): # Given goto_rule = { 'id': 'next-question', 'when': [{ 'id': 'my_answer', 'condition': 'equals', 'value': 'Yes' }, { 'condition': 'equals', 'meta': 'sexual_identity', 'value': True }] } answer_store = AnswerStore() answer_store.add(Answer(answer_id='my_answer', value='Yes')) metadata = {'sexual_identity': True} # When goto = evaluate_goto(goto_rule, metadata, answer_store, 0) # Then self.assertTrue(goto)
def test_evaluate_goto_returns_true_when_answer_value_not_equals_any_match_values( self): goto = { "id": "next-question", "when": [{ "id": "my_answers", "condition": "not equals any", "values": ["answer1", "answer2"], }], } answer_store = AnswerStore() answer_store.add_or_update( Answer(answer_id="my_answers", value="answer3")) current_location = Location(section_id="some-section", block_id="some-block") self.assertTrue( evaluate_goto( goto_rule=goto, schema=get_schema(), metadata={}, answer_store=answer_store, list_store=ListStore(), current_location=current_location, ))
def _evaluate_routing_rules( self, this_location, blocks, routing_rules, block_index, routing_path_block_ids ): for rule in filter(is_goto_rule, routing_rules): should_goto = evaluate_goto( rule["goto"], self.schema, self.metadata, self.answer_store, self.list_store, current_location=this_location, routing_path_block_ids=routing_path_block_ids, ) if should_goto: next_block_id = self._get_next_block_id(rule) next_block_index = PathFinder._block_index_for_block_id( blocks, next_block_id ) 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) routing_path_block_ids.append(next_block_id) return None return next_block_index
def test_should_not_go_to_next_question_when_second_condition_fails(self): # Given goto_rule = { "id": "next-question", "when": [ { "id": "my_answer", "condition": "equals", "value": "Yes" }, { "condition": "equals", "meta": "sexual_identity", "value": False }, ], } answer_store = AnswerStore() answer_store.add_or_update(Answer(answer_id="my_answer", value="Yes")) metadata = {"sexual_identity": True} current_location = Location(section_id="some-section", block_id="some-block") self.assertFalse( evaluate_goto( goto_rule=goto_rule, schema=get_schema(), metadata=metadata, answer_store=answer_store, list_store=ListStore(), current_location=current_location, ))
def test_should_not_go_to_next_question_when_second_condition_fails(self): # Given goto_rule = { 'id': 'next-question', 'when': [ { 'id': 'my_answer', 'condition': 'equals', 'value': 'Yes' }, { 'condition': 'equals', 'meta': 'sexual_identity', 'value': False } ] } answer_store = AnswerStore({}) answer_store.add(Answer(answer_id='my_answer', value='Yes')) metadata = {'sexual_identity': True} # When goto = evaluate_goto(goto_rule, get_schema_mock(), metadata, answer_store, 0) # Then self.assertFalse(goto)
def test_do_not_go_to_next_question_for_multiple_answers(self): # Given goto_rule = { "id": "next-question", "when": [ { "id": "my_answer", "condition": "equals", "value": "Yes" }, { "id": "my_other_answer", "condition": "equals", "value": "2" }, ], } answer_store = AnswerStore() answer_store.add_or_update(Answer(answer_id="my_answer", value="No")) current_location = Location(section_id="some-section", block_id="some-block") self.assertFalse( evaluate_goto( goto_rule=goto_rule, schema=get_schema(), metadata={}, answer_store=answer_store, list_store=ListStore(), current_location=current_location, ))
def test_meta_comparison_missing(self): # Given goto_rule = { "id": "next-question", "when": [{ "condition": "equals", "meta": "variant_flags.does_not_exist.does_not_exist", "value": True, }], } answer_store = AnswerStore() answer_store.add_or_update(Answer(answer_id="my_answer", value="Yes")) metadata = {"varient_flags": {"sexual_identity": True}} current_location = Location(section_id="some-section", block_id="some-block") self.assertFalse( evaluate_goto( goto_rule=goto_rule, schema=get_schema(), metadata=metadata, answer_store=answer_store, list_store=ListStore(), current_location=current_location, ))
def test_do_not_go_to_next_question_for_date_answer(self): goto_rule = { "id": "next-question", "when": [{ "id": "date-answer", "condition": "equals", "date_comparison": { "value": "2018-01" }, }], } answer_store = AnswerStore({}) answer_store.add_or_update( Answer(answer_id="date-answer", value="2018-02-01")) current_location = Location(section_id="some-section", block_id="some-block") self.assertFalse( evaluate_goto( goto_rule=goto_rule, schema=get_schema_mock(), metadata={}, answer_store=answer_store, list_store=ListStore(), current_location=current_location, ))
def can_reach_summary(self, routing_path): """ Determines whether the end of a given routing path can be reached given a set of answers :param routing_path: :return: """ blocks = self.get_blocks() last_routing_block_id = routing_path[-1].block_id last_block_id = blocks[-1]['block']['id'] if last_block_id == last_routing_block_id: return True routing_block_id_index = next( index for (index, b) in enumerate(blocks) if b['block']["id"] == last_routing_block_id) last_routing_block = blocks[routing_block_id_index]['block'] if 'routing_rules' in last_routing_block: for rule in last_routing_block['routing_rules']: goto_rule = rule['goto'] if 'id' in goto_rule.keys() and goto_rule['id'] == 'summary': return evaluate_goto(goto_rule, self.metadata, self.answer_store, 0) return False
def test_evaluate_goto_returns_false_when_checkbox_question_not_answered( self): goto_contains = { "id": "next-question", "when": [{ "id": "my_answers", "condition": "contains", "value": "answer1" }], } goto_not_contains = { "id": "next-question", "when": [{ "id": "my_answers", "condition": "not contains", "value": "answer1" }], } answer_store = AnswerStore() current_location = Location(section_id="some-section", block_id="some-block") self.assertFalse( evaluate_goto( goto_rule=goto_contains, schema=get_schema(), metadata={}, answer_store=answer_store, list_store=ListStore(), current_location=current_location, )) self.assertFalse( evaluate_goto( goto_rule=goto_not_contains, schema=get_schema(), metadata={}, answer_store=answer_store, list_store=ListStore(), current_location=current_location, ))
def _evaluate_routing_rules(self, this_location, blocks, block, block_index, path): for rule in filter(is_goto_rule, block['routing_rules']): should_goto = evaluate_goto(rule['goto'], self.schema, self.metadata, self.answer_store, this_location.group_instance) if should_goto: return self._follow_routing_rule(this_location, rule, blocks, block_index, path)
def test_do_not_go_to_next_question_for_answer(self): # Given goto_rule = { 'id': 'next-question', 'when': [{ 'id': 'my_answer', 'condition': 'equals', 'value': 'Yes' }] } answer_store = AnswerStore() answer_store.add(Answer(answer_id='my_answer', value='No')) self.assertFalse(evaluate_goto(goto_rule, {}, answer_store, 0))
def test_evaluate_goto_returns_true_when_value_not_contained_in_list(self): goto = { 'id': 'next-question', 'when': [ { 'id': 'my_answers', 'condition': 'not contains', 'value': 'answer1' } ] } answer_store = AnswerStore({}) answer_store.add(Answer(answer_id='my_answers', value=['answer2', 'answer3'])) self.assertTrue(evaluate_goto(goto, get_schema_mock(), {}, answer_store, 0))
def test_go_to_next_question_for_answer(self): # Given goto = { 'id': 'next-question', 'when': [{ 'id': 'my_answer', 'condition': 'equals', 'value': 'Yes' }] } answer_store = AnswerStore({}) answer_store.add_or_update(Answer(answer_id='my_answer', value='Yes')) self.assertTrue( evaluate_goto(goto, get_schema_mock(), {}, answer_store, 0))
def test_do_not_go_to_next_question_for_date_answer(self): goto_rule = { 'id': 'next-question', 'when': [{ 'id': 'date-answer', 'condition': 'equals', 'date_comparison': { 'value': '2018-01' } }] } answer_store = AnswerStore({}) answer_store.add(Answer(answer_id='date_answer', value='2018-02-01')) self.assertFalse(evaluate_goto(goto_rule, get_schema_mock(), {}, answer_store, 0))
def test_go_to_next_question_for_multiple_answers(self): # Given goto = { 'id': 'next-question', 'when': [{ 'id': 'my_answer', 'condition': 'equals', 'value': 'Yes' }, { 'id': 'my_other_answer', 'condition': 'equals', 'value': '2' }] } answer_store = AnswerStore() answer_store.add(Answer(answer_id='my_answer', value='Yes')) answer_store.add(Answer(answer_id='my_other_answer', value='2')) self.assertTrue(evaluate_goto(goto, {}, answer_store, 0))
def test_do_not_go_to_next_question_for_multiple_answers(self): # Given goto_rule = { 'id': 'next-question', 'when': [{ 'id': 'my_answer', 'condition': 'equals', 'value': 'Yes' }, { 'id': 'my_other_answer', 'condition': 'equals', 'value': '2' }] } answer_store = AnswerStore({}) answer_store.add_or_update(Answer(answer_id='my_answer', value='No')) self.assertFalse( evaluate_goto(goto_rule, get_schema_mock(), {}, answer_store, 0))
def test_meta_comparison_missing(self): # Given goto_rule = { 'id': 'next-question', 'when': [ { 'condition': 'equals', 'meta': 'varient_flags.does_not_exist.does_not_exist', 'value': True } ] } answer_store = AnswerStore({}) answer_store.add(Answer(answer_id='my_answer', value='Yes')) metadata = {'varient_flags': {'sexual_identity': True}} # When goto = evaluate_goto(goto_rule, get_schema_mock(), metadata, answer_store, 0) # Then self.assertFalse(goto)
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: block_index = PathFinder._block_index_for_location( blocks, this_location) if block_index is None: logger.error('block index is none (invalid location)', **this_location.__dict__) 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