insales_api 0.0.13 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -0
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +1 -1
  5. data/README.md +57 -0
  6. data/Rakefile +8 -1
  7. data/insales_api.gemspec +12 -12
  8. data/lib/insales/controller/autologin.rb +45 -0
  9. data/lib/insales/controller/base_helpers.rb +52 -0
  10. data/lib/insales/controller/installer_actions.rb +19 -0
  11. data/lib/insales/controller/session_actions.rb +39 -0
  12. data/lib/insales/controller.rb +10 -0
  13. data/lib/insales.rb +7 -0
  14. data/lib/insales_api/account.rb +1 -9
  15. data/lib/insales_api/active_resource_proxy.rb +31 -0
  16. data/lib/insales_api/app.rb +57 -38
  17. data/lib/insales_api/application_charge.rb +3 -0
  18. data/lib/insales_api/asset.rb +3 -1
  19. data/lib/insales_api/base.rb +41 -5
  20. data/lib/insales_api/category.rb +8 -2
  21. data/lib/insales_api/client.rb +3 -1
  22. data/lib/insales_api/client_group.rb +3 -0
  23. data/lib/insales_api/collect.rb +8 -2
  24. data/lib/insales_api/discount_code.rb +3 -0
  25. data/lib/insales_api/helpers/has_insales_object.rb +34 -0
  26. data/lib/insales_api/helpers/init_api.rb +98 -0
  27. data/lib/insales_api/image.rb +5 -0
  28. data/lib/insales_api/order.rb +13 -5
  29. data/lib/insales_api/order_line.rb +2 -2
  30. data/lib/insales_api/product.rb +3 -1
  31. data/lib/insales_api/product_field.rb +3 -0
  32. data/lib/insales_api/product_field_value.rb +22 -0
  33. data/lib/insales_api/property.rb +3 -0
  34. data/lib/insales_api/resource/countable.rb +9 -0
  35. data/lib/insales_api/resource/paginated.rb +26 -0
  36. data/lib/insales_api/resource/with_updated_since.rb +28 -0
  37. data/lib/insales_api/theme.rb +7 -1
  38. data/lib/insales_api/variant.rb +18 -6
  39. data/lib/insales_api/version.rb +7 -1
  40. data/lib/insales_api.rb +74 -17
  41. data/spec/lib/insales_api/active_resource_proxy_spec.rb +59 -0
  42. data/spec/lib/insales_api/app_spec.rb +67 -0
  43. data/spec/lib/insales_api_spec.rb +110 -0
  44. data/spec/spec_helper.rb +3 -4
  45. metadata +54 -20
  46. data/README +0 -32
  47. data/spec/lib/insales_app_spec.rb +0 -63
