shopify_api 9.0.4 → 9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/ISSUE_TEMPLATE.md +36 -0
  4. data/.github/workflows/build.yml +44 -0
  5. data/.gitignore +2 -1
  6. data/.rubocop.yml +23 -3
  7. data/.rubocop_todo.yml +75 -0
  8. data/CHANGELOG.md +41 -0
  9. data/CONTRIBUTING.md +1 -1
  10. data/Gemfile +5 -0
  11. data/Gemfile.lock +153 -0
  12. data/Gemfile_ar51 +1 -1
  13. data/README.md +42 -32
  14. data/RELEASING +10 -6
  15. data/Rakefile +16 -5
  16. data/SECURITY.md +59 -0
  17. data/dev.yml +11 -0
  18. data/docs/_config.yml +1 -0
  19. data/docs/_includes/footer.html +28 -0
  20. data/docs/_includes/head.html +28 -0
  21. data/docs/_layouts/index.html +57 -0
  22. data/docs/graphql.md +47 -2
  23. data/docs/index.md +639 -0
  24. data/lib/active_resource/connection_ext.rb +1 -0
  25. data/lib/active_resource/detailed_log_subscriber.rb +10 -7
  26. data/lib/active_resource/json_errors.rb +8 -2
  27. data/lib/shopify_api/api_access.rb +57 -0
  28. data/lib/shopify_api/api_version.rb +6 -5
  29. data/lib/shopify_api/connection.rb +1 -0
  30. data/lib/shopify_api/countable.rb +3 -2
  31. data/lib/shopify_api/disable_prefix_check.rb +2 -2
  32. data/lib/shopify_api/events.rb +2 -1
  33. data/lib/shopify_api/graphql.rb +28 -8
  34. data/lib/shopify_api/hmac_params.rb +23 -0
  35. data/lib/shopify_api/limits.rb +3 -2
  36. data/lib/shopify_api/message_enricher.rb +11 -9
  37. data/lib/shopify_api/meta.rb +0 -1
  38. data/lib/shopify_api/metafields.rb +5 -4
  39. data/lib/shopify_api/pagination_link_headers.rb +5 -4
  40. data/lib/shopify_api/resources/access_scope.rb +1 -1
  41. data/lib/shopify_api/resources/access_token.rb +1 -0
  42. data/lib/shopify_api/resources/address.rb +1 -0
  43. data/lib/shopify_api/resources/announcement.rb +2 -1
  44. data/lib/shopify_api/resources/application_charge.rb +1 -0
  45. data/lib/shopify_api/resources/application_credit.rb +1 -0
  46. data/lib/shopify_api/resources/article.rb +3 -2
  47. data/lib/shopify_api/resources/asset.rb +6 -5
  48. data/lib/shopify_api/resources/assigned_fulfillment_order.rb +1 -1
  49. data/lib/shopify_api/resources/base.rb +12 -8
  50. data/lib/shopify_api/resources/billing_address.rb +1 -0
  51. data/lib/shopify_api/resources/blog.rb +2 -1
  52. data/lib/shopify_api/resources/carrier_service.rb +1 -0
  53. data/lib/shopify_api/resources/cart.rb +2 -1
  54. data/lib/shopify_api/resources/collect.rb +1 -0
  55. data/lib/shopify_api/resources/collection_listing.rb +1 -0
  56. data/lib/shopify_api/resources/comment.rb +20 -5
  57. data/lib/shopify_api/resources/country.rb +1 -0
  58. data/lib/shopify_api/resources/custom_collection.rb +4 -3
  59. data/lib/shopify_api/resources/customer.rb +2 -1
  60. data/lib/shopify_api/resources/customer_group.rb +1 -0
  61. data/lib/shopify_api/resources/customer_invite.rb +1 -0
  62. data/lib/shopify_api/resources/customer_saved_search.rb +2 -1
  63. data/lib/shopify_api/resources/discount_code.rb +1 -0
  64. data/lib/shopify_api/resources/discount_code_batch.rb +4 -2
  65. data/lib/shopify_api/resources/draft_order.rb +1 -0
  66. data/lib/shopify_api/resources/draft_order_invoice.rb +1 -0
  67. data/lib/shopify_api/resources/event.rb +1 -0
  68. data/lib/shopify_api/resources/fulfillment.rb +12 -3
  69. data/lib/shopify_api/resources/fulfillment_event.rb +1 -0
  70. data/lib/shopify_api/resources/fulfillment_order.rb +30 -16
  71. data/lib/shopify_api/resources/fulfillment_order_locations_for_move.rb +1 -0
  72. data/lib/shopify_api/resources/fulfillment_request.rb +1 -0
  73. data/lib/shopify_api/resources/fulfillment_service.rb +1 -0
  74. data/lib/shopify_api/resources/fulfillment_v2.rb +3 -2
  75. data/lib/shopify_api/resources/gift_card.rb +1 -0
  76. data/lib/shopify_api/resources/image.rb +2 -1
  77. data/lib/shopify_api/resources/inventory_level.rb +3 -4
  78. data/lib/shopify_api/resources/line_item.rb +4 -3
  79. data/lib/shopify_api/resources/location.rb +1 -1
  80. data/lib/shopify_api/resources/marketing_event.rb +1 -0
  81. data/lib/shopify_api/resources/metafield.rb +1 -0
  82. data/lib/shopify_api/resources/note_attribute.rb +1 -0
  83. data/lib/shopify_api/resources/option.rb +1 -0
  84. data/lib/shopify_api/resources/order.rb +2 -1
  85. data/lib/shopify_api/resources/order_risk.rb +1 -0
  86. data/lib/shopify_api/resources/page.rb +1 -0
  87. data/lib/shopify_api/resources/payment_details.rb +1 -0
  88. data/lib/shopify_api/resources/policy.rb +1 -0
  89. data/lib/shopify_api/resources/price_rule.rb +1 -1
  90. data/lib/shopify_api/resources/product.rb +27 -3
  91. data/lib/shopify_api/resources/product_listing.rb +1 -0
  92. data/lib/shopify_api/resources/province.rb +1 -0
  93. data/lib/shopify_api/resources/receipt.rb +1 -0
  94. data/lib/shopify_api/resources/recurring_application_charge.rb +4 -1
  95. data/lib/shopify_api/resources/redirect.rb +1 -0
  96. data/lib/shopify_api/resources/refund.rb +2 -1
  97. data/lib/shopify_api/resources/report.rb +1 -0
  98. data/lib/shopify_api/resources/resource_feedback.rb +1 -1
  99. data/lib/shopify_api/resources/rule.rb +1 -0
  100. data/lib/shopify_api/resources/script_tag.rb +1 -0
  101. data/lib/shopify_api/resources/shipping_address.rb +1 -0
  102. data/lib/shopify_api/resources/shipping_line.rb +1 -0
  103. data/lib/shopify_api/resources/shipping_zone.rb +1 -0
  104. data/lib/shopify_api/resources/shop.rb +2 -1
  105. data/lib/shopify_api/resources/smart_collection.rb +4 -8
  106. data/lib/shopify_api/resources/storefront_access_token.rb +1 -0
  107. data/lib/shopify_api/resources/tax_line.rb +1 -0
  108. data/lib/shopify_api/resources/tax_service.rb +1 -0
  109. data/lib/shopify_api/resources/theme.rb +1 -0
  110. data/lib/shopify_api/resources/transaction.rb +1 -0
  111. data/lib/shopify_api/resources/usage_charge.rb +1 -0
  112. data/lib/shopify_api/resources/user.rb +1 -0
  113. data/lib/shopify_api/resources/variant.rb +35 -0
  114. data/lib/shopify_api/resources/webhook.rb +1 -0
  115. data/lib/shopify_api/resources.rb +1 -0
  116. data/lib/shopify_api/session.rb +51 -20
  117. data/lib/shopify_api/version.rb +2 -1
  118. data/lib/shopify_api.rb +9 -1
  119. data/lib/verify_docs.rb +8 -0
  120. data/service.yml +2 -5
  121. data/shopify_api.gemspec +13 -7
  122. data/test/access_token_test.rb +6 -5
  123. data/test/active_resource/json_errors_test.rb +6 -6
  124. data/test/api_access_test.rb +153 -0
  125. data/test/api_version_test.rb +3 -3
  126. data/test/application_charge_test.rb +30 -27
  127. data/test/application_credit_test.rb +10 -9
  128. data/test/article_test.rb +34 -35
  129. data/test/asset_test.rb +14 -6
  130. data/test/assigned_fulfillment_order_test.rb +5 -4
  131. data/test/base_test.rb +64 -49
  132. data/test/blog_test.rb +4 -3
  133. data/test/carrier_service_test.rb +7 -6
  134. data/test/cart_test.rb +2 -1
  135. data/test/collect_test.rb +4 -3
  136. data/test/collection_listing_test.rb +21 -16
  137. data/test/collection_publication_test.rb +8 -8
  138. data/test/collection_test.rb +20 -19
  139. data/test/countable_test.rb +3 -2
  140. data/test/currency_test.rb +5 -5
  141. data/test/custom_collection_test.rb +4 -3
  142. data/test/customer_saved_search_test.rb +18 -8
  143. data/test/customer_test.rb +22 -14
  144. data/test/detailed_log_subscriber_test.rb +16 -12
  145. data/test/discount_code_batch_test.rb +11 -10
  146. data/test/discount_code_test.rb +21 -15
  147. data/test/draft_order_test.rb +68 -52
  148. data/test/fixtures/assigned_fulfillment_orders.json +2 -0
  149. data/test/fixtures/fulfillment_order.json +1 -0
  150. data/test/fixtures/fulfillment_orders.json +2 -0
  151. data/test/fulfillment_event_test.rb +31 -26
  152. data/test/fulfillment_order_test.rb +217 -149
  153. data/test/fulfillment_order_test_helper.rb +1 -0
  154. data/test/fulfillment_request_test.rb +10 -8
  155. data/test/fulfillment_service_test.rb +13 -12
  156. data/test/fulfillment_test.rb +81 -66
  157. data/test/fulfillment_v2_test.rb +16 -12
  158. data/test/gift_card_test.rb +6 -4
  159. data/test/graphql_test.rb +55 -23
  160. data/test/hmac_params_test.rb +25 -0
  161. data/test/image_test.rb +19 -17
  162. data/test/inventory_level_test.rb +24 -15
  163. data/test/lib/webmock_extensions/last_request.rb +1 -1
  164. data/test/limits_test.rb +2 -1
  165. data/test/location_test.rb +2 -1
  166. data/test/marketing_event_test.rb +20 -20
  167. data/test/message_enricher_test.rb +6 -6
  168. data/test/meta_test.rb +9 -11
  169. data/test/metafield_test.rb +30 -20
  170. data/test/order_risk_test.rb +17 -16
  171. data/test/order_test.rb +43 -28
  172. data/test/pagination_test.rb +89 -56
  173. data/test/policy_test.rb +6 -5
  174. data/test/price_rule_test.rb +20 -15
  175. data/test/product_listing_test.rb +20 -20
  176. data/test/product_publication_test.rb +8 -8
  177. data/test/product_test.rb +71 -20
  178. data/test/publication_test.rb +3 -3
  179. data/test/recurring_application_charge_test.rb +104 -42
  180. data/test/redirect_test.rb +4 -3
  181. data/test/refund_test.rb +22 -17
  182. data/test/report_test.rb +12 -10
  183. data/test/resource_feedback_test.rb +14 -13
  184. data/test/script_tag_test.rb +10 -9
  185. data/test/session_test.rb +319 -45
  186. data/test/shipping_zone_test.rb +4 -3
  187. data/test/shop_test.rb +47 -33
  188. data/test/smart_collection_test.rb +5 -29
  189. data/test/storefront_access_token_test.rb +13 -15
  190. data/test/tax_service_test.rb +7 -4
  191. data/test/tender_transaction_test.rb +3 -3
  192. data/test/test_helper.rb +14 -12
  193. data/test/transaction_test.rb +4 -3
  194. data/test/usage_charge_test.rb +12 -8
  195. data/test/user_test.rb +4 -3
  196. data/test/variant_test.rb +50 -23
  197. data/test/webhook_test.rb +10 -7
  198. metadata +44 -17
  199. data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +0 -1027
  200. data/.travis.yml +0 -23
  201. data/bin/shopify +0 -3
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Transaction < Base
3
4
  init_prefix :order
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class UsageCharge < Base
3
4
  init_prefix :recurring_application_charge
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class User < Base
3
4
  end
