Skip to content

Commit

Permalink
Merge pull request stripe-ruby-mock#897 from stripe-ruby-mock/init-v4
Browse files Browse the repository at this point in the history
First bunch of changes for v4
  • Loading branch information
alexmamonchik authored Aug 6, 2024
2 parents c1b537e + 928156c commit f0ec4ef
Show file tree
Hide file tree
Showing 25 changed files with 680 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rspec_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.6', '2.7', '3.0']
ruby-version: ['2.7', '3.0', '3.2']

steps:
- uses: actions/checkout@v3
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
### Unreleased

- [#830](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/830) Implement search API by [@adamstegman](https://github.com/adamstegman)
- [#848](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/848) Extending runtime dependency on Stripe gem from version 5 through 11 by [@smakani](https://github.com/smakani)
- [#893](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/893) Adds support for stripe-ruby v10 by [@fabianoarruda](https://github.com/fabianoarruda)


### 3.1.0 (2024-01-03)
- [#693](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/693) gemspec: add change,issue,source_code URL by [@mtmail](https://github.com/mtmail)
- [#700](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/700) update the balance API to respond with instant_available by [@iamnader](https://github.com/iamnader)
- [#687](https://github.com/stripe-ruby-mock/stripe-ruby-mock/pull/687) Add PaymentIntent Webhooks by [@klaustopher](https://github.com/klaustopher)
Expand Down
5 changes: 0 additions & 5 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
source 'https://rubygems.org'

platforms :ruby_19 do
gem 'mime-types', '~> 2.6'
gem 'rest-client', '~> 1.8'
end

group :test do
gem 'rake'
gem 'dotenv'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ version `3.0.0` has [breaking changes](https://github.com/stripe-ruby-mock/strip

### Requirements

* ruby >= 2.6.0
* stripe >= 5.0.0
* ruby >= 2.7.0
* stripe > 5 & < 11

### Specifications

Expand Down
1 change: 1 addition & 0 deletions lib/stripe_mock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
require 'stripe_mock/request_handlers/helpers/card_helpers.rb'
require 'stripe_mock/request_handlers/helpers/charge_helpers.rb'
require 'stripe_mock/request_handlers/helpers/coupon_helpers.rb'
require 'stripe_mock/request_handlers/helpers/search_helpers.rb'
require 'stripe_mock/request_handlers/helpers/subscription_helpers.rb'
require 'stripe_mock/request_handlers/helpers/token_helpers.rb'

Expand Down
2 changes: 1 addition & 1 deletion lib/stripe_mock/api/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def self.stop_client(opts={})

private

def self.redirect_to_mock_server(method, url, api_key: nil, api_base: nil, params: {}, headers: {})
def self.redirect_to_mock_server(method, url, api_key: nil, api_base: nil, usage: [], params: {}, headers: {})
handler = Instance.handler_for_method_url("#{method} #{url}")

if mock_error = client.error_queue.error_for_handler_name(handler[:name])
Expand Down
2 changes: 1 addition & 1 deletion lib/stripe_mock/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def initialize
@base_strategy = TestStrategies::Base.new
end

def mock_request(method, url, api_key: nil, api_base: nil, params: {}, headers: {})
def mock_request(method, url, api_key: nil, api_base: nil, usage: [], params: {}, headers: {})
return {} if method == :xtest

api_key ||= (Stripe.api_key || DUMMY_API_KEY)
Expand Down
21 changes: 20 additions & 1 deletion lib/stripe_mock/request_handlers/charges.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module Charges
def Charges.included(klass)
klass.add_handler 'post /v1/charges', :new_charge
klass.add_handler 'get /v1/charges', :get_charges
klass.add_handler 'get /v1/charges/(.*)', :get_charge
klass.add_handler 'get /v1/charges/search', :search_charges
klass.add_handler 'get /v1/charges/((?!search).*)', :get_charge
klass.add_handler 'post /v1/charges/(.*)/capture', :capture_charge
klass.add_handler 'post /v1/charges/(.*)/refund', :refund_charge
klass.add_handler 'post /v1/charges/(.*)/refunds', :refund_charge
Expand Down Expand Up @@ -90,6 +91,24 @@ def get_charges(route, method_url, params, headers)
Data.mock_list_object(clone.values, params)
end

SEARCH_FIELDS = [
"amount",
"currency",
"customer",
"payment_method_details.card.brand",
"payment_method_details.card.exp_month",
"payment_method_details.card.exp_year",
"payment_method_details.card.fingerprint",
"payment_method_details.card.last4",
"status",
].freeze
def search_charges(route, method_url, params, headers)
require_param(:query) unless params[:query]

results = search_results(charges.values, params[:query], fields: SEARCH_FIELDS, resource_name: "charges")
Data.mock_list_object(results, params)
end

def get_charge(route, method_url, params, headers)
route =~ method_url
charge_id = $1 || params[:charge]
Expand Down
13 changes: 12 additions & 1 deletion lib/stripe_mock/request_handlers/customers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ module Customers
def Customers.included(klass)
klass.add_handler 'post /v1/customers', :new_customer
klass.add_handler 'post /v1/customers/([^/]*)', :update_customer
klass.add_handler 'get /v1/customers/([^/]*)', :get_customer
klass.add_handler 'get /v1/customers/((?!search)[^/]*)', :get_customer
klass.add_handler 'delete /v1/customers/([^/]*)', :delete_customer
klass.add_handler 'get /v1/customers', :list_customers
klass.add_handler 'get /v1/customers/search', :search_customers
klass.add_handler 'delete /v1/customers/([^/]*)/discount', :delete_customer_discount
end

Expand Down Expand Up @@ -140,6 +141,16 @@ def list_customers(route, method_url, params, headers)
Data.mock_list_object(customers[stripe_account]&.values, params)
end

SEARCH_FIELDS = ["email", "name", "phone"].freeze
def search_customers(route, method_url, params, headers)
require_param(:query) unless params[:query]

stripe_account = headers && headers[:stripe_account] || Stripe.api_key
all_customers = customers[stripe_account]&.values
results = search_results(all_customers, params[:query], fields: SEARCH_FIELDS, resource_name: "customers")
Data.mock_list_object(results, params)
end

def delete_customer_discount(route, method_url, params, headers)
stripe_account = headers && headers[:stripe_account] || Stripe.api_key
route =~ method_url
Expand Down
67 changes: 67 additions & 0 deletions lib/stripe_mock/request_handlers/helpers/search_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module StripeMock
module RequestHandlers
module Helpers
# Only supports exact matches on a single field, e.g.
# - 'amount:100'
# - 'email:"[email protected]"'
# - 'name:"Foo Bar"'
# - 'metadata["foo"]:"bar"'
QUERYSTRING_PATTERN = /\A(?<field>[\w\.]+)(\[['"](?<metadata_key>[^'"]*)['"]\])?:['"]?(?<value>[^'"]*)['"]?\z/
def search_results(all_values, querystring, fields: [], resource_name:)
values = all_values.dup
query_match = QUERYSTRING_PATTERN.match(querystring)
raise Stripe::InvalidRequestError.new(
'We were unable to parse your search query.' \
' Try using the format `metadata["key"]:"value"` to query for metadata or key:"value" to query for other fields.',
nil,
http_status: 400,
) unless query_match

case query_match[:field]
when *fields
values = values.select { |resource|
exact_match?(actual: field_value(resource, field: query_match[:field]), expected: query_match[:value])
}
when "metadata"
values = values.select { |resource|
resource[:metadata] &&
exact_match?(actual: resource[:metadata][query_match[:metadata_key].to_sym], expected: query_match[:value])
}
else
raise Stripe::InvalidRequestError.new(
"Field `#{query_match[:field]}` is an unsupported search field for resource `#{resource_name}`." \
" See http://stripe.com/docs/search#query-fields-for-#{resource_name.gsub('_', '-')} for a list of supported fields.",
nil,
http_status: 400,
)
end

values
end

def exact_match?(actual:, expected:)
# allow comparisons of integers
if actual.respond_to?(:to_i) && actual.to_i == actual
expected = expected.to_i
end
# allow comparisons of boolean
case expected
when "true"
expected = true
when "false"
expected = false
end

actual == expected
end

def field_value(resource, field:)
value = resource
field.split('.').each do |segment|
value = value[segment.to_sym]
end
value
end
end
end
end
11 changes: 10 additions & 1 deletion lib/stripe_mock/request_handlers/invoices.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ def Invoices.included(klass)
klass.add_handler 'post /v1/invoices', :new_invoice
klass.add_handler 'get /v1/invoices/upcoming', :upcoming_invoice
klass.add_handler 'get /v1/invoices/(.*)/lines', :get_invoice_line_items
klass.add_handler 'get /v1/invoices/(.*)', :get_invoice
klass.add_handler 'get /v1/invoices/((?!search).*)', :get_invoice
klass.add_handler 'get /v1/invoices/search', :search_invoices
klass.add_handler 'get /v1/invoices', :list_invoices
klass.add_handler 'post /v1/invoices/(.*)/pay', :pay_invoice
klass.add_handler 'post /v1/invoices/(.*)', :update_invoice
Expand All @@ -25,6 +26,14 @@ def update_invoice(route, method_url, params, headers)
invoices[$1].merge!(params)
end

SEARCH_FIELDS = ["currency", "customer", "number", "receipt_number", "subscription", "total"].freeze
def search_invoices(route, method_url, params, headers)
require_param(:query) unless params[:query]

results = search_results(invoices.values, params[:query], fields: SEARCH_FIELDS, resource_name: "invoices")
Data.mock_list_object(results, params)
end

def list_invoices(route, method_url, params, headers)
params[:offset] ||= 0
params[:limit] ||= 10
Expand Down
11 changes: 10 additions & 1 deletion lib/stripe_mock/request_handlers/payment_intents.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module PaymentIntents
def PaymentIntents.included(klass)
klass.add_handler 'post /v1/payment_intents', :new_payment_intent
klass.add_handler 'get /v1/payment_intents', :get_payment_intents
klass.add_handler 'get /v1/payment_intents/(.*)', :get_payment_intent
klass.add_handler 'get /v1/payment_intents/((?!search).*)', :get_payment_intent
klass.add_handler 'get /v1/payment_intents/search', :search_payment_intents
klass.add_handler 'post /v1/payment_intents/(.*)/confirm', :confirm_payment_intent
klass.add_handler 'post /v1/payment_intents/(.*)/capture', :capture_payment_intent
klass.add_handler 'post /v1/payment_intents/(.*)/cancel', :cancel_payment_intent
Expand Down Expand Up @@ -70,6 +71,14 @@ def get_payment_intent(route, method_url, params, headers)
payment_intent
end

SEARCH_FIELDS = ["amount", "currency", "customer", "status"].freeze
def search_payment_intents(route, method_url, params, headers)
require_param(:query) unless params[:query]

results = search_results(payment_intents.values, params[:query], fields: SEARCH_FIELDS, resource_name: "payment_intents")
Data.mock_list_object(results, params)
end

def capture_payment_intent(route, method_url, params, headers)
route =~ method_url
payment_intent = assert_existence :payment_intent, $1, payment_intents[$1]
Expand Down
17 changes: 13 additions & 4 deletions lib/stripe_mock/request_handlers/prices.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ module RequestHandlers
module Prices

def Prices.included(klass)
klass.add_handler 'post /v1/prices', :new_price
klass.add_handler 'post /v1/prices/(.*)', :update_price
klass.add_handler 'get /v1/prices/(.*)', :get_price
klass.add_handler 'get /v1/prices', :list_prices
klass.add_handler 'post /v1/prices', :new_price
klass.add_handler 'post /v1/prices/(.*)', :update_price
klass.add_handler 'get /v1/prices/((?!search).*)', :get_price
klass.add_handler 'get /v1/prices/search', :search_prices
klass.add_handler 'get /v1/prices', :list_prices
end

def new_price(route, method_url, params, headers)
Expand Down Expand Up @@ -57,6 +58,14 @@ def list_prices(route, method_url, params, headers)

Data.mock_list_object(price_data.first(limit), params.merge!(limit: limit))
end

SEARCH_FIELDS = ["active", "currency", "lookup_key", "product", "type"].freeze
def search_prices(route, method_url, params, headers)
require_param(:query) unless params[:query]

results = search_results(prices.values, params[:query], fields: SEARCH_FIELDS, resource_name: "prices")
Data.mock_list_object(results, params)
end
end
end
end
19 changes: 14 additions & 5 deletions lib/stripe_mock/request_handlers/products.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ module StripeMock
module RequestHandlers
module Products
def self.included(base)
base.add_handler 'post /v1/products', :create_product
base.add_handler 'get /v1/products/(.*)', :retrieve_product
base.add_handler 'post /v1/products/(.*)', :update_product
base.add_handler 'get /v1/products', :list_products
base.add_handler 'delete /v1/products/(.*)', :destroy_product
base.add_handler 'post /v1/products', :create_product
base.add_handler 'get /v1/products/((?!search).*)', :retrieve_product
base.add_handler 'get /v1/products/search', :search_products
base.add_handler 'post /v1/products/(.*)', :update_product
base.add_handler 'get /v1/products', :list_products
base.add_handler 'delete /v1/products/(.*)', :destroy_product
end

def create_product(_route, _method_url, params, _headers)
Expand All @@ -32,6 +33,14 @@ def list_products(_route, _method_url, params, _headers)
Data.mock_list_object(products.values.take(limit), params)
end

SEARCH_FIELDS = ["active", "description", "name", "shippable", "url"].freeze
def search_products(route, method_url, params, headers)
require_param(:query) unless params[:query]

results = search_results(products.values, params[:query], fields: SEARCH_FIELDS, resource_name: "products")
Data.mock_list_object(results, params)
end

def destroy_product(route, method_url, _params, _headers)
id = method_url.match(route).captures.first
assert_existence :product, id, products[id]
Expand Down
11 changes: 10 additions & 1 deletion lib/stripe_mock/request_handlers/subscriptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ module Subscriptions
def Subscriptions.included(klass)
klass.add_handler 'get /v1/subscriptions', :retrieve_subscriptions
klass.add_handler 'post /v1/subscriptions', :create_subscription
klass.add_handler 'get /v1/subscriptions/(.*)', :retrieve_subscription
klass.add_handler 'get /v1/subscriptions/((?!search).*)', :retrieve_subscription
klass.add_handler 'post /v1/subscriptions/(.*)', :update_subscription
klass.add_handler 'get /v1/subscriptions/search', :search_subscriptions
klass.add_handler 'delete /v1/subscriptions/(.*)', :cancel_subscription

klass.add_handler 'post /v1/customers/(.*)/subscription(?:s)?', :create_customer_subscription
Expand Down Expand Up @@ -343,6 +344,14 @@ def cancel_subscription(route, method_url, params, headers)
subscription
end

SEARCH_FIELDS = ["status"].freeze
def search_subscriptions(route, method_url, params, headers)
require_param(:query) unless params[:query]

results = search_results(subscriptions.values, params[:query], fields: SEARCH_FIELDS, resource_name: "subscriptions")
Data.mock_list_object(results, params)
end

private

def get_subscription_plans_from_params(params)
Expand Down
12 changes: 5 additions & 7 deletions spec/shared_stripe_examples/bank_token_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,19 @@
expect(bank_token).to match /^test_btok/
end

it "assigns the generated bank account to a new recipient" do
it "assigns the generated bank account to a new customer" do
bank_token = StripeMock.generate_bank_token(
:bank_name => "Bank Token Mocking",
:last4 => "7777"
)

recipient = Stripe::Recipient.create({
customer = Stripe::Customer.create({
name: "Fred Flinstone",
type: "individual",
email: '[email protected]',
bank_account: bank_token
source: bank_token
})
expect(recipient.active_account.last4).to eq("7777")
expect(recipient.active_account.bank_name).to eq("Bank Token Mocking")
expect(customer.sources.first.last4).to eq("7777")
expect(customer.sources.first.bank_name).to eq("Bank Token Mocking")
end

it "retrieves a created token" do
Expand All @@ -55,5 +54,4 @@
expect(token.bank_account.last4).to eq("3939")
expect(token.bank_account.bank_name).to eq("Cha-ching Banking")
end

end
Loading

0 comments on commit f0ec4ef

Please sign in to comment.