@@ -0,0 +1,98 @@
1
+ module InsalesApi::Helpers
2
+ module InitApi
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :insales_app_class
7
+ self.insales_app_class = InsalesApi::App
8
+ end
9
+
10
+ module ClassMethods
11
+ # Wraps methods into +init_api+ block. So you can be sure that method
12
+ # will run for certain account.
13
+ #
14
+ # class Account < ActiveRecord::Base
15
+ # include InsalesApi::Helpers
16
+ #
17
+ # def find_products_by_name(name)
18
+ # # ...
19
+ # end
20
+ #
21
+ # init_api_for :find_products_by_name
22
+ # end
23
+ #
24
+ # account1 = Account.find(1)
25
+ # account2 = Account.find(2)
26
+ #
27
+ # products1 = account1.find_products_by_name('smth')
28
+ # products2 = account2.find_products_by_name('smth_else')
29
+ #
30
+ # # instead of
31
+ # # products1 = account1.init_api { find_products_by_name('smth') }
32
+ # # products2 = account2.init_api { find_products_by_name('smth_else') }
33
+ #
34
+ # Can be used in nessted classes like this:
35
+ #
36
+ # class Order < ActiveRecord::Base
37
+ # extend InsalesApi::Helpers::ClassMethods
38
+ #
39
+ # belongs_to :account
40
+ #
41
+ # delegate :init_api, to: :account
42
+ #
43
+ # def insales_order
44
+ # InsalesApi::Order.find(insales_id)
45
+ # end
46
+ #
47
+ # init_api_for :insales_order
48
+ # end
49
+ #
50
+ # insales_order = Order.first.insales_order
51
+ #
52
+ def init_api_for(*methods)
53
+ file, line = caller.first.split(':', 2)
54
+ methods.each do |method|
55
+ alias_method_chain method, :init_api do |target, punct|
56
+ class_eval <<-RUBY, file, line.to_i - 1
57
+ def #{target}_with_init_api#{punct}(*args, &block) # def sell_with_init_api(*args, &block)
58
+ init_api { #{target}_without_init_api#{punct}(*args, &block) } # init_api { sell_without_init_api(*args, &block) }
59
+ end # end
60
+ RUBY
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ # Configures api with credentials taken from +self.insales_domain+ and
67
+ # +self.insales_password+.
68
+ #
69
+ # If block is given, it is evaluated and its result is returned.
70
+ # After this old configuration is restored.
71
+ #
72
+ # account1 = Account.find(1)
73
+ # account2 = Account.find(2)
74
+ #
75
+ # account1.init_api
76
+ # # account1 credentials are used
77
+ # product1 = InsalesApi::Product.find(1)
78
+ # # will search within second account
79
+ # product2 = account2.init_api { InsalesApi::Product.find(2) }
80
+ # # configuration is restored
81
+ # variant1 = InsalesApi::Variants.find(1)
82
+ #
83
+ def init_api
84
+ if block_given?
85
+ old_config = insales_app_class.dump_config
86
+ begin
87
+ init_api
88
+ yield
89
+ ensure
90
+ insales_app_class.restore_config(old_config)
91
+ end
92
+ else
93
+ insales_app_class.configure_api(insales_domain, insales_password)
94
+ self
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ module InsalesApi
2
+ class Image < Base
3
+ self.prefix = "#{prefix}products/:product_id/"
4
+ end
5
+ end
@@ -1,15 +1,23 @@
1
+ # coding: utf-8
1
2
  module InsalesApi
2
3
  class Order < Base
4
+ extend Resource::WithUpdatedSince
5
+
3
6
  def order_lines_attributes
4
- @order_lines_attributes = []
5
- order_lines.each do |order_line|
6
- @order_lines_attributes << order_line.as_json['order_line']
7
+ @order_lines_attributes = order_lines.map do |order_line|
8
+ ol = order_line.as_json
9
+ # при смене версии рельсов (видимо) изменилась сериализация
10
+ ol = ol['order_line'] if ol['order_line']
11
+ ol
7
12
  end
8
- @order_lines_attributes
9
13
  end
10
14
 
11
15
  def to_xml(options = {})
12
- super(options.merge({:methods => :order_lines_attributes}))
16
+ super(options.merge(methods: :order_lines_attributes))
17
+ end
18
+
19
+ def paid?
20
+ financial_status == 'paid'
13
21
  end
14
22
  end
15
23
  end
@@ -1,5 +1,5 @@
1
1
  module InsalesApi
2
2
  class OrderLine < Base
3
- self.prefix = '/admin/orders/:order_id/'
3
+ self.prefix = "#{prefix}orders/:order_id/"
4
4
  end
5
- end
5
+ end
@@ -1,3 +1,5 @@
1
1
  module InsalesApi
2
- class Product < Base; end
2
+ class Product < Base
3
+ extend Resource::WithUpdatedSince
4
+ end
3
5
  end
@@ -0,0 +1,3 @@
1
+ module InsalesApi
2
+ class ProductField < Base; end
3
+ end
@@ -0,0 +1,22 @@
1
+ module InsalesApi
2
+ class ProductFieldValue < Base
3
+ self.prefix = "#{prefix}products/:product_id/"
4
+
5
+ class << self
6
+ def find_by_field_id(params)
7
+ field_id = params[:product_field_id]
8
+ all(params: {product_id: params[:product_id]}).
9
+ find { |x| x.product_field_id == field_id }
10
+ end
11
+
12
+ def create_or_update(params)
13
+ if value = find_by_field_id(params)
14
+ value.update_attribute(:value, params[:value])
15
+ value
16
+ else
17
+ create(params)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module InsalesApi
2
+ class Property < Base; end
3
+ end
@@ -0,0 +1,9 @@
1
+ module InsalesApi
2
+ module Resource
3
+ module Countable
4
+ def count(options = {})
5
+ get(:count, options).to_i
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ module InsalesApi
2
+ PER_PAGE_DEFAULT = 100
3
+
4
+ module Resource
5
+ module Paginated
6
+ def find_each(*args)
7
+ find_in_batches(*args) do |batch|
8
+ batch.each { |record| yield record }
9
+ end
10
+ end
11
+
12
+ def find_in_batches(options = {})
13
+ per_page = options[:per_page] || PER_PAGE_DEFAULT
14
+ params = {per_page: per_page}.merge(options[:params] || {})
15
+ page = 1
16
+ loop do
17
+ items = all(params: params.merge(page: page))
18
+ return unless items.any?
19
+ yield items
20
+ return if items.count < per_page
21
+ page += 1
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module InsalesApi
2
+ module Resource
3
+ module WithUpdatedSince
4
+ def find_in_batches(options = {}, &block)
5
+ return super unless updated_since = options.delete(:updated_since)
6
+ find_updated_since(updated_since, options, &block)
7
+ end
8
+
9
+ def find_updated_since(updated_since, options = {})
10
+ per_page = options[:per_page] || PER_PAGE_DEFAULT
11
+ params = { per_page: per_page }.merge(options[:params] || {})
12
+ last_id = nil
13
+ loop do
14
+ items = all(params: params.merge(
15
+ updated_since: updated_since,
16
+ from_id: last_id,
17
+ ))
18
+ return unless items.any?
19
+ yield items
20
+ return if items.count < per_page
21
+ last_item = items.last
22
+ last_id = last_item.id
23
+ updated_since = last_item.updated_at
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,9 @@
1
1
  module InsalesApi
2
- class Theme < Base; end
2
+ class Theme < Base
3
+
4
+ def assets
5
+ InsalesApi::Asset.all(params: {theme_id: id})
6
+ end
7
+
8
+ end
3
9
  end
@@ -1,11 +1,23 @@
1
1
  module InsalesApi
2
2
  class Variant < Base
3
- self.prefix = "/admin/products/:product_id/"
4
-
5
- # variants_attrs - массив c модицикациями в формате [{:id => 1, :price => 340, :quantity => 4}, {:id => 2, :price => 350, :quantity => 5}]
6
- def self.group_update variants_attrs
7
- connection.put("/admin/products/variants_group_update.xml", { :variants => variants_attrs }.to_xml, headers)
3
+ class << self
4
+ # Updates all given variants. +variants+ should be array:
5
+ #
6
+ # [
7
+ # {
8
+ # id: 1,
9
+ # price: 340,
10
+ # quantity: 4,
11
+ # },
12
+ # {
13
+ # id: 2,
14
+ # price: 350,
15
+ # quantity: 5,
16
+ # },
17
+ # ]
18
+ def group_update(variants)
19
+ put(:group_update, {}, format.encode(variants, root: :variants))
20
+ end
8
21
  end
9
-
10
22
  end
11
23
  end
@@ -1,3 +1,9 @@
1
1
  module InsalesApi
2
- VERSION = "0.0.13"
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].compact.join('.')
8
+ end
3
9
  end
