def test_drill_started_and_completed(self): user_id = self._make_user_and_get_id() event = DrillStarted( phone_number=self.phone_number, user_profile=UserProfile(True), drill=Drill(slug=get_all_drill_slugs()[0], name="drill", prompts=[]), first_prompt=self.prompt, ) self.repo.update_user(self._make_batch([event])) drill_status = self.repo.get_drill_status(user_id, get_all_drill_slugs()[0]) self.assertEqual(event.created_time, drill_status.started_time) self.assertIsNone(drill_status.completed_time) event2 = DrillCompleted( phone_number=self.phone_number, user_profile=UserProfile(True), drill_instance_id=event.drill_instance_id, ) self.repo.update_user(self._make_batch([event2])) drill_status = self.repo.get_drill_status(user_id, get_all_drill_slugs()[0]) self.assertEqual(event.created_time, drill_status.started_time) self.assertEqual(event2.created_time, drill_status.completed_time)
def test_idempotence(self): user_id = self._make_user_and_get_id() event = DrillStarted( phone_number=self.phone_number, user_profile=UserProfile(True), drill=self.drill, first_prompt=self.prompt, ) batch1 = self._make_batch([event]) self.repo.update_user(batch1) user = self.repo.get_user(user_id) self.assertEqual(event.created_time, user.last_interacted_time) event2 = FailedPrompt( phone_number=self.phone_number, user_profile=UserProfile(True), prompt=self.prompt, drill_instance_id=event.drill_instance_id, response="go", abandoned=True, ) batch2 = self._make_batch([event2]) batch2.seq = batch1.seq self.repo.update_user(batch2) user = self.repo.get_user(user_id) self.assertEqual(event.created_time, user.last_interacted_time)
def test_initiates_subsequent_drill(self): batch1 = DialogEventBatch( phone_number="123456789", seq="0", events=[ NextDrillRequested( phone_number="123456789", user_profile=UserProfile(True), code_validation_payload=CodeValidationPayload(valid=True), ), DrillStarted( phone_number="123456789", user_profile=UserProfile(True), drill=self.drill, first_prompt=self.drill.prompts[0], ), ], ) batch2 = DialogEventBatch( phone_number="987654321", seq="1", events=[ DrillStarted( phone_number="987654321", user_profile=UserProfile(True), drill=self.drill, first_prompt=self.drill.prompts[0], ) ], ) self.assertTrue(initiates_subsequent_drill(batch1)) self.assertFalse(initiates_subsequent_drill(batch2))
def test_user_revalidated(self): user_id = self._make_user_and_get_id() for slug in get_all_drill_slugs(): drill = copy(self.drill) drill.slug = slug event = DrillStarted( phone_number=self.phone_number, user_profile=UserProfile(True), drill=Drill(slug=slug, name="name", prompts=[]), first_prompt=self.prompt, ) event2 = DrillCompleted( phone_number=self.phone_number, user_profile=UserProfile(True), drill_instance_id=event.drill_instance_id, ) self.repo._mark_drill_started(user_id, event, self.repo.engine) self.repo._mark_drill_completed(event2, self.repo.engine) drill_status = self.repo.get_drill_status(user_id, slug) self.assertIsNotNone(drill_status.started_time) self.assertIsNotNone(drill_status.completed_time) self.repo.update_user( self._make_batch([ UserValidated( phone_number=self.phone_number, user_profile=UserProfile(True), code_validation_payload=CodeValidationPayload(valid=True), ) ])) for slug in get_all_drill_slugs(): drill_status = self.repo.get_drill_status(user_id, slug) self.assertIsNone(drill_status.drill_instance_id) self.assertIsNone(drill_status.started_time) self.assertIsNone(drill_status.completed_time)
def test_get_progress_empty(self): drill_progresses = list( self.repo.get_progress_for_users_who_need_drills(30)) self.assertEqual(0, len(drill_progresses)) # no started drills, so we won't receive anything self._make_user_and_get_id() drill_progresses = list( self.repo.get_progress_for_users_who_need_drills(30)) self.assertEqual(0, len(drill_progresses)) for slug in get_all_drill_slugs(): event = DrillStarted( phone_number=self.phone_number, user_profile=UserProfile(True), drill=Drill(slug=slug, name="name", prompts=[]), first_prompt=self.prompt, ) event2 = DrillCompleted( phone_number=self.phone_number, user_profile=UserProfile(True), drill_instance_id=event.drill_instance_id, ) self.repo.update_user(self._make_batch([event, event2])) # all drills complete, so we won't receive anything drill_progresses = list( self.repo.get_progress_for_users_who_need_drills(30)) self.assertEqual(0, len(drill_progresses))
def test_save_and_fetch(self): dialog_state = self.repo.fetch_dialog_state(self.phone_number) self.assertEqual(self.phone_number, dialog_state.phone_number) event1 = CompletedPrompt( phone_number=self.phone_number, user_profile=UserProfile(validated=True), prompt=Prompt( slug="one", messages=[ PromptMessage(text="one"), PromptMessage(text="two") ], ), response="hi", drill_instance_id=uuid.uuid4(), ) event2 = AdvancedToNextPrompt( phone_number=self.phone_number, user_profile=UserProfile(validated=True), prompt=Prompt( slug="two", messages=[ PromptMessage(text="three"), PromptMessage(text="four") ], ), drill_instance_id=event1.drill_instance_id, ) dialog_state = DialogState( phone_number=self.phone_number, seq="0", user_profile=UserProfile(validated=True, language="de"), drill_instance_id=event1.drill_instance_id, ) batch = DialogEventBatch(phone_number=self.phone_number, events=[event1, event2], seq="216") self.repo.persist_dialog_state(batch, dialog_state) dialog_state2 = self.repo.fetch_dialog_state(self.phone_number) self.assertEqual(dialog_state.phone_number, dialog_state2.phone_number) self.assertEqual(dialog_state.user_profile.validated, dialog_state2.user_profile.validated) self.assertEqual(dialog_state.user_profile.language, dialog_state2.user_profile.language) batch_retrieved = self.repo.fetch_dialog_event_batch( self.phone_number, batch.batch_id) event1_retrieved = batch_retrieved.events[0] self.assertEqual(event1.response, event1_retrieved.response) # type: ignore event2_retrieved = batch_retrieved.events[1] self.assertEqual(event2.prompt.slug, event2_retrieved.prompt.slug) # type: ignore
def test_get_progress_multiple_users(self): user_id1 = self._make_user_and_get_id() user_id2 = self._make_user_and_get_id(phone_number="987654321") event = DrillStarted( phone_number=self.phone_number, user_profile=UserProfile(True), drill=Drill(slug=get_all_drill_slugs()[0], name="name", prompts=[]), first_prompt=self.prompt, created_time=datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=32), ) self.repo.update_user(self._make_batch([event])) event2 = DrillStarted( phone_number="987654321", user_profile=UserProfile(True), drill=Drill(slug=get_all_drill_slugs()[1], name="name", prompts=[]), first_prompt=self.prompt, created_time=datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=32), ) self.repo.update_user(self._make_batch([event2])) drill_progresses = list( self.repo.get_progress_for_users_who_need_drills(30)) self.assertEqual(2, len(drill_progresses)) if drill_progresses[0].user_id == user_id1: drill_progress1 = drill_progresses[0] drill_progress2 = drill_progresses[1] else: drill_progress1 = drill_progresses[1] drill_progress2 = drill_progresses[0] self.assertEqual( DrillProgress( phone_number=self.phone_number, user_id=user_id1, first_unstarted_drill_slug=get_all_drill_slugs()[1], first_incomplete_drill_slug=get_all_drill_slugs()[0], ), drill_progress1, ) self.assertEqual( DrillProgress( phone_number="987654321", user_id=user_id2, first_unstarted_drill_slug=get_all_drill_slugs()[0], first_incomplete_drill_slug=get_all_drill_slugs()[0], ), drill_progress2, )
def test_create_or_update_user(self): batch = DialogEventBatch( phone_number=self.phone_number, seq=self._seq(), events=[ DrillCompleted( phone_number=self.phone_number, user_profile=UserProfile( True, language="zh", account_info={ "employer_id": Decimal(123), "unit_id": Decimal(456) }, ), drill_instance_id=uuid.uuid4(), ) ], ) user_id = self.repo._create_or_update_user(batch, None, self.repo.engine) user = self.repo.get_user(user_id) self.assertEqual(user_id, user.user_id) self.assertEqual({ "employer_id": 123, "unit_id": 456 }, user.profile["account_info"]) self.assertEqual(True, user.profile["validated"]) self.assertEqual("zh", user.profile["language"]) self.assertIsNone(user.last_interacted_time) self.assertEqual(batch.seq, user.seq) batch2 = self._make_batch([ DrillCompleted( phone_number=self.phone_number, user_profile=UserProfile(True, account_info={ "foo": "bar", "one": "two" }), drill_instance_id=uuid.uuid4(), ) ]) self.repo._create_or_update_user(batch2, None, self.repo.engine) user = self.repo.get_user(user_id) self.assertEqual({ "foo": "bar", "one": "two" }, user.profile["account_info"]) self.assertEqual(batch2.seq, user.seq)
def test_user_revalidated(self): profile = UserProfile(validated=True, is_demo=True) dialog_state = DialogState( "123456789", "0", user_profile=profile, current_drill=DRILL, drill_instance_id=uuid.uuid4(), current_prompt_state=PromptState(slug=DRILL.prompts[0].slug, start_time=NOW), ) event = UserValidated( phone_number="123456789", user_profile=profile, code_validation_payload=CodeValidationPayload( valid=True, is_demo=False, account_info={"foo": "bar"}), ) event.apply_to(dialog_state) self.assertTrue(dialog_state.user_profile.validated) self.assertFalse(dialog_state.user_profile.is_demo) self.assertIsNone(dialog_state.current_drill) self.assertIsNone(dialog_state.current_prompt_state) self.assertIsNone(dialog_state.drill_instance_id) self.assertEqual({"foo": "bar"}, dialog_state.user_profile.account_info)
def test_user_validation_failed(self): profile = UserProfile(validated=False) dialog_state = DialogState("123456789", "0", user_profile=profile) event = UserValidationFailed(phone_number="123456789", user_profile=profile) event.apply_to(dialog_state) self.assertFalse(dialog_state.user_profile.validated)
def test_user_validated(self): profile = UserProfile(validated=False) dialog_state = DialogState(phone_number="123456789", seq="0", user_profile=profile) event = UserValidated( phone_number="123456789", user_profile=profile, code_validation_payload=CodeValidationPayload( valid=True, is_demo=False, account_info={ "employer_id": 1, "unit_id": 1, "employer_name": "employer_name", "unit_name": "unit_name", }, ), ) event.apply_to(dialog_state) self.assertTrue(dialog_state.user_profile.validated) self.assertEqual( { "employer_id": 1, "unit_id": 1, "employer_name": "employer_name", "unit_name": "unit_name", }, dialog_state.user_profile.account_info, )
def test_failed_and_not_abandoned(self): profile = UserProfile(validated=True) event = FailedPrompt( phone_number="123456789", user_profile=profile, prompt=DRILL.prompts[2], drill_instance_id=uuid.uuid4(), response="b", abandoned=False, ) dialog_state = DialogState( "123456789", seq="0", user_profile=profile, current_drill=DRILL, drill_instance_id=event.drill_instance_id, current_prompt_state=PromptState(DRILL.prompts[2].slug, start_time=NOW), ) event.apply_to(dialog_state) self.assertEqual( PromptState( slug=DRILL.prompts[2].slug, start_time=NOW, last_response_time=event.created_time, failures=1, ), dialog_state.current_prompt_state, )
def test_get_progress_for_users_one_user(self): user_id = self._make_user_and_get_id() event = DrillStarted( phone_number=self.phone_number, user_profile=UserProfile(True), drill=Drill(slug=get_all_drill_slugs()[0], name="name", prompts=[]), first_prompt=self.prompt, created_time=datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=32), ) self.repo.update_user(self._make_batch([event])) drill_progresses = list( self.repo.get_progress_for_users_who_need_drills(30)) self.assertEqual(1, len(drill_progresses)) self.assertEqual( DrillProgress( user_id=user_id, phone_number=self.phone_number, first_incomplete_drill_slug=get_all_drill_slugs()[0], first_unstarted_drill_slug=get_all_drill_slugs()[1], ), drill_progresses[0], ) event2 = DrillCompleted( phone_number=self.phone_number, user_profile=UserProfile(True), drill_instance_id=event.drill_instance_id, created_time=datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=31), ) self.repo.update_user(self._make_batch([event2])) drill_progresses = list( self.repo.get_progress_for_users_who_need_drills(30)) self.assertEqual(1, len(drill_progresses)) self.assertEqual( DrillProgress( user_id=user_id, phone_number=self.phone_number, first_incomplete_drill_slug=get_all_drill_slugs()[1], first_unstarted_drill_slug=get_all_drill_slugs()[1], ), drill_progresses[0], )
def test_unhandled_message_received(self): original = UnhandledMessageReceived( phone_number="123456789", user_profile=UserProfile(validated=True), message="blabla") serialized = original.dict() deserialized = event_from_dict(serialized) self._make_base_assertions(original, deserialized)
def test_menu_requested(self): original = MenuRequested( phone_number="123456789", user_profile=UserProfile(validated=True), ) serialized = original.dict() deserialized = event_from_dict(serialized) self._make_base_assertions(original, deserialized)
def test_opted_out(self): original = OptedOut("123456789", user_profile=UserProfile(True), drill_instance_id=uuid.uuid4()) serialized = original.to_dict() deserialized: OptedOut = event_from_dict(serialized) # type: ignore self._make_base_assertions(original, deserialized) self.assertEqual(original.drill_instance_id, deserialized.drill_instance_id)
def test_send_adhoc_message(self): original = AdHocMessageSent( phone_number="123456789", user_profile=UserProfile(validated=True), sms=SMS(body="foobar"), ) serialized = original.dict() deserialized = event_from_dict(serialized) self._make_base_assertions(original, deserialized)
def test_user_validated(self): original = UserValidated( "123456789", user_profile=UserProfile(True), code_validation_payload=CodeValidationPayload(valid=True, is_demo=True), ) serialized = original.to_dict() deserialized = event_from_dict(serialized) self._make_base_assertions(original, deserialized)
def test_drill_completed(self): original = DrillCompleted(phone_number="12345678", user_profile=UserProfile(True), drill_instance_id=uuid.uuid4()) serialized = original.to_dict() deserialized: DrillCompleted = event_from_dict( serialized) # type: ignore self._make_base_assertions(original, deserialized) self.assertEqual(original.drill_instance_id, deserialized.drill_instance_id)
def test_next_drill_requested(self): profile = UserProfile(validated=True, opted_out=True) event = NextDrillRequested("123456789", user_profile=profile, drill_instance_id=uuid.uuid4()) dialog_state = DialogState("123456789", seq="0", user_profile=profile) self.assertTrue(profile.opted_out) event.apply_to(dialog_state) self.assertFalse(profile.opted_out)
def test_opted_out_no_drill(self): user_id = self._make_user_and_get_id() event = OptedOut(phone_number=self.phone_number, user_profile=UserProfile(True), drill_instance_id=None) self.repo.update_user(self._make_batch([event])) drill_status = self.repo.get_drill_status(user_id, get_all_drill_slugs()[0]) self.assertIsNone(drill_status.started_time) self.assertIsNone(drill_status.completed_time)
def fetch_dialog_state(self, phone_number: str) -> DialogState: if phone_number in self.repo: state = DialogStateSchema().loads(self.repo[phone_number]) return state else: return DialogState( phone_number=phone_number, seq="0", user_profile=UserProfile(False, language=self.lang), )
def test_completed_and_stored(self): profile = UserProfile(validated=True) event = CompletedPrompt( "123456789", user_profile=profile, prompt=DRILL.prompts[1], drill_instance_id=uuid.uuid4(), response="7", ) dialog_state = DialogState( "123456789", "0", user_profile=profile, current_drill=DRILL, current_prompt_state=PromptState(DRILL.prompts[0].slug, NOW), ) event.apply_to(dialog_state) self.assertEqual(UserProfile(validated=True, self_rating_1="7"), dialog_state.user_profile) self.assertIsNone(dialog_state.current_prompt_state)
def test_opted_out_during_drill(self): self.repo._save_drill_instance(self.drill_instance) event = OptedOut( phone_number=self.phone_number, user_profile=UserProfile(True), drill_instance_id=self.drill_instance.drill_instance_id, ) self.repo.update_user(self._make_batch([event])) retrieved = self.repo.get_drill_instance( self.drill_instance.drill_instance_id) self.assertFalse(retrieved.is_valid)
def test_last_interacted(self): user_id = self._make_user_and_get_id() event = DrillStarted( phone_number=self.phone_number, user_profile=UserProfile(True), drill=self.drill, first_prompt=self.prompt, ) self.repo.update_user(self._make_batch([event])) user = self.repo.get_user(user_id) self.assertEqual(event.created_time, user.last_interacted_time) event2 = CompletedPrompt( phone_number=self.phone_number, user_profile=UserProfile(True), prompt=self.prompt, drill_instance_id=event.drill_instance_id, response="go", ) self.repo.update_user(self._make_batch([event2])) user = self.repo.get_user(user_id) self.assertEqual(event2.created_time, user.last_interacted_time)
def setUp(self): self.phone = "+15554238324" self.validated_user_profile = UserProfile( validated=True, language="en", name="Mario", is_demo=False, account_info=AccountInfo( employer_id=1, employer_name="Tacombi", unit_id=1, unit_name="unit name", ), ) self.non_validated_user_profile = UserProfile(validated=False, language="en", name="Luigi", is_demo=False) self.drill = Drill( name="Test Drill", slug="test-drill", prompts=[ Prompt(slug="ignore-response-1", messages=[PromptMessage(text="Hello")]), Prompt( slug="graded-response-1", messages=[ PromptMessage(text="Intro!"), PromptMessage(text="Question 1"), ], correct_response="a) Philadelphia", ), Prompt( slug="graded-response-2", messages=[PromptMessage(text="Question 2")], correct_response="b", ), ], )
def _make_user_and_get_id(self, **overrides) -> uuid.UUID: return self.repo._create_or_update_user( self._make_batch([ UserValidated( phone_number=overrides.get("phone_number", self.phone_number), user_profile=UserProfile(True), code_validation_payload=CodeValidationPayload(valid=True), ) ]), None, self.repo.engine, )
def test_language_change_drill_requested(self): original = LanguageChangeDrillRequested( phone_number="123456789", user_profile=UserProfile(validated=True), abandoned_drill_instance_id="11111111-1111-1111-1111-111111111111", ) serialized = original.dict() deserialized = event_from_dict(serialized) self._make_base_assertions(original, deserialized) self.assertEqual( deserialized.abandoned_drill_instance_id, uuid.UUID("11111111-1111-1111-1111-111111111111"), )
def test_opted_out_no_drill(self): profile = UserProfile(validated=True) event = OptedOut("123456789", user_profile=profile, drill_instance_id=None) dialog_state = DialogState("123456789", seq="0", user_profile=profile) self.assertFalse(profile.opted_out) event.apply_to(dialog_state) self.assertTrue(profile.opted_out) self.assertIsNone(dialog_state.drill_instance_id) self.assertIsNone(dialog_state.current_prompt_state) self.assertIsNone(dialog_state.current_drill)
def test_user_validated(self): profile = UserProfile(validated=False) dialog_state = DialogState("123456789", "0", user_profile=profile) event = UserValidated( phone_number="123456789", user_profile=profile, code_validation_payload=CodeValidationPayload( valid=True, is_demo=False, account_info={"foo": "bar"}), ) event.apply_to(dialog_state) self.assertTrue(dialog_state.user_profile.validated) self.assertEqual({"foo": "bar"}, dialog_state.user_profile.account_info)