auto_top_up_amount and monthly_overage_limit from CustomerRepo

Stephen Paul Weber created

Move load of auto_top_up_amount to the repo, and load monthly_overage_limit in
the same operation.

Change summary

lib/customer.rb            |  3 +
lib/customer_info.rb       | 20 +++++----------
lib/customer_plan.rb       | 21 +++++++++++----
lib/customer_repo.rb       | 51 +++++++++++++++++++++++++--------------
lib/low_balance.rb         | 20 +++++++-------
test/test_customer_info.rb | 12 ---------
test/test_helper.rb        | 28 +++++++++++----------
test/test_low_balance.rb   | 16 ++----------
8 files changed, 85 insertions(+), 86 deletions(-)

Detailed changes

lib/customer.rb 🔗

@@ -22,7 +22,8 @@ class Customer
 	attr_reader :customer_id, :balance, :jid, :tndetails
 
 	def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
-	               :currency, :merchant_account, :plan_name, :auto_top_up_amount
+	               :currency, :merchant_account, :plan_name,
+	               :auto_top_up_amount, :monthly_overage_limit
 	def_delegators :@sgx, :register!, :registered?, :set_ogm_url,
 	               :fwd, :transcription_enabled
 	def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage

lib/customer_info.rb 🔗

@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 require "bigdecimal"
+require "forwardable"
 require "relative_time"
 require "value_semantics/monkey_patched"
 require_relative "proxied_jid"
@@ -8,17 +9,15 @@ require_relative "customer_plan"
 require_relative "form_template"
 
 class PlanInfo
+	extend Forwardable
+
+	def_delegators :plan, :expires_at, :auto_top_up_amount
+
 	def self.for(plan)
 		return EMPromise.resolve(NoPlan.new) unless plan&.plan_name
 
-		EMPromise.all([
-			plan.activation_date,
-			plan.auto_top_up_amount
-		]).then do |adate, atua|
-			new(
-				plan: plan, start_date: adate,
-				auto_top_up_amount: atua
-			)
+		plan.activation_date.then do |adate|
+			new(plan: plan, start_date: adate)
 		end
 	end
 
@@ -39,11 +38,6 @@ class PlanInfo
 	value_semantics do
 		plan CustomerPlan
 		start_date Time
-		auto_top_up_amount Integer
-	end
-
-	def expires_at
-		plan.expires_at
 	end
 
 	def template

lib/customer_plan.rb 🔗

@@ -3,19 +3,32 @@
 require "forwardable"
 
 require_relative "em"
+require_relative "plan"
 
 class CustomerPlan
 	extend Forwardable
 
-	attr_reader :expires_at
+	attr_reader :expires_at, :auto_top_up_amount, :monthly_overage_limit
 
 	def_delegator :@plan, :name, :plan_name
 	def_delegators :@plan, :currency, :merchant_account, :monthly_price
 
-	def initialize(customer_id, plan: nil, expires_at: Time.now)
+	def self.for(customer_id, plan_name: nil, **kwargs)
+		new(customer_id, plan: plan_name&.then(&Plan.method(:for)), **kwargs)
+	end
+
+	def initialize(
+		customer_id,
+		plan: nil,
+		expires_at: Time.now,
+		auto_top_up_amount: 0,
+		monthly_overage_limit: 0
+	)
 		@customer_id = customer_id
 		@plan = plan || OpenStruct.new
 		@expires_at = expires_at
+		@auto_top_up_amount = auto_top_up_amount
+		@monthly_overage_limit = monthly_overage_limit
 	end
 
 	def active?
@@ -30,10 +43,6 @@ class CustomerPlan
 		)
 	end
 
-	def auto_top_up_amount
-		REDIS.get("jmp_customer_auto_top_up_amount-#{@customer_id}").then(&:to_i)
-	end
-
 	def bill_plan
 		EM.promise_fiber do
 			DB.transaction do

lib/customer_repo.rb 🔗

@@ -65,10 +65,8 @@ class CustomerRepo
 		@bandwidth_tn_repo.put_lidb_name(customer.registered?.phone, lidb_name)
 	end
 
-	def put_transcription_enabled(customer, transcription_enabled)
-		@sgx_repo.put_transcription_enabled(
-			customer.customer_id, transcription_enabled
-		)
+	def put_transcription_enabled(customer, enabled)
+		@sgx_repo.put_transcription_enabled(customer.customer_id, enabled)
 	end
 
 	def put_fwd(customer, customer_fwd)