data/lib/insales_api.rb CHANGED
@@ -1,40 +1,97 @@
1
+ require 'digest/md5'
2
+ require 'active_support'
1
3
  require 'active_support/core_ext'
2
4
  require 'active_resource'
3
5
  # backport from 4.0
4
6
  require 'active_resource/singleton' unless ActiveResource.const_defined?(:Singleton, false)
5
- require 'digest/md5'
6
7
 
7
8
  module InsalesApi
8
9
  extend ActiveSupport::Autoload
9
10
 
11
+ Deprecator = ActiveSupport::Deprecation.new('1.0', name)
12
+
10
13
  eager_autoload do
11
- autoload :Version
12
- autoload :App
14
+ autoload :VERSION
15
+ autoload :Base
13
16
  autoload :Password
17
+ autoload :App
14
18
 
15
- autoload :Base
16
19
  autoload :Account
20
+ autoload :ApplicationCharge
21
+ autoload :ApplicationWidget
22
+ autoload :Asset
17
23
  autoload :Category
18
24
  autoload :Client
19
- autoload :Collection
25
+ autoload :ClientGroup
20
26
  autoload :Collect
27
+ autoload :Collection
28
+ autoload :DeliveryVariant
29
+ autoload :DiscountCode
30
+ autoload :Domain
31
+ autoload :Field
32
+ autoload :File
33
+ autoload :Image
34
+ autoload :JsTag
21
35
  autoload :OptionName
