diff --git a/config-schema.dhall b/config-schema.dhall index 4c4bb4fa49dab303a3f2380ffb6ded0b9f8696bd..d2e21e0d896d454adae905ce29a8a800a7d850a3 100644 --- a/config-schema.dhall +++ b/config-schema.dhall @@ -43,6 +43,7 @@ , keepgo : Optional { access_token : Text, api_key : Text } , notify_admin : Text , notify_from : Text +, offer_codes : List { mapKey : Text, mapValue : Text } , ogm_path : Text , ogm_web_root : Text , onboarding_domain : Text diff --git a/config.dhall.sample b/config.dhall.sample index 73e2673d5e135bf735e1bd3a6c4b7afdaeb2c814..0a1f573af331f828f50cf486fc75c661fa43ea70 100644 --- a/config.dhall.sample +++ b/config.dhall.sample @@ -110,6 +110,7 @@ in upstream_domain = "example.net", approved_domains = toMap { `example.com` = Some "customer_id" }, parented_domains = toMap { `example.com` = { customer_id = "customer_id", plan_name = "usd" } }, + offer_codes = toMap { someone = "xmpp:thing" }, keepgo = Some { api_key = "", access_token = "" }, simpleswap_api_key = "", reachability_senders = [ "+14445556666" ], diff --git a/lib/tel_selections.rb b/lib/tel_selections.rb index 06e86cc0cf23442fc934cc0265b09ea8cdbb5fc3..6485e52061cb561a9f3242df9441cfea4be9952f 100644 --- a/lib/tel_selections.rb +++ b/lib/tel_selections.rb @@ -440,6 +440,37 @@ class TelSelections [] end + class OfferCode < Q + Q.register(/\A[0-9A-F]{8}\Z/) { |q, **kw| self.for(q, **kw) } + + def self.for(q, customer:, redis:, db:, **) + InvitesRepo.new(db, redis) + .claim_code(customer.customer_id, q) { |claimed| + # HACK: assume USD plan for all offer code claims + customer.with_plan("USD").activate_plan_starting_now + claimed + }.then { |claimed| + if (source = CONFIG[:offer_codes][claimed&.dig("creator_id")]) + new(source) + end + }.catch_only(InvitesRepo::Invalid) {} + end + + def initialize(source) + @source = source + end + + def iris_query; end + + def sql_query + [ + "SELECT * FROM tel_inventory " \ + "WHERE available_after < LOCALTIMESTAMP AND source=$1", + @source + ] + end + end + { areaCode: [:AreaCode, /\A[2-9][0-9]{2}\Z/], npaNxx: [:NpaNxx, /\A(?:[2-9][0-9]{2}){2}\Z/], diff --git a/test/test_helper.rb b/test/test_helper.rb index 4e567ba167108cce308148d1d40a904db1aeb1c9..b9da2f8c16865dd0df2d84f823966ad566cfef12 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -107,6 +107,14 @@ CONFIG = { minutes: { included: 10440, price: 87 }, allow_register: true }, + { + name: "USD", + currency: :USD, + monthly_price: 10000, + messages: :unlimited, + minutes: { included: 10440, price: 87 }, + allow_register: true + }, { name: "test_bad_currency", currency: :BAD @@ -166,6 +174,9 @@ CONFIG = { plan_name: "test_usd" } }, + offer_codes: { + "pplus" => "xmpp:pplus" + }, bandwidth_site: "test_site", bandwidth_peer: "test_peer", keepgo: { api_key: "keepgokey", access_token: "keepgotoken" }, @@ -389,8 +400,16 @@ class FakeDB @items = items end - def query_defer(_, args) - EMPromise.resolve(@items.fetch(args, []).to_a) + def transaction + yield + end + + def exec(_, args) + @items.fetch(args, []).to_a + end + + def query_defer(sql, args) + EMPromise.resolve(exec(sql, args)) end def query_one(_, *args, field_names_as: :symbol, default: nil) diff --git a/test/test_tel_selections.rb b/test/test_tel_selections.rb index 19681217263909d8c2d4f5d3c0e9a7bba348e93c..6f09a947e1ba0c09165dbf4e80c0a7e94661258b 100644 --- a/test/test_tel_selections.rb +++ b/test/test_tel_selections.rb @@ -503,5 +503,74 @@ class TelSelectionsTest < Minitest::Test assert_raises { TelSelections::ChooseTel::Q.for("garbage").sync } end em :test_for_garbage + + def test_offer_code + CustomerPlan::DB.expect( + :exec, + OpenStruct.new(cmd_tuples: 1), + [String, ["test", "USD", nil]] + ) + CustomerPlan::DB.expect( + :exec, + OpenStruct.new(cmd_tuples: 0), + [String, ["test"]] + ) + db = FakeDB.new( + ["test", "DEADBEEF"] => [{ "creator_id" => "pplus" }] + ) + q = TelSelections::ChooseTel::Q.for( + "DEADBEEF", + customer: customer, + redis: FakeRedis.new, db: db, memcache: FakeMemcache.new + ).sync + assert_equal( + [ + "SELECT * FROM tel_inventory " \ + "WHERE available_after < LOCALTIMESTAMP AND source=$1", + "xmpp:pplus" + ], + q.sql_query + ) + assert_mock CustomerPlan::DB + end + em :test_offer_code + + def test_offer_code_invalid + db = FakeDB.new + assert_raises do + q = TelSelections::ChooseTel::Q.for( + "DEADBEEF", + customer: customer, + redis: FakeRedis.new, db: db, memcache: FakeMemcache.new + ).sync + end + assert_mock CustomerPlan::DB + end + em :test_offer_code_invalid + + def test_offer_code_just_invite + CustomerPlan::DB.expect( + :exec, + OpenStruct.new(cmd_tuples: 1), + [String, ["test", "USD", nil]] + ) + CustomerPlan::DB.expect( + :exec, + OpenStruct.new(cmd_tuples: 0), + [String, ["test"]] + ) + db = FakeDB.new( + ["test", "DEADBEEF"] => [{ "creator_id" => "notpplus" }] + ) + assert_raises do + q = TelSelections::ChooseTel::Q.for( + "DEADBEEF", + customer: customer, + redis: FakeRedis.new, db: db, memcache: FakeMemcache.new + ).sync + end + assert_mock CustomerPlan::DB + end + em :test_offer_code_just_invite end end