M lib/call_attempt.rb => lib/call_attempt.rb +18 -4
@@ 2,6 2,8 @@
require "value_semantics/monkey_patched"
+require_relative "low_balance"
+
class CallAttempt
EXPENSIVE_ROUTE = {
"usd_beta_unlimited-v20210223" => 0.9,
@@ 9,15 11,14 @@ class CallAttempt
}.freeze
def self.for(customer, other_tel, rate, usage, direction:, **kwargs)
+ kwargs.merge!(direction: direction)
included_credit = [customer.minute_limit.to_d - usage, 0].max
if !rate || rate >= EXPENSIVE_ROUTE.fetch(customer.plan_name, 0.1)
Unsupported.new(direction: direction)
elsif included_credit + customer.balance < rate * 10
- NoBalance.new(balance: customer.balance, direction: direction)
+ NoBalance.for(customer, other_tel, rate, usage, **kwargs)
else
- for_ask_or_go(
- customer, other_tel, rate, usage, direction: direction, **kwargs
- )
+ for_ask_or_go(customer, other_tel, rate, usage, **kwargs)
end
end
@@ 58,6 59,19 @@ class CallAttempt
end
class NoBalance
+ def self.for(customer, other_tel, rate, usage, direction:, **kwargs)
+ LowBalance.for(customer).then(&:notify!).then do |amount|
+ if amount&.positive?
+ CallAttempt.for(
+ customer.with_balance(customer.balance + amount),
+ other_tel, rate, usage, direction: direction, **kwargs
+ )
+ else
+ NoBalance.new(balance: customer.balance, direction: direction)
+ end
+ end
+ end
+
value_semantics do
balance Numeric
direction Either(:inbound, :outbound)
M lib/call_attempt_repo.rb => lib/call_attempt_repo.rb +1 -0
@@ 1,6 1,7 @@
# frozen_string_literal: true
require "value_semantics/monkey_patched"
+require "lazy_object"
require_relative "call_attempt"
M lib/customer.rb => lib/customer.rb +10 -4
@@ 45,13 45,19 @@ class Customer
@sgx = sgx
end
+ def with_balance(balance)
+ self.class.new(
+ @customer_id, @jid,
+ plan: @plan, balance: balance,
+ tndetails: @tndetails, sgx: @sgx
+ )
+ end
+
def with_plan(plan_name)
self.class.new(
- @customer_id,
- @jid,
+ @customer_id, @jid,
plan: @plan.with_plan_name(plan_name),
- balance: @balance,
- sgx: @sgx
+ balance: @balance, tndetails: @tndetails, sgx: @sgx
)
end
M test/test_helper.rb => test/test_helper.rb +1 -0
@@ 66,6 66,7 @@ CONFIG = {
username: "test_bw_user",
password: "test_bw_password"
},
+ notify_from: "notify_from@example.org",
activation_amount: 1,
plans: [
{
M test/test_web.rb => test/test_web.rb +85 -1
@@ 2,11 2,15 @@
require "rack/test"
require "test_helper"
+require "bwmsgsv2_repo"
+require "customer_repo"
require_relative "../web"
+ExpiringLock::REDIS = Minitest::Mock.new
Customer::BLATHER = Minitest::Mock.new
CustomerFwd::BANDWIDTH_VOICE = Minitest::Mock.new
Web::BANDWIDTH_VOICE = Minitest::Mock.new
+LowBalance::AutoTopUp::Transaction = Minitest::Mock.new
class WebTest < Minitest::Test
include Rack::Test::Methods
@@ 18,6 22,10 @@ class WebTest < Minitest::Test
"catapult_jid-+15551234567" => "customer_customerid@component",
"jmp_customer_jid-customerid_low" => "customer@example.com",
"catapult_jid-+15551234560" => "customer_customerid_low@component",
+ "jmp_customer_jid-customerid_topup" => "customer@example.com",
+ "jmp_customer_auto_top_up_amount-customerid_topup" => "15",
+ "jmp_customer_monthly_overage_limit-customerid_topup" => "99999",
+ "catapult_jid-+15551234562" => "customer_customerid_topup@component",
"jmp_customer_jid-customerid_limit" => "customer@example.com",
"catapult_jid-+15551234561" => "customer_customerid_limit@component"
),
@@ 32,6 40,11 @@ class WebTest < Minitest::Test
"plan_name" => "test_usd",
"expires_at" => Time.now + 100
}],
+ ["customerid_topup"] => [{
+ "balance" => BigDecimal("0.01"),
+ "plan_name" => "test_usd",
+ "expires_at" => Time.now + 100
+ }],
["customerid_limit"] => [{
"balance" => BigDecimal(10),
"plan_name" => "test_usd",
@@ 52,6 65,12 @@ class WebTest < Minitest::Test
"customer_customerid@component" => IBR.new.tap do |ibr|
ibr.phone = "+15551234567"
end,
+ "customer_customerid_low@component" => IBR.new.tap do |ibr|
+ ibr.phone = "+15551234567"
+ end,
+ "customer_customerid_topup@component" => IBR.new.tap do |ibr|
+ ibr.phone = "+15551234567"
+ end,
"customer_customerid_limit@component" => IBR.new.tap do |ibr|
ibr.phone = "+15551234567"
end
@@ 64,7 83,8 @@ class WebTest < Minitest::Test
["test_usd", "+15557654321", :outbound] => [{ "rate" => 0.01 }],
["test_usd", "+15557654321", :inbound] => [{ "rate" => 0.01 }],
["customerid_limit"] => [{ "a" => -1000 }],
- ["customerid_low"] => [{ "a" => -1000 }]
+ ["customerid_low"] => [{ "a" => -1000 }],
+ ["customerid_topup"] => [{ "a" => -1000 }]
)
)
Web.opts[:common_logger] = FakeLog.new
@@ 94,6 114,12 @@ class WebTest < Minitest::Test
em :test_outbound_forwards
def test_outbound_low_balance
+ ExpiringLock::REDIS.expect(
+ :exists,
+ EMPromise.resolve(1),
+ ["jmp_customer_low_balance-customerid_low"]
+ )
+
post(
"/outbound/calls",
{
@@ 111,9 137,60 @@ class WebTest < Minitest::Test
"complete this call.</SpeakSentence></Response>",
last_response.body
)
+ assert_mock ExpiringLock::REDIS
end
em :test_outbound_low_balance
+ def test_outbound_low_balance_top_up
+ LowBalance::AutoTopUp::Transaction.expect(
+ :sale,
+ EMPromise.resolve(
+ OpenStruct.new(insert: EMPromise.resolve(nil), total: 15)
+ ),
+ [Customer, { amount: 15 }]
+ )
+
+ ExpiringLock::REDIS.expect(
+ :exists,
+ nil,
+ ["jmp_customer_low_balance-customerid_topup"]
+ )
+
+ ExpiringLock::REDIS.expect(
+ :setex,
+ nil,
+ ["jmp_customer_low_balance-customerid_topup", Integer, Time]
+ )
+
+ Customer::BLATHER.expect(
+ :<<,
+ nil,
+ [Blather::Stanza]
+ )
+
+ post(
+ "/outbound/calls",
+ {
+ from: "customerid_topup",
+ to: "+15557654321",
+ callId: "acall"
+ }.to_json,
+ { "CONTENT_TYPE" => "application/json" }
+ )
+
+ assert last_response.ok?
+ assert_equal(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
+ "<Forward from=\"+15551234567\" to=\"+15557654321\" />" \
+ "</Response>",
+ last_response.body
+ )
+ assert_mock ExpiringLock::REDIS
+ assert_mock Customer::BLATHER
+ assert_mock LowBalance::AutoTopUp::Transaction
+ end
+ em :test_outbound_low_balance_top_up
+
def test_outbound_unsupported
post(
"/outbound/calls",
@@ 213,6 290,12 @@ class WebTest < Minitest::Test
em :test_inbound
def test_inbound_low
+ ExpiringLock::REDIS.expect(
+ :exists,
+ EMPromise.resolve(1),
+ ["jmp_customer_low_balance-customerid_low"]
+ )
+
post(
"/inbound/calls",
{
@@ 231,6 314,7 @@ class WebTest < Minitest::Test
last_response.body
)
assert_mock CustomerFwd::BANDWIDTH_VOICE
+ assert_mock ExpiringLock::REDIS
end
em :test_inbound_low
M web.rb => web.rb +1 -0
@@ 10,6 10,7 @@ require "sentry-ruby"
require_relative "lib/call_attempt_repo"
require_relative "lib/cdr"
+require_relative "lib/oob"
require_relative "lib/roda_capture"
require_relative "lib/roda_em_promise"
require_relative "lib/rack_fiber"