22
36
  autoload :OptionValue
23
- autoload :Product
24
- autoload :Variant
25
- autoload :Webhook
26
37
  autoload :Order
27
38
  autoload :OrderLine
28
- autoload :ApplicationWidget
29
- autoload :Field
30
- autoload :DeliveryVariant
31
- autoload :PaymentGateway
32
- autoload :JsTag
33
- autoload :Domain
34
39
  autoload :Page
35
- autoload :Theme
36
- autoload :Asset
40
+ autoload :PaymentGateway
41
+ autoload :Product
42
+ autoload :ProductField
43
+ autoload :ProductFieldValue
44
+ autoload :Property
37
45
  autoload :RecurringApplicationCharge
38
- autoload :File
46
+ autoload :Theme
47
+ autoload :Variant
48
+ autoload :Webhook
39
49
  end
50
+
51
+ class << self
52
+ # Calls the supplied block. If the block raises <tt>ActiveResource::ServerError</tt> with 503
53
+ # code which means Insales API request limit is reached, it will wait for the amount of seconds
54
+ # specified in 'Retry-After' response header. The called block will receive a parameter with
55
+ # current attempt number.
56
+ #
57
+ # ==== Params:
58
+ #
59
+ # +max_attempts+:: maximum number of attempts. Defaults to +nil+ (unlimited).
60
+ # +callback+:: +Proc+ or lambda to execute before waiting. Will receive four arguments: number
61
+ # of seconds we are going to wait, number of failed attempts, maximum number of
62
+ # attempts and the caught <tt>ActiveResource::ServerError</tt>. Defaults to +nil+
63
+ # (no callback).
64
+ #
65
+ # ==== Example
66
+ #
67
+ # notify_user = Proc.new do |wait_for, attempt, max_attempts, ex|
68
+ # puts "API limit reached. Waiting for #{wait_for} seconds. Attempt #{attempt}/#{max_attempts}"
69
+ # end
70
+ #
71
+ # InsalesApi.wait_retry(10, notify_user) do |x|
72
+ # puts "Attempt ##{x}."
73
+ # products = InsalesApi::Products.all
74
+ # end
75
+ def wait_retry(max_attempts = nil, callback = nil, &block)
76
+ attempts = 0
77
+
78
+ begin
79
+ attempts += 1
80
+ yield attempts
81
+ rescue ActiveResource::ServerError => ex
82
+ raise ex if '503' != ex.response.code.to_s
83
+ raise ex if max_attempts && attempts >= max_attempts
84
+ retry_after = (ex.response['Retry-After'] || 150).to_i
85
+ callback.call(retry_after, attempts, max_attempts, ex) if callback
86
+ sleep(retry_after)
87
+ retry
88
+ end
89
+ end
90
+ end
91
+
40
92
  end
