Detailed changes
@@ -45,6 +45,18 @@ if @admin_info.fwd.uri
)
end
+field(
+ var: "call_info",
+ label: "Call Status",
+ value: @admin_info.call_info
+)
+
+field(
+ var: "trust_level",
+ label: "Trust Level",
+ value: @admin_info.trust_level
+)
+
field(
var: "api",
label: "API",
@@ -6,12 +6,15 @@ require_relative "financial_info"
require_relative "form_template"
class AdminCommand
- def initialize(target_customer)
+ def initialize(target_customer, customer_repo)
@target_customer = target_customer
+ @customer_repo = customer_repo
end
def start
- action_info.then { menu_or_done }
+ @target_customer.admin_info.then { |info|
+ reply(info.form)
+ }.then { menu_or_done }
end
def reply(form)
@@ -40,19 +43,19 @@ class AdminCommand
end
def new_context(q)
- CustomerInfoForm.new.parse_something(q).then do |new_customer|
- if new_customer.respond_to?(:customer_id)
- AdminCommand.new(new_customer).start
- else
- reply(new_customer.form)
+ CustomerInfoForm.new(@customer_repo)
+ .parse_something(q).then do |new_customer|
+ if new_customer.respond_to?(:customer_id)
+ AdminCommand.new(new_customer, @customer_repo).start
+ else
+ reply(new_customer.form)
+ end
end
- end
end
def action_info
- @target_customer.admin_info.then do |info|
- reply(info.form)
- end
+ # Refresh the data
+ new_context(@target_customer.customer_id)
end
def action_financial
@@ -48,6 +48,10 @@ class CallAttempt
["#{direction}/connect", { locals: to_h }]
end
+ def to_s
+ "Allowed(max_minutes: #{max_minutes}, limit_remaining: #{limit_remaining})"
+ end
+
def create_call(fwd, *args, &block)
fwd.create_call(*args, &block)
end
@@ -119,6 +123,10 @@ class CallAttempt
[view]
end
+ def to_s
+ "Unsupported"
+ end
+
def create_call(*); end
def as_json(*)
@@ -135,8 +143,8 @@ class CallAttempt
self.for(rate: rate, **kwargs) if credit < rate * 10
end
- def self.for(customer:, direction:, **kwargs)
- LowBalance.for(customer).then(&:notify!).then do |amount|
+ def self.for(customer:, direction:, low_balance: LowBalance, **kwargs)
+ low_balance.for(customer).then(&:notify!).then do |amount|
if amount&.positive?
CallAttempt.for(
customer: customer.with_balance(customer.balance + amount),
@@ -165,6 +173,10 @@ class CallAttempt
[view, { locals: to_h }]
end
+ def to_s
+ "NoBalance"
+ end
+
def create_call(*); end
def as_json(*)
@@ -211,6 +223,11 @@ class CallAttempt
[view, { locals: to_h }]
end
+ def to_s
+ "AtLimit(max_minutes: #{max_minutes}, "\
+ "limit_remaining: #{limit_remaining})"
+ end
+
def create_call(fwd, *args, &block)
fwd.create_call(*args, &block)
end
@@ -111,8 +111,9 @@ class Customer
API.for(self)
end
- def admin_info
- AdminInfo.for(self, @plan)
+ # kwargs are passed through for dependency injection from tests
+ def admin_info(**kwargs)
+ AdminInfo.for(self, @plan, **kwargs)
end
def info
@@ -7,6 +7,7 @@ require "value_semantics/monkey_patched"
require_relative "proxied_jid"
require_relative "customer_plan"
require_relative "form_template"
+require_relative "promise_hash"
class PlanInfo
extend Forwardable
@@ -78,14 +79,12 @@ class CustomerInfo
end
def self.for(customer, plan)
- PlanInfo.for(plan).then do |plan_info|
- new(
- plan_info: plan_info,
- tel: customer.registered? ? customer.registered?.phone : nil,
- balance: customer.balance,
- cnam: customer.tndetails.dig(:features, :lidb, :subscriber_information)
- )
- end
+ PromiseHash.all(
+ plan_info: PlanInfo.for(plan),
+ tel: customer.registered? ? customer.registered?.phone : nil,
+ balance: customer.balance,
+ cnam: customer.tndetails.dig(:features, :lidb, :subscriber_information)
+ ).then(&method(:new))
end
def form
@@ -100,18 +99,43 @@ class AdminInfo
fwd Either(CustomerFwd, nil)
info CustomerInfo
api API
+ call_info String
+ trust_level String
+ end
+
+ def self.for(
+ customer, plan,
+ trust_level_repo: TrustLevelRepo.new,
+ call_attempt_repo: CallAttemptRepo.new
+ )
+ PromiseHash.all(
+ jid: customer.jid,
+ customer_id: customer.customer_id,
+ fwd: customer.fwd,
+ info: CustomerInfo.for(customer, plan),
+ api: customer.api,
+ call_info: call_info(customer, call_attempt_repo),
+ trust_level: trust_level_repo.find(customer).then(&:to_s)
+ ).then(&method(:new))
+ end
+
+ class FakeLowBalance
+ def self.for(_)
+ self
+ end
+
+ def self.notify!
+ EMPromise.resolve(0)
+ end
end
- def self.for(customer, plan)
- EMPromise.all([
- CustomerInfo.for(customer, plan),
- customer.api
- ]).then do |info, api_value|
- new(
- jid: customer.jid,
- customer_id: customer.customer_id,
- fwd: customer.fwd, info: info, api: api_value
- )
+ def self.call_info(customer, call_attempt_repo)
+ if customer.registered?
+ call_attempt_repo
+ .find_outbound(customer, "+1", call_id: "dry_run")
+ .then(&:to_s)
+ else
+ EMPromise.resolve("No calling")
end
end
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require "em_promise"
+
+module PromiseHash
+ def self.all(**kwargs)
+ keys = kwargs.keys
+ EMPromise.all(kwargs.values).then { |results|
+ Hash[keys.zip(results)]
+ }
+ end
+end
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "delegate"
+
module TrustLevel
def self.for(plan_name:, settled_amount: 0, manual: nil)
@levels.each do |level|
@@ -8,7 +10,7 @@ module TrustLevel
settled_amount: settled_amount,
manual: manual
)
- return tl if tl
+ return manual ? Manual.new(tl) : tl if tl
end
raise "No TrustLevel matched"
@@ -19,6 +21,12 @@ module TrustLevel
@levels << maybe_mk
end
+ class Manual < SimpleDelegator
+ def to_s
+ "Manual(#{super})"
+ end
+ end
+
class Tomb
TrustLevel.register do |manual:, **|
new if manual == "Tomb"
@@ -31,6 +39,10 @@ module TrustLevel
def send_message?(*)
false
end
+
+ def to_s
+ "Tomb"
+ end
end
class Basement
@@ -45,6 +57,10 @@ module TrustLevel
def send_message?(messages_today)
messages_today < 200
end
+
+ def to_s
+ "Basement"
+ end
end
class Paragon
@@ -59,6 +75,10 @@ module TrustLevel
def send_message?(messages_today)
messages_today < 700
end
+
+ def to_s
+ "Paragon"
+ end
end
class Customer
@@ -86,5 +106,9 @@ module TrustLevel
def send_message?(messages_today)
messages_today < 500
end
+
+ def to_s
+ "Customer"
+ end
end
end
@@ -27,7 +27,7 @@ protected
def fetch_settled_amount(customer_id)
db.query_one(<<~SQL, customer_id, default: {})
- SELECT SUM(amount) AS settled_amount FROM transactions
+ SELECT COALESCE(SUM(amount), 0) AS settled_amount FROM transactions
WHERE customer_id=$1 AND settled_after < LOCALTIMESTAMP AND amount > 0
SQL
end
@@ -755,16 +755,18 @@ Command.new(
Command.customer.then do |customer|
raise AuthError, "You are not an admin" unless customer&.admin?
+ customer_repo = CustomerRepo.new(
+ sgx_repo: Bwmsgsv2Repo.new,
+ bandwidth_tn_repo: EmptyRepo.new # No CNAM in admin
+ )
+
Command.reply { |reply|
reply.allowed_actions = [:next]
reply.command << FormTemplate.render("customer_picker")
}.then { |response|
- CustomerInfoForm.new(CustomerRepo.new(
- sgx_repo: Bwmsgsv2Repo.new,
- bandwidth_tn_repo: EmptyRepo.new # No CNAM in admin
- )).find_customer(response)
+ CustomerInfoForm.new(customer_repo).find_customer(response)
}.then do |target_customer|
- AdminCommand.new(target_customer).start
+ AdminCommand.new(target_customer, customer_repo).start
end
end
}.register(self).then(&CommandList.method(:register))
@@ -2,6 +2,8 @@
require "test_helper"
require "customer_info"
+require "trust_level_repo"
+require "trust_level"
API::REDIS = FakeRedis.new
CustomerPlan::REDIS = Minitest::Mock.new
@@ -37,6 +39,7 @@ class CustomerInfoTest < Minitest::Test
sgx.expect(:registered?, false)
fwd = CustomerFwd.for(uri: "tel:+12223334444", timeout: 15)
sgx.expect(:fwd, fwd)
+ sgx.expect(:registered?, false)
CustomerPlan::DB.expect(
:query_one,
@@ -45,11 +48,49 @@ class CustomerInfoTest < Minitest::Test
)
cust = customer(sgx: sgx, plan_name: "test_usd")
- assert cust.admin_info.sync.form
+
+ trust_repo = Minitest::Mock.new
+ trust_repo.expect(:find, TrustLevel::Basement, [cust])
+
+ assert cust.admin_info(trust_level_repo: trust_repo).sync.form
assert_mock sgx
+ assert_mock trust_repo
end
em :test_admin_info_does_not_crash
+ def test_admin_info_with_tel_does_not_crash
+ registered = Struct.new(:phone).new("+12223334444")
+ fwd = CustomerFwd.for(uri: "tel:+12223334444", timeout: 15)
+ sgx = Struct.new(:registered?, :fwd).new(registered, fwd)
+
+ CustomerPlan::DB.expect(
+ :query_one,
+ EMPromise.resolve({ start_date: Time.now }),
+ [String, "test"]
+ )
+
+ cust = customer(sgx: sgx, plan_name: "test_usd")
+
+ call_attempt_repo = Minitest::Mock.new
+ call_attempt_repo.expect(
+ :find_outbound,
+ CallAttempt::Unsupported.new(direction: :outbound),
+ [cust, "+1", { call_id: "dry_run" }]
+ )
+
+ trust_repo = Minitest::Mock.new
+ trust_repo.expect(:find, TrustLevel::Basement, [cust])
+
+ assert cust
+ .admin_info(
+ trust_level_repo: trust_repo,
+ call_attempt_repo: call_attempt_repo
+ ).sync.form
+ assert_mock call_attempt_repo
+ assert_mock trust_repo
+ end
+ em :test_admin_info_with_tel_does_not_crash
+
def test_inactive_info_does_not_crash
sgx = Minitest::Mock.new
sgx.expect(:registered?, false)
@@ -69,6 +110,7 @@ class CustomerInfoTest < Minitest::Test
def test_inactive_admin_info_does_not_crash
sgx = Minitest::Mock.new
sgx.expect(:registered?, false)
+ sgx.expect(:registered?, false)
sgx.expect(:fwd, CustomerFwd::None.new(uri: nil, timeout: nil))
plan = CustomerPlan.new("test", plan: nil, expires_at: nil)
@@ -79,8 +121,12 @@ class CustomerInfoTest < Minitest::Test
sgx: sgx
)
- assert cust.admin_info.sync.form
+ trust_repo = Minitest::Mock.new
+ trust_repo.expect(:find, TrustLevel::Basement, [cust])
+
+ assert cust.admin_info(trust_level_repo: trust_repo).sync.form
assert_mock sgx
+ assert_mock trust_repo
end
em :test_inactive_admin_info_does_not_crash
@@ -10,7 +10,7 @@ class TrustLevelRepoTest < Minitest::Test
"jmp_customer_trust_level-test" => "Tomb"
)
).find(OpenStruct.new(customer_id: "test", plan_name: "usd")).sync
- assert_kind_of TrustLevel::Tomb, trust_level
+ assert_equal "Manual(Tomb)", trust_level.to_s
end
em :test_manual_tomb
@@ -21,7 +21,7 @@ class TrustLevelRepoTest < Minitest::Test
"jmp_customer_trust_level-test" => "Basement"
)
).find(OpenStruct.new(customer_id: "test", plan_name: "usd")).sync
- assert_kind_of TrustLevel::Basement, trust_level
+ assert_equal "Manual(Basement)", trust_level.to_s
end
em :test_manual_basement
@@ -32,7 +32,7 @@ class TrustLevelRepoTest < Minitest::Test
"jmp_customer_trust_level-test" => "Customer"
)
).find(OpenStruct.new(customer_id: "test", plan_name: "usd")).sync
- assert_kind_of TrustLevel::Customer, trust_level
+ assert_equal "Manual(Customer)", trust_level.to_s
end
em :test_manual_customer
@@ -43,7 +43,7 @@ class TrustLevelRepoTest < Minitest::Test
"jmp_customer_trust_level-test" => "Paragon"
)
).find(OpenStruct.new(customer_id: "test", plan_name: "usd")).sync
- assert_kind_of TrustLevel::Paragon, trust_level
+ assert_equal "Manual(Paragon)", trust_level.to_s
end
em :test_manual_paragon
@@ -54,7 +54,7 @@ class TrustLevelRepoTest < Minitest::Test
"jmp_customer_trust_level-test" => "UNKNOWN"
)
).find(OpenStruct.new(customer_id: "test", plan_name: "usd")).sync
- assert_kind_of TrustLevel::Customer, trust_level
+ assert_equal "Manual(Customer)", trust_level.to_s
end
em :test_manual_unknown