Limit creation of new subaccounts by trust level

Stephen Paul Weber created

Change summary

lib/parent_code_repo.rb   | 34 +++++++++++++--
lib/registration.rb       |  6 +-
lib/trust_level.rb        | 20 +++++++++
test/test_registration.rb | 89 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 139 insertions(+), 10 deletions(-)

Detailed changes

lib/parent_code_repo.rb 🔗

@@ -3,17 +3,23 @@
 require "multibases"
 require "securerandom"
 
+require_relative "customer"
+require_relative "trust_level_repo"
+
 class ParentCodeRepo
-	def initialize(redis=REDIS)
+	def initialize(
+		redis: REDIS,
+		db: DB,
+		trust_level_repo: TrustLevelRepo.new(redis: redis, db: db)
+	)
 		@redis = redis
+		@db = db
+		@trust_level_repo = trust_level_repo
 	end
 
 	def find(code)
 		@redis.hget("jmp_parent_codes", code).then do |parent_id|
-			(parent_id ? @redis.get("jmp_customer_trust_level-#{parent_id}") : nil)
-				.then do |tl|
-					tl == "Tomb" ? nil : parent_id
-				end
+			trust_level_guard(parent_id).then { parent_id }
 		end
 	end
 
@@ -28,4 +34,22 @@ class ParentCodeRepo
 			]).then { code }
 		end
 	end
+
+	def existing_subaccounts(parent_id)
+		@db.query_one(<<~SQL, parent_id, default: { c: 0 }).then { |r| r[:c] }
+			SELECT COUNT(*) AS c FROM customer_plans WHERE parent_customer_id=$1
+		SQL
+	end
+
+	def trust_level_guard(parent_id)
+		return unless parent_id
+
+		@trust_level_repo.find(Customer.new(parent_id, "")).then { |tl|
+			existing_subaccounts(parent_id).then { |c| [tl, c] }
+		}.then do |(tl, number_of_subaccounts)|
+			unless tl.create_subaccount?(number_of_subaccounts)
+				raise "Please contact support to create a subaccount"
+			end
+		end
+	end
 end

lib/registration.rb 🔗

@@ -126,7 +126,7 @@ class Registration
 		end
 
 		def save_customer_plan(iq, code)
-			ParentCodeRepo.new(REDIS).find(code).then do |parent|
+			ParentCodeRepo.new(redis: REDIS, db: DB).find(code).then do |parent|
 				plan = Plan.for_registration(iq.form.field("plan_name").value.to_s)
 				@customer = @customer.with_plan(plan.name, parent_customer_id: parent)
 				@customer.save_plan!
@@ -139,7 +139,7 @@ class Registration
 				@google_play_userid = google_play_userid
 				@tel = tel
 				@invites = InvitesRepo.new(DB, REDIS)
-				@parent_code_repo = ParentCodeRepo.new(REDIS)
+				@parent_code_repo = ParentCodeRepo.new(redis: REDIS, db: DB)
 			end
 
 			def used
@@ -449,7 +449,7 @@ class Registration
 				@tel = tel
 				@error = error
 				@finish = finish
-				@parent_code_repo = ParentCodeRepo.new(REDIS)
+				@parent_code_repo = ParentCodeRepo.new(redis: REDIS, db: DB)
 			end
 
 			def add_form(reply)

lib/trust_level.rb 🔗

@@ -44,6 +44,10 @@ module TrustLevel
 			false
 		end
 
+		def create_subaccount?(*)
+			false
+		end
+
 		def to_s
 			"Tomb"
 		end
@@ -66,6 +70,10 @@ module TrustLevel
 			amount <= 35 && declines <= 2
 		end
 
+		def create_subaccount?(already_have)
+			already_have < 2
+		end
+
 		def to_s
 			"Basement"
 		end
@@ -88,6 +96,10 @@ module TrustLevel
 			amount <= 500 && declines <= 3
 		end
 
+		def create_subaccount?(already_have)
+			already_have < 10
+		end
+
 		def to_s
 			"Paragon"
 		end
@@ -110,6 +122,10 @@ module TrustLevel
 			true
 		end
 
+		def create_subaccount?(*)
+			true
+		end
+
 		def to_s
 			"Olympias"
 		end
@@ -147,6 +163,10 @@ module TrustLevel
 			amount <= 130 && declines <= 2
 		end
 
+		def create_subaccount?(already_have)
+			already_have < 2
+		end
+
 		def to_s
 			"Customer"
 		end

test/test_registration.rb 🔗