@@ -1,8 +1,43 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Variant < Base
3
4
  include Metafields
4
5
  include DisablePrefixCheck
5
6
 
6
7
  conditional_prefix :product
8
+
9
+ def initialize(*)
10
+ super
11
+ attributes.except!('old_inventory_quantity')
12
+ end
13
+
14
+ def inventory_quantity_adjustment=(new_value)
15
+ raise_deprecated_inventory_call('inventory_quantity_adjustment')
16
+ super
17
+ end
18
+
19
+ def inventory_quantity=(new_value)
20
+ raise_deprecated_inventory_call('inventory_quantity')
21
+ super
22
+ end
23
+
24
+ def old_inventory_quantity=(new_value)
25
+ raise_deprecated_inventory_call('old_inventory_quantity')
26
+ super
27
+ end
28
+
29
+ def save
30
+ attributes.except!('inventory_quantity')
31
+ super
32
+ end
33
+
34
+ private
35
+
36
+ def raise_deprecated_inventory_call(parameter)
37
+ raise(
38
+ ShopifyAPI::ValidationException,
39
+ "'#{parameter}' is deprecated - see https://help.shopify.com/en/api/guides/inventory-migration-guide",
40
+ )
41
+ end
7
42
  end
8
43
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
3
  class Webhook < Base
