Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
def main() -> None:
    global SEQ
    if len(sys.argv) > 1:
        lang = sys.argv[1]
    else:
        lang = "en"
    repo = InMemoryRepository(lang)
    validator = FakeRegistrationValidator()

    # kick off the language choice drill
    process_command(
        ProcessSMSMessage(PHONE_NUMBER,
                          "00-language",
                          registration_validator=validator),
        "1",
        repo=repo,
    )
    try:
        while True:
            message = input("> ")
            SEQ += 1
            process_command(
                ProcessSMSMessage(PHONE_NUMBER,
                                  message,
                                  registration_validator=validator),
                str(SEQ),
                repo=repo,
            )
    except EOFError:
        pass
    dialog_state = repo.fetch_dialog_state(PHONE_NUMBER)
    print(f"{dialog_state.user_profile}")
Beispiel #4
0
    def test_ask_for_mas(self):
        self.dialog_state.user_profile.validated = True
        self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
        command = ProcessSMSMessage(self.phone_number, "mas")
        batch = self._process_command(command)
        self._assert_event_types(batch, DialogEventType.NEXT_DRILL_REQUESTED)

        command = ProcessSMSMessage(self.phone_number, "más")
        batch = self._process_command(command)
        self._assert_event_types(batch, DialogEventType.NEXT_DRILL_REQUESTED)
Beispiel #5
0
    def test_ask_for_english_lesson_drill(self):
        self.dialog_state.user_profile.validated = True
        self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
        self.dialog_state.current_drill = None
        command = ProcessSMSMessage(self.phone_number, "english")
        batch = self._process_command(command)
        self._assert_event_types(batch, DialogEventType.ENGLISH_LESSON_DRILL_REQUESTED)

        command = ProcessSMSMessage(self.phone_number, "esl")
        batch = self._process_command(command)
        self._assert_event_types(batch, DialogEventType.ENGLISH_LESSON_DRILL_REQUESTED)
Beispiel #6
0
    def test_conclude_with_too_many_wrong_answers(self, *args):
        self.dialog_state.user_profile.validated = True
        self._set_current_prompt(3)
        self.dialog_state.current_prompt_state.failures = 1

        command = ProcessSMSMessage(self.phone_number, "completely wrong answer")
        batch = self._process_command(command)
        self._assert_event_types(
            batch,
            DialogEventType.FAILED_PROMPT,
            DialogEventType.ADVANCED_TO_NEXT_PROMPT,
            DialogEventType.DRILL_COMPLETED,
        )

        failed_event: FailedPrompt = batch.events[0]  # type: ignore
        self.assertEqual(failed_event.prompt, self.drill.prompts[3])
        self.assertTrue(failed_event.abandoned)
        self.assertEqual(failed_event.response, "completely wrong answer")
        self.assertEqual(failed_event.drill_instance_id, self.drill_instance_id)
        self.assertEqual(batch.events[0].user_profile_updates, None)

        advanced_event: AdvancedToNextPrompt = batch.events[1]  # type: ignore
        self.assertEqual(self.drill.prompts[4], advanced_event.prompt)
        self.assertEqual(advanced_event.drill_instance_id, self.drill_instance_id)

        drill_completed_event: DrillCompleted = batch.events[2]  # type: ignore
        self.assertEqual(drill_completed_event.drill_instance_id, self.drill_instance_id)
        self.assertEqual(self.dialog_state.drill_instance_id, None)
Beispiel #7
0
 def test_opt_back_in(self):
     self.dialog_state.user_profile.validated = True
     self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
     self.dialog_state.user_profile.opted_out = True
     command = ProcessSMSMessage(self.phone_number, "start")
     batch = self._process_command(command)
     self._assert_event_types(batch, DialogEventType.NEXT_DRILL_REQUESTED)
Beispiel #8
0
    def test_first_message_validates_user(self):
        validator = MagicMock()
        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)
        self.assertFalse(self.dialog_state.user_profile.validated)

        batch = self._process_command(command)
        self._assert_event_types(batch, DialogEventType.USER_VALIDATED)
        self.assertEqual(
            validation_payload, batch.events[0].code_validation_payload  # type: ignore
        )
        # and account info is set on the event and user profile
        self.assertEqual(
            batch.events[0].user_profile.account_info,
            AccountInfo(
                employer_id=1, unit_id=1, employer_name="employer_name", unit_name="unit_name"
            ),
        )
