def test_revalidate_demo_user(self): validator = MagicMock() validation_payload = CodeValidationPayload(valid=True, is_demo=True) validator.validate_code = MagicMock(return_value=validation_payload) self.assertFalse(self.dialog_state.user_profile.validated) command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator) batch = self._process_command(command) self._assert_event_types(batch, DialogEventType.USER_VALIDATED) command = StartDrill(self.phone_number, self.drill.slug, self.drill.dict(), uuid.uuid4()) self._process_command(command) validation_payload = CodeValidationPayload( valid=True, account_info={ "employer_id": 1, "unit_id": 1, "employer_name": "employer_name", "unit_name": "unit_name", }, ) validator.validate_code = MagicMock(return_value=validation_payload) command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator) batch = self._process_command(command) self._assert_event_types(batch, DialogEventType.USER_VALIDATED)
def test_start_drill_opted_out(self): self.dialog_state.user_profile.validated = True self.dialog_state.user_profile.opted_out = True command = StartDrill(self.phone_number, self.drill.slug, self.drill.dict(), uuid.uuid4()) batch = self._process_command(command) self.assertEqual(0, len(batch.events))
def test_start_drill_not_validated(self, get_drill_mock): self.dialog_state.user_profile.validated = False self.dialog_state.user_profile.opted_out = False command = StartDrill(self.phone_number, self.drill.slug) batch = self._process_command(command) self.assertEqual(0, len(batch.events))
def test_start_drill_not_validated(self): self.dialog_state.user_profile.validated = False self.dialog_state.user_profile.opted_out = False command = StartDrill(self.phone_number, self.drill.slug, self.drill.dict(), uuid.uuid4()) batch = self._process_command(command) self._assert_event_types(batch, DialogEventType.DRILL_STARTED)
def handle_inbound_commands(commands: List[InboundCommand]): for command in commands: if command.command_type == InboundCommandType.INBOUND_SMS: process_command( ProcessSMSMessage(phone_number=command.payload["From"], content=command.payload["Body"]), command.sequence_number, ) elif command.command_type == InboundCommandType.START_DRILL: process_command( StartDrill( phone_number=command.payload["phone_number"], drill_slug=command.payload["drill_slug"], ), command.sequence_number, ) elif command.command_type == InboundCommandType.TRIGGER_REMINDER: process_command( TriggerReminder( phone_number=command.payload["phone_number"], drill_instance_id=uuid.UUID( command.payload["drill_instance_id"]), prompt_slug=command.payload["prompt_slug"], ), command.sequence_number, ) else: raise RuntimeError(f"Unknown command: {command.command_type}") return {"statusCode": 200}
def test_advance_demo_user(self, get_drill_mock): validator = MagicMock() validation_payload = CodeValidationPayload(valid=True, is_demo=True) validator.validate_code = MagicMock(return_value=validation_payload) self.assertFalse(self.dialog_state.user_profile.validated) command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator) batch = self._process_command(command) self._assert_event_types(batch, DialogEventType.USER_VALIDATED) command = StartDrill(self.phone_number, self.drill.slug) self._process_command(command) # the user's next message isn't a validation code - so we just keep going validation_payload = CodeValidationPayload(valid=False) validator.validate_code = MagicMock(return_value=validation_payload) command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator) batch = self._process_command(command) self._assert_event_types(batch, DialogEventType.COMPLETED_PROMPT, DialogEventType.ADVANCED_TO_NEXT_PROMPT)
def test_start_drill(self): self.dialog_state.user_profile.validated = True command = StartDrill(self.phone_number, self.drill.slug, self.drill.dict(), uuid.uuid4()) batch = self._process_command(command) self._assert_event_types(batch, DialogEventType.DRILL_STARTED) event: DrillStarted = batch.events[0] # type: ignore self.assertEqual(self.drill.dict(), event.drill.dict()) self.assertEqual(self.drill.first_prompt().slug, event.first_prompt.slug) self.assertIsNotNone(event.drill_instance_id)
def handle_inbound_commands(commands: List[InboundCommand]) -> dict: for command in commands: if command.command_type is InboundCommandType.INBOUND_SMS: process_command( ProcessSMSMessage( phone_number=command.payload["From"], content=command.payload["Body"], ), command.sequence_number, ) elif command.command_type is InboundCommandType.START_DRILL: process_command( StartDrill( phone_number=command.payload["phone_number"], drill_slug=command.payload["drill_slug"], drill_body=command.payload["drill_body"], drill_instance_id=command.payload["drill_instance_id"], ), command.sequence_number, ) elif command.command_type is InboundCommandType.SEND_AD_HOC_MESSAGE: process_command( SendAdHocMessage( phone_number=command.payload["phone_number"], message=command.payload["message"], media_url=command.payload["media_url"], ), command.sequence_number, ) elif command.command_type is InboundCommandType.UPDATE_USER: process_command( UpdateUser( phone_number=command.payload["phone_number"], user_profile_data=command.payload["user_profile_data"], purge_drill_state=command.payload.get("purge_drill_state") or False, ), command.sequence_number, ) else: raise RuntimeError(f"Unknown command: {command.command_type}") return {"statusCode": 200}
def test_revalidate_demo_user(self, get_drill_mock): validator = MagicMock() validation_payload = CodeValidationPayload(valid=True, is_demo=True) validator.validate_code = MagicMock(return_value=validation_payload) self.assertFalse(self.dialog_state.user_profile.validated) command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator) batch = self._process_command(command) self._assert_event_types(batch, DialogEventType.USER_VALIDATED) command = StartDrill(self.phone_number, self.drill.slug) self._process_command(command) validation_payload = CodeValidationPayload( valid=True, account_info={"company": "WeWork"}) validator.validate_code = MagicMock(return_value=validation_payload) command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator) batch = self._process_command(command) self._assert_event_types(batch, DialogEventType.USER_VALIDATED)
def persist_dialog_state( # noqa: C901 self, event_batch: DialogEventBatch, dialog_state: DialogState): self.repo[dialog_state.phone_number] = DialogStateSchema().dumps( dialog_state) drill_to_start = None for event in event_batch.events: if isinstance(event, AdvancedToNextPrompt): fake_sms( event.phone_number, dialog_state.user_profile, [ message.text for message in event.prompt.messages if message.text is not None ], with_initial_pause=True, ) elif isinstance(event, FailedPrompt): if not event.abandoned: fake_sms(event.phone_number, dialog_state.user_profile, [TRY_AGAIN]) else: fake_sms( event.phone_number, dialog_state.user_profile, ["{{corrected_answer}}"], correct_answer=localize( event.prompt.correct_response, # type: ignore dialog_state.user_profile.language, ), ) elif isinstance(event, CompletedPrompt): if event.prompt.correct_response is not None: fake_sms(event.phone_number, dialog_state.user_profile, ["{{match_correct_answer}}"]) elif isinstance(event, UserValidated): drill_to_start = dialog_state.user_profile.account_info["code"] elif isinstance(event, OptedOut): print("(You've been opted out.)") if event.drill_instance_id: del STARTED_DRILLS[event.drill_instance_id] elif isinstance(event, NextDrillRequested): unstarted_drills = [ code for code in DRILLS.keys() if DRILLS[code].slug not in STARTED_DRILLS.values() ] if unstarted_drills: drill_to_start = unstarted_drills[0] else: print("(You're all out of drills.)") elif isinstance(event, UserValidationFailed): print(f"(try {', '.join(DRILLS.keys())})") elif isinstance(event, DrillStarted): STARTED_DRILLS[ event.drill_instance_id] = dialog_state.current_drill.slug fake_sms( event.phone_number, dialog_state.user_profile, [ message.text for message in event.first_prompt.messages if message.text is not None ], ) elif isinstance(event, DrillCompleted): print( "(The drill is complete. Type 'more' for another drill or crtl-D to exit.)" ) if drill_to_start: global SEQ SEQ += 1 process_command(StartDrill(PHONE_NUMBER, DRILLS[drill_to_start].slug), str(SEQ), repo=self)
def persist_dialog_state( # noqa: C901 self, event_batch: DialogEventBatch, dialog_state: DialogState) -> None: self.repo[dialog_state.phone_number] = dialog_state.json() assert dialog_state.user_profile.language drill_to_start = None for event in event_batch.events: if isinstance(event, AdvancedToNextPrompt): fake_sms( event.phone_number, dialog_state.user_profile, [ message.text for message in event.prompt.messages if message.text is not None ], with_initial_pause=True, ) elif isinstance(event, FailedPrompt): if not event.abandoned: fake_sms( event.phone_number, dialog_state.user_profile, [ translate( dialog_state.user_profile.language, SupportedTranslation.INCORRECT_ANSWER, ) ], ) else: fake_sms( event.phone_number, dialog_state.user_profile, [ translate( dialog_state.user_profile.language, SupportedTranslation.CORRECTED_ANSWER, correct_answer=event.prompt.correct_response, ) ], ) elif isinstance(event, CompletedPrompt): if event.prompt.correct_response is not None: fake_sms( event.phone_number, dialog_state.user_profile, [ correct_answer_response( dialog_state.user_profile.language, ) ], ) elif isinstance(event, UserValidated): assert dialog_state.user_profile.account_info drill_to_start = dialog_state.user_profile.account_info.employer_name elif isinstance(event, OptedOut): print("(You've been opted out.)") if event.drill_instance_id: del STARTED_DRILLS[event.drill_instance_id] elif isinstance(event, NextDrillRequested): drill_to_start = self.get_next_unstarted_drill() if not drill_to_start: print("(You're all out of drills.)") elif isinstance(event, UserValidationFailed): print(f"(try {', '.join(DRILLS.keys())})") elif isinstance(event, DrillStarted): assert dialog_state.current_drill STARTED_DRILLS[ event.drill_instance_id] = dialog_state.current_drill.slug fake_sms( event.phone_number, dialog_state.user_profile, [ message.text for message in event.first_prompt.messages if message.text is not None ], ) elif isinstance(event, DrillCompleted): print( "(The drill is complete. Type 'more' for another drill or crtl-D to exit.)" ) elif isinstance(event, SchedulingDrillRequested): print("Scheduling drill requested") if drill_to_start: global SEQ SEQ += 1 drill = DRILLS[drill_to_start] process_command( StartDrill(PHONE_NUMBER, drill.slug, drill.dict(), uuid.uuid4()), str(SEQ), repo=self, )