3
4
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'shopify_api/resources/base'
2
3
  require 'shopify_api/resources/array_base'
3
4
  Dir.glob("#{File.dirname(__FILE__)}/resources/*").each { |file| require(file) }
@@ -1,26 +1,27 @@
1
+ # frozen_string_literal: true
1
2
  require 'openssl'
2
3
  require 'rack'
3
4
 
4
5
  module ShopifyAPI
5
-
6
6
  class ValidationException < StandardError
7
7
  end
8
8
 
9
9
  class Session
10
+ SECONDS_IN_A_DAY = 24 * 60 * 60
11
+
10
12
  cattr_accessor :api_key, :secret, :myshopify_domain
11
13
  self.myshopify_domain = 'myshopify.com'
12
14
 
13
15
  attr_accessor :domain, :token, :name, :extra
14
- attr_reader :api_version
16
+ attr_reader :api_version, :access_scopes
15
17
  alias_method :url, :domain
16
18
 
17
19
  class << self
18
-
19
20
  def setup(params)
20
- params.each { |k,value| public_send("#{k}=", value) }
21
+ params.each { |k, value| public_send("#{k}=", value) }
21
22
  end
22
23
 
23
- def temp(domain:, token:, api_version:, &block)
24
+ def temp(domain:, token:, api_version: ShopifyAPI::Base.api_version, &block)
24
25
  session = new(domain: domain, token: token, api_version: api_version)
