diff --git a/lib/call_attempt.rb b/lib/call_attempt.rb index 53156b50df6b7192833f6dbc1436fe2599020951..3107e0fc262f9f5b73934339b891d21dad56b58a 100644 --- a/lib/call_attempt.rb +++ b/lib/call_attempt.rb @@ -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) diff --git a/lib/call_attempt_repo.rb b/lib/call_attempt_repo.rb index dc9a6e0a51164f1a5a99132fb6ca44302f8074d5..fb16917765059f03f0ae684b7be345eb970b7f46 100644 --- a/lib/call_attempt_repo.rb +++ b/lib/call_attempt_repo.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "value_semantics/monkey_patched" +require "lazy_object" require_relative "call_attempt" diff --git a/lib/customer.rb b/lib/customer.rb index dce3a07e025aab95e4f97463b49970c13eded0ed..a295875811855cb47014614502405f4d23ff4041 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb index e105fe448c6e8aace27a2ae60e67eaf508ee980f..1e6bb86529df34269766adf8eee82af3a6e42975 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -66,6 +66,7 @@ CONFIG = { username: "test_bw_user", password: "test_bw_password" }, + notify_from: "notify_from@example.org", activation_amount: 1, plans: [ { diff --git a/test/test_web.rb b/test/test_web.rb index 8fecd8f8533e028784ce8c361e7d62308e415a7e..3856ff9b71cb4b6573222f75e693070e0b290b4c 100644 --- a/test/test_web.rb +++ b/test/test_web.rb @@ -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.", 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( + "" \ + "" \ + "", + 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 diff --git a/web.rb b/web.rb index 74482b8e6a4f04c5ccec5059a4ae0c2025081192..80e78ae1ba2cf3419cde23bfeae588a310bd9c55 100644 --- a/web.rb +++ b/web.rb @@ -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"