@@ -113,7 +113,12 @@ class RegistrationTest < Minitest::Test
 	class ActivationTest < Minitest::Test
 		Registration::Activation::DB = Minitest::Mock.new
 		Registration::Activation::REDIS = FakeRedis.new(
-			"jmp_parent_codes" => { "PARENT_CODE" => 1 }
+			"jmp_parent_codes" => {
+				"PARENT_CODE" => "1",
+				"PARENT_TOMB" => "2",
+				"PARENT_MANY_SUBACCOUNTS" => "many_subaccounts"
+			},
+			"jmp_customer_trust_level-2" => "Tomb"
 		)
 		Registration::Activation::Payment = Minitest::Mock.new
 		Registration::Activation::Finish = Minitest::Mock.new
@@ -290,9 +295,15 @@ class RegistrationTest < Minitest::Test
 					)
 				end]
 			)
+			Registration::Activation::DB.expect(
+				:query_one, {}, [String, "1"], default: {}
+			)
+			Registration::Activation::DB.expect(
+				:query_one, { c: 0 }, [String, "1"], default: { c: 0 }
+			)
 			@customer.expect(:with_plan, @customer) do |*args, **kwargs|
 				assert_equal ["test_usd"], args
-				assert_equal({ parent_customer_id: 1 }, kwargs)
+				assert_equal({ parent_customer_id: "1" }, kwargs)
 			end
 			@customer.expect(:save_plan!, EMPromise.resolve(nil), [])
 			Registration::Activation::DB.expect(:transaction, []) { |&blk| blk.call }
@@ -316,6 +327,74 @@ class RegistrationTest < Minitest::Test
 			assert_mock Registration::Activation::DB
 		end
 		em :test_write_with_parent_code
+
+		def test_write_with_parent_code_tombed_parent
+			Command::COMMAND_MANAGER.expect(
+				:write,
+				EMPromise.resolve(Blather::Stanza::Iq::Command.new.tap { |iq|
+					iq.form.fields = [
+						{ var: "plan_name", value: "test_usd" },
+						{ var: "code", value: "PARENT_TOMB" }
+					]
+				}),
+				[Matching.new do |iq|
+					assert_equal :form, iq.form.type
+					assert_equal(
+						"You've selected +15555550000 as your JMP number.",
+						iq.form.instructions.lines.first.chomp
+					)
+				end]
+			)
+			Registration::Activation::DB.expect(
+				:query_one, {}, [String, "2"], default: {}
+			)
+			Registration::Activation::DB.expect(
+				:query_one, { c: 0 }, [String, "2"], default: { c: 0 }
+			)
+			assert_equal(
+				"Please contact support to create a subaccount",
+				execute_command { @activation.write.catch(&:to_s) }
+			)
+			assert_mock Command::COMMAND_MANAGER
+			assert_mock @customer
+			assert_mock Registration::Activation::Payment
+			assert_mock Registration::Activation::DB
+		end
+		em :test_write_with_parent_code_tombed_parent
+
+		def test_write_with_parent_code_too_many
+			Command::COMMAND_MANAGER.expect(
+				:write,
+				EMPromise.resolve(Blather::Stanza::Iq::Command.new.tap { |iq|
+					iq.form.fields = [
+						{ var: "plan_name", value: "test_usd" },
+						{ var: "code", value: "PARENT_MANY_SUBACCOUNTS" }
+					]
+				}),
+				[Matching.new do |iq|
+					assert_equal :form, iq.form.type
+					assert_equal(
+						"You've selected +15555550000 as your JMP number.",
+						iq.form.instructions.lines.first.chomp
+					)
+				end]
+			)
+			Registration::Activation::DB.expect(
+				:query_one, {}, [String, "many_subaccounts"], default: {}
+			)
+			Registration::Activation::DB.expect(
+				:query_one, { c: 2 }, [String, "many_subaccounts"], default: { c: 0 }
+			)
+			assert_equal(
+				"Please contact support to create a subaccount",
+				execute_command { @activation.write.catch(&:to_s) }
+			)
+			assert_mock Command::COMMAND_MANAGER
+			assert_mock @customer
+			assert_mock Registration::Activation::Payment
+			assert_mock Registration::Activation::DB
+		end
+		em :test_write_with_parent_code_too_many
 	end
 
 	class AllowTest < Minitest::Test
@@ -811,6 +890,12 @@ class RegistrationTest < Minitest::Test
 					:new,
 					OpenStruct.new(write: nil)
 				) { |*| true }
+				Registration::Payment::InviteCode::DB.expect(
+					:query_one, {}, [String, "parent_customer"], default: {}
+				)
+				Registration::Payment::InviteCode::DB.expect(
+					:query_one, { c: 0 }, [String, "parent_customer"], default: { c: 0 }
+				)
 				execute_command do
 					Registration::Payment::InviteCode::REDIS.expect(
 						:hget,