@@ -82,14 +80,17 @@ protected
 		TrivialBackendSgxRepo.new.get(customer_id).with(registered?: false)
 	end
 
-	def hydrate_plan(customer_id, raw_customer)
-		raw_customer.dup.tap do |data|
-			data[:plan] = CustomerPlan.new(
-				customer_id,
-				plan: data.delete(:plan_name)&.then(&Plan.method(:for)),
-				expires_at: data.delete(:expires_at)
-			)
-		end
+	def mget(*keys)
+		@redis.mget(keys).then { |values| Hash[keys.zip(values)] }
+	end
+
+	def fetch_redis(customer_id)
+		mget(
+			"jmp_customer_auto_top_up_amount-#{customer_id}",
+			"jmp_customer_monthly_overage_limit-#{customer_id}"
+		).then { |r|
+			r.transform_keys { |k| k.match(/^jmp_customer_([^-]+)/)[1].to_sym }
+		}
 	end
 
 	SQL = <<~SQL
@@ -98,6 +99,20 @@ protected
 		WHERE customer_id=$1 LIMIT 1
 	SQL
 
+	def fetch_sql(customer_id)
+		@db.query_defer(SQL, [customer_id]).then do |rows|
+			rows.first&.transform_keys(&:to_sym) || { balance: 0 }
+		end
+	end
+
+	def fetch_all(customer_id)
+		EMPromise.all([
+			@sgx_repo.get(customer_id),
+			fetch_sql(customer_id),
+			fetch_redis(customer_id)
+		]).then { |sgx, sql, redis| [sgx, sql.merge(redis)] }
+	end
+
 	def tndetails(sgx)
 		return unless sgx.registered?
 
@@ -105,14 +120,14 @@ protected
 	end
 
 	def find_inner(customer_id, jid)
-		result = @db.query_defer(SQL, [customer_id])
-		EMPromise.all([@sgx_repo.get(customer_id), result]).then do |(sgx, rows)|
-			data = hydrate_plan(
-				customer_id, rows.first&.transform_keys(&:to_sym) || {}
-			)
+		fetch_all(customer_id).then do |(sgx, data)|
 			Customer.new(
 				customer_id, Blather::JID.new(jid),
-				sgx: sgx, tndetails: tndetails(sgx), **data
+				sgx: sgx, balance: data[:balance], tndetails: tndetails(sgx),
+				plan: CustomerPlan.for(
+					customer_id,
+					**data.delete_if { |(k, _)| k == :balance }
+				)
 			)
 		end
 	end

lib/low_balance.rb 🔗

@@ -11,15 +11,13 @@ class LowBalance
 			"jmp_customer_low_balance-#{customer.customer_id}",
 			expiry: 60 * 60 * 24 * 7
 		).with(-> { Locked.new }) do
-			customer.auto_top_up_amount.then do |auto_top_up_amount|
-				for_auto_top_up_amount(customer, auto_top_up_amount)
-			end
+			for_auto_top_up_amount(customer)
 		end
 	end
 
-	def self.for_auto_top_up_amount(customer, auto_top_up_amount)
-		if auto_top_up_amount.positive?
-			AutoTopUp.new(customer, auto_top_up_amount)
+	def self.for_auto_top_up_amount(customer)
+		if customer.auto_top_up_amount.positive?
+			AutoTopUp.new(customer)
 		else
 			customer.btc_addresses.then do |btc_addresses|
 				new(customer, btc_addresses)
@@ -49,15 +47,17 @@ class LowBalance
 	end
 
 	class AutoTopUp
-		def initialize(customer, auto_top_up_amount)
+		def initialize(customer)
 			@customer = customer
-			@auto_top_up_amount = auto_top_up_amount
 			@message = Blather::Stanza::Message.new
 			@message.from = CONFIG[:notify_from]
 		end
 
 		def sale
-			Transaction.sale(@customer, amount: @auto_top_up_amount).then do |tx|
+			Transaction.sale(
+				@customer,
+				amount: @customer.auto_top_up_amount
+			).then do |tx|
 				tx.insert.then { tx }
 			end
 		end
