def test_is_publicly_accessible_expired_session(self): q_graph = create_diamond_plus() # Sessions that have expired because the submit_before deadline was # passed are no longer publicly available: t_creation = datetime(2021, 1, 1, 0, 0, 0) t_now = datetime(2021, 6, 1, 0, 0, 0) session_expired_i = SessionFactory.create(questionnaire__graph=q_graph, created_at=t_creation, started_at=None, submit_before=t_creation + timedelta(days=7), duration=None, frozen=False) service_expired_i = SessionService(session_expired_i) with freeze_time(t_now): with self.assertRaises(SessionExpired): service_expired_i.is_publicly_accessible() # Sessions that have expired because too long was taken to fill out # the questionnaire are no longer publicly available: session_expired_ii = SessionFactory.create( questionnaire__graph=q_graph, created_at=t_creation, started_at=t_creation, submit_before=None, duration=timedelta(seconds=60 * 60 * 2), frozen=False) service_expired_ii = SessionService(session_expired_ii) with freeze_time(t_now): with self.assertRaises(SessionExpired): service_expired_ii.is_publicly_accessible()
def test_validate_question_graph_with_decision_with_default_not_required( self): graph = _question_graph_with_decision_with_default_no_required_answers( ) q_yes = Question.objects.get(retrieval_key='q_yes') q_no = Question.objects.get(retrieval_key='q_no') # Test default branch by not providing an answer, that will work because # the first question is not required. session_1 = SessionFactory.create(questionnaire__graph=graph) AnswerFactory(session=session_1, question=q_yes, payload='yes happy') session_service_1 = get_session_service(session_1.uuid) session_service_1.refresh_from_db() self.assertTrue(session_service_1.can_freeze) # Test default branch (by providing a non-matching, but valid answer). # This case will fail because the default branch is chosen. session_2 = SessionFactory.create(questionnaire__graph=graph) AnswerFactory(session=session_2, question=q_no, payload='not happy') session_service_2 = get_session_service(session_2.uuid) session_service_2.refresh_from_db() self.assertFalse(session_service_2.can_freeze)
def test_session_detail_reaction_requested(self): questionnaire = QuestionnaireFactory.create(flow=Questionnaire.REACTION_REQUEST) session = SessionFactory.create(questionnaire=questionnaire) response = self.client.get(f'{self.base_endpoint}{session.uuid}') self.assertEqual(response.status_code, 500) self.assertEqual(response.json()['detail'], f'Session {session.uuid} is not associated with a Signal.') signal = SignalFactory.create(status__state=GEMELD) session._signal = signal session.save() response = self.client.get(f'{self.base_endpoint}{session.uuid}') self.assertEqual(response.status_code, 500) self.assertEqual(response.json()['detail'], f'Session {session.uuid} is invalidated, associated signal not in state REACTIE_GEVRAAGD.') status = StatusFactory(state=REACTIE_GEVRAAGD, _signal=signal) signal.status = status signal.save() latest_session = SessionFactory.create(questionnaire=questionnaire, _signal=signal) response = self.client.get(f'{self.base_endpoint}{session.uuid}') self.assertEqual(response.status_code, 500) self.assertEqual(response.json()['detail'], f'Session {session.uuid} is invalidated, a newer reaction request was issued.') response = self.client.get(f'{self.base_endpoint}{latest_session.uuid}') self.assertEqual(response.status_code, 200)
def test_session_factory(self): SessionFactory.create() self.assertEqual(Answer.objects.count(), 0) self.assertEqual(Session.objects.count(), 1) self.assertEqual(Questionnaire.objects.count(), 1) self.assertEqual(Question.objects.count(), 1)
def setUp(self): self.session_extra_properties = SessionFactory.create( questionnaire__flow=Questionnaire.EXTRA_PROPERTIES) self.session_reaction_request = SessionFactory.create( questionnaire__flow=Questionnaire.REACTION_REQUEST) self.session_feedback_request = SessionFactory.create( questionnaire__flow=Questionnaire.FEEDBACK_REQUEST)
def test_get_session_reaction_request_flow_two_requests(self): # A session for reaction request flow for a signal in a state other # than REACTIE_GEVRAAGD should raise SessionInvalidated. signal = SignalFactory.create(status__state=workflow.GEMELD) session = SessionFactory.create( _signal=signal, questionnaire__flow=Questionnaire.REACTION_REQUEST) session_service = get_session_service(session.uuid) with self.assertRaises(SessionInvalidated) as cm: session_service.is_publicly_accessible() self.assertIn('associated signal not in state REACTIE_GEVRAAGD', str(cm.exception)) # A session for reaction request flow for a signal that also has a more # recent session, should raise SessionInvalidated. status = StatusFactory.create(state=workflow.REACTIE_GEVRAAGD) signal.status = status signal.save() SessionFactory.create( _signal=signal, questionnaire__flow=Questionnaire.REACTION_REQUEST) # more recent session_service.refresh_from_db() with self.assertRaises(SessionInvalidated) as cm: session_service.is_publicly_accessible() self.assertIn('a newer reaction request was issued', str(cm.exception))
def test_get_session_reaction_request_flow(self): # A session for reaction request flow with no associated Signal should # raise an SessionInvalidated. session_no_signal = SessionFactory.create(questionnaire__flow=Questionnaire.REACTION_REQUEST) with self.assertRaises(SessionInvalidated): QuestionnairesService.get_session(session_no_signal.uuid) # A session for reaction request flow for a signal in a state other # than REACTIE_GEVRAAGD should raise SessionInvalidated. signal = SignalFactory.create() session = SessionFactory.create(_signal=signal, questionnaire__flow=Questionnaire.REACTION_REQUEST) with self.assertRaises(SessionInvalidated): QuestionnairesService.get_session(session.uuid) # <-- !!! FAAL # A session for reaction request flow for a signal that also has a more # recent session, should raise SessionInvalidated. status = StatusFactory.create(state=workflow.REACTIE_GEVRAAGD) signal.status = status signal.save() SessionFactory.create(_signal=signal, questionnaire__flow=Questionnaire.REACTION_REQUEST) # more recent with self.assertRaises(SessionInvalidated): QuestionnairesService.get_session(session.uuid)
def test_validate_question_graph_with_decision(self): graph = _question_graph_with_decision() q_yes = Question.objects.get(retrieval_key='q_yes') q_no = Question.objects.get(retrieval_key='q_no') # Test yes branch session_1 = SessionFactory.create(questionnaire__graph=graph) AnswerFactory(session=session_1, question=graph.first_question, payload='yes') AnswerFactory(session=session_1, question=q_yes, payload='yes happy') session_service_1 = get_session_service(session_1.uuid) session_service_1.refresh_from_db() self.assertTrue(session_service_1.can_freeze) # Test no branch session_2 = SessionFactory.create(questionnaire__graph=graph) AnswerFactory(session=session_2, question=graph.first_question, payload='no') AnswerFactory(session=session_2, question=q_no, payload='no unhappy') session_service_2 = get_session_service(session_2.uuid) session_service_2.refresh_from_db() self.assertTrue(session_service_2.can_freeze) # Test missing data session_3 = SessionFactory.create(questionnaire__graph=graph) AnswerFactory(session=session_3, question=graph.first_question, payload='yes') session_service_3 = get_session_service(session_3.uuid) session_service_3.refresh_from_db() self.assertFalse(session_service_3.can_freeze) # Test showing a question halfway through the questionnaire can be # considered an endpoint. # TODO: consider whether this behavior is desirable (likely not). session_4 = SessionFactory.create(questionnaire__graph=graph) AnswerFactory(session=session_4, question=graph.first_question, payload='not a choice, but valid') session_service_4 = get_session_service(session_4.uuid) session_service_4.refresh_from_db() self.assertFalse(session_service_4.can_freeze)
def test_question_with_default_next(self): # set up our questions and questionnaires graph = _question_graph_with_decision_with_default() session = SessionFactory.create(questionnaire__graph=graph) session_service = get_session_service(session.uuid) session_service.refresh_from_db() question_1 = session.questionnaire.first_question answer_payload_1 = 'WILL NOT MATCH ANYTHING' # to trigger default # We will answer the questionnaire, until we reach a None next question. # In the first step we have no Session reference yet. answer_1 = session_service.create_answer(answer_payload_1, question_1) self.assertIsInstance(answer_1, Answer) self.assertEqual(answer_1.question, question_1) session = answer_1.session session_id = session.id self.assertIsNotNone(session) self.assertIsNone(session.submit_before) self.assertEqual(session.duration, timedelta(seconds=SESSION_DURATION)) question_2 = session_service.get_next_question(question_1, answer_1) self.assertEqual(question_2.ref, 'q_yes') # get the default option answer_payload_2 = 'Yippee' answer_2 = session_service.create_answer(answer_payload_2, question_2) self.assertIsInstance(answer_2, Answer) self.assertEqual(answer_2.question, question_2) self.assertEqual(answer_2.session_id, session_id) next_question = session_service.get_next_question(question_2, answer_2) self.assertIsNone(next_question)
def test_question_no_default_next(self): """ Behavior of get_next_question when no default is set. """ graph = _create_graph_no_defaults() session = SessionFactory.create(questionnaire__graph=graph) session_service = get_session_service(session.uuid) session_service.refresh_from_db() question_1 = session.questionnaire.graph.first_question # Try following both branches: answer = session_service.create_answer('yes', question_1) self.assertIsNotNone( session_service.get_next_question(question_1, answer)) answer = session_service.create_answer('no', question_1) self.assertIsNotNone( session_service.get_next_question(question_1, answer)) # Try an answer that does not match a branch in the question graph. # Since the graph has no default question following the first question # we will get a next_question of None: answer = session_service.create_answer('NOT AN OPTION', question_1) self.assertIsNone(session_service.get_next_question( question_1, answer))
def test_question_not_required(self): # set up our questions and questionnaires graph = _question_graph_no_required_answers() questionnaire = QuestionnaireFactory.create(graph=graph) session = SessionFactory.create(questionnaire__graph=graph) session_service = get_session_service(session.uuid) session_service.refresh_from_db() question_1 = questionnaire.graph.first_question answer_payload_1 = None # We will answer the questionnaire, until we reach a None next question. # In the first step we have no Session reference yet. answer_1 = session_service.create_answer(answer_payload_1, question_1) self.assertIsInstance(answer_1, Answer) self.assertEqual(answer_1.question, question_1) session = answer_1.session session_id = session.id self.assertIsNotNone(session) self.assertIsNone(session.submit_before) self.assertEqual(session.duration, timedelta(seconds=SESSION_DURATION)) question_2 = session_service.get_next_question(question_1, answer_1) self.assertEqual(question_2.ref, 'two') answer_payload_2 = None answer_2 = session_service.create_answer(answer_payload_2, question_2) self.assertIsInstance(answer_2, Answer) self.assertEqual(answer_2.question, question_2) self.assertEqual(answer_2.session_id, session_id) next_question = session_service.get_next_question(question_2, answer_2) self.assertIsNone(next_question)
def test_answers_by_analysis_key_one_path(self): q_graph = create_diamond_plus() session = SessionFactory.create(questionnaire__graph=q_graph) service = SessionService(session) # get references to questions service.question_graph_service.refresh_from_db() q_by_analysis_key = { q.analysis_key: q for q in service.question_graph_service._questions } # Answer questions Answer.objects.create(session=session, question=q_by_analysis_key['q1'], payload='q1') Answer.objects.create(session=session, question=q_by_analysis_key['q2'], payload='q2') Answer.objects.create(session=session, question=q_by_analysis_key['q4'], payload='q4') Answer.objects.create(session=session, question=q_by_analysis_key['q5'], payload='q5') service.refresh_from_db() answers_by_analysis_key = service.answers_by_analysis_key self.assertEqual(len(answers_by_analysis_key), 4) for key in ['q1', 'q2', 'q4', 'q5']: self.assertEqual(answers_by_analysis_key[key].payload, key)
def test_create_answers_one_path(self): q_graph = create_diamond_plus() session = SessionFactory.create(questionnaire__graph=q_graph) service = SessionService(session) # get references to questions service.question_graph_service.refresh_from_db() q_by_analysis_key = { q.analysis_key: q for q in service.question_graph_service._questions } # Answer questions service.create_answers(['q1', 'q2', 'q4', 'q5'], [ q_by_analysis_key['q1'], q_by_analysis_key['q2'], q_by_analysis_key['q4'], q_by_analysis_key['q5'] ]) service.refresh_from_db() answers_by_analysis_key = service.answers_by_analysis_key self.assertEqual(len(answers_by_analysis_key), 4) for key in ['q1', 'q2', 'q4', 'q5']: self.assertEqual(answers_by_analysis_key[key].payload, key) self.assertTrue(service.can_freeze) self.assertEqual(len(service.path_validation_errors_by_uuid), 0) self.assertEqual(len(service.path_answered_question_uuids), 4) self.assertEqual(len(service.path_unanswered_question_uuids), 0)
def test_deadline_and_duration(self): session = SessionFactory.create(submit_before=self.now + timedelta(weeks=2), duration=timedelta(hours=2)) # submit on time and within allowed duration session.started_at = self.now with freeze_time(self.now + timedelta(hours=1)): self.assertFalse(session.is_expired) # submit before deadline but outside allowed duration with freeze_time(self.now + timedelta(hours=4)): self.assertTrue(session.is_expired) # submit after deadline within allowed duration session.started_at = session.submit_before + timedelta(minutes=5) with freeze_time(session.submit_before + timedelta(minutes=10)): self.assertTrue(session.is_expired) # submit after deadline and outside allowed duration session.started_at = session.submit_before + timedelta(minutes=5) with freeze_time(session.submit_before + timedelta(hours=4)): self.assertTrue(session.is_expired) # start just before deadline, submit just after deadline but within allowed duration session.started_at = session.submit_before - timedelta(minutes=5) with freeze_time(session.submit_before + timedelta(minutes=5)): self.assertTrue(session.is_expired)
def setUp(self): self.questionnaire = QuestionnaireFactory.create() self.session = SessionFactory.create(questionnaire=self.questionnaire) self.detail_schema = self.load_json_schema( os.path.join(THIS_DIR, '../json_schema/public_get_session_detail.json'))
def test_session_detail_frozen(self): session = SessionFactory.create(questionnaire=self.questionnaire, frozen=True) response = self.client.get(f'{self.base_endpoint}{session.uuid}') self.assertEqual(response.status_code, 410) self.assertEqual(response.json()['detail'], 'Already used!')
def test_create_answer_one_path(self): q_graph = create_diamond_plus() session = SessionFactory.create(questionnaire__graph=q_graph) service = SessionService(session) # get references to questions service.question_graph_service.refresh_from_db() q_by_analysis_key = { q.analysis_key: q for q in service.question_graph_service._questions } # Answer questions service.create_answer('q1', q_by_analysis_key['q1']) service.create_answer('q2', q_by_analysis_key['q2']) service.create_answer('q4', q_by_analysis_key['q4']) service.create_answer('q5', q_by_analysis_key['q5']) service.refresh_from_db() answers_by_analysis_key = service.answers_by_analysis_key self.assertEqual(len(answers_by_analysis_key), 4) for key in ['q1', 'q2', 'q4', 'q5']: self.assertEqual(answers_by_analysis_key[key].payload, key) # Test our is_accessible here: service.is_publicly_accessible()
def test_handle_frozen_session_on_commit_triggered(self): question = QuestionFactory.create(field_type='plain_text', label='Is het goed weer?', short_label='Goed weer?') session = SessionFactory.create( questionnaire__flow=Questionnaire.REACTION_REQUEST, questionnaire__first_question=question, frozen=False, _signal=self.signal_reaction_requested) answer = AnswerFactory(session=session, payload='Het antwoord!') submit_question = Question.objects.get_by_reference('submit') with self.captureOnCommitCallbacks(execute=False) as callbacks: QuestionnairesService.create_answer( answer_payload=None, question=submit_question, questionnaire=session.questionnaire, session=session) self.assertEqual(len(callbacks), 1) callbacks[0]() self.signal_reaction_requested.refresh_from_db() self.assertEqual(self.signal_reaction_requested.status.state, workflow.REACTIE_ONTVANGEN) self.assertEqual(self.signal_reaction_requested.status.text, answer.payload)
def test_answers_by_question_uuid_one_path(self): q_graph = create_diamond_plus() session = SessionFactory.create(questionnaire__graph=q_graph) service = SessionService(session) # get references to questions service.question_graph_service.refresh_from_db() q_by_analysis_key = { q.analysis_key: q for q in service.question_graph_service._questions } # Answer questions service.create_answer(str(q_by_analysis_key['q1'].uuid), q_by_analysis_key['q1']) service.create_answer(str(q_by_analysis_key['q2'].uuid), q_by_analysis_key['q2']) service.create_answer(str(q_by_analysis_key['q4'].uuid), q_by_analysis_key['q4']) service.create_answer(str(q_by_analysis_key['q5'].uuid), q_by_analysis_key['q5']) service.refresh_from_db() answers_by_question_uuid = service.answers_by_question_uuid self.assertEqual(len(answers_by_question_uuid), 4) for key in ['q1', 'q2', 'q4', 'q5']: uuid = q_by_analysis_key[key].uuid self.assertEqual(answers_by_question_uuid[uuid].payload, str(uuid))
def test_can_freeze_one_path(self): q_graph = create_diamond_plus() session = SessionFactory.create(questionnaire__graph=q_graph) service = SessionService(session) # get references to questions service.question_graph_service.refresh_from_db() q_by_analysis_key = { q.analysis_key: q for q in service.question_graph_service._questions } # Answer questions Answer.objects.create(session=session, question=q_by_analysis_key['q1'], payload='q1') Answer.objects.create(session=session, question=q_by_analysis_key['q2'], payload='q2') Answer.objects.create(session=session, question=q_by_analysis_key['q4'], payload='q4') Answer.objects.create(session=session, question=q_by_analysis_key['q5'], payload='q5') service.refresh_from_db() self.assertTrue(service.can_freeze)
def test_answer_question_create_existing_session(self): session = SessionFactory.create(questionnaire=self.questionnaire) self.assertEqual(0, Answer.objects.count()) self.assertEqual(1, Session.objects.count()) data = { 'payload': 'This is my answer for testing!', 'session': session.uuid } response = self.client.post( f'{self.base_endpoint}{self.question.uuid}/answer', data=data, format='json') self.assertEqual(response.status_code, 201) response_data = response.json() self.assertJsonSchema(self.post_answer_schema, response_data) self.assertEqual(1, Answer.objects.count()) self.assertEqual(1, Session.objects.count()) answer = Answer.objects.first() session = Session.objects.first() self.assertEqual(data['payload'], response_data['payload']) self.assertEqual(data['payload'], answer.payload) self.assertEqual(str(session.uuid), str(response_data['session'])) self.assertIsNotNone(response_data['next_question']) self.assertEqual(response_data['next_question']['field_type'], 'submit') self.assertEqual(answer.session.pk, session.pk) self.assertEqual(self.questionnaire.pk, session.questionnaire.pk) self.assertEqual(self.question.pk, answer.question.pk)
def test_answer_question_create_invalid_data(self): self.assertEqual(0, Answer.objects.count()) self.assertEqual(0, Session.objects.count()) data = {'payload': {'value': 'This is my answer for testing!'}, } response = self.client.post(f'{self.base_endpoint}{self.question.uuid}/answer', data=data, format='json') self.assertEqual(response.status_code, 400) self.assertEqual(0, Answer.objects.count()) self.assertEqual(0, Session.objects.count()) data = {'payload': 'This is my answer for testing!', 'session': '00000000-0000-0000-0000-000000000000', 'questionnaire': '00000000-0000-0000-0000-000000000000'} response = self.client.post(f'{self.base_endpoint}{self.question.uuid}/answer', data=data, format='json') self.assertEqual(response.status_code, 400) self.assertEqual(0, Answer.objects.count()) self.assertEqual(0, Session.objects.count()) session = SessionFactory.create(questionnaire=self.questionnaire) data = {'payload': 'This is my answer for testing!', 'session': session.uuid, 'questionnaire': session.questionnaire.uuid} response = self.client.post(f'{self.base_endpoint}{self.question.uuid}/answer', data=data, format='json') self.assertEqual(response.status_code, 400) self.assertEqual(0, Answer.objects.count()) self.assertEqual(1, Session.objects.count())
def test_answers_by_question_uuid_no_answer(self): q_graph = create_diamond_plus() session = SessionFactory.create(questionnaire__graph=q_graph) service = SessionService(session) answers_by_question_uuid = service.answers_by_question_uuid self.assertEqual(answers_by_question_uuid, {})
def test_get_extra_properties_one_path(self): q_graph = create_diamond_plus() session = SessionFactory.create(questionnaire__graph=q_graph) service = SessionService(session) # get references to questions service.question_graph_service.refresh_from_db() q_by_analysis_key = { q.analysis_key: q for q in service.question_graph_service._questions } # Answer questions Answer.objects.create(session=session, question=q_by_analysis_key['q1'], payload='q1') Answer.objects.create(session=session, question=q_by_analysis_key['q2'], payload='q2') Answer.objects.create(session=session, question=q_by_analysis_key['q4'], payload='q4') Answer.objects.create(session=session, question=q_by_analysis_key['q5'], payload='q5') service.refresh_from_db() extra_properties = service.get_extra_properties('URL') self.assertEqual(len(extra_properties), 4) self.assertEqual(extra_properties[0]['category_url'], 'URL') extra_properties_no_url = service.get_extra_properties() self.assertEqual(len(extra_properties_no_url), 4) self.assertNotIn('category_url', extra_properties_no_url[0])
def test_deadline_and_duration_none(self): session = SessionFactory.create(submit_before=None, duration=None) # submit waaayyyy long after starting the questionnaire session.started_at = self.now with freeze_time(self.now + timedelta(days=10000)): self.assertFalse(session.is_expired)
def test_handle_frozen_session_REACTION_REQUEST_wrong_flow(self): session = SessionFactory.create( questionnaire__flow=Questionnaire.EXTRA_PROPERTIES, frozen=True, _signal=self.signal_reaction_requested) with self.assertRaises(WrongFlow): ReactionRequestService.handle_frozen_session_REACTION_REQUEST( session)
def test_handle_frozen_session_REACTION_REQUEST_not_frozen(self): session = SessionFactory.create( questionnaire__flow=Questionnaire.REACTION_REQUEST, frozen=False, _signal=self.signal_reaction_requested) with self.assertRaises(SessionNotFrozen): ReactionRequestService.handle_frozen_session_REACTION_REQUEST( session)
def test_is_publicly_accessible_frozen_session(self): # Sessions that are frozen are no longer publicly accessible: q_graph = create_diamond_plus() session_frozen = SessionFactory.create(questionnaire__graph=q_graph, frozen=True) service_frozen = SessionService(session_frozen) with self.assertRaises(SessionFrozen): service_frozen.is_publicly_accessible()
def test_get_session_reaction_request_flow_no_signal(self): # A session for reaction request flow with no associated Signal should # raise an SessionInvalidated. session_no_signal = SessionFactory.create( questionnaire__flow=Questionnaire.REACTION_REQUEST) session_service_no_signal = get_session_service(session_no_signal.uuid) with self.assertRaises(SessionInvalidated): session_service_no_signal.is_publicly_accessible()
def test_session_detail_gone(self): now = timezone.now() with freeze_time(now - timezone.timedelta(days=1)): session = SessionFactory.create(questionnaire=self.questionnaire, started_at=now - timezone.timedelta(hours=6)) response = self.client.get(f'{self.base_endpoint}{session.uuid}') self.assertEqual(response.status_code, 410) self.assertEqual(response.json()['detail'], 'Expired!') now = timezone.now() with freeze_time(now - timezone.timedelta(days=1)): session = SessionFactory.create(questionnaire=self.questionnaire, submit_before=now-timezone.timedelta(hours=1)) response = self.client.get(f'{self.base_endpoint}{session.uuid}') self.assertEqual(response.status_code, 410) self.assertEqual(response.json()['detail'], 'Expired!')