25
26
 
26
27
  with_session(session, &block)
@@ -28,12 +29,17 @@ module ShopifyAPI
28
29
 
29
30
  def with_session(session, &_block)
30
31
  original_session = extract_current_session
32
+ original_user = ShopifyAPI::Base.user
33
+ original_password = ShopifyAPI::Base.password
31
34
 
32
35
  begin
36
+ ShopifyAPI::Base.clear_session
33
37
  ShopifyAPI::Base.activate_session(session)
34
38
  yield
35
39
  ensure
36
40
  ShopifyAPI::Base.activate_session(original_session)
41
+ ShopifyAPI::Base.user = original_user
42
+ ShopifyAPI::Base.password = original_password
37
43
  end
38
44
  end
39
45
 
@@ -51,7 +57,7 @@ module ShopifyAPI
51
57
  # extract host, removing any username, password or path
52
58
  shop = URI.parse("https://#{domain}").host
53
59
  # extract subdomain of .myshopify.com
54
- if idx = shop.index(".")
60
+ if (idx = shop.index("."))
55
61
  shop = shop.slice(0, idx)
56
62
  end
57
63
  return nil if shop.empty?
@@ -62,20 +68,17 @@ module ShopifyAPI
62
68
 
63
69
  def validate_signature(params)
64
70
  params = (params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params).with_indifferent_access
65
- return false unless signature = params[:hmac]
71
+ return false unless (signature = params[:hmac])
66
72
 
67
- calculated_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new(), secret, encoded_params_for_signature(params))
73
+ calculated_signature = OpenSSL::HMAC.hexdigest(
74
+ OpenSSL::Digest.new('SHA256'), secret, ShopifyAPI::HmacParams.encode(params)
75
+ )
68
76
 
69
77
  Rack::Utils.secure_compare(calculated_signature, signature)
70
78
  end
71
79
 
72
80
  private
73
81
 
74
- def encoded_params_for_signature(params)
75
- params = params.except(:signature, :hmac, :action, :controller)
76
- params.map{|k,v| "#{URI.escape(k.to_s, '&=%')}=#{URI.escape(v.to_s, '&%')}"}.sort.join('&')
77
- end
78
-
79
82
  def extract_current_session
80
83
  site = ShopifyAPI::Base.site.to_s
81
84
  token = ShopifyAPI::Base.headers['X-Shopify-Access-Token']
@@ -84,23 +87,26 @@ module ShopifyAPI
84
87
  end
85
88
  end
86
89
 
87
- def initialize(domain:, token:, api_version:, extra: {})
90
+ def initialize(domain:, token:, access_scopes: nil, api_version: ShopifyAPI::Base.api_version, extra: {})
88
91
  self.domain = self.class.prepare_domain(domain)
89
92
  self.api_version = api_version
90
93
  self.token = token
94
+ self.access_scopes = access_scopes
91
95
  self.extra = extra
92
96
  end
93
97
 
94
98
  def create_permission_url(scope, redirect_uri, options = {})
95
- params = { client_id: api_key, scope: scope.join(','), redirect_uri: redirect_uri }
99
+ params = { client_id: api_key, scope: ShopifyAPI::ApiAccess.new(scope).to_s, redirect_uri: redirect_uri }
96
100
  params[:state] = options[:state] if options[:state]
101
+ params["grant_options[]".to_sym] = options[:grant_options] if options[:grant_options]
97
102
  construct_oauth_url("authorize", params)
98
103
  end
99
104
 
100
105
  def request_token(params)
101
106
  return token if token
102
107
 
103
- unless self.class.validate_signature(params) && params[:timestamp].to_i > 24.hours.ago.utc.to_i
108
+ twenty_four_hours_ago = Time.now.utc.to_i - SECONDS_IN_A_DAY
109
+ unless self.class.validate_signature(params) && params[:timestamp].to_i > twenty_four_hours_ago
104
110
  raise ShopifyAPI::ValidationException, "Invalid Signature: Possible malicious login"
