.rubocop.yml 🔗
@@ -16,6 +16,7 @@ Metrics/BlockLength:
ExcludedMethods:
- route
- "on"
+ - json
Exclude:
- test/*
Stephen Paul Weber created
.rubocop.yml | 1
config-schema.dhall | 2
config.dhall.sample | 3
lib/bandwidth_tn_order.rb | 23 ++
lib/transaction.rb | 2
test/test_helper.rb | 6
test/test_web.rb | 290 ++++++++++++++++++++++++++++++++++++++++
web.rb | 69 +++++++++
8 files changed, 388 insertions(+), 8 deletions(-)
@@ -16,6 +16,7 @@ Metrics/BlockLength:
ExcludedMethods:
- route
- "on"
+ - json
Exclude:
- test/*
@@ -12,6 +12,8 @@
, private_key : Text
, public_key : Text
}
+, bulk_order_tokens :
+ List { mapKey : Text, mapValue : { customer_id : Text, peer_id : Text } }
, churnbuster : { account_id : Text, api_key : Text }
, component : { jid : Text, secret : Text }
, credit_card_url : forall (jid : Text) -> forall (customer_id : Text) -> Text
@@ -118,5 +118,6 @@ in
churnbuster = {
api_key = "",
account_id = ""
- }
+ },
+ bulk_order_tokens = toMap { sometoken = { customer_id = "somecustomer", peer_id = "" } }
}
@@ -26,12 +26,18 @@ class BandwidthTNOrder
reservation_id: nil,
**kwargs
)
+ create_custom(
+ name: name, **kwargs,
+ existing_telephone_number_order_type: {
+ telephone_number_list: { telephone_number: [tel.sub(/^\+?1?/, "")] }
+ }.merge(ReservationIdList.new(reservation_id).to_h)
+ )
+ end
+
+ def self.create_custom(name:, **kwargs)
EMPromise.resolve(nil).then do
Received.new(BandwidthIris::Order.create(
- name: name, **ORDER_DEFAULTS, **kwargs,
- existing_telephone_number_order_type: {
- telephone_number_list: { telephone_number: [tel.sub(/^\+?1?/, "")] }
- }.merge(ReservationIdList.new(reservation_id).to_h)
+ name: name, **ORDER_DEFAULTS, **kwargs
))
end
end
@@ -79,7 +85,14 @@ class BandwidthTNOrder
end
def tel
- "+1#{@order.completed_numbers[:telephone_number][:full_number]}"
+ tels[0]
+ end
+
+ def tels
+ tns = @order.completed_numbers[:telephone_number]
+ (tns.is_a?(Array) ? tns : [tns]).map do |item|
+ "+1#{item[:full_number]}"
+ end
end
def poll
@@ -15,6 +15,7 @@ class Transaction
amount BigDecimal, coerce: ->(x) { BigDecimal(x, 4) }
note String
bonus_eligible? Bool(), default: true
+ ignore_duplicate Bool(), default: false
end
def insert
@@ -72,6 +73,7 @@ class Transaction
(customer_id, transaction_id, created_at, settled_after, amount, note)
VALUES
($1, $2, $3, $4, $5, $6)
+ #{ignore_duplicate ? 'ON CONFLICT (transaction_id) DO NOTHING' : ''}
SQL
end
@@ -163,7 +163,11 @@ CONFIG = {
onboarding_domain: "onboarding.example.com",
adr: "A Mailing Address",
interac: "interac@example.com",
- support_link: ->(*) { "https://support.com" }
+ support_link: ->(*) { "https://support.com" },
+ bulk_order_tokens: {
+ sometoken: { customer_id: "bulkcustomer", peer_id: "bulkpeer" },
+ lowtoken: { customer_id: "customerid_low", peer_id: "lowpeer" }
+ }
}.freeze
def panic(e)
@@ -11,6 +11,7 @@ Customer::BLATHER = Minitest::Mock.new
CustomerFwd::BANDWIDTH_VOICE = Minitest::Mock.new
Web::BANDWIDTH_VOICE = Minitest::Mock.new
LowBalance::AutoTopUp::CreditCardSale = Minitest::Mock.new
+Transaction::DB = Minitest::Mock.new
ReachableRedis = Minitest::Mock.new
@@ -38,7 +39,8 @@ class WebTest < Minitest::Test
"catapult_jid-+15551234563" => "customer_customerid_reach@component",
"jmp_customer_jid-customerid2" => "customer2@example.com",
"catapult_jid-+15551230000" => "customer_customerid2@component",
- "jmp_customer_jid-customerid_tombed" => "customer_tombed@example.com"
+ "jmp_customer_jid-customerid_tombed" => "customer_tombed@example.com",
+ "jmp_customer_jid-bulkcustomer" => "bulk@example.com"
),
db: FakeDB.new(
["customerid"] => [{
@@ -75,6 +77,11 @@ class WebTest < Minitest::Test
"balance" => BigDecimal(10),
"plan_name" => "test_usd",
"expires_at" => Time.now + 100
+ }],
+ ["bulkcustomer"] => [{
+ "balance" => BigDecimal(1000),
+ "plan_name" => "test_usd",
+ "expires_at" => Time.now + 100
}]
),
sgx_repo: Bwmsgsv2Repo.new(
@@ -865,4 +872,285 @@ class WebTest < Minitest::Test
assert_mock ReachableRedis
end
em :test_inbound_from_reachability_during_reachability
+
+ def test_bulk_order
+ create_order = stub_request(
+ :post,
+ "https://dashboard.bandwidth.com/v1.0/accounts//orders"
+ ).to_return(status: 201, body: <<~RESPONSE)
+ <OrderResponse>
+ <Order>
+ <id>test_order</id>
+ </Order>
+ </OrderResponse>
+ RESPONSE
+
+ post(
+ "/orders",
+ { quantity: 100 },
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer sometoken"
+ )
+
+ assert last_response.ok?
+ assert_equal(
+ { id: "test_order" }.to_json,
+ last_response.body
+ )
+ assert_requested create_order
+ end
+ em :test_bulk_order
+
+ def test_bulk_order_low_balance
+ post(
+ "/orders",
+ { quantity: 100 },
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer lowtoken"
+ )
+
+ assert_equal 402, last_response.status
+ end
+ em :test_bulk_order_low_balance
+
+ def test_bulk_order_bad_token
+ post(
+ "/orders",
+ { quantity: 100 },
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer badtoken"
+ )
+
+ assert_equal 401, last_response.status
+ end
+ em :test_bulk_order_bad_token
+
+ def test_order_get
+ stub_request(
+ :get,
+ "https://dashboard.bandwidth.com/v1.0/accounts//orders/testorder"
+ ).to_return(status: 200, body: <<~RESPONSE)
+ <OrderResponse>
+ <id>testorder</id>
+ <OrderStatus>complete</OrderStatus>
+ <CompletedNumbers>
+ <TelephoneNumber>
+ <FullNumber>5551234567</FullNumber>
+ </TelephoneNumber>
+ </CompletedNumbers>
+ </OrderResponse>
+ RESPONSE
+
+ Transaction::DB.expect(:transaction, nil) do |&blk|
+ blk.call
+ true
+ end
+ Transaction::DB.expect(
+ :exec,
+ nil,
+ [String, Matching.new { |params|
+ assert_equal "bulkcustomer", params[0]
+ assert_equal "testorder", params[1]
+ assert_equal(-1.75, params[4])
+ assert_equal "Bulk order", params[5]
+ }]
+ )
+
+ get(
+ "/orders/testorder",
+ "",
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer sometoken"
+ )
+
+ assert last_response.ok?
+ assert_equal(
+ { id: "testorder", status: "complete", tels: ["+15551234567"] }.to_json,
+ last_response.body
+ )
+ assert_mock Transaction::DB
+ end
+ em :test_order_get
+
+ def test_order_get_multi
+ stub_request(
+ :get,
+ "https://dashboard.bandwidth.com/v1.0/accounts//orders/testorder"
+ ).to_return(status: 200, body: <<~RESPONSE)
+ <OrderResponse>
+ <id>testorder</id>
+ <OrderStatus>complete</OrderStatus>
+ <CompletedNumbers>
+ <TelephoneNumber>
+ <FullNumber>5551234567</FullNumber>
+ </TelephoneNumber>
+ <TelephoneNumber>
+ <FullNumber>5551234568</FullNumber>
+ </TelephoneNumber>
+ </CompletedNumbers>
+ </OrderResponse>
+ RESPONSE
+
+ Transaction::DB.expect(:transaction, nil) do |&blk|
+ blk.call
+ true
+ end
+ Transaction::DB.expect(
+ :exec,
+ nil,
+ [String, Matching.new { |params|
+ assert_equal "bulkcustomer", params[0]
+ assert_equal "testorder", params[1]
+ assert_equal (-1.75 * 2), params[4]
+ assert_equal "Bulk order", params[5]
+ }]
+ )
+
+ get(
+ "/orders/testorder",
+ "",
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer sometoken"
+ )
+
+ assert last_response.ok?
+ assert_equal(
+ {
+ id: "testorder",
+ status: "complete",
+ tels: ["+15551234567", "+15551234568"]
+ }.to_json,
+ last_response.body
+ )
+ assert_mock Transaction::DB
+ end
+ em :test_order_get_multi
+
+ def test_order_get_received
+ stub_request(
+ :get,
+ "https://dashboard.bandwidth.com/v1.0/accounts//orders/testorder"
+ ).to_return(status: 200, body: <<~RESPONSE)
+ <OrderResponse>
+ <id>testorder</id>
+ <OrderStatus>received</OrderStatus>
+ </OrderResponse>
+ RESPONSE
+
+ get(
+ "/orders/testorder",
+ "",
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer sometoken"
+ )
+
+ assert last_response.ok?
+ assert_equal(
+ { id: "testorder", status: "received" }.to_json,
+ last_response.body
+ )
+ end
+ em :test_order_get_received
+
+ def test_order_get_failed
+ stub_request(
+ :get,
+ "https://dashboard.bandwidth.com/v1.0/accounts//orders/testorder"
+ ).to_return(status: 200, body: <<~RESPONSE)
+ <OrderResponse>
+ <id>testorder</id>
+ <OrderStatus>failed</OrderStatus>
+ </OrderResponse>
+ RESPONSE
+
+ get(
+ "/orders/testorder",
+ "",
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer sometoken"
+ )
+
+ assert last_response.ok?
+ assert_equal(
+ { id: "testorder", status: "failed" }.to_json,
+ last_response.body
+ )
+ end
+ em :test_order_get_failed
+
+ def test_order_get_bad_token
+ get(
+ "/orders/testorder",
+ "",
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer badtoken"
+ )
+
+ assert_equal 401, last_response.status
+ end
+ em :test_order_get_bad_token
+
+ def test_delete_tel
+ stub_request(
+ :get,
+ "https://dashboard.bandwidth.com/v1.0/tns/+15551234567/tndetails"
+ ).to_return(status: 200, body: <<~RESPONSE)
+ <Response>
+ <TelephoneNumberDetails>
+ <SipPeer><PeerId>bulkpeer</PeerId></SipPeer>
+ </TelephoneNumberDetails>
+ </Response>
+ RESPONSE
+
+ req = stub_request(
+ :post,
+ "https://dashboard.bandwidth.com/v1.0/accounts//disconnects"
+ ).to_return(status: 200, body: "")
+
+ delete(
+ "/orders/tels/+15551234567",
+ "",
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer sometoken"
+ )
+
+ assert last_response.ok?
+ assert_requested req
+ end
+ em :test_delete_tel
+
+ def test_delete_tel_not_yours
+ stub_request(
+ :get,
+ "https://dashboard.bandwidth.com/v1.0/tns/+15551234567/tndetails"
+ ).to_return(status: 200, body: <<~RESPONSE)
+ <Response>
+ <TelephoneNumberDetails>
+ <SipPeer><PeerId>mainpeer</PeerId></SipPeer>
+ </TelephoneNumberDetails>
+ </Response>
+ RESPONSE
+
+ delete(
+ "/orders/tels/+15551234567",
+ "",
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer sometoken"
+ )
+
+ assert_equal 401, last_response.status
+ end
+ em :test_delete_tel_not_yours
+
+ def test_delete_tel_bad_token
+ delete(
+ "/orders/tels/+15551234567",
+ "",
+ "HTTP_ACCEPT" => "application/json",
+ "HTTP_AUTHORIZATION" => "Bearer badtoken"
+ )
+
+ assert_equal 401, last_response.status
+ end
+ em :test_delete_tel_bad_token
end
@@ -9,6 +9,7 @@ require "roda"
require "sentry-ruby"
require "thin"
+require_relative "lib/bandwidth_tn_order"
require_relative "lib/call_attempt_repo"
require_relative "lib/trust_level_repo"
require_relative "lib/cdr"
@@ -503,6 +504,74 @@ class Web < Roda
end
end
+ r.on "orders" do
+ token = r.env["HTTP_AUTHORIZATION"].to_s.sub(/\ABearer\s+/, "")
+
+ r.json do
+ if (orderer = CONFIG[:bulk_order_tokens][token.to_sym])
+ r.get :order_id do |order_id|
+ BandwidthTNOrder.get(order_id).then do |order|
+ if order.status == :complete
+ Transaction.new(
+ customer_id: orderer[:customer_id],
+ transaction_id: order.id,
+ amount: order.tels.length * -1.75,
+ note: "Bulk order",
+ ignore_duplicate: true
+ ).insert.then do
+ {
+ id: order.id,
+ status: order.status,
+ tels: order.tels
+ }.to_json
+ end
+ else
+ { id: order.id, status: order.status }.to_json
+ end
+ end
+ end
+
+ r.on "tels" do
+ r.on :tel, method: :delete do |tel|
+ tn_repo = BandwidthTnRepo.new
+ tn = tn_repo.find(tel)
+ if tn&.dig(:sip_peer, :peer_id).to_s == orderer[:peer_id]
+ tn_repo.disconnect(tel, orderer[:customer_id])
+ { status: "disconnected" }.to_json
+ else
+ response.status = 401
+ { error: "Number not found" }.to_json
+ end
+ end
+ end
+
+ r.post do
+ customer_repo.find(orderer[:customer_id]).then do |customer|
+ if customer.balance >= 1.75 * params["quantity"].to_i
+ BandwidthTNOrder.create_custom(
+ name: "Bulk order",
+ customer_order_id: orderer[:customer_id],
+ peer_id: orderer[:peer_id],
+ state_search_and_order_type: {
+ quantity: params["quantity"].to_i,
+ state: ["CA", "TX", "IL", "NY", "FL"].sample
+ }
+ ).then do |order|
+ { id: order.id }.to_json
+ end
+ else
+ response.status = 402
+ { error: "Balance too low" }.to_json
+ end
+ end
+ end
+ else
+ response.status = 401
+ { error: "Bad token" }.to_json
+ end
+ end
+ end
+
r.public
end
end