def test_remove_all_answers_with_list_item_id(self): answer_store = AnswerStore(existing_answers=[ { "answer_id": "test1", "value": 1, "list_item_id": "abcdef" }, { "answer_id": "test2", "value": 2, "list_item_id": "abcdef" }, { "answer_id": "test3", "value": 3, "list_item_id": "uvwxyz" }, ]) questionnaire_store = MagicMock( spec=QuestionnaireStore, completed_blocks=[], answer_store=answer_store, list_store=MagicMock(spec=ListStore), progress_store=ProgressStore(), ) self.questionnaire_store_updater = QuestionnaireStoreUpdater( self.location, self.schema, questionnaire_store, self.current_question) self.questionnaire_store_updater.remove_list_item_and_answers( "abc", "abcdef") assert len(answer_store) == 1 assert answer_store.get_answer("test3", "uvwxyz")
def _deserialize(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.deserialize(json_data.get("LISTS")) self.collection_metadata = json_data.get("COLLECTION_METADATA", {})
def test_minimum_and_maximum_offset_dates(app, value_source_resolver, rule_evaluator): value_source_resolver.metadata = {"date": "2018-02-20"} answer_store = AnswerStore() test_answer_id = Answer(answer_id="date", value="2018-03-20") answer_store.add_or_update(test_answer_id) value_source_resolver.answer_store = answer_store answer = { "id": "date_answer", "type": "Date", "minimum": { "value": {"identifier": "date", "source": "metadata"}, "offset_by": {"days": -10}, }, "maximum": { "value": {"identifier": "date", "source": "answers"}, "offset_by": {"years": 1}, }, } handler = DateHandler(answer, value_source_resolver, rule_evaluator, error_messages) minimum_date = handler.get_date_value("minimum") maximum_date = handler.get_date_value("maximum") assert minimum_date == parse_datetime("2018-02-10") assert maximum_date == parse_datetime("2019-03-20")
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 relationship_answer_store(): answer_store = AnswerStore() answer_store.add_or_update( Answer( answer_id="relationship-answer", value=[ { "list_item_id": "abc123", "to_list_item_id": "xyz987", "relationship": "Husband or Wife", }, { "list_item_id": "abc123", "to_list_item_id": "123abc", "relationship": "Son or Daughter", }, { "list_item_id": "xyz987", "to_list_item_id": "123abc", "relationship": "Son or Daughter", }, ], ) ) return answer_store
def test_detail_answer_mandatory_only_checked_if_option_selected(self): # The detail_answer can only be mandatory if the option it is associated with is answered with self.app_request_context(): schema = load_schema_from_name("test_checkbox_detail_answer_multiple") question_schema = schema.get_block("mandatory-checkbox").get("question") # Option is selected therefore the detail answer should be mandatory (schema defined) form = generate_form( schema, question_schema, AnswerStore(), metadata=None, form_data=MultiDict({"mandatory-checkbox-answer": "Your choice"}), ) detail_answer_field = getattr(form, "your-choice-answer-mandatory") self.assertIsInstance(detail_answer_field.validators[0], ResponseRequired) # Option not selected therefore the detail answer should not be mandatory form = generate_form( schema, question_schema, AnswerStore(), metadata=None, data={"mandatory-checkbox-answer": "Ham"}, ) detail_answer_field = getattr(form, "your-choice-answer-mandatory") self.assertEqual(detail_answer_field.validators, ())
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 test_evaluate_when_rule_fetches_answer_using_list_item_id(self): when = { "when": [{ "id": "my_answer", "condition": "equals", "value": "an answer" }] } list_item_id = "abc123" answer_store = AnswerStore() answer_store.add_or_update( Answer(answer_id="my_answer", value="an answer", list_item_id=list_item_id)) current_location = Location(section_id="some-section", block_id="some-block", list_item_id=list_item_id) schema = Mock(get_schema()) schema.get_list_item_id_for_answer_id = Mock(return_value=list_item_id) self.assertTrue( evaluate_when_rules( when_rules=when["when"], schema=schema, metadata={}, answer_store=answer_store, list_store=ListStore(), current_location=current_location, ))
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 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_get_referenced_offset_value_with_list_item_id(app, schema_mock): list_item_id = "abcde" answer_store = AnswerStore() test_answer_id = Answer(answer_id="date", value="2018-03-20", list_item_id=list_item_id) location = Location(section_id="test", list_item_id=list_item_id) answer_store.add_or_update(test_answer_id) answer = { "maximum": { "value": { "identifier": "date", "source": "answers" }, "offset_by": { "months": 1 }, } } handler = DateHandler(answer, answer_store=answer_store, location=location) maximum_date = handler.get_date_value("maximum") assert maximum_date == convert_to_datetime("2018-04-20")
def test_bespoke_message_for_sum_validation(self): store = AnswerStore() answer_total = Answer(answer_id="total-answer", value=10) store.add_or_update(answer_total) with self.app_request_context(): schema = load_schema_from_name("test_sum_equal_validation_against_total") question_schema = QuestionnaireSchema.get_mutable_deepcopy( schema.get_block("breakdown-block").get("question") ) question_schema["validation"] = { "messages": {"TOTAL_SUM_NOT_EQUALS": "Test Message"} } form_data = MultiDict({"breakdown-1": "3", "breakdown-2": "5"}) form = generate_form( schema, question_schema, store, metadata=None, form_data=form_data ) with patch( "app.questionnaire.questionnaire_schema.QuestionnaireSchema.get_all_questions_for_block", return_value=[question_schema], ): form.validate() self.assertIn( form.question_errors["breakdown-question"], "Test Message" )
def _deserialize(self, data: str) -> None: json_data = json_loads(data) 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.deserialize(json_data.get("LISTS")) self.response_metadata = json_data.get("RESPONSE_METADATA", {})
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_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_get_referenced_offset_value_with_list_item_id( app, value_source_resolver, rule_evaluator ): list_item_id = "abcde" test_answer_id = Answer( answer_id="date", value="2018-03-20", list_item_id=list_item_id ) location = Location(section_id="test", list_item_id=list_item_id) answer_store = AnswerStore() answer_store.add_or_update(test_answer_id) value_source_resolver.answer_store = answer_store value_source_resolver.location = location value_source_resolver.list_item_id = list_item_id answer = { "maximum": { "value": {"identifier": "date", "source": "answers"}, "offset_by": {"months": 1}, } } handler = DateHandler(answer, value_source_resolver, rule_evaluator, error_messages) maximum_date = handler.get_date_value("maximum") assert maximum_date == parse_datetime("2018-04-20")
def test_evaluate_when_rule_with_invalid_list_item_id(self): when = { "when": [{ "id": "my_answer", "condition": "equals", "value": "an answer" }] } answer_store = AnswerStore() answer_store.add_or_update( Answer(answer_id="my_answer", value="an answer", list_item_id="abc123")) current_location = Location(section_id="some-section", block_id="some-block", list_item_id="123abc") schema = Mock(get_schema()) schema.is_repeating_answer = Mock(return_value=False) self.assertFalse( evaluate_when_rules( when_rules=when["when"], schema=schema, metadata={}, answer_store=answer_store, list_store=ListStore(), current_location=current_location, ))
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_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 setUp(self): super().setUp() self.schema = load_schema_from_name("test_calculated_summary") answers = [ {"value": 1, "answer_id": "first-number-answer"}, {"value": 2, "answer_id": "second-number-answer"}, {"value": 3, "answer_id": "second-number-answer-unit-total"}, {"value": 4, "answer_id": "second-number-answer-also-in-total"}, {"value": 5, "answer_id": "third-number-answer"}, {"value": 6, "answer_id": "third-and-a-half-number-answer-unit-total"}, {"value": "No", "answer_id": "skip-fourth-block-answer"}, {"value": 7, "answer_id": "fourth-number-answer"}, {"value": 8, "answer_id": "fourth-and-a-half-number-answer-also-in-total"}, {"value": 9, "answer_id": "fifth-percent-answer"}, {"value": 10, "answer_id": "fifth-number-answer"}, {"value": 11, "answer_id": "sixth-percent-answer"}, {"value": 12, "answer_id": "sixth-number-answer"}, ] self.block_type = "CalculatedSummary" self.language = "en" self.metadata = {} self.response_metadata = {} self.answer_store = AnswerStore(answers) self.list_store = ListStore() self.progress_store = ProgressStore()
def test_sum_calculated_field(self): store = AnswerStore() answer_total = Answer(answer_id="total-answer", value=10) store.add_or_update(answer_total) with self.app_request_context(): schema = load_schema_from_name("test_sum_equal_validation_against_total") question_schema = schema.get_block("breakdown-block").get("question") form_data = MultiDict( { "breakdown-1": "", "breakdown-2": "5", "breakdown-3": "4", "breakdown-4": "1", } ) expected_form_data = { "csrf_token": "", "breakdown-1": None, "breakdown-2": Decimal("5"), "breakdown-3": Decimal("4"), "breakdown-4": Decimal("1"), } form = generate_form( schema, question_schema, store, metadata=None, form_data=form_data ) form.validate() self.assertEqual(form.data, expected_form_data)
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_generate_form_with_title_and_no_answer_label(self): """ Checks that the form is still generated when there is no answer label but there is a question title """ store = AnswerStore() conditional_answer = Answer(answer_id="behalf-of-answer", value="chad") store.add_or_update(conditional_answer) with self.app_request_context(): schema = load_schema_from_name("test_title") question_schema = schema.get_block("single-title-block").get("question") form_data = MultiDict({"feeling-answer": "good"}) expected_form_data = {"csrf_token": "", "feeling-answer": "good"} with patch( "app.questionnaire.path_finder.evaluate_goto", return_value=False ): form = generate_form( schema, question_schema, store, metadata={}, form_data=form_data ) form.validate() assert form.data == expected_form_data
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 __init__(self, storage: EncryptedQuestionnaireStorage, version: Optional[int] = None): self._storage = storage if version is None: version = self.get_latest_version_number() self.version = version self._metadata: dict[str, Any] = {} # self.metadata is a read-only view over self._metadata self.metadata: Mapping[str, Any] = MappingProxyType(self._metadata) self.response_metadata: Mapping[str, Any] = {} self.list_store = ListStore() self.answer_store = AnswerStore() self.progress_store = ProgressStore() self.submitted_at: Optional[datetime] self.collection_exercise_sid: Optional[str] ( raw_data, self.collection_exercise_sid, version, self.submitted_at, ) = self._storage.get_user_data() if raw_data: self._deserialize(raw_data) if version is not None: self.version = version
def test_invalid_date_range_and_single_date_periods(self): with self.app_request_context(): store = AnswerStore() test_answer_id = Answer(answer_id="date", value="2017-03-20") store.add_or_update(test_answer_id) schema = load_schema_from_name("test_date_validation_range") question_schema = { "id": "date-range-question", "type": "DateRange", "answers": [ { "id": "date-range-from", "label": "Period from", "mandatory": True, "type": "Date", "minimum": {"value": "2018-01-10", "offset_by": {"days": -5}}, }, { "id": "date-range-to", "label": "Period to", "mandatory": True, "type": "Date", "maximum": { "value": {"identifier": "date", "source": "answers"}, "offset_by": {"days": 5}, }, }, ], } form_data = MultiDict( { "date-range-from-day": "6", "date-range-from-month": "1", "date-range-from-year": "2018", "date-range-to-day": "15", "date-range-to-month": "1", "date-range-to-year": "2018", } ) metadata = {"schema_name": "test_date_validation_range"} form = generate_form( schema, question_schema, store, metadata=metadata, form_data=form_data ) with self.assertRaises(Exception) as ite: with patch( "app.questionnaire.questionnaire_schema.QuestionnaireSchema.get_all_questions_for_block", return_value=[question_schema], ): form.validate() self.assertEqual( "The schema has invalid date answer limits for date-range-question", str(ite.exception), )
def setUp(self): super().setUp() self.answer_schema = MagicMock() self.answer_store = AnswerStore() self.list_store = ListStore() self.schema = MagicMock() self.metadata = {} self.response_metadata = {}
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 test_multi_calculation(self): store = AnswerStore() answer_total = Answer(answer_id="total-answer", value=10) store.add_or_update(answer_total) with self.app_request_context(): schema = load_schema_from_name("test_sum_multi_validation_against_total") question_schema = schema.get_block("breakdown-block").get("question") form_data = MultiDict( { "breakdown-1": "", "breakdown-2": "", "breakdown-3": "", "breakdown-4": "", } ) # With no answers question validation should pass form = generate_form( schema, question_schema, store, metadata=None, form_data=form_data ) form.validate() self.assertEqual(len(form.question_errors), 0) # With the data equaling the total question validation should pass form_data["breakdown-1"] = "10" form = generate_form( schema, question_schema, store, metadata=None, form_data=form_data ) form.validate() self.assertEqual(len(form.question_errors), 0) # With the data not equaling zero or the total, question validation should fail form_data["breakdown-1"] = "1" form = generate_form( schema, question_schema, store, metadata=None, form_data=form_data ) form.validate() self.assertEqual( form.question_errors["breakdown-question"], schema.error_messages["TOTAL_SUM_NOT_EQUALS"] % {"total": "10"}, )