From c7e522f1dbac19d354d4bdbdbba6804a540c9e93 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 25 Feb 2026 20:31:06 -0500 Subject: [PATCH] Restrict new users from outbound call/text Create a new trust level "Cellar" that cannot call or text or invite (simlar to Tomb, but semantically different and can pay etc). This is now the default trust level until an account receives at least one text "from a person". Accounts before a certain date are grandfathered in to now get this level. Port ins will get to bypass this because the reachability SMS reply will count as a text from a person. Accounts invited in any way except a group code will also bypass Cellar. The instructions on the payment form and the welcome message are both updated to warn of this new restriction. Note that the welcome message is conditional to only include the warning if it applies to the customer by that point. Texts from short codes or with links or codes or from support do not count as "from a person". There is also a block list of numbers in redis to restrict what numbers are "from a person". The number that got an account past Cellar is stored for possible addition to the block list. --- forms/registration/activate.rb | 2 + lib/invites_repo.rb | 26 +++++--- lib/registration.rb | 32 +++++++-- lib/trust_level.rb | 44 ++++++++++--- lib/trust_level_repo.rb | 32 ++++++++- lib/welcome_message.rb | 30 +++++++-- sgx_jmp.rb | 7 +- test/test_admin_command.rb | 12 ++++ test/test_buy_account_credit_form.rb | 14 ++++ test/test_credit_card_sale.rb | 84 ++++++++++++++++++++++++ test/test_helper.rb | 10 +++ test/test_registration.rb | 49 ++++++++++++-- test/test_trust_level_repo.rb | 66 ++++++++++++++++++- test/test_web.rb | 98 +++++++++++++++++++++++++++- 14 files changed, 464 insertions(+), 42 deletions(-) diff --git a/forms/registration/activate.rb b/forms/registration/activate.rb index 95886767e9ebc5ef6d5d57ce7a59a752fa43d813..ff29bcd0085d20965f280b4e4cdd67e75a7f4581 100644 --- a/forms/registration/activate.rb +++ b/forms/registration/activate.rb @@ -5,6 +5,8 @@ instructions <<~I You've selected #{@tel} as your JMP number. To activate your account, you can either deposit $#{'%.2f' % (CONFIG[:activation_amount] + @tel.price)} to your balance or enter your referral code if you have one. (If you'd like to pay in another cryptocurrency, currently we recommend using a service like simpleswap.io, morphtoken.com, changenow.io, or godex.io.) + + After payment is complete, your number will be activated for inbound calls and texts. Calling out, or sending a text, will often be restricted until you receive at least one text from a person or port in a number. I field( diff --git a/lib/invites_repo.rb b/lib/invites_repo.rb index f5713f17333c8e4dc19b497d10b986e571d40315..eb12a77ffd1d37ded2ded5ee1f3ac3b41ee426c1 100644 --- a/lib/invites_repo.rb +++ b/lib/invites_repo.rb @@ -3,19 +3,25 @@ require "multibases" require "securerandom" +require_relative "trust_level_repo" + class InvitesRepo class Invalid < StandardError; end def initialize(db=DB, redis=REDIS) @db = db @redis = redis + @trust_level_repo = TrustLevelRepo.new(db: db, redis: redis) end - def unused_invites(customer_id) - promise = @db.query_defer(<<~SQL, [customer_id]) - SELECT code FROM unused_invites WHERE creator_id=$1 - SQL - promise.then { |result| result.map { |row| row["code"] } } + def unused_invites(customer) + @trust_level_repo.find(customer).then { |tl| + next [] unless tl.invite? + + @db.query_defer(<<~SQL, [customer_id]) + SELECT code FROM unused_invites WHERE creator_id=$1 + SQL + }.then { |result| result.map { |row| row["code"] } } end def find_or_create_group_code(customer_id) @@ -44,7 +50,7 @@ class InvitesRepo ]).then do |(_, credit_to)| next false if credit_to.to_s.strip == "" - create_claimed_code(credit_to, customer_id) + create_claimed_code(credit_to, customer_id, trusted: false) credit_to end end @@ -69,10 +75,10 @@ class InvitesRepo end end - def create_claimed_code(creator_id, used_by_id) - @db.exec(<<~SQL, [creator_id, used_by_id]) - INSERT INTO invites (creator_id, used_by_id, used_at) - VALUES ($1, $2, LOCALTIMESTAMP) + def create_claimed_code(creator_id, used_by_id, trusted: true) + @db.exec(<<~SQL, [creator_id, used_by_id, trusted]) + INSERT INTO invites (creator_id, used_by_id, used_at, trusted) + VALUES ($1, $2, LOCALTIMESTAMP, $3) SQL end diff --git a/lib/registration.rb b/lib/registration.rb index 928442d70bd060bd75b12abd7b48e71612767eda..086bc6094f1d94355d87e671aabc2a3ac9b08db1 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -718,10 +718,17 @@ class Registration class Finish TN_UNAVAILABLE = "The JMP number %s is no longer available." - def initialize(customer, tel) + def initialize( + customer, tel, + trust_level_repo: TrustLevelRepo.new( + db: LazyObject.new { DB }, + redis: LazyObject.new { REDIS } + ) + ) @customer = customer @tel = tel @invites = InvitesRepo.new(DB, REDIS) + @trust_level_repo = trust_level_repo end def write @@ -796,18 +803,27 @@ class Registration @tel.charge(customer) ]) }.then do - FinishOnboarding.for(customer, @tel).then(&:write) + FinishOnboarding.for( + customer, @tel, trust_level_repo: @trust_level_repo + ).then(&:write) end end end module FinishOnboarding - def self.for(customer, tel, db: LazyObject.new { DB }) + def self.for( + customer, tel, + db: LazyObject.new { DB }, + trust_level_repo: TrustLevelRepo.new( + db: LazyObject.new { DB }, + redis: LazyObject.new { REDIS } + ) + ) jid = ProxiedJID.new(customer.jid).unproxied if jid.domain == CONFIG[:onboarding_domain] Snikket.for(customer, tel, db: db) else - NotOnboarding.new(customer, tel) + NotOnboarding.new(customer, tel, trust_level_repo: trust_level_repo) end end @@ -1006,13 +1022,17 @@ class Registration end class NotOnboarding - def initialize(customer, tel) + def initialize(customer, tel, trust_level_repo:) @customer = customer @tel = tel + @trust_level_repo = trust_level_repo end def write - WelcomeMessage.new(@customer, @tel).welcome + WelcomeMessage.for( + @customer, @tel, + trust_level_repo: @trust_level_repo + ).then(&:welcome) Command.finish("Your JMP account has been activated as #{@tel}") end end diff --git a/lib/trust_level.rb b/lib/trust_level.rb index 10668cac7dcc5116b887f91be939dee7488078cf..b54112c1baf67b85108665e370deba6966c9e5b3 100644 --- a/lib/trust_level.rb +++ b/lib/trust_level.rb @@ -3,12 +3,15 @@ require "delegate" module TrustLevel - def self.for(plan_name:, settled_amount: 0, manual: nil) + def self.for( + customer:, settled_amount: 0, manual: nil, + activated: nil, invited: false, activater: nil + ) @levels.each do |level| tl = level.call( - plan_name: plan_name, - settled_amount: settled_amount, - manual: manual + customer: customer, + settled_amount: settled_amount, activated: activated, + manual: manual, invited: invited, activater: activater ) return manual ? Manual.new(tl) : tl if tl end @@ -32,6 +35,10 @@ module TrustLevel new if manual == "Tomb" end + def invite? + false + end + def write_cdr? false end @@ -70,8 +77,13 @@ module TrustLevel end class Cellar - TrustLevel.register do |manual:, **| - new if manual == "Cellar" + TrustLevel.register do |manual:, activated:, invited:, activater:, **| + new if manual == "Cellar" || (!manual && !activater && !invited && \ + (!activated || activated > Time.parse("2026-02-26"))) + end + + def invite? + false end def write_cdr? @@ -117,6 +129,10 @@ module TrustLevel new if manual == "Basement" || (!manual && settled_amount < 10) end + def invite? + true + end + def write_cdr? true end @@ -171,6 +187,10 @@ module TrustLevel new if manual == "Paragon" || (!manual && settled_amount > 60) end + def invite? + true + end + def write_cdr? true end @@ -225,6 +245,10 @@ module TrustLevel new if manual == "Olympias" end + def invite? + true + end + def write_cdr? true end @@ -253,12 +277,12 @@ module TrustLevel end class Customer - TrustLevel.register do |manual:, plan_name:, **| + TrustLevel.register do |manual:, customer:, **| if manual && manual != "Customer" Sentry.capture_message("Unknown TrustLevel: #{manual}") end - new(plan_name) + new(customer.plan_name) end EXPENSIVE_ROUTE = { @@ -272,6 +296,10 @@ module TrustLevel @max_rate = EXPENSIVE_ROUTE.fetch(plan_name, 0.1) end + def invite? + true + end + def write_cdr? true end diff --git a/lib/trust_level_repo.rb b/lib/trust_level_repo.rb index 4cad9ba90cfa4648a71412e748e6056509c2366f..e7b0b30eaac9dc0c72889e44bb4bddd297cb9526 100644 --- a/lib/trust_level_repo.rb +++ b/lib/trust_level_repo.rb @@ -14,11 +14,17 @@ class TrustLevelRepo def find(customer) EMPromise.all([ find_manual(customer.customer_id), + redis.get("jmp_customer_activater-#{customer.customer_id}"), + customer.activation_date, + fetch_was_invited(customer.customer_id), fetch_settled_amount(customer.billing_customer_id) - ]).then do |(manual, row)| + ]).then do |(manual, activater, activated, invited, row)| TrustLevel.for( + customer: customer, manual: manual, - plan_name: customer.plan_name, + activated: activated, + invited: invited[:used_at], + activater: activater, **row ) end @@ -36,6 +42,22 @@ class TrustLevelRepo end end + def incoming_message(customer, stanza) + from = stanza.from.node.to_s + return if from =~ /^$|^[^+]/ # don't count short codes + + body = m.body.to_s + return if body =~ /^$|http|code/i + + redis.sismember("jmp_blocked_activation_source", from).then do |blocked| + next if blocked + + redis.set( + "jmp_customer_activater-#{customer.customer_id}", stanza.from.node, "NX" + ) + end + end + protected def fetch_settled_amount(customer_id) @@ -44,4 +66,10 @@ protected WHERE customer_id=$1 AND settled_after < LOCALTIMESTAMP AND amount > 0 SQL end + + def fetch_was_invited(customer_id) + db.query_one(<<~SQL, customer_id, default: {}) + SELECT used_at FROM invites WHERE used_by_id=$1 AND trusted LIMIT 1 + SQL + end end diff --git a/lib/welcome_message.rb b/lib/welcome_message.rb index 00fbd918509dc0e15db3a8b685f5a3e6d4bade36..ca5690ef5dfd7547b976e4126fa0595dd5e5097d 100644 --- a/lib/welcome_message.rb +++ b/lib/welcome_message.rb @@ -1,9 +1,18 @@ # frozen_string_literal: true +require_relative "trust_level_repo" + class WelcomeMessage - def initialize(customer, tel) + def self.for(customer, tel, trust_level_repo: TrustLevelRepo.new) + trust_level_repo.find(customer).then do |tl| + new(customer, tel, tl) + end + end + + def initialize(customer, tel, trust_level) @customer = customer @tel = tel + @trust_level = trust_level end def welcome @@ -13,15 +22,24 @@ class WelcomeMessage end end + def warning + return if @trust_level.support_call?(0, 0) && @trust_level.send_message?(0) + + "\n\nYour account is activated for inbound calls and texts, but you " \ + "won't be able to call out or send a text until you receive at least " \ + "one text from a person at your new number, or port in a number from " \ + "outside." + end + def message m = Blather::Stanza::Message.new m.from = CONFIG[:notify_from] m.body = - "Welcome to JMP! Your JMP number is #{@tel}. This is an automated " \ - "message, but anything you send here will go direct to real humans " \ - "who will be happy to help you. Such support requests will get a " \ - "reply within 8 business hours.\n\n" \ - "FAQ: https://jmp.chat/faq\n\n" \ + "Welcome to JMP! Your JMP number is #{@tel}.#{warning}\n\nThis is an " \ + "automated message, but anything you send here will go direct to real " \ + "humans who will be happy to help you. Such support requests will " \ + "get a reply within 8 business hours.\n\n" \ + "FAQ: https://jmp.chat/faq\n" \ "Account Settings: xmpp:cheogram.com?command" m end diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 7aaef894ec29d0195356f2e4776aa6294c80cf45..c4acf4f3932b1159a27db536a256f76e633ece62 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -276,6 +276,8 @@ before nil, to: /\Acustomer_/, from: /(\A|@)#{FROM_BACKEND}(\/|\Z)/ do |s| CustomerRepo.new(set_user: Sentry.method(:set_user)).find( s.to.node.delete_prefix("customer_") ).then do |customer| + # Intentionally called outside filter so ported-in numbers activate here + TrustLevelRepo.new.incoming_message(customer, s) ReachabilityRepo::SMS.new .find(customer, s.from.node, stanza: s).then do |reach| reach.filter do @@ -304,6 +306,7 @@ message( CustomerRepo .new(set_user: Sentry.method(:set_user)) .find_by_jid(address["jid"]).then { |customer| + TrustLevelRepo.new.incoming_message(customer, s) m.from = m.from.with(domain: CONFIG[:component][:jid]) m.to = m.to.with(domain: customer.jid.domain) address["jid"] = customer.jid.to_s @@ -716,7 +719,7 @@ Command.new( Command.customer.then { |customer| EMPromise.all([ repo.find_or_create_group_code(customer.customer_id), - repo.unused_invites(customer.customer_id) + repo.unused_invites(customer) ]) }.then do |(group_code, invites)| if invites.empty? @@ -1092,7 +1095,7 @@ Command.new( jid = ProxiedJID.new(customer.jid).unproxied if jid.domain == CONFIG[:onboarding_domain] CustomerRepo.new.find(customer.customer_id).then do |cust| - WelcomeMessage.new(cust, customer.registered?.phone).welcome + WelcomeMessage.for(cust, customer.registered?.phone).then(&:welcome) end end Command.finish { |reply| diff --git a/test/test_admin_command.rb b/test/test_admin_command.rb index 7534c5c5b4c07c3d2f9d869ad76e0b7b99125c0b..040805082ebf0c7f5b0f8a2a772e3aa2acf74984 100644 --- a/test/test_admin_command.rb +++ b/test/test_admin_command.rb @@ -164,6 +164,18 @@ class AdminCommandTest < Minitest::Test ["jmp_customer_trust_level-testuser"] ) + TrustLevelRepo::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_activater-testuser"] + ) + + TrustLevelRepo::DB.expect( + :query_one, + EMPromise.resolve({}), + [String, "testuser"], default: {} + ) + TrustLevelRepo::DB.expect( :query_one, EMPromise.resolve({ settled_amount: 0 }), diff --git a/test/test_buy_account_credit_form.rb b/test/test_buy_account_credit_form.rb index 8fc84c67a18f800040072b5b8c4b0ac81b7c181b..2573db8ae19d142fc3c267ecd42bc79d9a725580 100644 --- a/test/test_buy_account_credit_form.rb +++ b/test/test_buy_account_credit_form.rb @@ -36,6 +36,19 @@ class BuyAccountCreditFormTest < Minitest::Test EMPromise.resolve("Customer"), ["jmp_customer_trust_level-test"] ) + TrustLevelRepo::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_activater-test"] + ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "test"] + ) + TrustLevelRepo::DB.expect( + :query_one, + EMPromise.resolve({}), + [String, "test"], default: {} + ) TrustLevelRepo::DB.expect( :query_one, EMPromise.resolve({}), @@ -47,6 +60,7 @@ class BuyAccountCreditFormTest < Minitest::Test BuyAccountCreditForm.for(customer).sync ) + assert_mock CustomerPlan::DB assert_mock TrustLevelRepo::REDIS assert_mock TrustLevelRepo::DB end diff --git a/test/test_credit_card_sale.rb b/test/test_credit_card_sale.rb index 2d460da513e4c39af7c23f5edc2776ec4c8462f8..a39123f9191666b9343852ad3c159fc33373edec 100644 --- a/test/test_credit_card_sale.rb +++ b/test/test_credit_card_sale.rb @@ -32,6 +32,19 @@ class CreditCardSaleTest < Minitest::Test EMPromise.resolve("Customer"), ["jmp_customer_trust_level-test"] ) + TrustLevelRepo::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_activater-test"] + ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "test"] + ) + TrustLevelRepo::DB.expect( + :query_one, + EMPromise.resolve({}), + [String, "test"], default: {} + ) TrustLevelRepo::DB.expect( :query_one, EMPromise.resolve({}), @@ -72,6 +85,7 @@ class CreditCardSaleTest < Minitest::Test ).sale.sync end assert_mock CustomerFinancials::REDIS + assert_mock CustomerPlan::DB assert_mock CreditCardSale::REDIS assert_mock TrustLevelRepo::REDIS assert_mock TrustLevelRepo::DB @@ -89,6 +103,19 @@ class CreditCardSaleTest < Minitest::Test EMPromise.resolve("Customer"), ["jmp_customer_trust_level-test"] ) + TrustLevelRepo::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_activater-test"] + ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "test"] + ) + TrustLevelRepo::DB.expect( + :query_one, + EMPromise.resolve({}), + [String, "test"], default: {} + ) TrustLevelRepo::DB.expect( :query_one, EMPromise.resolve({}), @@ -107,6 +134,7 @@ class CreditCardSaleTest < Minitest::Test ).sale.sync end assert_mock CustomerFinancials::REDIS + assert_mock CustomerPlan::DB assert_mock CreditCardSale::REDIS assert_mock TrustLevelRepo::REDIS assert_mock TrustLevelRepo::DB @@ -124,6 +152,19 @@ class CreditCardSaleTest < Minitest::Test EMPromise.resolve("Customer"), ["jmp_customer_trust_level-test"] ) + TrustLevelRepo::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_activater-test"] + ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "test"] + ) + TrustLevelRepo::DB.expect( + :query_one, + EMPromise.resolve({}), + [String, "test"], default: {} + ) TrustLevelRepo::DB.expect( :query_one, EMPromise.resolve({}), @@ -144,6 +185,7 @@ class CreditCardSaleTest < Minitest::Test end assert_mock CustomerFinancials::REDIS + assert_mock CustomerPlan::DB assert_mock CreditCardSale::REDIS assert_mock TrustLevelRepo::REDIS assert_mock TrustLevelRepo::DB @@ -161,6 +203,19 @@ class CreditCardSaleTest < Minitest::Test EMPromise.resolve("Customer"), ["jmp_customer_trust_level-test"] ) + TrustLevelRepo::REDIS.expect( + :get, + EMPromise.resolve("Customer"), + ["jmp_customer_activater-test"] + ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "test"] + ) + TrustLevelRepo::DB.expect( + :query_one, + EMPromise.resolve({}), + [String, "test"], default: {} + ) TrustLevelRepo::DB.expect( :query_one, EMPromise.resolve({}), @@ -181,6 +236,7 @@ class CreditCardSaleTest < Minitest::Test end assert_mock CustomerFinancials::REDIS + assert_mock CustomerPlan::DB assert_mock CreditCardSale::REDIS assert_mock TrustLevelRepo::REDIS assert_mock TrustLevelRepo::DB @@ -218,6 +274,19 @@ class CreditCardSaleTest < Minitest::Test EMPromise.resolve("Customer"), ["jmp_customer_trust_level-test"] ) + TrustLevelRepo::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_activater-test"] + ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "test"] + ) + TrustLevelRepo::DB.expect( + :query_one, + EMPromise.resolve({}), + [String, "test"], default: {} + ) TrustLevelRepo::DB.expect( :query_one, EMPromise.resolve({}), @@ -255,6 +324,7 @@ class CreditCardSaleTest < Minitest::Test ).sale.sync assert_equal FAKE_BRAINTREE_TRANSACTION, result assert_mock CustomerFinancials::REDIS + assert_mock CustomerPlan::DB assert_mock CreditCardSale::REDIS assert_mock TrustLevelRepo::REDIS assert_mock TrustLevelRepo::DB @@ -309,6 +379,19 @@ class CreditCardSaleTest < Minitest::Test EMPromise.resolve("Customer"), ["jmp_customer_trust_level-test"] ) + TrustLevelRepo::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_activater-test"] + ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "test"] + ) + TrustLevelRepo::DB.expect( + :query_one, + EMPromise.resolve({}), + [String, "test"], default: {} + ) TrustLevelRepo::DB.expect( :query_one, EMPromise.resolve({}), @@ -367,6 +450,7 @@ class CreditCardSaleTest < Minitest::Test assert_mock transaction_class assert_mock transaction assert_mock CustomerFinancials::REDIS + assert_mock CustomerPlan::DB assert_mock CreditCardSale::REDIS assert_mock TrustLevelRepo::REDIS assert_mock TrustLevelRepo::DB diff --git a/test/test_helper.rb b/test/test_helper.rb index ac1946ed4319cf25f95108c611b623f764864a5f..91c9b5b03e15f23de26405803034858e6e1bd934 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -478,6 +478,16 @@ class FakeIBRRepo end end +class FakeTrustLevelRepo + def initialize(levels) + @levels = levels + end + + def find(customer) + TrustLevel.for(customer: customer, manual: @levels[customer.customer_id]) + end +end + module EventMachine class << self # Patch EM.add_timer to be instant in tests diff --git a/test/test_registration.rb b/test/test_registration.rb index 264e1ddfdce3ce4713411812b425426a5e969831..f84c721c75e1b1a82a4b06a276df95bb62cb5acc 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -220,6 +220,7 @@ class RegistrationTest < Minitest::Test "PARENT_TOMB" => "2", "PARENT_MANY_SUBACCOUNTS" => "many_subaccounts" }, + "jmp_customer_trust_level-1" => "Basement", "jmp_customer_trust_level-2" => "Tomb" ) Registration::Activation::Payment = Minitest::Mock.new @@ -436,6 +437,12 @@ class RegistrationTest < Minitest::Test ) end] ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "1"] + ) + Registration::Activation::DB.expect( + :query_one, {}, [String, "1"], default: {} + ) Registration::Activation::DB.expect( :query_one, {}, [String, "1"], default: {} ) @@ -466,6 +473,7 @@ class RegistrationTest < Minitest::Test ) end assert_mock Command::COMMAND_MANAGER + assert_mock CustomerPlan::DB assert_mock @customer assert_mock Registration::Activation::Payment assert_mock Registration::Activation::DB @@ -490,6 +498,12 @@ class RegistrationTest < Minitest::Test ) end] ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "1"] + ) + Registration::Activation::DB.expect( + :query_one, {}, [String, "1"], default: {} + ) Registration::Activation::DB.expect( :query_one, {}, [String, "1"], default: {} ) @@ -531,6 +545,12 @@ class RegistrationTest < Minitest::Test ) end] ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "2"] + ) + Registration::Activation::DB.expect( + :query_one, {}, [String, "2"], default: {} + ) Registration::Activation::DB.expect( :query_one, {}, [String, "2"], default: {} ) @@ -542,6 +562,7 @@ class RegistrationTest < Minitest::Test execute_command { @activation.write.catch(&:to_s) } ) assert_mock Command::COMMAND_MANAGER + assert_mock CustomerPlan::DB assert_mock @customer assert_mock Registration::Activation::Payment assert_mock Registration::Activation::DB @@ -565,6 +586,12 @@ class RegistrationTest < Minitest::Test ) end] ) + CustomerPlan::DB.expect( + :query_one, {}, [String, "many_subaccounts"] + ) + Registration::Activation::DB.expect( + :query_one, {}, [String, "many_subaccounts"], default: {} + ) Registration::Activation::DB.expect( :query_one, {}, [String, "many_subaccounts"], default: {} ) @@ -576,6 +603,7 @@ class RegistrationTest < Minitest::Test execute_command { @activation.write.catch(&:to_s) } ) assert_mock Command::COMMAND_MANAGER + assert_mock CustomerPlan::DB assert_mock @customer assert_mock Registration::Activation::Payment assert_mock Registration::Activation::DB @@ -654,7 +682,7 @@ class RegistrationTest < Minitest::Test Registration::Activation::Allow::DB.expect( :exec, nil, - [String, ["refercust", "test"]] + [String, ["refercust", "test", true]] ) cust.expect(:with_plan, cust, ["test_usd"]) cust.expect(:activate_plan_starting_now, nil) @@ -1039,6 +1067,12 @@ class RegistrationTest < Minitest::Test :new, OpenStruct.new(write: nil) ) { |*| true } + CustomerPlan::DB.expect( + :query_one, {}, [String, "parent_customer"] + ) + Registration::Payment::InviteCode::DB.expect( + :query_one, {}, [String, "parent_customer"], default: {} + ) Registration::Payment::InviteCode::DB.expect( :query_one, {}, [String, "parent_customer"], default: {} ) @@ -1053,9 +1087,14 @@ class RegistrationTest < Minitest::Test ) Registration::Payment::InviteCode::REDIS.expect( :get, - EMPromise.resolve(nil), + EMPromise.resolve("Basement"), ["jmp_customer_trust_level-parent_customer"] ) + Registration::Payment::InviteCode::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_activater-parent_customer"] + ) CustomerPlan::DB.expect( :query, [{ "plan_name" => "test_usd" }], @@ -1090,6 +1129,7 @@ class RegistrationTest < Minitest::Test ).write end assert_mock Command::COMMAND_MANAGER + assert_mock CustomerPlan::DB assert_mock Registration::Payment::InviteCode::DB assert_mock Registration::Payment::InviteCode::REDIS assert_mock Registration::Payment::MaybeBill::BillPlan @@ -1314,7 +1354,8 @@ class RegistrationTest < Minitest::Test iq.from = "test\\40example.com@cheogram.com" @finish = Registration::Finish.new( customer(sgx: @sgx, plan_name: "test_usd"), - TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") + TelSelections::ChooseTel::Tn.for_pending_value("+15555550000"), + trust_level_repo: FakeTrustLevelRepo.new("test" => "Cellar") ) end @@ -1468,7 +1509,7 @@ class RegistrationTest < Minitest::Test Registration::Finish::DB.expect( :exec, EMPromise.resolve(nil), - [String, ["test-inviter", "test"]] + [String, ["test-inviter", "test", false]] ) Transaction::DB.expect(:transaction, nil) do |&blk| blk.call diff --git a/test/test_trust_level_repo.rb b/test/test_trust_level_repo.rb index b8e7b86063e8118274fcee96f01df3f02d2dc478..9b7bed6e19c631ca81173cbb5361dd7302fa67d7 100644 --- a/test/test_trust_level_repo.rb +++ b/test/test_trust_level_repo.rb @@ -3,6 +3,16 @@ require "trust_level_repo" class TrustLevelRepoTest < Minitest::Test + def setup + CustomerPlan::DB.expect( + :query_one, {}, [String, "test"] + ) + end + + def teardown + assert_mock CustomerPlan::DB + end + def test_manual_tomb trust_level = TrustLevelRepo.new( db: FakeDB.new, @@ -14,6 +24,17 @@ class TrustLevelRepoTest < Minitest::Test end em :test_manual_tomb + def test_manual_cellar + trust_level = TrustLevelRepo.new( + db: FakeDB.new, + redis: FakeRedis.new( + "jmp_customer_trust_level-test" => "Cellar" + ) + ).find(customer(plan_name: "test_usd")).sync + assert_equal "Manual(Cellar)", trust_level.to_s + end + em :test_manual_cellar + def test_manual_basement trust_level = TrustLevelRepo.new( db: FakeDB.new, @@ -63,14 +84,51 @@ class TrustLevelRepoTest < Minitest::Test db: FakeDB.new, redis: FakeRedis.new ).find(customer(plan_name: "test_usd")).sync - assert_kind_of TrustLevel::Basement, trust_level + assert_kind_of TrustLevel::Cellar, trust_level end em :test_new_customer + def test_new_customer_with_activater + trust_level = TrustLevelRepo.new( + db: FakeDB.new, + redis: FakeRedis.new( + "jmp_customer_activater-test" => "+15551234567" + ) + ).find(customer(plan_name: "test_usd")).sync + assert_kind_of TrustLevel::Basement, trust_level + end + em :test_new_customer_with_activater + + def test_invited_customer + trust_level = TrustLevelRepo.new( + db: FakeDB.new( + ["test"] => FakeDB::MultiResult.new([{ used_at: Time.now }], []) + ), + redis: FakeRedis.new + ).find(customer(plan_name: "test_usd")).sync + assert_kind_of TrustLevel::Basement, trust_level + end + em :test_invited_customer + + def test_old_customer + CustomerPlan::DB.query_one("", "test") + CustomerPlan::DB.expect( + :query_one, { start_date: Time.parse("2026-02-25") }, [String, "test"] + ) + trust_level = TrustLevelRepo.new( + db: FakeDB.new, + redis: FakeRedis.new + ).find(customer(plan_name: "test_usd")).sync + assert_kind_of TrustLevel::Basement, trust_level + end + em :test_old_customer + def test_regular_customer trust_level = TrustLevelRepo.new( db: FakeDB.new(["test"] => [{ "settled_amount" => 15 }]), - redis: FakeRedis.new + redis: FakeRedis.new( + "jmp_customer_activater-test" => "+15551234567" + ) ).find(customer(plan_name: "test_usd")).sync assert_kind_of TrustLevel::Customer, trust_level end @@ -79,7 +137,9 @@ class TrustLevelRepoTest < Minitest::Test def test_settled_customer trust_level = TrustLevelRepo.new( db: FakeDB.new(["test"] => [{ "settled_amount" => 61 }]), - redis: FakeRedis.new + redis: FakeRedis.new( + "jmp_customer_activater-test" => "+15551234567" + ) ).find(customer(plan_name: "test_usd")).sync assert_kind_of TrustLevel::Paragon, trust_level end diff --git a/test/test_web.rb b/test/test_web.rb index 8bc2cc36a2a44489fbb0b56437dbb303566b87e3..8677f382159312555b27ef12e1dfd714a9a63deb 100644 --- a/test/test_web.rb +++ b/test/test_web.rb @@ -124,7 +124,13 @@ class WebTest < Minitest::Test ) ) Web.opts[:call_attempt_repo] = CallAttemptRepo.new( - redis: FakeRedis.new, + redis: FakeRedis.new( + "jmp_customer_activater-customerid" => "+15551234567", + "jmp_customer_activater-customerid2" => "+15551234567", + "jmp_customer_activater-customerid_limit" => "+15551234567", + "jmp_customer_activater-customerid_low" => "+15551234567", + "jmp_customer_activater-customerid_topup" => "+15551234567" + ), db: FakeDB.new( ["test_usd", "+15557654321", :outbound] => [{ "rate" => 0.01 }], ["test_usd", "+1911", :outbound] => [{ "rate" => 0.01 }], @@ -134,14 +140,17 @@ class WebTest < Minitest::Test ["test_usd", "+18001234567", :outbound] => [{ "rate" => 0.00 }], ["customerid_limit"] => FakeDB::MultiResult.new( [{ "a" => 1000 }], + [], [{ "settled_amount" => 15 }] ), ["customerid_low"] => FakeDB::MultiResult.new( [{ "a" => 1000 }], + [], [{ "settled_amount" => 15 }] ), ["customerid_topup"] => FakeDB::MultiResult.new( [{ "a" => 1000 }], + [], [{ "settled_amount" => 15 }] ) ) @@ -163,6 +172,10 @@ class WebTest < Minitest::Test end def test_outbound_forwards + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) + post( "/outbound/calls", { @@ -180,10 +193,15 @@ class WebTest < Minitest::Test "+15557654321", last_response.body ) + assert_mock CustomerPlan::DB end em :test_outbound_forwards def test_outbound_low_balance + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid_low"] + ) + ExpiringLock::REDIS.expect( :set, EMPromise.resolve(nil), @@ -208,10 +226,15 @@ class WebTest < Minitest::Test last_response.body ) assert_mock ExpiringLock::REDIS + assert_mock CustomerPlan::DB end em :test_outbound_low_balance def test_outbound_low_balance_top_up + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid_topup"] + ) + LowBalance::AutoTopUp::CreditCardSale.expect( :create, EMPromise.resolve( @@ -273,10 +296,15 @@ class WebTest < Minitest::Test assert_mock ExpiringLock::REDIS assert_mock Customer::BLATHER assert_mock LowBalance::AutoTopUp::CreditCardSale + assert_mock CustomerPlan::DB end em :test_outbound_low_balance_top_up def test_outbound_unsupported + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) + post( "/outbound/calls", { @@ -294,10 +322,15 @@ class WebTest < Minitest::Test "supported on your account.", last_response.body ) + assert_mock CustomerPlan::DB end em :test_outbound_unsupported def test_outbound_unsupported_short_numbers_911 + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) + post( "/outbound/calls", { @@ -315,10 +348,15 @@ class WebTest < Minitest::Test "supported on your account.", last_response.body ) + assert_mock CustomerPlan::DB end em :test_outbound_unsupported_short_numbers_911 def test_outbound_supported_9116 + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) + post( "/outbound/calls", { @@ -336,10 +374,15 @@ class WebTest < Minitest::Test "+19116", last_response.body ) + assert_mock CustomerPlan::DB end em :test_outbound_supported_9116 def test_outbound_atlimit + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid_limit"] + ) + post( "/outbound/calls", { @@ -360,6 +403,7 @@ class WebTest < Minitest::Test "charges. You can hang up to cancel.", last_response.body ) + assert_mock CustomerPlan::DB end em :test_outbound_atlimit @@ -385,6 +429,10 @@ class WebTest < Minitest::Test em :test_outbound_no_customer def test_outbound_atlimit_digits + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid_limit"] + ) + post( "/outbound/calls", { @@ -403,10 +451,15 @@ class WebTest < Minitest::Test "+15557654321", last_response.body ) + assert_mock CustomerPlan::DB end em :test_outbound_atlimit_digits def test_outbound_toll_free + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) + post( "/outbound/calls", { @@ -424,10 +477,15 @@ class WebTest < Minitest::Test "+18001234567", last_response.body ) + assert_mock CustomerPlan::DB end em :test_outbound_toll_free def test_outbound_disconnect + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) + post( "/outbound/calls/status", { @@ -444,10 +502,15 @@ class WebTest < Minitest::Test assert last_response.ok? assert_equal("OK", last_response.body) + assert_mock CustomerPlan::DB end em :test_outbound_disconnect def test_outbound_disconnect_tombed + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid_tombed"] + ) + @cdr_repo.stub(:put, ->(*) { raise "put called" }) do post( "/outbound/calls/status", @@ -466,10 +529,14 @@ class WebTest < Minitest::Test assert last_response.ok? assert_equal("OK", last_response.body) + assert_mock CustomerPlan::DB end em :test_outbound_disconnect_tombed def test_inbound + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) CustomerFwd::BANDWIDTH_VOICE.expect( :create_call, OpenStruct.new(data: OpenStruct.new(call_id: "ocall")), @@ -500,10 +567,14 @@ class WebTest < Minitest::Test last_response.body ) assert_mock CustomerFwd::BANDWIDTH_VOICE + assert_mock CustomerPlan::DB end em :test_inbound def test_inbound_from_reachability + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) CustomerFwd::BANDWIDTH_VOICE.expect( :create_call, OpenStruct.new(data: OpenStruct.new(call_id: "ocall")), @@ -541,10 +612,14 @@ class WebTest < Minitest::Test ) assert_mock CustomerFwd::BANDWIDTH_VOICE assert_mock ReachableRedis + assert_mock CustomerPlan::DB end em :test_inbound_from_reachability def test_inbound_no_bwmsgsv2 + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid2"] + ) CustomerFwd::BANDWIDTH_VOICE.expect( :create_call, OpenStruct.new(data: OpenStruct.new(call_id: "ocall")), @@ -575,10 +650,15 @@ class WebTest < Minitest::Test last_response.body ) assert_mock CustomerFwd::BANDWIDTH_VOICE + assert_mock CustomerPlan::DB end em :test_inbound_no_bwmsgsv2 def test_inbound_low + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid_low"] + ) + ExpiringLock::REDIS.expect( :set, EMPromise.resolve(nil), @@ -604,10 +684,15 @@ class WebTest < Minitest::Test ) assert_mock CustomerFwd::BANDWIDTH_VOICE assert_mock ExpiringLock::REDIS + assert_mock CustomerPlan::DB end em :test_inbound_low def test_inbound_leg2 + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid"] + ) + post( "/inbound/calls/acall?customer_id=customerid", { @@ -625,10 +710,15 @@ class WebTest < Minitest::Test "", last_response.body ) + assert_mock CustomerPlan::DB end em :test_inbound_leg2 def test_inbound_limit_leg2 + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid_limit"] + ) + path = "/inbound/calls/acall?customer_id=customerid_limit" post( @@ -652,10 +742,15 @@ class WebTest < Minitest::Test "", last_response.body ) + assert_mock CustomerPlan::DB end em :test_inbound_limit_leg2 def test_inbound_limit_digits_leg2 + CustomerPlan::DB.expect( + :query_one, {}, [String, "customerid_limit"] + ) + post( "/inbound/calls/acall?customer_id=customerid_limit", { @@ -674,6 +769,7 @@ class WebTest < Minitest::Test "", last_response.body ) + assert_mock CustomerPlan::DB end em :test_inbound_limit_digits_leg2