105
111
  end
106
112
 
@@ -109,12 +115,12 @@ module ShopifyAPI
109
115
  self.extra = JSON.parse(response.body)
110
116
  self.token = extra.delete('access_token')
111
117
 
112
- if expires_in = extra.delete('expires_in')
118
+ if (expires_in = extra.delete('expires_in'))
113
119
  extra['expires_at'] = Time.now.utc.to_i + expires_in
114
120
  end
115
121
  token
116
122
  else
117
- raise RuntimeError, response.msg
123
+ raise response.msg
118
124
  end
119
125
  end
120
126
 
@@ -127,7 +133,11 @@ module ShopifyAPI
127
133
  end
128
134
 
129
135
  def api_version=(version)
130
- @api_version = ApiVersion::NullVersion.matches?(version) ? ApiVersion::NullVersion : ApiVersion.find_version(version)
136
+ @api_version = if ApiVersion::NullVersion.matches?(version)
137
+ ApiVersion::NullVersion
138
+ else
139
+ ApiVersion.find_version(version)
140
+ end
131
141
  end
132
142
 
133
143
  def valid?
@@ -149,10 +159,31 @@ module ShopifyAPI
149
159
  expires_in <= 0
150
160
  end
151
161
 
162
+ def hash
163
+ state.hash
164
+ end
165
+
166
+ def ==(other)
167
+ self.class == other.class && state == other.state
168
+ end
169
+
170
+ alias_method :eql?, :==
171
+
172
+ protected
173
+
174
+ def state
175
+ [domain, token, api_version, extra]
176
+ end
177
+
152
178
  private
153
179
 
180
+ def access_scopes=(access_scopes)
181
+ return unless access_scopes
182
+ @access_scopes = ShopifyAPI::ApiAccess.new(access_scopes)
183
+ end
184
+
154
185
  def parameterize(params)
155
- URI.escape(params.collect { |k, v| "#{k}=#{v}" }.join('&'))
186
+ URI.encode_www_form(params)
156
187
  end
157
188
 
158
189
  def access_token_request(code)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyAPI
2
- VERSION = "9.0.4"
3
+ VERSION = "9.5"
3
4
  end
data/lib/shopify_api.rb CHANGED
@@ -1,4 +1,5 @@
1
- $:.unshift File.dirname(__FILE__)
1
+ # frozen_string_literal: true
2
+ $:.unshift(File.dirname(__FILE__))
2
3
  require 'active_resource'
3
4
  require 'active_support/core_ext/class/attribute_accessors'
4
5
  require 'digest/md5'
@@ -20,6 +21,8 @@ require 'shopify_api/metafields'
20
21
  require 'shopify_api/countable'
21
22
  require 'shopify_api/resources'
22
23
  require 'shopify_api/session'
24
+ require 'shopify_api/hmac_params'
25
+ require 'shopify_api/api_access'
23
26
  require 'shopify_api/message_enricher'
24
27
  require 'shopify_api/connection'
25
28
  require 'shopify_api/pagination_link_headers'
@@ -31,3 +34,8 @@ if ShopifyAPI::Base.respond_to?(:connection_class)
31
34
  else
32
35
  require 'active_resource/connection_ext'
33
36
  end
37
+
38
+ if ENV["SHOPIFY_LOG_PATH"]
39
+ ActiveResource::Base.logger = Logger.new(ENV["SHOPIFY_LOG_PATH"])
40
+ ActiveResource::DetailedLogSubscriber.attach_to(:active_resource_detailed)
41
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ class VerifyDocs
3
+ def self.call
4
+ readme_content = File.read("README.md").lines[2..-1]
5
+ docs_content = File.read("docs/index.md").lines[4..-1]
6
+ readme_content == docs_content
7
+ end
8
+ end
data/service.yml CHANGED
@@ -1,8 +1,5 @@
1
1
  audience: partner
2
2
  classification: library
3
- org_line: App & Partner Platform
4
- owners:
5
- - Shopify/app-partner-dev-tools-education
6
3
  slack_channels:
7
- - dev-tools-education
8
- - api-patterns-team
4
+ - core-build-learn
5
+ - help-api-patterns
data/shopify_api.gemspec CHANGED
@@ -1,4 +1,5 @@
1
- $:.push File.expand_path("../lib", __FILE__)
1
+ # frozen_string_literal: true
2
+ $:.push(File.expand_path("../lib", __FILE__))
2
3
  require "shopify_api/version"
3
4
 
4
5
  Gem::Specification.new do |s|
@@ -7,7 +8,12 @@ Gem::Specification.new do |s|
7
8
  s.author = "Shopify"
8
9
 
9
10
  s.summary = %q{The Shopify API gem is a lightweight gem for accessing the Shopify admin REST web services}
