From 497b442bba246370bd5b724b5c2fa547ceb61ce6 Mon Sep 17 00:00:00 2001 From: Christopher Vollick <0@psycoti.ca> Date: Wed, 25 Aug 2021 16:01:31 -0400 Subject: [PATCH] Customer Info This should allow us, the admins, to query information about a customer without having to dive in and run a couple redis queries and some database queries before getting the full picture of who we're talking to. It also allows the users to request some data about themselves. Balance and phone number are already visible in other places, but their expiry is currently not, and people have been asking about it. --- .rubocop.yml | 3 + config-schema.dhall | 2 + config.dhall.sample | 4 +- lib/api.rb | 52 ++++++++++++ lib/customer.rb | 19 +++++ lib/customer_info.rb | 87 +++++++++++++++++++ lib/customer_info_form.rb | 82 ++++++++++++++++++ lib/customer_repo.rb | 26 +++++- lib/legacy_customer.rb | 65 ++++++++++++++ lib/proxied_jid.rb | 28 +++++++ sgx_jmp.rb | 44 ++++++++++ test/test_customer_info.rb | 114 +++++++++++++++++++++++++ test/test_customer_info_form.rb | 117 ++++++++++++++++++++++++++ test/test_customer_repo.rb | 144 ++++++++++++++++++++++---------- test/test_helper.rb | 33 +++++++- test/test_proxied_jid.rb | 34 ++++++++ 16 files changed, 807 insertions(+), 47 deletions(-) create mode 100644 lib/api.rb create mode 100644 lib/customer_info.rb create mode 100644 lib/customer_info_form.rb create mode 100644 lib/legacy_customer.rb create mode 100644 lib/proxied_jid.rb create mode 100644 test/test_customer_info.rb create mode 100644 test/test_customer_info_form.rb create mode 100644 test/test_proxied_jid.rb diff --git a/.rubocop.yml b/.rubocop.yml index 1deee46..eb580f9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -51,6 +51,9 @@ Style/DoubleNegation: EnforcedStyle: allowed_in_returns Enabled: false +Style/PerlBackrefs: + Enabled: false + Style/RegexpLiteral: EnforcedStyle: slashes AllowInnerSlashes: true diff --git a/config-schema.dhall b/config-schema.dhall index 08696aa..d4617a5 100644 --- a/config-schema.dhall +++ b/config-schema.dhall @@ -1,4 +1,5 @@ { activation_amount : Natural +, admins : List Text , adr : Text , bandwidth_peer : Text , bandwidth_site : Text @@ -41,6 +42,7 @@ , server : { host : Text, port : Natural } , sgx : Text , sip_host : Text +, upstream_domain : Text , web_register : { from : Text, to : Text } , xep0157 : List { label : Text, value : Text, var : Text } } diff --git a/config.dhall.sample b/config.dhall.sample index 149e619..ae4bf1a 100644 --- a/config.dhall.sample +++ b/config.dhall.sample @@ -71,5 +71,7 @@ adr = "", interac = "", payable = "", - notify_from = "+15551234567@example.net" + notify_from = "+15551234567@example.net", + admins = ["test\\40example.com@example.net"], + upstream_domain = "example.net" } diff --git a/lib/api.rb b/lib/api.rb new file mode 100644 index 0000000..fab9fb3 --- /dev/null +++ b/lib/api.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class API + def self.for(customer) + EMPromise.all([ + sgx_jmp?(customer), + api_version(customer) + ]).then do |is_jmp, api| + is_jmp ? JMP.new : api + end + end + + def self.sgx_jmp?(customer) + key = "catapult_cred-customer_#{customer.customer_id}@jmp.chat" + REDIS.exists(key).then { |is_sgx| is_sgx == 1 } + end + + def self.api_version(customer) + REDIS.lindex("catapult_cred-#{customer.jid}", 0).then do |api| + case api + when CONFIG.dig(:catapult, :user) + V1.new + when CONFIG.dig(:creds, :account) + V2.new + else + new + end + end + end + + class V1 < API + def to_s + "v1" + end + end + + class V2 < API + def to_s + "v2" + end + end + + class JMP < V2 + def to_s + "sgx-jmp" + end + end + + def to_s + "not JMP" + end +end diff --git a/lib/customer.rb b/lib/customer.rb index 01f54fc..d5e7d1c 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -2,13 +2,16 @@ require "forwardable" +require_relative "./api" require_relative "./blather_ext" +require_relative "./customer_info" require_relative "./customer_plan" require_relative "./customer_usage" require_relative "./backend_sgx" require_relative "./ibr" require_relative "./payment_methods" require_relative "./plan" +require_relative "./proxied_jid" require_relative "./sip_account" class Customer @@ -95,5 +98,21 @@ class Customer end end + def admin? + CONFIG[:admins].include?(jid.to_s) + end + + def api + API.for(self) + end + + def admin_info + AdminInfo.for(self, @plan, expires_at) + end + + def info + CustomerInfo.for(self, @plan, expires_at) + end + protected def_delegator :@plan, :expires_at end diff --git a/lib/customer_info.rb b/lib/customer_info.rb new file mode 100644 index 0000000..d3b8b38 --- /dev/null +++ b/lib/customer_info.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require "value_semantics/monkey_patched" +require_relative "proxied_jid" +require_relative "customer_plan" + +class CustomerInfo + value_semantics do + plan CustomerPlan + tel Either(String, nil) + balance BigDecimal + expires_at Either(Time, nil) + end + + def self.for(customer, plan, expires_at) + customer.registered?.then do |registration| + new( + plan: plan, + tel: registration&.phone, + balance: customer.balance, + expires_at: expires_at + ) + end + end + + def account_status + if plan.plan_name.nil? + "Transitional" + elsif plan.active? + "Active" + else + "Expired" + end + end + + def next_renewal + { var: "Next renewal", value: expires_at.strftime("%Y-%m-%d") } if expires_at + end + + def fields + [ + { var: "Account Status", value: account_status }, + { var: "Phone Number", value: tel || "Not Registered" }, + { var: "Balance", value: "$%.4f" % balance }, + next_renewal + ].compact + end +end + +class AdminInfo + value_semantics do + jid ProxiedJID, coerce: ProxiedJID.method(:new) + customer_id String + info CustomerInfo + api API + end + + def self.for(customer, plan, expires_at) + EMPromise.all([ + CustomerInfo.for(customer, plan, expires_at), + customer.api + ]).then do |info, api_value| + new( + jid: customer.jid, + customer_id: customer.customer_id, + info: info, api: api_value + ) + end + end + + def plan_fields + [ + { var: "Plan", value: info.plan.plan_name || "No Plan" }, + { var: "Currency", value: (info.plan.currency || "No Currency").to_s } + ] + end + + def fields + info.fields + [ + { var: "JID", value: jid.unproxied.to_s }, + { var: "Cheo JID", value: jid.to_s }, + { var: "Customer ID", value: customer_id }, + *plan_fields, + { var: "API", value: api.to_s } + ] + end +end diff --git a/lib/customer_info_form.rb b/lib/customer_info_form.rb new file mode 100644 index 0000000..50238f4 --- /dev/null +++ b/lib/customer_info_form.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require_relative "customer_repo" +require_relative "proxied_jid" +require_relative "legacy_customer" + +class CustomerInfoForm + def initialize(customer_repo=CustomerRepo.new) + @customer_repo = customer_repo + end + + def picker_form + form = Blather::Stanza::X.new(:form) + form.title = "Pick Customer" + form.instructions = "Tell us something about the customer and we'll try " \ + "to get more information for you" + + form.fields = { + var: "q", type: "text-single", + label: "Something about the customer", + description: "Supported things include: customer ID, JID, phone number" + } + + form + end + + def find_customer(response) + parse_something(response.form.field("q").value) + end + + class NoCustomer + class AdminInfo + def fields + [{ var: "Account Status", value: "Not Found" }] + end + end + + def admin_info + AdminInfo.new + end + end + + def parse_something(value) + parser = Parser.new(@customer_repo) + + EMPromise.all([ + parser.as_customer_id(value), + parser.as_jid(value), + parser.as_phone(value), + EMPromise.resolve(NoCustomer.new) + ]).then { |approaches| approaches.compact.first } + end + + class Parser + def initialize(customer_repo) + @customer_repo = customer_repo + end + + def as_customer_id(value) + @customer_repo.find(value).catch { nil } + end + + def as_cheo(value) + ProxiedJID.proxy(Blather::JID.new(value)) + end + + def as_jid(value) + EMPromise.all([ + @customer_repo.find_by_jid(value).catch { nil }, + @customer_repo.find_by_jid(as_cheo(value)).catch { nil } + ]).then { |approaches| approaches.compact.first } + end + + def as_phone(value) + unless value.gsub(/[^0-9]/, "") =~ /^\+?1?(\d{10})$/ + return EMPromise.resolve(nil) + end + + @customer_repo.find_by_tel("+1#{$1}").catch { nil } + end + end +end diff --git a/lib/customer_repo.rb b/lib/customer_repo.rb index 0f57904..642076f 100644 --- a/lib/customer_repo.rb +++ b/lib/customer_repo.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "customer" +require_relative "legacy_customer" require_relative "polyfill" class CustomerRepo @@ -18,9 +19,21 @@ class CustomerRepo end def find_by_jid(jid) - @redis.get("jmp_customer_id-#{jid}").then do |customer_id| - raise "No customer id" unless customer_id - find_inner(customer_id, jid) + if jid.to_s =~ /\Acustomer_(.+)@jmp.chat\Z/ + find($1) + else + @redis.get("jmp_customer_id-#{jid}").then { |customer_id| + raise "No customer id" unless customer_id + find_inner(customer_id, jid) + }.catch do + find_legacy_customer(jid) + end + end + end + + def find_by_tel(tel) + @redis.get("catapult_jid-#{tel}").then do |jid| + find_by_jid(jid) end end @@ -39,6 +52,13 @@ class CustomerRepo protected + def find_legacy_customer(jid) + @redis.lindex("catapult_cred-#{jid}", 3).then do |tel| + raise "No customer" unless tel + LegacyCustomer.new(Blather::JID.new(jid), tel) + end + end + def hydrate_plan(customer_id, raw_customer) raw_customer.dup.tap do |data| data[:plan] = CustomerPlan.new( diff --git a/lib/legacy_customer.rb b/lib/legacy_customer.rb new file mode 100644 index 0000000..c67e329 --- /dev/null +++ b/lib/legacy_customer.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "value_semantics/monkey_patched" +require_relative "proxied_jid" + +class LegacyCustomer + attr_reader :jid, :tel + + def initialize(jid, tel) + @jid = jid + @tel = tel + end + + def customer_id + nil + end + + def info + EMPromise.resolve(nil).then do + Info.new(jid: jid, tel: tel) + end + end + + def admin_info + EMPromise.all([ + info, + api + ]).then do |info, api| + AdminInfo.new(info: info, api: api) + end + end + + def api + API.for(self) + end + + class Info + value_semantics do + jid ProxiedJID, coerce: ProxiedJID.method(:new) + tel String + end + + def fields + [ + { var: "JID", value: jid.unproxied.to_s }, + { var: "Phone Number", value: tel } + ] + end + end + + class AdminInfo + value_semantics do + info Info + api API + end + + def fields + info.fields + [ + { var: "Account Status", value: "Legacy" }, + { var: "Cheo JID", value: info.jid.to_s }, + { var: "API", value: api.to_s } + ] + end + end +end diff --git a/lib/proxied_jid.rb b/lib/proxied_jid.rb new file mode 100644 index 0000000..c0fd1f3 --- /dev/null +++ b/lib/proxied_jid.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "delegate" +require "blather" + +class ProxiedJID < SimpleDelegator + ESCAPED = /20|22|26|27|2f|3a|3c|3e|40|5c/ + def unproxied + Blather::JID.new( + node.gsub(/\\(#{ESCAPED})/) { |s| + s[1..-1].to_i(16).chr + } + ) + end + + def self.proxy(jid, suffix=CONFIG[:upstream_domain]) + ProxiedJID.new( + Blather::JID.new( + jid.stripped.to_s + .gsub(/([ "&'\/:<>@]|\\(?=#{ESCAPED}))/) { |s| + "\\#{s.ord.to_s(16)}" + }, + suffix, + jid.resource + ) + ) + end +end diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 074cdd2..8e4c2bf 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -68,6 +68,7 @@ require_relative "lib/buy_account_credit_form" require_relative "lib/command" require_relative "lib/command_list" require_relative "lib/customer" +require_relative "lib/customer_info_form" require_relative "lib/customer_repo" require_relative "lib/electrum" require_relative "lib/expiring_lock" @@ -101,6 +102,8 @@ def new_sentry_hub(stanza, name: nil) hub end +class AuthError < StandardError; end + # Braintree is not async, so wrap in EM.defer for now class AsyncBraintree def initialize(environment:, merchant_id:, public_key:, private_key:, **) @@ -553,6 +556,47 @@ command :execute?, node: "web-register", sessionid: nil do |iq| end end +Command.new( + "info", + "Show Account Info", + list_for: ->(*) { true } +) { + Command.customer.then(&:info).then do |info| + Command.finish do |reply| + form = Blather::Stanza::X.new(:result) + form.title = "Account Info" + form.fields = info.fields + reply.command << form + end + end +}.register(self).then(&CommandList.method(:register)) + +Command.new( + "customer info", + "Show Customer Info", + list_for: ->(customer: nil, **) { customer&.admin? } +) { + Command.customer.then do |customer| + raise AuthError, "You are not an admin" unless customer&.admin? + + customer_info = CustomerInfoForm.new + Command.reply { |reply| + reply.command << customer_info.picker_form + }.then { |response| + customer_info.find_customer(response) + }.then do |target_customer| + target_customer.admin_info.then do |info| + Command.finish do |reply| + form = Blather::Stanza::X.new(:result) + form.title = "Customer Info" + form.fields = info.fields + reply.command << form + end + end + end + end +}.register(self).then(&CommandList.method(:register)) + command sessionid: /./ do |iq| COMMAND_MANAGER.fulfill(iq) end diff --git a/test/test_customer_info.rb b/test/test_customer_info.rb new file mode 100644 index 0000000..a3840b7 --- /dev/null +++ b/test/test_customer_info.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require "test_helper" + +API::REDIS = Minitest::Mock.new + +class CustomerInfoTest < Minitest::Test + def test_info_does_not_crash + sgx = Minitest::Mock.new + sgx.expect(:registered?, EMPromise.resolve(nil)) + + cust = customer(sgx: sgx) + assert cust.info.sync.fields + assert_mock sgx + end + em :test_info_does_not_crash + + def test_admin_info_does_not_crash + sgx = Minitest::Mock.new + sgx.expect(:registered?, EMPromise.resolve(nil)) + + API::REDIS.expect( + :exists, + EMPromise.resolve(nil), + ["catapult_cred-customer_test@jmp.chat"] + ) + + API::REDIS.expect( + :lindex, + EMPromise.resolve(nil), + ["catapult_cred-test@example.net", 0] + ) + + cust = customer(sgx: sgx) + assert cust.admin_info.sync.fields + assert_mock sgx + end + em :test_admin_info_does_not_crash + + def test_inactive_info_does_not_crash + sgx = Minitest::Mock.new + sgx.expect(:registered?, EMPromise.resolve(nil)) + + plan = CustomerPlan.new("test", plan: nil, expires_at: nil) + cust = Customer.new( + "test", + Blather::JID.new("test@example.net"), + plan: plan, + sgx: sgx + ) + assert cust.info.sync.fields + assert_mock sgx + end + em :test_inactive_info_does_not_crash + + def test_inactive_admin_info_does_not_crash + sgx = Minitest::Mock.new + sgx.expect(:registered?, EMPromise.resolve(nil)) + + API::REDIS.expect( + :exists, + EMPromise.resolve(nil), + ["catapult_cred-customer_test@jmp.chat"] + ) + + API::REDIS.expect( + :lindex, + EMPromise.resolve(nil), + ["catapult_cred-test@example.net", 0] + ) + + plan = CustomerPlan.new("test", plan: nil, expires_at: nil) + cust = Customer.new( + "test", + Blather::JID.new("test@example.net"), + plan: plan, + sgx: sgx + ) + + assert cust.admin_info.sync.fields + assert_mock sgx + end + em :test_inactive_admin_info_does_not_crash + + def test_legacy_customer_info_does_not_crash + cust = LegacyCustomer.new( + Blather::JID.new("legacy@example.com"), + "+12223334444" + ) + assert cust.info.sync.fields + end + em :test_legacy_customer_info_does_not_crash + + def test_legacy_customer_admin_info_does_not_crash + API::REDIS.expect( + :exists, + EMPromise.resolve(nil), + ["catapult_cred-customer_@jmp.chat"] + ) + + API::REDIS.expect( + :lindex, + EMPromise.resolve(nil), + ["catapult_cred-legacy@example.com", 0] + ) + + cust = LegacyCustomer.new( + Blather::JID.new("legacy@example.com"), + "+12223334444" + ) + assert cust.admin_info.sync.fields + end + em :test_legacy_customer_admin_info_does_not_crash +end diff --git a/test/test_customer_info_form.rb b/test/test_customer_info_form.rb new file mode 100644 index 0000000..822f075 --- /dev/null +++ b/test/test_customer_info_form.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "test_helper" +require "forwardable" +require "customer_info_form" +require "customer_repo" + +class FakeRepo + def initialize(customers) + @customers = customers + end + + def find(id) + EMPromise.resolve(nil).then do + @customers.find { |cust| cust.customer_id == id } || raise("No Customer") + end + end + + def find_by_jid(jid) + EMPromise.resolve(nil).then do + @customers.find { |cust| cust.jid.to_s == jid.to_s } || raise("No Customer") + end + end + + def find_by_tel(tel) + EMPromise.resolve(nil).then do + @customers.find { |cust| cust.tel == tel } || raise("No Customer") + end + end +end + +class CustomerInfoFormTest < Minitest::Test + def setup + @customer_test = OpenStruct.new( + customer_id: "test", + jid: "test\\40example.com@example.net", + tel: "+13334445555" + ) + @customer_v2 = OpenStruct.new( + customer_id: "test_v2", + jid: "test_v2\\40example.com@example.net", + tel: "+14445556666" + ) + @repo = FakeRepo.new([@customer_test, @customer_v2]) + @info_form = CustomerInfoForm.new(@repo) + end + + def test_nothing + assert_kind_of( + CustomerInfoForm::NoCustomer, + @info_form.parse_something("").sync + ) + end + em :test_nothing + + def test_find_customer_id + result = @info_form.parse_something("test").sync + assert_equal @customer_test, result + end + em :test_find_customer_id + + def test_find_real_jid + result = @info_form.parse_something("test@example.com").sync + assert_equal @customer_test, result + end + em :test_find_real_jid + + def test_find_cheo_jid + result = @info_form.parse_something( + "test\\40example.com@example.net" + ).sync + assert_equal @customer_test, result + end + em :test_find_cheo_jid + + def test_find_sgx_jmp_customer_by_phone + result = @info_form.parse_something("+13334445555").sync + assert_equal @customer_test, result + end + em :test_find_sgx_jmp_customer_by_phone + + def test_find_sgx_jmp_customer_by_phone_friendly_format + result = @info_form.parse_something("13334445555").sync + assert_equal @customer_test, result + + result = @info_form.parse_something("3334445555").sync + assert_equal @customer_test, result + + result = @info_form.parse_something("(333) 444-5555").sync + assert_equal @customer_test, result + end + em :test_find_sgx_jmp_customer_by_phone_friendly_format + + def test_find_v2_customer_by_phone + result = @info_form.parse_something("+14445556666").sync + assert_equal @customer_v2, result + end + em :test_find_v2_customer_by_phone + + def test_missing_customer_by_phone + result = @info_form.parse_something("+17778889999").sync + assert_kind_of( + CustomerInfoForm::NoCustomer, + result + ) + end + em :test_missing_customer_by_phone + + def test_garbage + result = @info_form.parse_something("garbage").sync + assert_kind_of( + CustomerInfoForm::NoCustomer, + result + ) + end + em :test_garbage +end diff --git a/test/test_customer_repo.rb b/test/test_customer_repo.rb index fd18040..80df567 100644 --- a/test/test_customer_repo.rb +++ b/test/test_customer_repo.rb @@ -4,69 +4,129 @@ require "test_helper" require "customer_repo" class CustomerRepoTest < Minitest::Test + FAKE_REDIS = FakeRedis.new( + # sgx-jmp customer + "jmp_customer_jid-test" => "test@example.com", + "jmp_customer_id-test@example.com" => "test", + "catapult_jid-+13334445555" => "customer_test@jmp.chat", + "catapult_cred-customer_test@jmp.chat" => [ + "test_bw_customer", "", "", "+13334445555" + ], + # sgx-jmp customer, empty DB + "jmp_customer_jid-empty" => "empty@example.com", + "jmp_customer_id-empty@example.com" => "empty", + "catapult_jid-+16667778888" => "customer_empty@jmp.chat", + "catapult_cred-customer_empty@jmp.chat" => [ + "test_bw_customer", "", "", "+16667778888" + ], + # v2 customer + "jmp_customer_jid-test_v2" => "test_v2@example.com", + "jmp_customer_id-test_v2@example.com" => "test_v2", + "catapult_jid-+14445556666" => "test_v2@example.com", + "catapult_cred-test_v2@example.com" => [ + "test_bw_customer", "", "", "+14445556666" + ], + # legacy customer + "catapult_cred-legacy@example.com" => [ + "catapult_user", "", "", "+12223334444" + ], + "catapult_jid-+12223334444" => "legacy@example.com" + ) + + FAKE_DB = FakeDB.new( + ["test"] => [{ + "balance" => BigDecimal(1234), + "plan_name" => "test_usd", + "expires_at" => Time.now + 100 + }], + ["test_v2"] => [{ + "balance" => BigDecimal(2345), + "plan_name" => "test_usd", + "expires_at" => Time.now + 100 + }] + ) + def mkrepo( - redis: Minitest::Mock.new, - db: Minitest::Mock.new, + redis: FAKE_REDIS, + db: FAKE_DB, braintree: Minitest::Mock.new ) CustomerRepo.new(redis: redis, db: db, braintree: braintree) end + def setup + @repo = mkrepo + end + def test_find_by_jid - redis = Minitest::Mock.new - db = Minitest::Mock.new - repo = mkrepo(redis: redis, db: db) - redis.expect( - :get, - EMPromise.resolve(1), - ["jmp_customer_id-test@example.com"] - ) - db.expect( - :query_defer, - EMPromise.resolve([{ balance: 1234, plan_name: "test_usd" }]), - [String, [1]] - ) - customer = repo.find_by_jid("test@example.com").sync + customer = @repo.find_by_jid("test@example.com").sync assert_kind_of Customer, customer assert_equal 1234, customer.balance assert_equal "merchant_usd", customer.merchant_account - assert_mock redis - assert_mock db end em :test_find_by_jid + def test_find_by_id + customer = @repo.find("test").sync + assert_kind_of Customer, customer + assert_equal 1234, customer.balance + assert_equal "merchant_usd", customer.merchant_account + end + em :test_find_by_id + + def test_find_by_customer_jid + customer = @repo.find_by_jid("customer_test@jmp.chat").sync + assert_kind_of Customer, customer + assert_equal 1234, customer.balance + assert_equal "merchant_usd", customer.merchant_account + end + em :test_find_by_customer_jid + def test_find_by_jid_not_found - redis = Minitest::Mock.new - repo = mkrepo(redis: redis) - redis.expect( - :get, - EMPromise.resolve(nil), - ["jmp_customer_id-test2@example.com"] - ) assert_raises do - repo.find_by_jid("test2@example.com").sync + @repo.find_by_jid("test2@example.com").sync end - assert_mock redis end em :test_find_by_jid_not_found + def test_find_legacy_customer + customer = @repo.find_by_jid("legacy@example.com").sync + assert_kind_of LegacyCustomer, customer + assert_equal "+12223334444", customer.tel + end + em :test_find_legacy_customer + + def test_find_sgx_customer_by_phone + customer = @repo.find_by_tel("+13334445555").sync + assert_kind_of Customer, customer + assert_equal "test", customer.customer_id + end + em :test_find_sgx_customer_by_phone + + def test_find_v2_customer_by_phone + customer = @repo.find_by_tel("+14445556666").sync + assert_kind_of Customer, customer + assert_equal "test_v2", customer.customer_id + end + em :test_find_v2_customer_by_phone + + def test_find_legacy_customer_by_phone + customer = @repo.find_by_tel("+12223334444").sync + assert_kind_of LegacyCustomer, customer + assert_equal "legacy@example.com", customer.jid.to_s + end + em :test_find_legacy_customer_by_phone + + def test_find_missing_phone + assert_raises do + @repo.find_by_tel("+15556667777").sync + end + end + em :test_find_missing_phone + def test_find_db_empty - db = Minitest::Mock.new - redis = Minitest::Mock.new - redis.expect( - :get, - EMPromise.resolve("test@example.net"), - ["jmp_customer_jid-7357"] - ) - repo = mkrepo(db: db, redis: redis) - db.expect( - :query_defer, - EMPromise.resolve([]), - [String, [7357]] - ) - customer = repo.find(7357).sync + customer = @repo.find("empty").sync assert_equal BigDecimal(0), customer.balance - assert_mock db end em :test_find_db_empty diff --git a/test/test_helper.rb b/test/test_helper.rb index c60e547..a61cb32 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -94,7 +94,8 @@ CONFIG = { } }, credit_card_url: ->(*) { "http://creditcard.example.com" }, - electrum_notify_url: ->(*) { "http://notify.example.com" } + electrum_notify_url: ->(*) { "http://notify.example.com" }, + upstream_domain: "example.net" }.freeze def panic(e) @@ -131,6 +132,36 @@ class PromiseMock < Minitest::Mock end end +class FakeRedis + def initialize(values) + @values = values + end + + def get(key) + EMPromise.resolve(@values[key]) + end + + def exists(*keys) + EMPromise.resolve( + @values.select { |k, _| keys.include? k }.size + ) + end + + def lindex(key, index) + get(key).then { |v| v&.fetch(index) } + end +end + +class FakeDB + def initialize(items) + @items = items + end + + def query_defer(_, args) + EMPromise.resolve(@items.fetch(args, [])) + end +end + module EventMachine class << self # Patch EM.add_timer to be instant in tests diff --git a/test/test_proxied_jid.rb b/test/test_proxied_jid.rb new file mode 100644 index 0000000..022f7bf --- /dev/null +++ b/test/test_proxied_jid.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "test_helper" +require "proxied_jid" + +class ProxiedJIDTest < Minitest::Test + def test_unproxied + jid = ProxiedJID.new(Blather::JID.new("test\\40example.com@example.net")) + assert_equal "test@example.com", jid.unproxied.to_s + end + + def test_proxied + jid = ProxiedJID.proxy(Blather::JID.new("test@example.com")) + assert_equal "test\\40example.com@example.net", jid.to_s + end + + def test_escape + jid = ProxiedJID.proxy(Blather::JID.new("test \"&'/:<>", "example.com")) + assert_equal( + "test\\20\\22\\26\\27\\2f\\3a\\3c\\3e\\40example.com@example.net", + jid.to_s + ) + end + + def test_backlash_necessary + jid = ProxiedJID.proxy(Blather::JID.new("moop\\27@example.com")) + assert_equal "moop\\5c27\\40example.com@example.net", jid.to_s + end + + def test_backslash_unnecessary + jid = ProxiedJID.proxy(Blather::JID.new("moop\\things@example.com")) + assert_equal "moop\\things\\40example.com@example.net", jid.to_s + end +end -- 2.45.2