@@ -70,7 +70,7 @@ class LowBalance
 			}.catch { |e|
 				@message.body =
 					"Automatic top-up transaction for " \
-					"$#{@auto_top_up_amount} failed: #{e.message}"
+					"$#{customer.auto_top_up_amount} failed: #{e.message}"
 			}.then { @customer.stanza_to(@message) }
 		end
 	end

test/test_customer_info.rb 🔗

@@ -19,12 +19,6 @@ class CustomerInfoTest < Minitest::Test
 		sgx = Minitest::Mock.new
 		sgx.expect(:registered?, false)
 
-		CustomerPlan::REDIS.expect(
-			:get,
-			EMPromise.resolve(nil),
-			["jmp_customer_auto_top_up_amount-test"]
-		)
-
 		CustomerPlan::DB.expect(
 			:query_defer,
 			EMPromise.resolve([{ "start_date" => Time.now }]),
@@ -44,12 +38,6 @@ class CustomerInfoTest < Minitest::Test
 		fwd = CustomerFwd.for(uri: "tel:+12223334444", timeout: 15)
 		sgx.expect(:fwd, fwd)
 
-		CustomerPlan::REDIS.expect(
-			:get,
-			EMPromise.resolve(nil),
-			["jmp_customer_auto_top_up_amount-test"]
-		)
-
 		CustomerPlan::DB.expect(
 			:query_defer,
 			EMPromise.resolve([{ "start_date" => Time.now }]),

test/test_helper.rb 🔗

@@ -39,19 +39,21 @@ require "tel_selections"
 $VERBOSE = nil
 Sentry.init
 
-def customer(customer_id="test", plan_name: nil, **kwargs)
-	jid = kwargs.delete(:jid) || Blather::JID.new("#{customer_id}@example.net")
-	if plan_name
-		expires_at = kwargs.delete(:expires_at) || Time.now
-		plan = CustomerPlan.new(
-			customer_id,
-			plan: Plan.for(plan_name),
-			expires_at: expires_at
-		)
-		Customer.new(customer_id, jid, plan: plan, **kwargs)
-	else
-		Customer.new(customer_id, jid, **kwargs)
-	end
+def customer(
+	customer_id="test",
+	plan_name: nil,
+	jid: Blather::JID.new("#{customer_id}@example.net"),
+	expires_at: Time.now,
+	auto_top_up_amount: 0,
+	**kwargs
+)
+	plan = CustomerPlan.for(
+		customer_id,
+		plan_name: plan_name,
+		expires_at: expires_at,
+		auto_top_up_amount: auto_top_up_amount
+	)
+	Customer.new(customer_id, jid, plan: plan, **kwargs)
 end
 
 CONFIG = {

test/test_low_balance.rb 🔗

@@ -24,11 +24,6 @@ class LowBalanceTest < Minitest::Test
 			EMPromise.resolve(0),
 			["jmp_customer_low_balance-test"]
 		)
-		CustomerPlan::REDIS.expect(
-			:get,
-			EMPromise.resolve(nil),
-			["jmp_customer_auto_top_up_amount-test"]
-		)
 		Customer::REDIS.expect(
 			:smembers,
 			EMPromise.resolve([]),
@@ -53,11 +48,6 @@ class LowBalanceTest < Minitest::Test
 			EMPromise.resolve(0),
 			["jmp_customer_low_balance-test"]
 		)
-		CustomerPlan::REDIS.expect(
-			:get,
-			EMPromise.resolve("15"),
-			["jmp_customer_auto_top_up_amount-test"]
-		)
 		ExpiringLock::REDIS.expect(
 			:setex,
 			EMPromise.resolve(nil),
@@ -65,7 +55,7 @@ class LowBalanceTest < Minitest::Test
 		)
 		assert_kind_of(
 			LowBalance::AutoTopUp,
-			LowBalance.for(customer).sync
+			LowBalance.for(customer(auto_top_up_amount: 15)).sync
 		)
 		assert_mock ExpiringLock::REDIS
 	end
@@ -75,8 +65,8 @@ class LowBalanceTest < Minitest::Test
 		LowBalance::AutoTopUp::Transaction = Minitest::Mock.new
 
 		def setup
-			@customer = customer
-			@auto_top_up = LowBalance::AutoTopUp.new(@customer, 100)
+			@customer = customer(auto_top_up_amount: 100)
+			@auto_top_up = LowBalance::AutoTopUp.new(@customer)
 		end
 
 		def test_notify!