10
- s.description = %q{The Shopify API gem allows Ruby developers to programmatically access the admin section of Shopify stores. The API is implemented as JSON or XML over HTTP using all four verbs (GET/POST/PUT/DELETE). Each resource, like Order, Product, or Collection, has its own URL and is manipulated in isolation.}
11
+ s.description = <<~HERE
12
+ The Shopify API gem allows Ruby developers to programmatically access the admin
13
+ section of Shopify stores. The API is implemented as JSON or XML over HTTP using
14
+ all four verbs (GET/POST/PUT/DELETE). Each resource, like Order, Product, or
15
+ Collection, has its own URL and is manipulated in isolation.
16
+ HERE
11
17
  s.email = %q{developers@jadedpixel.com}
12
18
  s.homepage = %q{http://www.shopify.com/partners/apps}
13
19
 
@@ -15,11 +21,10 @@ Gem::Specification.new do |s|
15
21
 
16
22
  s.extra_rdoc_files = [
17
23
  "LICENSE",
18
- "README.md"
24
+ "README.md",
19
25
  ]
20
26
  s.files = `git ls-files`.split("\n")
21
27
  s.test_files = `git ls-files -- {test}/*`.split("\n")
22
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
28
 
24
29
  s.rdoc_options = ["--charset=UTF-8"]
25
30
  s.summary = %q{ShopifyAPI is a lightweight gem for accessing the Shopify admin REST web services}
@@ -30,13 +35,14 @@ Gem::Specification.new do |s|
30
35
  s.add_runtime_dependency("activeresource", ">= 4.1.0", "< 6.0.0")
31
36
  s.add_runtime_dependency("rack")
32
37
  s.add_runtime_dependency("graphql-client")
38
+ s.add_runtime_dependency("webrick")
33
39
 
34
- s.add_development_dependency("mocha", ">= 0.9.8")
40
+ s.add_development_dependency("mocha", ">= 1.4.0")
35
41
  s.add_development_dependency("webmock")
36
- s.add_development_dependency("minitest", ">= 4.0")
42
+ s.add_development_dependency("minitest", ">= 5.14")
37
43
  s.add_development_dependency("rake")
38
44
  s.add_development_dependency("timecop")
39
- s.add_development_dependency("rubocop")
45
+ s.add_development_dependency("rubocop-shopify")
40
46
  s.add_development_dependency("pry")
41
47
  s.add_development_dependency("pry-byebug")
42
48
  end
@@ -1,19 +1,20 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  class AccessTokenTest < Test::Unit::TestCase
4
-
5
5
  def test_delegate_access_token
6
- fake "access_tokens/delegate.json?delegate_access_scope%5B%5D=write_orders&" \
6
+ fake(
7
+ "access_tokens/delegate.json?delegate_access_scope%5B%5D=write_orders&" \
7
8
  "delegate_access_scope%5B%5D=read_products&expires_in=",
8
9
  method: :post,
9
10
  status: 201,
10
11
  body: load_fixture('access_token_delegate'),
11
12
  extension: false
12
-
13
+ )
13
14
  delegate_scope = ['write_orders', 'read_products']
14
15
  token = ShopifyAPI::AccessToken.delegate(delegate_scope)
15
16
 
16
- assert_equal 'abracadabra', token.access_token
17
- assert_equal 'write_orders,read_products', token.scope
17
+ assert_equal('abracadabra', token.access_token)
18
+ assert_equal('write_orders,read_products', token.scope)
18
19
  end
19
20
  end
@@ -1,19 +1,19 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  module ActiveResource
4
5
  class JsonErrorsTest < Test::Unit::TestCase
5
-
6
6
  def test_parsing_of_error_json_hash
7
7
  @model = ShopifyAPI::Order.new
8
- @model.errors.from_json({errors: {name: ['missing']}}.to_json)
9
- assert_equal ['missing'], @model.errors[:name]
8
+ @model.errors.from_json({ errors: { name: ['missing'] } }.to_json)
9
+ assert_equal(['missing'], @model.errors[:name])
10
10
  end
11
11
 
12
12
  def test_parsing_of_error_json_plain_string
13
13
  @model = ShopifyAPI::Order.new
14
- @model.errors.from_json({errors: 'some generic error'}.to_json)
15
- assert_equal ['some generic error'], @model.errors[:base]
16
- assert_equal 'some generic error', @model.errors.full_messages.to_sentence
14
+ @model.errors.from_json({ errors: 'some generic error' }.to_json)
15
+ assert_equal(['some generic error'], @model.errors[:base])
16
+ assert_equal('some generic error', @model.errors.full_messages.to_sentence)
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+ require 'test_helper'
3
+
4
+ class ApiAccessTest < Minitest::Test
5
+ def test_write_is_the_same_access_as_read_write_on_the_same_resource
6
+ read_write_orders = ShopifyAPI::ApiAccess.new(%w(read_orders write_orders))
7
+ write_orders = ShopifyAPI::ApiAccess.new(%w(write_orders))
8
+
9
+ assert_equal write_orders, read_write_orders
10
+ end
11
+
12
+ def test_write_is_the_same_access_as_read_write_on_the_same_unauthenticated_resource
13
+ unauthenticated_read_write_orders = ShopifyAPI::ApiAccess.new(%w(unauthenticated_read_orders unauthenticated_write_orders))
14
+ unauthenticated_write_orders = ShopifyAPI::ApiAccess.new(%w(unauthenticated_write_orders))
15
+
16
+ assert_equal unauthenticated_write_orders, unauthenticated_read_write_orders
17
+ end
18
+
19
+ def test_read_is_not_the_same_as_read_write_on_the_same_resource
20
+ read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
21
+ read_write_orders = ShopifyAPI::ApiAccess.new(%w(write_orders read_orders))
22
+
23
+ refute_equal read_write_orders, read_orders
24
+ end
25
+
26
+ def test_two_different_resources_are_not_equal
27
+ read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
28
+ read_products = ShopifyAPI::ApiAccess.new(%w(read_products))
29
+
30
+ refute_equal read_orders, read_products
31
+ end
32
+
33
+ def test_two_identical_scopes_are_equal
34
+ read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
35
+ read_orders_identical = ShopifyAPI::ApiAccess.new(%w(read_orders))
36
+
37
+ assert_equal read_orders_identical, read_orders
38
+ end
39
+
40
+ def test_unauthenticated_is_not_implied_by_authenticated_access
41
+ unauthenticated_orders = ShopifyAPI::ApiAccess.new(%w(unauthenticated_read_orders))
42
+ authenticated_read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
43
+ authenticated_write_orders = ShopifyAPI::ApiAccess.new(%w(write_orders))
44
+
45
+ refute_equal unauthenticated_orders, authenticated_read_orders
46
+ refute_equal unauthenticated_orders, authenticated_write_orders
47
+ end
48
+
49
+ def test_scopes_covers_is_truthy_for_same_scopes
50
+ read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
51
+ read_orders_identical = ShopifyAPI::ApiAccess.new(%w(read_orders))
52
+
53
+ assert read_orders.covers?(read_orders_identical)
54
+ end
55
+
56
+ def test_covers_is_falsy_for_different_scopes
57
+ read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
58
+ read_products = ShopifyAPI::ApiAccess.new(%w(read_products))
59
+
60
+ refute read_orders.covers?(read_products)
61
+ end
62
+
63
+ def test_covers_is_truthy_for_read_when_the_set_has_read_write
64
+ write_products = ShopifyAPI::ApiAccess.new(%w(write_products))
65
+ read_products = ShopifyAPI::ApiAccess.new(%w(read_products))
66
+
67
+ assert write_products.covers?(read_products)
68
+ end
69
+
70
+ def test_covers_is_truthy_for_read_when_the_set_has_read_write_for_that_resource_and_others
71
+ write_products_and_orders = ShopifyAPI::ApiAccess.new(%w(write_products, write_orders))
72
+ read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
73
+
74
+ assert write_products_and_orders.covers?(read_orders)
75
+ end
76
+
77
+ def test_covers_is_truthy_for_write_when_the_set_has_read_write_for_that_resource_and_others
78
+ write_products_and_orders = ShopifyAPI::ApiAccess.new(%w(write_products, write_orders))
79
+ write_orders = ShopifyAPI::ApiAccess.new(%w(write_orders))
80
+
81
+ assert write_products_and_orders.covers?(write_orders)
82
+ end
83
+
84
+ def test_covers_is_truthy_for_subset_of_scopes
85
+ write_products_orders_customers = ShopifyAPI::ApiAccess.new(%w(write_products write_orders write_customers))
86
+ write_orders_products = ShopifyAPI::ApiAccess.new(%w(write_orders read_products))
87
+
88
+ assert write_products_orders_customers.covers?(write_orders_products)
89
+ end
90
+
91
+ def test_covers_is_falsy_for_sets_of_scopes_that_have_no_common_elements
92
+ write_products_orders_customers = ShopifyAPI::ApiAccess.new(%w(write_products write_orders write_customers))
93
+ write_images_read_content = ShopifyAPI::ApiAccess.new(%w(write_images read_content))
94
+
95
+ refute write_products_orders_customers.covers?(write_images_read_content)
96
+ end
97
+
98
+ def test_covers_is_falsy_for_sets_of_scopes_that_have_only_some_common_access
99
+ write_products_orders_customers = ShopifyAPI::ApiAccess.new(%w(write_products write_orders write_customers))
100
+ write_products_read_content = ShopifyAPI::ApiAccess.new(%w(write_products read_content))
101
+
102
+ refute write_products_orders_customers.covers?(write_products_read_content)
103
+ end
104
+
105
+ def test_duplicate_scopes_resolve_to_one_scope
106
+ read_orders_duplicated = ShopifyAPI::ApiAccess.new(%w(read_orders read_orders read_orders read_orders))
107
+ read_orders = ShopifyAPI::ApiAccess.new(%w(read_orders))
108
+
109
+ assert_equal read_orders, read_orders_duplicated
110
+ end
111
+
112
+ def test_to_s_outputs_scopes_as_a_comma_separated_list_without_implied_read_scopes
113
+ serialized_read_products_write_orders = "read_products,write_orders"
114
+ read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products read_orders write_orders))
115
+
116
+ assert_equal read_products_write_orders.to_s, serialized_read_products_write_orders
117
+ end
118
+
119
+ def test_to_a_outputs_scopes_as_an_array_of_strings_without_implied_read_scopes
120
+ serialized_read_products_write_orders = %w(write_orders read_products)
121
+ read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products read_orders write_orders))
122
+
123
+ assert_equal read_products_write_orders.to_a.sort, serialized_read_products_write_orders.sort
124
+ end
125
+
126
+ def test_creating_scopes_removes_extra_whitespace_from_scope_name_and_blank_scope_names
127
+ deserialized_read_products_write_orders = ShopifyAPI::ApiAccess.new([' read_products', ' ', 'write_orders '])
128
+ serialized_read_products_write_orders = deserialized_read_products_write_orders.to_s
129
+ expected_read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products write_orders))
130
+
131
+ assert_equal expected_read_products_write_orders, ShopifyAPI::ApiAccess.new(serialized_read_products_write_orders)
132
+ end
133
+
134
+ def test_creating_scopes_from_a_string_works_with_a_comma_separated_list
135
+ deserialized_read_products_write_orders = ShopifyAPI::ApiAccess.new("read_products,write_orders")
136
+ serialized_read_products_write_orders = deserialized_read_products_write_orders.to_s
137
+ expected_read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products write_orders))
138
+
139
+ assert_equal expected_read_products_write_orders, ShopifyAPI::ApiAccess.new(serialized_read_products_write_orders)
140
+ end
141
+
142
+ def test_using_to_s_from_one_scopes_to_construct_another_will_be_equal
143
+ read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products write_orders))
144
+
145
+ assert_equal read_products_write_orders, ShopifyAPI::ApiAccess.new(read_products_write_orders.to_s)
146
+ end
147
+
148
+ def test_using_to_a_from_one_scopes_to_construct_another_will_be_equal
149
+ read_products_write_orders = ShopifyAPI::ApiAccess.new(%w(read_products write_orders))
150
+
151
+ assert_equal read_products_write_orders, ShopifyAPI::ApiAccess.new(read_products_write_orders.to_a)
152
+ end
153
+ end
@@ -45,7 +45,7 @@ class ApiVersionTest < Test::Unit::TestCase
45
45
  test "find_version does raise when coercing a string if no versions are defined when version_lookup_mode is :raise_on_unknown" do
