Detailed changes
@@ -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
@@ -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" ],
@@ -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/],
@@ -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)
@@ -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