Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
dwilkie committed Jul 9, 2024
1 parent 530f085 commit 881de00
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 451 deletions.
36 changes: 19 additions & 17 deletions components/app/app/workflows/execute_dial.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ class ExecuteDial < ExecuteTwiMLVerb
def call
answer!
phone_calls = create_outbound_calls
dial_status = context.dial(build_dial_params(phone_calls))
dial_params = build_dial_params(phone_calls)
dial_status = context.dial(dial_params)

return if verb.action.blank?

redirect(build_callback_params(dial_status))
callback_params = build_callback_params(dial_status)
redirect(callback_params)
end

private

def create_outbound_calls
call_platform_client.create_outbound_calls(
destinations: verb.nested_nouns.map { |nested_noun| nested_noun.content.strip },
parent_call_sid: call_properties.call_sid,
from: verb.caller_id
)
end

def build_dial_params(phone_calls)
phone_calls.each_with_object({}) do |phone_call, result|
dial_string, from = build_dial_string(phone_call)
Expand All @@ -36,12 +46,13 @@ def build_dial_params(phone_calls)
end
end

def create_outbound_calls
call_platform_client.create_outbound_calls(
destinations: verb.nested_nouns.map { |nested_noun| nested_noun.content.strip },
parent_call_sid: call_properties.call_sid,
from: verb.caller_id
)
def build_dial_string(phone_call_response)
if phone_call_response.address.present?
DialString.new(address: phone_call_response.address)
else
dial_string = DialString.new(phone_call_response.routing_parameters)
[ dial_string, dial_string.format_number(phone_call_response.from) ]
end
end

def redirect(params)
Expand Down Expand Up @@ -72,13 +83,4 @@ def find_joined_call(dial_status)
return outbound_call if join_status.result == :joined
end
end

def build_dial_string(phone_call_response)
if phone_call_response.address.present?
DialString.new(address: phone_call_response.address)
else
dial_string = DialString.new(phone_call_response.routing_parameters)
[ dial_string, dial_string.format_number(phone_call_response.from) ]
end
end
end
4 changes: 0 additions & 4 deletions components/app/lib/call_platform/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ def notify_media_stream_event(params)
notify_request("/services/media_stream_events", params)
end

def build_routing_parameters(params)
make_request("/services/routing_parameters", params: params)
end

def create_inbound_call(params)
json_response = make_request("/services/inbound_phone_calls", params: params)
InboundPhoneCallResponse.new(
Expand Down
81 changes: 45 additions & 36 deletions components/app/spec/call_controllers/dial_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

RSpec.describe CallController, type: :call_controller do
describe "<Dial>" do
let(:parent_call_sid) { "15f55641-7728-4cab-8e2e-8077c4b3c6b4" } # From VCR cassette

# From: https://www.twilio.com/docs/api/twiml/dial

# The <Dial> verb connects the current caller to another phone.
Expand Down Expand Up @@ -29,10 +31,7 @@
controller = build_controller(
stub_voice_commands: [ :play_audio, { dial: build_dial_status } ],
call_properties: {
account_sid: "96b4557a-341c-46b3-ba3c-a1793e9dae3c",
call_sid: "15f55641-7728-4cab-8e2e-8077c4b3c6b4",
from: "855715100860",
direction: "inbound"
call_sid: parent_call_sid
}
)

Expand All @@ -48,19 +47,24 @@

expect(controller).to have_received(:dial).with(
include(
dial_string("85516701721") => { for: 30.seconds, from: "855715100860" }
dial_string("85516701721") => hash_including(
for: 30.seconds,
from: match(/\A855/),
headers: hash_including(
"X-Somleng-CallSid" => be_present,
"X-Somleng-AccountSid" => be_present
)
)
)
)
expect(controller).to have_received(:play_audio).with("foo.mp3")
end

it "handles national dialing", :vcr, cassette: :build_routing_parameters_with_national_dialing do
it "handles national dialing", :vcr, cassette: :dial_with_national_dialing do
controller = build_controller(
stub_voice_commands: [ :play_audio, { dial: build_dial_status } ],
call_properties: {
account_sid: "ea471a9f-d4b3-4035-966e-f507b8da6d34",
to: "855715100860",
direction: "outbound-api"
call_sid: parent_call_sid
}
)

Expand All @@ -75,16 +79,16 @@

expect(controller).to have_received(:dial).with(
include(
dial_string("016701721") => hash_including(from: "0715100860")
dial_string("016701721") => hash_including(from: match(/\A0/))
)
)
end

it "handles numbers without symmetric latching support", :vcr, cassette: :build_routing_parameters_without_symmetric_latching do
it "handles numbers without symmetric latching support", :vcr, cassette: :dial_without_symmetric_latching do
controller = build_controller(
stub_voice_commands: [ { dial: build_dial_status } ],
call_properties: {
account_sid: "ea471a9f-d4b3-4035-966e-f507b8da6d34"
call_sid: parent_call_sid
}
)

Expand All @@ -104,13 +108,11 @@
)
end