Beispiel #9
0
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}
Beispiel #10
0
    def test_conclude_with_right_answer(self, get_drill_mock):
        self.dialog_state.user_profile.validated = True
        self._set_current_prompt(3, should_advance=True)
        command = ProcessSMSMessage(self.phone_number, "foo")
        batch = self._process_command(command)
        self._assert_event_types(
            batch,
            DialogEventType.COMPLETED_PROMPT,
            DialogEventType.ADVANCED_TO_NEXT_PROMPT,
            DialogEventType.DRILL_COMPLETED,
        )
        completed_event: CompletedPrompt = batch.events[0]  # type: ignore
        self.assertEqual(completed_event.prompt, self.drill.prompts[3])
        self.assertEqual(completed_event.response, "foo")
        self.assertEqual(completed_event.drill_instance_id,
                         self.dialog_state.drill_instance_id)

        advanced_event: AdvancedToNextPrompt = batch.events[1]  # type: ignore
        self.assertEqual(self.drill.prompts[4], advanced_event.prompt)

        self.assertEqual(self.dialog_state.drill_instance_id,
                         advanced_event.drill_instance_id)

        drill_completed_event: DrillCompleted = batch.events[2]  # type: ignore
        self.assertEqual(self.dialog_state.drill_instance_id,
                         drill_completed_event.drill_instance_id)
Beispiel #11
0
 def test_unhandled_message_received(self):
     self.dialog_state.user_profile.validated = True
     self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
     command = ProcessSMSMessage(self.phone_number, "BLABLABLA")
     batch = self._process_command(command)
     self._assert_event_types(batch, DialogEventType.UNHANDLED_MESSAGE_RECEIVED)
     self.assertEqual(batch.events[0].message, "BLABLABLA")
Beispiel #12
0
    def test_drill_with_one_prompt(self, *args):
        choose_language_drill = Drill(
            name="test-drill",
            slug="test-drill",
            prompts=[
                Prompt(
                    slug="ignore-response-1",
                    messages=[PromptMessage(text="{{msg1}}")],
                    response_user_profile_key="language",
                ),
            ],
        )
        self.dialog_state.user_profile.validated = True
        self.dialog_state.current_drill = choose_language_drill
        self._set_current_prompt(0, drill=choose_language_drill)
        command = ProcessSMSMessage(self.phone_number, "es")
        batch = self._process_command(command)

        self._assert_event_types(
            batch,
            DialogEventType.COMPLETED_PROMPT,
            DialogEventType.DRILL_COMPLETED,
        )
        self.assertEqual(batch.events[0].user_profile_updates, {"language": "es"})
        self.assertEqual(batch.user_profile.language, "es")
Beispiel #13
0
    def test_first_message_does_not_validate_user(self):
        validator = MagicMock()
        validation_payload = CodeValidationPayload(valid=False)
        validator.validate_code = MagicMock(return_value=validation_payload)
        command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator)
        self.assertFalse(self.dialog_state.user_profile.validated)

        batch = self._process_command(command)

        self._assert_event_types(batch, DialogEventType.USER_VALIDATION_FAILED)
Beispiel #14
0
 def test_menu_requested(self):
     for message in ["menu", "menú"]:
         self.dialog_state.user_profile.validated = True
         self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
         self.dialog_state.current_drill = "balbla"
         self.dialog_state.drill_instance_id = "11111111-1111-1111-1111-111111111111"
         self.dialog_state.current_prompt_state = "blabla"
         command = ProcessSMSMessage(self.phone_number, message)
         batch = self._process_command(command)
         self._assert_event_types(batch, DialogEventType.MENU_REQUESTED)
Beispiel #15
0
 def test_advance_sequence_numbers(self, get_drill_mock):
     validator = MagicMock()
     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.assertEqual(1, len(batch.events))
     self.assertEqual("1", self.dialog_state.seq)
Beispiel #16
0
    def test_doesnt_revalidate_someone_with_an_org(self):
        validator = MagicMock()
        validation_payload = CodeValidationPayload(valid=True, is_demo=True)
        validator.validate_code = MagicMock(return_value=validation_payload)
        self.dialog_state.user_profile.validated = True
        self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
        command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator)
        batch = self._process_command(command)

        # not a USER_VALIDATED event
        self._assert_event_types(batch, DialogEventType.UNHANDLED_MESSAGE_RECEIVED)