46
46
  refute ShopifyAPI::ApiVersion.versions['made up version']
47
47
  ShopifyAPI::ApiVersion.version_lookup_mode = :raise_on_unknown
48
- assert_raises ShopifyAPI::ApiVersion::UnknownVersion do
48
+ assert_raises(ShopifyAPI::ApiVersion::UnknownVersion) do
49
49
  ShopifyAPI::ApiVersion.find_version('made up version')
50
50
  end
51
51
  end
@@ -54,7 +54,7 @@ class ApiVersionTest < Test::Unit::TestCase
54
54
  ShopifyAPI::ApiVersion.clear_known_versions
55
55
  ShopifyAPI::ApiVersion.version_lookup_mode = :define_on_unknown
56
56
  assert_equal :define_on_unknown, ShopifyAPI::ApiVersion.version_lookup_mode
57
- assert_raises ArgumentError do
57
+ assert_raises(ArgumentError) do
58
58
  ShopifyAPI::ApiVersion.find_version(ShopifyAPI::ApiVersion::NullVersion)
59
59
  end
60
60
  end
@@ -108,7 +108,6 @@ class ApiVersionTest < Test::Unit::TestCase
108
108
  }
109
109
  )
110
110
  silence_warnings do
111
-
112
111
  refute_equal(
113
112
  ShopifyAPI::ApiVersion.new(handle: '2019-01'),
114
113
  ShopifyAPI::ApiVersion.latest_stable_version
@@ -151,6 +150,7 @@ class ApiVersionTest < Test::Unit::TestCase
151
150
 
152
151
  class TestApiVersion < ShopifyAPI::ApiVersion
153
152
  def initialize(name)
153
+ super(name)
154
154
  @version_name = name
155
155
  end
156
156
  end