diff --git a/.github/actions/validate/Dockerfile b/.github/actions/validate/Dockerfile index 6977b083..191a052e 100644 --- a/.github/actions/validate/Dockerfile +++ b/.github/actions/validate/Dockerfile @@ -3,6 +3,7 @@ FROM python:3 # Copies your code file from your action repository to the filesystem path `/` of the container COPY entrypoint.sh /entrypoint.sh +COPY bxml_tests.py /bxml_tests.py #Make entrypoint.sh exacutable RUN chmod +x /entrypoint.sh diff --git a/.github/actions/validate/bxml_tests.py b/.github/actions/validate/bxml_tests.py new file mode 100644 index 00000000..5ad6ad4b --- /dev/null +++ b/.github/actions/validate/bxml_tests.py @@ -0,0 +1,366 @@ +""" +bxml_tests.py + +Unit tests for BXML + +@copyright Bandwidth INC +""" +from bandwidth.voice.bxml.response import Response +from bandwidth.voice.bxml.verbs import * + +import unittest + + +class BxmlTests(unittest.TestCase): + """ + Class for the BXML tests + """ + def test_forward_xml_with_optional_fields(self): + response = Response() + forward = Forward( + to="+10987654321", + from_="+11234567890", + call_timeout=100, + diversion_treatment="propagate", + diversion_reason="away" + ) + response.add_verb(forward) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_gather_no_nested(self): + response = Response() + gather = Gather( + gather_url="https://gather.url/nextBXML", + gather_method="POST", + terminating_digits="#", + tag="tag", + max_digits=20, + inter_digit_timeout=50, + username="user", + password="password", + first_digit_timeout=10, + repeat_count=3, + gather_fallback_url="https://test.com", + gather_fallback_method="GET", + fallback_username="fuser", + fallback_password="fpass" + ) + response.add_verb(gather) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_gather_with_speak_sentence(self): + response = Response() + speak_sentence = SpeakSentence( + sentence="Phrase.", + voice="kate", + locale="en_US", + gender="female" + ) + gather = Gather( + gather_url="https://gather.url/nextBXML", + gather_method="POST", + terminating_digits="#", + tag="tag", + max_digits=20, + inter_digit_timeout=50, + username="user", + password="password", + first_digit_timeout=10, + repeat_count=3, + speak_sentence=speak_sentence + ) + expected_bxml = expected_bxml = 'Phrase.' + response.add_verb(gather) + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_gather_play_audio(self): + response = Response() + play_audio_1 = PlayAudio(url="https://audio.url/audio1.wav") + gather = Gather( + gather_url="https://gather.url/nextBXML", + gather_method="POST", + terminating_digits="#", + tag="tag", + max_digits=20, + inter_digit_timeout=50, + username="user", + password="password", + first_digit_timeout=10, + repeat_count=3, + play_audio=play_audio_1 + ) + expected_bxml = 'https://audio.url/audio1.wav' + response.add_verb(gather) + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_hangup(self): + response = Response() + hang_up = Hangup() + response.add_verb(hang_up) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_pause(self): + response = Response() + pause = Pause(duration=400) + response.add_verb(pause) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_pause_recording(self): + response = Response() + pause_recording = PauseRecording() + response.add_verb(pause_recording) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_play_audio(self): + response = Response() + play_audio_1 = PlayAudio( + url="https://audio.url/audio1.wav", + username="user", + password="pass" + ) + response.add_verb(play_audio_1) + expected_bxml = 'https://audio.url/audio1.wav' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_record(self): + response = Response() + record = Record( + tag = "tag", + username = "user", + password = "pass", + record_complete_url="https://record.url.server/record", + record_complete_method="POST", + recording_available_url="https://record.url.server/available", + recording_available_method="GET", + terminating_digits="#", + max_duration=90, + file_format="mp3", + transcribe=False, + transcription_available_url="https://transcribe.url.server/available", + transcription_available_method="POST", + silence_timeout=90, + record_complete_fallback_url="https://test.com", + record_complete_fallback_method="GET", + fallback_username="fuser", + fallback_password="fpass" + ) + response.add_verb(record) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_redirect(self): + response = Response() + redirect = Redirect( + redirect_url="http://flow.url/newFlow", + redirect_method="POST", + tag="tag", + username="user", + password="pass", + redirect_fallback_url="https://test.com", + redirect_fallback_method="GET", + fallback_username="fuser", + fallback_password="fpass" + ) + response.add_verb(redirect) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_resume_recording(self): + response = Response() + resume_recording = ResumeRecording() + response.add_verb(resume_recording) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_dtmf(self): + response = Response() + send_dtmf = SendDtmf( + dtmf = "1234", + tone_duration = 200, + tone_interval = 450 + ) + expected_bxml = '1234' + response.add_verb(send_dtmf) + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_speak_sentence(self): + response = Response() + speak_sentence = SpeakSentence( + sentence="Phrase.", + voice="kate", + locale="en_US", + gender="female" + ) + expected_bxml = 'Phrase.' + response.add_verb(speak_sentence) + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_speak_sentence_SSML(self): + response = Response() + speak_sentence = SpeakSentence( + sentence='Hydrogen is the most abundant element in the universe.', + voice="kate", + locale="en_US", + gender="female" + ) + expected_bxml = 'Hydrogen is the most abundant element in the universe.' + response.add_verb(speak_sentence) + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_start_recording(self): + response = Response() + record = StartRecording( + tag = "tag", + username = "user", + password = "pass", + recording_available_url="https://record.url.server/available", + recording_available_method="GET", + file_format="mp3", + multi_channel=True, + transcribe=False, + transcription_available_url="https://transcribe.url.server/available", + transcription_available_method="POST" + ) + response.add_verb(record) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_stop_recording(self): + response = Response() + stop_recording = StopRecording() + response.add_verb(stop_recording) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_transfer(self): + response = Response() + phone1 = PhoneNumber( + number="+11234567891", + transfer_answer_url="https://transfer.com/answer", + transfer_answer_method="POST", + username="user", + password="pass", + tag="tag", + transfer_disconnect_method="POST", + transfer_disconnect_url="https://transfer.com/disconnect", + transfer_answer_fallback_url="https://test.com", + transfer_answer_fallback_method="GET", + fallback_username="fuser", + fallback_password="fpass" + ) + phone1_bxml = '+11234567891' + phone2 = PhoneNumber(number="+11234567892") + phone2_bxml = '+11234567892' + transfer = Transfer( + transfer_caller_id="+15555555555", + call_timeout=50, + tag="tag", + transfer_complete_url="https://transcribe.url.server/complete", + transfer_complete_method="POST", + username="user", + password="pass", + diversion_treatment="propagate", + diversion_reason="away", + phone_numbers=[phone1, phone2], + transfer_complete_fallback_url="https://test.com", + transfer_complete_fallback_method="GET", + fallback_username="fusern", + fallback_password="fpassw" + ) + response.add_verb(transfer) + expected_bxml = f'{phone1_bxml}{phone2_bxml}' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_conference(self): + conference = Conference("my-conference", mute=False, hold=True, call_ids_to_coach="c-123,c-345", + conference_event_url="https://test.com", conference_event_method="GET", username="user", + password="pass", tag="tag", conference_event_fallback_url="https://test2.com", + conference_event_fallback_method="POST", fallback_username="fuser", fallback_password="fpass") + + response = Response() + response.add_verb(conference) + expected_bxml = 'my-conference' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_conference_coach_array(self): + conference = Conference("my-conference", call_ids_to_coach=["c-123", "c-456"]) + + response = Response() + response.add_verb(conference) + expected_bxml = 'my-conference' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_bridge(self): + bridge = Bridge("c-95ac8d6e-1a31c52e-b38f-4198-93c1-51633ec68f8d", + bridge_complete_url="https://test.com", + bridge_complete_method="GET", + bridge_target_complete_url="https://test2.com", + bridge_target_complete_method="POST", + username="user", + password="pass", + tag="custom tag", + bridge_complete_fallback_url="https://test3.com", + bridge_complete_fallback_method="GET", + bridge_target_complete_fallback_url="https://test4.com", + bridge_target_complete_fallback_method="POST", + fallback_username="fuser", + fallback_password="fpass" + ) + + response = Response() + response.add_verb(bridge) + expected_bxml = 'c-95ac8d6e-1a31c52e-b38f-4198-93c1-51633ec68f8d' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_ring(self): + ring = Ring( + duration=5 + ) + + response = Response() + response.add_verb(ring) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_start_gather(self): + startGather = StartGather( + dtmfUrl= "https://test.com", + dtmfMethod = "POST", + username = "user", + password = "pass", + tag = "custom tag" + ) + + response = Response() + response.add_verb(startGather) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_stop_gather(self): + stopGather = StopGather() + + response = Response() + response.add_verb(stopGather) + expected_bxml = '' + self.assertEqual(response.to_bxml(), expected_bxml) + + def test_gather_speak_sentence_ssml(self): + response = Response() + speak_sentence = SpeakSentence( + sentence='Hello. Your number is asdf, lets play a game. What is 10 + 3. Press the pound key when finished.' + ) + gather = Gather( + speak_sentence=speak_sentence + ) + expected_bxml = 'Hello. Your number is asdf, lets play a game. What is 10 + 3. Press the pound key when finished.' + response.add_verb(gather) + self.assertEqual(response.to_bxml(), expected_bxml) + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/actions/validate/entrypoint.sh b/.github/actions/validate/entrypoint.sh index bb4e395c..0bb481d5 100644 --- a/.github/actions/validate/entrypoint.sh +++ b/.github/actions/validate/entrypoint.sh @@ -1,7 +1,5 @@ #!/bin/sh -pip install twine -pip install wheel -python setup.py sdist bdist_wheel - -twine check dist/* +pip install -e . +cp /bxml_tests.py . +python bxml_tests.py diff --git a/bandwidth/voice/bxml/verbs/gather.py b/bandwidth/voice/bxml/verbs/gather.py index b6aecde8..1a094a99 100644 --- a/bandwidth/voice/bxml/verbs/gather.py +++ b/bandwidth/voice/bxml/verbs/gather.py @@ -9,6 +9,9 @@ from lxml import etree from .base_verb import AbstractBxmlVerb +from .speak_sentence import SSML_REGEX + +import re GATHER_TAG = "Gather" @@ -97,4 +100,4 @@ def to_bxml(self): if self.nested_verbs is not None: for verb in self.nested_verbs: root.append(verb.to_etree_element()) - return etree.tostring(root).decode() + return re.sub(SSML_REGEX, r"<\1>", etree.tostring(root).decode()) diff --git a/setup.py b/setup.py index a518e388..0cff86ed 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='bandwidth-sdk', - version='6.14.0', + version='6.14.1', description='Bandwidth\'s set of APIs', long_description=long_description, long_description_content_type="text/markdown",