Beispiel #17
0
    def test_repeat_with_wrong_answer(self, *args):
        self.dialog_state.user_profile.validated = True
        self._set_current_prompt(2)
        command = ProcessSMSMessage(self.phone_number, "completely wrong answer")
        batch = self._process_command(command)
        self._assert_event_types(batch, DialogEventType.FAILED_PROMPT)

        failed_event: FailedPrompt = batch.events[0]  # type: ignore
        self.assertEqual(failed_event.prompt, self.drill.prompts[2])
        self.assertFalse(failed_event.abandoned)
        self.assertEqual(failed_event.response, "completely wrong answer")
        self.assertEqual(failed_event.drill_instance_id, self.dialog_state.drill_instance_id)
Beispiel #18
0
 def test_certain_keywords_ignored_while_during_lesson(self):
     for message in ["lang", "schedule", "horario", "more", "mas", "name"]:
         self.dialog_state.user_profile.validated = True
         self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
         self.dialog_state.drill_instance_id = "11111111-1111-1111-1111-111111111111"
         self._set_current_prompt(1)
         self.dialog_state.current_drill = self.drill
         command = ProcessSMSMessage(self.phone_number, message)
         batch = self._process_command(command)
         self._assert_event_types(
             batch, DialogEventType.COMPLETED_PROMPT, DialogEventType.ADVANCED_TO_NEXT_PROMPT
         )
Beispiel #19
0
 def test_dashboard_requested(self):
     for message in ["info"]:
         self.dialog_state.user_profile.validated = True
         self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
         self.dialog_state.current_drill = "balbla"
         self.dialog_state.drill_instance_id = "11111111-1111-1111-1111-111111111111"
         self.dialog_state.current_prompt_state = "blabla"
         command = ProcessSMSMessage(self.phone_number, message)
         batch = self._process_command(command)
         self._assert_event_types(batch, DialogEventType.DASHBOARD_REQUESTED)
         self.assertIsNone(
             batch.events[0].abandoned_drill_instance_id,
         )
Beispiel #20
0
    def test_fail_prompt_with_empty_response_stores_response_as_null(self, *args):
        self.dialog_state.user_profile.validated = True
        self._set_current_prompt(3)
        self.dialog_state.current_prompt_state.failures = 0

        command = ProcessSMSMessage(self.phone_number, "")
        batch = self._process_command(command)
        self._assert_event_types(
            batch,
            DialogEventType.FAILED_PROMPT,
        )

        failed_event: FailedPrompt = batch.events[0]  # type: ignore
        self.assertEqual(failed_event.response, None)
Beispiel #21
0
    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)
Beispiel #22
0
 def test_advance_sequence_numbers(self):
     validator = MagicMock()
     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.assertEqual(1, len(batch.events))
     self.assertEqual("1", self.dialog_state.seq)
Beispiel #23
0
    def test_first_message_validates_user(self, get_drill_mock):
        validator = MagicMock()
        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)
        self.assertFalse(self.dialog_state.user_profile.validated)

        batch = self._process_command(command)
        self._assert_event_types(batch, DialogEventType.USER_VALIDATED)
        self.assertEqual(
            validation_payload,
            batch.events[0].code_validation_payload  # type: ignore
        )
Beispiel #24
0
 def test_change_language_drill_requested(self):
     for message in ["lang", "language", "idioma"]:
         self.dialog_state.user_profile.validated = True
         self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
         self.dialog_state.drill_instance_id = "11111111-1111-1111-1111-111111111111"
         self.dialog_state.current_prompt_state = "blabla"
         command = ProcessSMSMessage(self.phone_number, message)
         batch = self._process_command(command)
         self._assert_event_types(batch, DialogEventType.LANGUAGE_CHANGE_DRILL_REQUESTED)
         self.assertIsNone(self.dialog_state.current_drill)
         self.assertIsNone(self.dialog_state.drill_instance_id)
         self.assertIsNone(self.dialog_state.current_prompt_state)
         self.assertEqual(
             batch.events[0].abandoned_drill_instance_id,
             uuid.UUID("11111111-1111-1111-1111-111111111111"),
         )