93
+
94
+ require 'insales_api/helpers/init_api'
95
+ require 'insales_api/helpers/has_insales_object'
96
+
97
+ ActiveSupport.run_load_hooks(:insales_api, InsalesApi)
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe InsalesApi::ActiveResourceProxy do
4
+ let(:proxy) { described_class.new(configurer, object) }
5
+ let(:configurer) { Object.new.tap { |x| x.stub(:init_api).and_yield } }
6
+
7
+ describe '#method_missing' do
8
+ let(:object) { {} }
9
+ let(:method_name) { :some_method }
10
+ subject { proxy.send(method_name) }
11
+
12
+ it 'should proxy method to object & pass result through #proxy_for' do
13
+ result1 = {test: :resut1}
14
+ result2 = {test: :resut2}
15
+ object.should_receive(method_name).and_return(result1)
16
+ proxy.should_receive(:proxy_for).with(result1).and_return(result2)
17
+ subject.should eq(result2)
18
+ end
19
+ end
20
+
21
+ describe '::need_proxy?' do
22
+ subject { described_class.need_proxy?(object) }
23
+
24
+ context 'for scalar' do
25
+ let(:object) { true }
26
+ it { should be false }
27
+ end
28
+
29
+ context 'for array' do
30
+ let(:object) { [] }
31
+ it { should be true }
32
+ end
33
+
34
+ context 'for hash' do
35
+ let(:object) { {} }
36
+ it { should be true }
37
+ end
38
+
39
+ context 'for InsalesApi::Base class' do
40
+ let(:object) { InsalesApi::Account }
41
+ it { should be true }
42
+ end
43
+
44
+ context 'for InsalesApi::Base object' do
45
+ let(:object) { InsalesApi::Account.new }
46
+ it { should be true }
47
+ end
48
+
49
+ context 'for ActiveResource::Collection object' do
50
+ let(:object) { ActiveResource::Collection.new }
51
+ it { should be true }
52
+ end
53
+
54
+ context 'for ActiveResource::Base object' do
55
+ let(:object) { ActiveResource::Base.new }
56
+ it { should be false }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe InsalesApi::App do
4
+ let(:domain) { 'my.shop.com' }
5
+ let(:password) { 'password' }
6
+ let(:app) { InsalesApi::App.new(domain, password) }
7
+
8
+ subject { app }
9
+
10
+ describe '#initialize' do
11
+ its(:domain) { should eq(domain) }
12
+ its(:password) { should eq(password) }
13
+ its(:authorized?) { should be false }
14
+ end
15
+
16
+ describe '#configure_api' do
17
+ it 'configures InsalesApi::Base' do
18
+ InsalesApi::Base.should_receive(:configure).with(described_class.api_key, domain, password)
19
+ app.configure_api
20
+ end
21
+ end
22
+
23
+ describe '#salt' do
24
+ its(:salt) { should be }
25
+ end
26
+
27
+ describe '#auth_token' do
28
+ its(:auth_token) { should eq(InsalesApi::Password.create(app.password, app.salt)) }
29
+ end
30
+
31
+ describe 'authorization_url' do
32
+ subject { app.authorization_url }
33
+ let(:expected) do
34
+ URI::Generic.build(
35
+ scheme: 'http',
36
+ host: domain,
37
+ path: "/admin/applications/#{app.api_key}/login",
38
+ query: {
39
+ token: app.salt,
40
+ login: app.api_autologin_url,
41
+ }.to_query,
42
+ ).to_s
43
+ end
44
+ it { should eq(expected) }
45
+ end
46
+
47
+ describe '#authorize' do
48
+ before { app.auth_token }
49
+ subject { app.authorize(token) }
50
+
51
+ context 'when valid token is given' do
52
+ let(:token) { app.auth_token }
53
+ it { should be true }
54
+ end
55
+
56
+ context 'when invalid token is given' do
57
+ let(:token) { 'bad_token' }
58
+ it { should be false }
59
+ end
60
+ end
61
+
62
+ describe '::password_by_token' do
63
+ let(:token) { 'test' }
64
+ subject { InsalesApi::App.password_by_token(token) }
65
+ it { should eq(InsalesApi::Password.create(InsalesApi::App.api_secret, token)) }
66
+ end
67
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe InsalesApi do
4
+
5
+ describe '.wait_retry' do
6
+
7
+ response_stub = Struct.new(:code, :retry_after) do
8
+ def [](key)
9
+ {'Retry-After' => retry_after}[key]
10
+ end
11
+ end
12
+
13
+ it 'should not run without block' do
14
+ expect do
15
+ described_class.wait_retry(20, nil)
16
+ end.to raise_error
17
+ end
18
+
19
+ it 'should handle succesfull request' do
20
+ counter = 0
21
+ described_class.wait_retry do
22
+ counter += 1
23
+ end
24
+
25
+ expect(counter).to eq(1)
26
+ end
27
+
28
+ it 'should make specified amount of attempts' do
29
+ counter = 0
30
+ described_class.wait_retry(3) do
31
+ counter += 1
32
+
33
+ if counter < 3
34
+ raise ActiveResource::ServerError.new(response_stub.new("503", "0"))
35
+ end
36
+ end
37
+
38
+ expect(counter).to eq(3)
39
+ end
40
+
41
+ it 'should use callback proc' do
42
+ callback = Proc.new{}
43
+ counter = 0
44
+ callback.should_receive(:call).with(0, 1, 3, instance_of(ActiveResource::ServerError))
45
+ callback.should_receive(:call).with(0, 2, 3, instance_of(ActiveResource::ServerError))
46
+
47
+ described_class.wait_retry(3, callback) do
48
+ counter += 1
49
+
50
+ if counter < 3
51
+ raise ActiveResource::ServerError.new(response_stub.new("503", "0"))
52
+ end
53
+ end
54
+ end
55
+
56
+ it 'should pass attempt number to block' do
57
+ last_attempt_no = 0
58
+ described_class.wait_retry(3) do |x|
59
+ last_attempt_no = x
60
+
61
+ if last_attempt_no < 3
62
+ raise ActiveResource::ServerError.new(response_stub.new("503", "0"))
63
+ end
64
+ end
65
+
66
+ expect(last_attempt_no).to eq(3)
67
+ end
68
+
69
+ it 'should raise if no attempts left' do
70
+ expect do
71
+ described_class.wait_retry(3) do |x|
72
+ raise ActiveResource::ServerError.new(response_stub.new("503", "0"))
73
+ end
74
+ end.to raise_error(ActiveResource::ServerError)
75
+ end
76
+
77
+ it 'should raise on user errors' do
78
+ attempts = 0
79
+ expect do
80
+ described_class.wait_retry(3) do |x|
81
+ attempts = x
82
+ raise 'Some other error'
83
+ end
84
+ end.to raise_error(StandardError)
85
+ expect(attempts).to eq(1)
86
+ end
87
+
88
+ it 'should raise on other server errors' do
89
+ attempts = 0
90
+ expect do
91
+ described_class.wait_retry(3) do |x|
92
+ attempts = x
93
+ raise ActiveResource::ServerError.new(response_stub.new("502", "0"))
94
+ end
95
+ end.to raise_error(ActiveResource::ServerError)
96
+ expect(attempts).to eq(1)
97
+ end
98
+
99
+ it 'should run until success' do |x|
100
+ success = false
101
+ described_class.wait_retry do |x|
102
+ raise ActiveResource::ServerError.new(response_stub.new("503", "0")) if x < 10
103
+ success = true
104
+ end
105
+
106
+ expect(success).to be_true
107
+ end
108
+ end
109
+
110
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,10 +2,9 @@
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
3
  require 'insales_api'
4
4
 
5
- InsalesApi::App.api_key = 'test'
6
- InsalesApi::App.api_secret = 'test'
7
- InsalesApi::App.api_host = 'myshop.insales.ru'
8
- InsalesApi::App.api_autologin_path = 'session/autologin'
5
+ InsalesApi::App.api_key = 'test'
6
+ InsalesApi::App.api_secret = 'test'
7
+ InsalesApi::App.api_autologin_url = 'https://host.com/session/autologin'
9
8
 
10
9
  RSpec.configure do |config|
11
10
  # == Mock Framework