diff --git a/api/models/ivr_prompt_response.py b/api/models/ivr_prompt_response.py index 9f66db6f..253ca8b1 100644 --- a/api/models/ivr_prompt_response.py +++ b/api/models/ivr_prompt_response.py @@ -7,6 +7,9 @@ class IvrPromptResponseQuery(BaseQuery): def get_by_call_log_id(self, call_log_id): return self.filter(IvrPromptResponse.call_log_id == call_log_id).all() + def get_latest_prompt(self): + return self.filter().order_by(IvrPromptResponse.id.desc()).first() + class IvrPromptResponse(TimestampMixin, db.Model): query_class = IvrPromptResponseQuery diff --git a/api/models/registration.py b/api/models/registration.py index 37d9ff10..8b6639cc 100644 --- a/api/models/registration.py +++ b/api/models/registration.py @@ -24,6 +24,9 @@ def get_latest_by_phone(self, phone): .first() ) + def get_latest_record(self): + return self.filter().order_by(Registration.id.desc()).first() + class Registration(TimestampMixin, db.Model): query_class = RegistrationQuery diff --git a/api/models/webhook_transaction_log.py b/api/models/webhook_transaction_log.py index ec97d66b..4bc61962 100644 --- a/api/models/webhook_transaction_log.py +++ b/api/models/webhook_transaction_log.py @@ -9,6 +9,9 @@ def get_by_id(self, webhook_transaction_log_id): WebhookTransactionLog.id == webhook_transaction_log_id ).first() + def get_last_record(self): + return self.filter().order_by(WebhookTransactionLog.id.desc()).first() + class WebhookTransactionLog(TimestampMixin, db.Model): query_class = WebhookTransactionLogQuery diff --git a/migrations/seeders/__init__.py b/migrations/seeders/__init__.py new file mode 100644 index 00000000..ed1485d8 --- /dev/null +++ b/migrations/seeders/__init__.py @@ -0,0 +1,14 @@ +from migrations.seeders.user import * +from migrations.seeders.system_phone import * +from migrations.seeders.partner import * +from migrations.seeders.registration import * +from migrations.seeders.program import * +from migrations.seeders.language import * +from migrations.seeders.content_version import * +from migrations.seeders.content import * +from migrations.seeders.user_program import * +from migrations.seeders.module import * +from migrations.seeders.module_content import * +from migrations.seeders.program_module import * +from migrations.seeders.program_sequence import * +from migrations.seeders.call_log import * diff --git a/migrations/seeders/call_log.py b/migrations/seeders/call_log.py new file mode 100644 index 00000000..8ab50bab --- /dev/null +++ b/migrations/seeders/call_log.py @@ -0,0 +1,25 @@ +from faker import Faker +from uuid import uuid4 +from api.models import CallLog as CallLogInstance +from api import db + + +class CallLog: + faker = Faker() # Kept for future use + + def create_call_log(self, phone_number, content_id, content_version_id): + call_log_record = CallLogInstance() + call_log_record.user_phone_number = phone_number + call_log_record.content_id = content_id + call_log_record.content_version_id = content_version_id + call_log_record.flow_run_uuid = str(uuid4()) + + db.session.add(call_log_record) + db.session.commit() + + +if __name__ == "__main__": + seeder = CallLog() + seeder.create_call_log( + phone_number="1234567890", content_id=1, content_version_id=1 + ) diff --git a/migrations/seeders/content_version.py b/migrations/seeders/content_version.py index 22fbb738..c14fdafa 100644 --- a/migrations/seeders/content_version.py +++ b/migrations/seeders/content_version.py @@ -22,6 +22,8 @@ def create_content_version(self, language_id: int, contents: List[ContentModel]) db.session.add_all(content_version_instances) db.session.commit() + return content_version_instances + if __name__ == "__main__": seeder = ContentVersion() diff --git a/migrations/seeders/module.py b/migrations/seeders/module.py new file mode 100644 index 00000000..acd6495f --- /dev/null +++ b/migrations/seeders/module.py @@ -0,0 +1,23 @@ +import datetime +from faker import Faker +from api.models import Module as ModuleInstance +from api import db + + +class Module: + faker = Faker() # Kept for future use + + def create_module(self, program_id): + module_instance = ModuleInstance() + module_instance.name = "Test Module" + module_instance.program_id = program_id + + db.session.add(module_instance) + db.session.commit() + + return module_instance + + +if __name__ == "__main__": + seeder = Module() + seeder.create_module(program_id=1) diff --git a/migrations/seeders/module_content.py b/migrations/seeders/module_content.py new file mode 100644 index 00000000..ead1e9aa --- /dev/null +++ b/migrations/seeders/module_content.py @@ -0,0 +1,23 @@ +import datetime +from faker import Faker +from api.models import ModuleContent as ModuleContentInstance +from api import db + + +class ModuleContent: + faker = Faker() # Kept for future use + + def create_module_content(self, module_id, content_id): + module_instance = ModuleContentInstance() + module_instance.module_id = module_id + module_instance.content_id = content_id + + db.session.add(module_instance) + db.session.commit() + + return module_instance + + +if __name__ == "__main__": + seeder = ModuleContent() + seeder.create_module_content(module_id=1, content_id=1) diff --git a/migrations/seeders/program_module.py b/migrations/seeders/program_module.py new file mode 100644 index 00000000..826504f7 --- /dev/null +++ b/migrations/seeders/program_module.py @@ -0,0 +1,24 @@ +import datetime +from faker import Faker +from api.models import ProgramModule as ProgramModuleInstance +from api import db + + +class ProgramModule: + faker = Faker() # Kept for future use + + def create_program_module(self, program_id, module_id, sequence_id=1): + program_module_instance = ProgramModuleInstance() + program_module_instance.program_id = program_id + program_module_instance.module_id = module_id + program_module_instance.sequence = sequence_id + + db.session.add(program_module_instance) + db.session.commit() + + return program_module_instance + + +if __name__ == "__main__": + seeder = ProgramModule() + seeder.create_program_module(program_id=1, module_id=1) diff --git a/migrations/seeders/program_sequence.py b/migrations/seeders/program_sequence.py new file mode 100644 index 00000000..9074dd2b --- /dev/null +++ b/migrations/seeders/program_sequence.py @@ -0,0 +1,24 @@ +import datetime +from faker import Faker +from api.models import ProgramSequence as ProgramSequenceInstance +from api import db + + +class ProgramSequence: + faker = Faker() # Kept for future use + + def create_program_sequence(self, content_id, program_id, module_id): + program_sequence_instance = ProgramSequenceInstance() + program_sequence_instance.program_id = program_id + program_sequence_instance.content_id = content_id + program_sequence_instance.module_id = module_id + + db.session.add(program_sequence_instance) + db.session.commit() + + return program_sequence_instance + + +if __name__ == "__main__": + seeder = ProgramSequence() + seeder.create_program_sequence(program_id=1, content_id=1, module_id=1) diff --git a/migrations/seeders/testing_seeder.py b/migrations/seeders/testing_seeder.py index 27f89647..c4c7ea3b 100644 --- a/migrations/seeders/testing_seeder.py +++ b/migrations/seeders/testing_seeder.py @@ -1,14 +1,6 @@ from faker import Faker from typing import List -from migrations.seeders.user import User -from migrations.seeders.system_phone import SystemPhone -from migrations.seeders.partner import Partner -from migrations.seeders.registration import Registration -from migrations.seeders.program import Program -from migrations.seeders.language import Language -from migrations.seeders.content_version import ContentVersion -from migrations.seeders.content import Content -from migrations.seeders.user_program import UserProgram +from migrations.seeders import * def main(): @@ -21,7 +13,7 @@ def main(): created_contents = content.create_content() content_version = ContentVersion() - content_version.create_content_version( + created_content_versions = content_version.create_content_version( language_id=language_instance.id, contents=created_contents ) @@ -49,3 +41,30 @@ def main(): created_user_programs: List = user_program.create_user_program( created_programs, [user.user_id for user in created_user_instances] ) + + module = Module() + created_module = module.create_module(program_id=created_programs[0].id) + + module_content = ModuleContent() + created_module_content = module_content.create_module_content( + module_id=created_module.id, content_id=created_contents[0].id + ) + + program_module = ProgramModule() + create_program_module = program_module.create_program_module( + program_id=created_programs[0].id, module_id=created_module.id + ) + + program_sequence = ProgramSequence() + created_program_sequence = program_sequence.create_program_sequence( + program_id=created_programs[0].id, + content_id=created_contents[0].id, + module_id=created_module.id, + ) + + call_log = CallLog() + call_log.create_call_log( + phone_number=created_user_instances[0].phone, + content_id=created_content_versions[0].content_id, + content_version_id=created_content_versions[0].id, + ) diff --git a/tests/payloads/registration_payload.json b/tests/payloads/registration_payload.json new file mode 100644 index 00000000..741c05fb --- /dev/null +++ b/tests/payloads/registration_payload.json @@ -0,0 +1,189 @@ +{ + "channel": { + "address": "", + "name": "", + "uuid": "a9efa603-dabe-4259-b449-f1de8dc878d9" + }, + "contact": { + "fields": { + "activities_week_6": null, + "address": null, + "age": null, + "agency": null, + "alumni_programme": "Yes", + "awcname": null, + "block": null, + "blocknumber": null, + "boygirl": null, + "case_classification": null, + "cg_a1_results": null, + "cg_a2_results": null, + "cg_a3_results": null, + "cg_a4_results": null, + "cg_a5_results": null, + "concerncoc": null, + "count_polls_completed": null, + "count_polls_responded": null, + "cv_opt_in": null, + "cvb_first_engagement": null, + "cvb_first_reach": null, + "cvb_first_start": null, + "cvb_hac_1_reach_def_2020_06_10": null, + "cvb_last_start": null, + "cvb_max_version": null, + "cvb_menu_setting": null, + "cvb_menus_visited": null, + "cvb_num_info": null, + "cvb_num_input": null, + "cvb_num_other_in_message": null, + "cvb_num_runs": null, + "cvb_num_starts": null, + "cvb_other": null, + "d_c_c": null, + "dat": null, + "date": "2024-01-01", + "day": null, + "deliveryedd": null, + "designation": null, + "district": null, + "district_audio": null, + "dost_cc": null, + "dost_flow_status": "Introdone", + "dost_module10_streak": null, + "dost_module5_streak": null, + "dost_module7_streak": null, + "dost_nudge_group": null, + "dost_program": "Program1", + "dost_program_completion": null, + "dost_program_status": null, + "dost_signup_status": "Yes", + "dost_time": "1", + "dost_ttr_group": null, + "dutystation": null, + "edulanguage": null, + "email": null, + "engagementcounter": null, + "feedback": null, + "fl_a10_results": null, + "fl_a11_results": null, + "fl_a12_results": null, + "fl_a1_results": null, + "fl_a2_results": null, + "fl_a3_results": null, + "fl_a4_results": null, + "fl_a5_results": null, + "fl_a6_results": null, + "fl_a7_results": null, + "fl_a8_results": null, + "fl_a9_results": null, + "gender": null, + "gram_panchayat": null, + "jharkhand_number_switch_impacteduser": null, + "language_id": null, + "last_incoming_call": null, + "lastactivedate": null, + "level": null, + "lname": null, + "location_coordinates": null, + "mobiletype": null, + "mothername": null, + "pin": null, + "pincode": null, + "polls_completed": null, + "polls_responded": null, + "pregnantmothername": null, + "program": null, + "programme_status": null, + "projectname": null, + "registration_date": "2023-04-04", + "role": null, + "sector": null, + "sm_contractual_status": null, + "sm_current_condition": null, + "sm_date_of_reporting": null, + "sm_dependent_current_condition": null, + "sm_email": null, + "sm_gender": null, + "sm_mobile_number": null, + "sm_name": null, + "sm_no_of_dependent_affected": null, + "sm_office": null, + "sm_patient_state": null, + "sm_section": null, + "sm_title": null, + "staff_type": null, + "stafftype": null, + "state": "India > Uttar Pradesh", + "stream": null, + "team": null, + "test_response_result": null, + "time": null, + "title": null, + "totalnochild": null, + "type_of_participant": null, + "updnumber": null, + "ups": null, + "upweek1": null, + "ward": null, + "week": null, + "whohavephone": null, + "whose_affected": null + }, + "groups": [ + { + "name": "Dost-Pilot-Intro", + "uuid": "4bf04450-cf03-4eed-8e25-f7e6f0dd3301" + } + ], + "name": "", + "urn": "", + "uuid": "1e745892-f454-46cd-9b68-d9348e7a270a" + }, + "content_id": "1", + "flow": { + "name": "Devteam-DOST-Pilot-OnboardingCall1.2", + "uuid": "bf63ea9f-a7c9-4ea5-bb1e-b632c8f54dbb" + }, + "flow_category": "registration", + "flow_run_details": { + "created_on": "2024-01-01T12:30:47.977099Z", + "uuid": "" + }, + "parent": null, + "program_details": { + "categories": [ + "INTRO_1-SIGNUP_1-SELECTION_PROGRAM-OPTIN_1" + ], + "categories_localized": [ + "INTRO_1-SIGNUP_1-SELECTION_PROGRAM-OPTIN_1" + ], + "category": "INTRO_1-SIGNUP_1-SELECTION_PROGRAM-OPTIN_1", + "category_localized": "INTRO_1-SIGNUP_1-SELECTION_PROGRAM-OPTIN_1", + "created_on": "2024-01-01T12:31:06.070339Z", + "extra": null, + "input": "1", + "name": "INTRO_1-SIGNUP_1-SELECTION_PROGRAM-OPTIN", + "node_uuid": "2676658b-853e-4de1-b395-6dbf327c8025", + "value": "1", + "values": [ + "1" + ] + }, + "results": { + "intro_1_signup_1_selection_program_optin": { + "category": "INTRO_1-SIGNUP_1-SELECTION_PROGRAM-OPTIN_1", + "name": "INTRO_1-SIGNUP_1-SELECTION_PROGRAM-OPTIN", + "value": "1" + }, + "intro_1_signup_1_selection_time_optin": { + "category": "INTRO_1-SIGNUP_1-SELECTION_TIME-OPTIN_MORNING", + "name": "INTRO_1-SIGNUP_1-SELECTION_TIME-OPTIN", + "value": "1" + }, + "result": { + "category": "Success", + "name": "Result", + "value": "200" + } + } +} diff --git a/tests/test_program_sequence_service.py b/tests/test_program_sequence_service.py new file mode 100644 index 00000000..08f6a5a0 --- /dev/null +++ b/tests/test_program_sequence_service.py @@ -0,0 +1,48 @@ +from __future__ import absolute_import +import json +import pytest +from uuid import uuid4 +from random import randint +from faker import Faker +from api.services.program_sequence_service import * +from api.models import * + +faker = Faker() + +# Load the json payload +payload = json.load(open("tests/payloads/call_log_payload.json")) + +# Generate a unique flow_run_uuid +new_run_uuid = str(uuid4()) + +# Insert into the payload +payload["flow_run_details"]["uuid"] = new_run_uuid + + +def test_get_program_sequence(app, db, setup_test_environment): + with app.app_context(): + # Get a user from User Program + user_program: UserProgram = UserProgram.query.all()[0] + + # Get a specific user + user: User = User.query.get_by_id(user_program.user_id) + + # Update user phone number to payload. + payload["contact"]["urn"] = f"tel:{user.phone}" + + # Get a specific system phone number + system_phone: SystemPhone = SystemPhone.query.get_by_id(1) + + # Update system phone number to payload. + payload["channel"]["address"] = f"+91{system_phone.phone}" + + # Get Content Module + module_content: ModuleContent = ModuleContent.query.all()[0] + + payload["content_id"] = module_content.content_id + + # Initialize the Program Sequence service. + program_sequence_service = ProgramSequenceService() + sequence_id = program_sequence_service.get_program_sequence_id(payload) + + assert sequence_id != None diff --git a/tests/test_prompt_service.py b/tests/test_prompt_service.py new file mode 100644 index 00000000..bad178bd --- /dev/null +++ b/tests/test_prompt_service.py @@ -0,0 +1,56 @@ +from __future__ import absolute_import +import json +import pytest +from faker import Faker +from api.services.prompt_service import * +from api.models import * +from api import db + +faker = Faker() + +# Load the json payload +payload = json.load(open("tests/payloads/registration_payload.json")) + + +def pytest_namespace(): + # We will record the content version from the create operation for future reference + return {"initial_content_version_id": None} + + +def test_prompt_response(app, db, setup_test_environment): + with app.app_context(): + # Get call log record + call_log: CallLog = CallLog.query.all()[0] + + # Get a specific user + user: User = User.query.get_by_phone(call_log.user_phone_number) + + # Insert into the payload + payload["flow_run_details"]["uuid"] = call_log.flow_run_uuid + + # Update user phone number to payload. + payload["contact"]["urn"] = f"tel:{user.phone}" + + # Get a specific system phone number + system_phone: SystemPhone = SystemPhone.query.get_by_id(1) + + # Update system phone number to payload. + payload["channel"]["address"] = f"+91{system_phone.phone}" + + # Get a specific content + content_version: ContentVersion = ContentVersion.query.get_by_id( + call_log.content_version_id + ) + + payload["content_id"] = content_version.content_id + payload["language_id"] = content_version.language_id + + # Initialize the Prompt service. + prompt_service_instance = PromptService() + prompt_service_instance.handle_prompt_response(payload) + + user_phone_number = ( + payload["contact"]["urn"].replace("tel:", "").replace("+", "") + ) + ivr_prompt_response = IvrPromptResponse.query.get_latest_prompt() + assert ivr_prompt_response.user_phone == user_phone_number diff --git a/tests/test_registration.py b/tests/test_registration.py new file mode 100644 index 00000000..db7ca70e --- /dev/null +++ b/tests/test_registration.py @@ -0,0 +1,60 @@ +from __future__ import absolute_import +import json +import pytest +from uuid import uuid4 +from random import randint +from faker import Faker +from api.services.registration_service import * +from api.models import * + +faker = Faker() + +# Load the json payload +payload = json.load(open("tests/payloads/registration_payload.json")) + +# Generate a unique flow_run_uuid +new_run_uuid = str(uuid4()) + +# Insert into the payload +payload["flow_run_details"]["uuid"] = new_run_uuid + + +def test_create_registration(app, db, setup_test_environment): + with app.app_context(): + # Get a specific user + user: User = User.query.get_by_id(1) + + # Update user phone number to payload. + payload["contact"]["urn"] = f"tel:{user.phone}" + + # Get a specific system phone number + system_phone: SystemPhone = SystemPhone.query.get_by_id(1) + + # Update system phone number to payload. + payload["channel"]["address"] = f"+91{system_phone.phone}" + + # Get a specific content + content_version: ContentVersion = ContentVersion.query.get_by_id(1) + + payload["content_id"] = content_version.content_id + payload["language_id"] = content_version.language_id + + # Initialize the registration service. + registration_service_instance = RegistrationService() + registration_service_instance.handle_registration(payload) + + user_phone_number = payload["contact"]["urn"].replace("tel:", "") + registration_model = Registration.query.get_latest_record() + assert registration_model.user_phone == user_phone_number + + +def test_update_registration(app, db, setup_test_environment): + with app.app_context(): + # Initialize the registration service. + registration_service_instance = RegistrationService() + registration_service_instance.handle_registration(payload) + + user_phone_number = payload["contact"]["urn"].replace("tel:", "") + registration_model = Registration.query.get_latest_record() + assert registration_model.user_phone == user_phone_number + assert registration_model.has_received_callback == True diff --git a/tests/test_transaction_log_service.py b/tests/test_transaction_log_service.py new file mode 100644 index 00000000..3c65e57d --- /dev/null +++ b/tests/test_transaction_log_service.py @@ -0,0 +1,47 @@ +from __future__ import absolute_import +import json +import pytest +from uuid import uuid4 +from random import randint +from faker import Faker +from api.services.transaction_log_service import * +from api.models import * + +faker = Faker() + +# Load the json payload +payload = json.load(open("tests/payloads/registration_payload.json")) + +# Generate a unique flow_run_uuid +new_run_uuid = str(uuid4()) + +# Insert into the payload +payload["flow_run_details"]["uuid"] = new_run_uuid + + +def test_create_transaction_log(app, db, setup_test_environment): + with app.app_context(): + # Get a specific user + user: User = User.query.get_by_id(1) + + # Update user phone number to payload. + payload["contact"]["urn"] = f"tel:{user.phone}" + + # Get a specific system phone number + system_phone: SystemPhone = SystemPhone.query.get_by_id(1) + + # Update system phone number to payload. + payload["channel"]["address"] = f"+91{system_phone.phone}" + + # Get a specific content version. + content_version: ContentVersion = ContentVersion.query.get_by_id(1) + + payload["content_id"] = content_version.content_id + payload["language_id"] = content_version.language_id + + # Initialize the Transaction Log Service. + transaction_log_instance = TransactionLogService() + transaction_log_instance.create_new_webhook_log(payload) + + last_transaction_log_record = WebhookTransactionLog.query.get_last_record() + assert last_transaction_log_record.payload == json.dumps(payload)