Beispiel #25
0
 def test_ask_for_scheduling_drill(self):
     messages = ["schedule", "calendario"]
     for message in messages:
         self.dialog_state.user_profile.validated = True
         self.dialog_state.user_profile.account_info = AccountInfo(employer_id=1)
         self.dialog_state.drill_instance_id = "11111111-1111-1111-1111-111111111111"
         self.dialog_state.current_prompt_state = "blabla"
         command = ProcessSMSMessage(self.phone_number, message)
         batch = self._process_command(command)
         self._assert_event_types(batch, DialogEventType.SCHEDULING_DRILL_REQUESTED)
         self.assertIsNone(self.dialog_state.current_drill)
         self.assertIsNone(self.dialog_state.drill_instance_id)
         self.assertIsNone(self.dialog_state.current_prompt_state)
         self.assertEqual(
             batch.events[0].abandoned_drill_instance_id,
             uuid.UUID("11111111-1111-1111-1111-111111111111"),
         )
Beispiel #26
0
 def test_revalidate_user_without_org(self):
     validator = MagicMock()
     validation_payload = CodeValidationPayload(valid=True, is_demo=True)
     validator.validate_code = MagicMock(return_value=validation_payload)
     self.dialog_state.user_profile.validated = True
     self.dialog_state.user_profile.account_info = AccountInfo(employer_id=None)
     command = ProcessSMSMessage(self.phone_number, "hey", registration_validator=validator)
     batch = self._process_command(command)
     self._assert_event_types(batch, DialogEventType.USER_VALIDATED)
     validation_payload = CodeValidationPayload(
         valid=True,
         account_info={
             "employer_id": 1,
             "unit_id": 1,
             "employer_name": "employer_name",
             "unit_name": "unit_name",
         },
     )
Beispiel #27
0
    def test_complete_and_advance(self):
        self.dialog_state.user_profile.validated = True
        self._set_current_prompt(0)
        command = ProcessSMSMessage(self.phone_number, "go")
        batch = self._process_command(command)
        self._assert_event_types(
            batch,
            DialogEventType.COMPLETED_PROMPT,
            DialogEventType.ADVANCED_TO_NEXT_PROMPT,
        )
        completed_event: CompletedPrompt = batch.events[0]  # type: ignore
        self.assertEqual(completed_event.prompt, self.drill.prompts[0])
        self.assertEqual(completed_event.response, "go")
        self.assertEqual(completed_event.drill_instance_id, self.dialog_state.drill_instance_id)

        advanced_event: AdvancedToNextPrompt = batch.events[1]  # type: ignore
        self.assertEqual(self.drill.prompts[1], advanced_event.prompt)
        self.assertEqual(self.dialog_state.drill_instance_id, advanced_event.drill_instance_id)
Beispiel #28
0
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}
Beispiel #29
0
    def test_advance_with_too_many_wrong_answers(self, get_drill_mock):
        self.dialog_state.user_profile.validated = True
        self._set_current_prompt(2, should_advance=False)
        self.dialog_state.current_prompt_state.failures = 1

        command = ProcessSMSMessage(self.phone_number,
                                    "completely wrong answer")
        batch = self._process_command(command)
        self._assert_event_types(batch, DialogEventType.FAILED_PROMPT,
                                 DialogEventType.ADVANCED_TO_NEXT_PROMPT)

        failed_event: FailedPrompt = batch.events[0]  # type: ignore
        self.assertEqual(failed_event.prompt, self.drill.prompts[2])
        self.assertTrue(failed_event.abandoned)
        self.assertEqual(failed_event.response, "completely wrong answer")
        self.assertEqual(failed_event.drill_instance_id,
                         self.dialog_state.drill_instance_id)

        advanced_event: AdvancedToNextPrompt = batch.events[1]  # type: ignore
        self.assertEqual(self.drill.prompts[3], advanced_event.prompt)
        self.assertEqual(self.dialog_state.drill_instance_id,
                         advanced_event.drill_instance_id)
Beispiel #30
0
 def test_skip_processed_sequence_numbers(self):
     command = Mock(wraps=ProcessSMSMessage(self.phone_number, "hey"))
     process_command(command, "0", repo=self.repo)
     self.assertFalse(command.execute.called)