it "dials to <Number>", :vcr, cassette: :build_multiple_routing_parameters do
it "dials to <Number>", :vcr, cassette: :dial_multiple do
controller = build_controller(
stub_voice_commands: { dial: build_dial_status },
call_properties: {
account_sid: "ea471a9f-d4b3-4035-966e-f507b8da6d34",
from: "855715200860",
direction: "inbound"
call_sid: parent_call_sid
}
)

Expand All @@ -129,19 +131,19 @@

expect(controller).to have_received(:dial).with(
include(
dial_string("85516701721") => hash_including(from: "855715200860"),
dial_string("0715100860", profile: "alternative-outbound") => hash_including(from: "0715200860"),
dial_string("85510555777") => hash_including(from: "855715200860")
dial_string("85516701721") => hash_including(from: match(/\A855/),),
dial_string("0715100860", profile: "alternative-outbound") => hash_including(from: match(/\A0/)),
dial_string("85510555777") => hash_including(from: match(/\A855/))
),
any_args
)
end

it "dials to <Sip>" do
it "dials to <Sip>", :vcr, cassette: :dial_sip do
controller = build_controller(
stub_voice_commands: { dial: build_dial_status },
call_properties: {
account_sid: "ea471a9f-d4b3-4035-966e-f507b8da6d34"
call_sid: parent_call_sid
}
)

Expand All @@ -158,14 +160,17 @@

expect(controller).to have_received(:dial).with(
include(
"sofia/external/[email protected]" => { for: 30.seconds }
"sofia/external/[email protected]" => hash_including(for: 30.seconds, headers: be_a_kind_of(Hash))
)
)
end

it "supports callerId", :vcr, cassette: :build_multiple_routing_parameters do
it "supports callerId", :vcr, cassette: :dial_multiple_with_caller_id do
controller = build_controller(
stub_voice_commands: { dial: build_dial_status }
stub_voice_commands: { dial: build_dial_status },
call_properties: {
call_sid: parent_call_sid
}
)

stub_twiml_request(controller, response: <<~TWIML)
Expand Down Expand Up @@ -251,7 +256,7 @@
# | | a properly formatted but non-existent phone number. |
# | canceled | The call was canceled via the REST API before it was answered. |

it "POSTS to the action url", :vcr, cassette: :build_routing_parameters do
it "POSTS to the action url", :vcr, cassette: :dial do
outbound_call = build_outbound_call(id: "481f77b9-a95b-4c6a-bbb1-23afcc42c959")
joins_status = build_dial_join_status(:joined, duration: 23.7)

Expand Down Expand Up @@ -284,7 +289,7 @@
})
end

it "handles multiple calls", :vcr, cassette: :build_multiple_routing_parameters do
it "handles multiple calls", :vcr, cassette: :dial_multiple do
joined_outbound_call = build_outbound_call(id: "481f77b9-a95b-4c6a-bbb1-23afcc42c959")
no_answer_outbound_call = build_outbound_call

Expand Down Expand Up @@ -334,7 +339,7 @@
# This attribute is modeled after the HTML form 'method' attribute.
# 'POST' is the default value.

it "executes a GET request", :vcr, cassette: :build_routing_parameters do
it "executes a GET request", :vcr, cassette: :dial do
controller = build_controller(
stub_voice_commands: { dial: build_dial_status }
)
Expand Down Expand Up @@ -389,30 +394,33 @@
# | callerId | a valid phone number, or client identifier | Caller's callerId |
# | | if you are dialing a <Client>. | |

it "sets the callerId", :vcr, cassette: :build_routing_parameters do
it "sets the callerId", :vcr, cassette: :dial_with_caller_id do
controller = build_controller(
stub_voice_commands: { dial: build_dial_status }
stub_voice_commands: { dial: build_dial_status },
call_properties: {
call_sid: parent_call_sid
}
)

stub_twiml_request(controller, response: <<~TWIML)
<?xml version="1.0" encoding="UTF-8" ?>
<Response>
<Dial callerId="2442">+85516701721</Dial>
<Dial callerId="85523238265">+85516701721</Dial>
</Response>
TWIML

controller.run

expect(controller).to have_received(:dial).with(
include(
dial_string("85516701721") => hash_including(from: "2442")
dial_string("85516701721") => hash_including(from: "85523238265")
)
)
end
end

describe "timeout" do
it "handles timeout", :vcr, cassette: :build_multiple_routing_parameters do
it "handles timeout", :vcr, cassette: :dial_multiple do
controller = build_controller(
stub_voice_commands: { dial: build_dial_status }
)
Expand All @@ -435,16 +443,17 @@
end
end

def build_dial_status(result = :answer, joins: {})
instance_double(Adhearsion::CallController::DialStatus, result:, joins:)
def build_dial_status(result = :answer, joins: {}, calls: Set.new)
calls << build_outbound_call if calls.empty?
instance_double(Adhearsion::CallController::DialStatus, result:, joins:, calls:)
end

def build_dial_join_status(result = :joined, options = {})
instance_double(Adhearsion::CallController::Dial::JoinStatus, result:, **options)
end

def build_outbound_call(options = {})
instance_double(Adhearsion::OutboundCall, options)
def build_outbound_call(**options)
instance_double(Adhearsion::OutboundCall, id: SecureRandom.uuid, **options)
end

def dial_string(number, profile: :external)
Expand Down
Loading

0 comments on commit 881de00

Please sign in to comment.