quicktravel_client 3.7.0 → 4.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +5 -4
  4. data/CHANGELOG.md +30 -0
  5. data/gemfiles/rails6.gemfile +8 -0
  6. data/lib/quick_travel.rb +2 -0
  7. data/lib/quick_travel/adapter.rb +13 -25
  8. data/lib/quick_travel/booking.rb +6 -5
  9. data/lib/quick_travel/cache.rb +24 -5
  10. data/lib/quick_travel/checkout.rb +1 -1
  11. data/lib/quick_travel/client.rb +9 -1
  12. data/lib/quick_travel/init_from_hash.rb +4 -0
  13. data/lib/quick_travel/package.rb +13 -0
  14. data/lib/quick_travel/passenger_type.rb +1 -1
  15. data/lib/quick_travel/product_configuration.rb +1 -1
  16. data/lib/quick_travel/products/base.rb +4 -4
  17. data/lib/quick_travel/reservation.rb +1 -1
  18. data/lib/quick_travel/resource.rb +1 -1
  19. data/lib/quick_travel/route.rb +1 -1
  20. data/lib/quick_travel/route_stop.rb +8 -1
  21. data/lib/quick_travel/vehicle_type.rb +1 -1
  22. data/lib/quick_travel/version.rb +1 -1
  23. data/quicktravel_client.gemspec +5 -5
  24. data/spec/adapter_spec.rb +34 -3
  25. data/spec/booking_spec.rb +10 -0
  26. data/spec/checkout_spec.rb +0 -182
  27. data/spec/clients_spec.rb +42 -0
  28. data/spec/discounts_spec.rb +21 -21
  29. data/spec/package_spec.rb +24 -0
  30. data/spec/passenger_type_spec.rb +1 -1
  31. data/spec/product_configuration_spec.rb +11 -0
  32. data/spec/reservation_spec.rb +20 -0
  33. data/spec/spec_helper.rb +6 -3
  34. data/spec/support/cassettes/accommodation_reserve.yml +12 -4
  35. data/spec/support/cassettes/basic_product_scheduled_trips.yml +3 -1
  36. data/spec/support/cassettes/basic_product_scheduled_trips_multi_sector.yml +3 -1
  37. data/spec/support/cassettes/basic_product_scheduled_trips_unbookable.yml +3 -1
  38. data/spec/support/cassettes/booking_activate.yml +6 -2
  39. data/spec/support/cassettes/booking_cancel.yml +6 -2
  40. data/spec/support/cassettes/booking_create.yml +3 -1
  41. data/spec/support/cassettes/booking_create_accommodation.yml +3 -1
  42. data/spec/support/cassettes/booking_documents.yml +3 -1
  43. data/spec/support/cassettes/booking_non_existant.yml +3 -1
  44. data/spec/support/cassettes/booking_price_changes.yml +3 -1
  45. data/spec/support/cassettes/booking_show.yml +3 -1
  46. data/spec/support/cassettes/booking_update.yml +6 -2
  47. data/spec/support/cassettes/booking_with_comments.yml +119 -0
  48. data/spec/support/cassettes/booking_with_documents.yml +6 -2
  49. data/spec/support/cassettes/booking_with_nested_attributes.yml +9 -3
  50. data/spec/support/cassettes/booking_with_price_changes.yml +3 -1
  51. data/spec/support/cassettes/checkout_client_token.yml +3 -1
  52. data/spec/support/cassettes/client_templates.yml +120 -0
  53. data/spec/support/cassettes/countries.yml +3 -1
  54. data/spec/support/cassettes/country_all.yml +3 -1
  55. data/spec/support/cassettes/create_reservation_fail.yml +3 -1
  56. data/spec/support/cassettes/create_reservation_with_booking.yml +3 -1
  57. data/spec/support/cassettes/locations.yml +3 -1
  58. data/spec/support/cassettes/opal_modern_pay_failed_booking.yml +3 -1
  59. data/spec/support/cassettes/opal_modern_pay_failed_create.yml +3 -1
  60. data/spec/support/cassettes/opal_modern_pay_failed_update.yml +3 -1
  61. data/spec/support/cassettes/opal_modern_pay_successful_booking.yml +3 -1
  62. data/spec/support/cassettes/opal_modern_pay_successful_response.yml +3 -1
  63. data/spec/support/cassettes/opal_modern_pay_successful_update_response.yml +3 -1
  64. data/spec/support/cassettes/opal_pay.yml +3 -1
  65. data/spec/support/cassettes/opal_pay_booking.yml +3 -1
  66. data/spec/support/cassettes/package_show.yml +83 -0
  67. data/spec/support/cassettes/package_show_product_type.yml +80 -0
  68. data/spec/support/cassettes/passenger_all.yml +3 -1
  69. data/spec/support/cassettes/payment_info.yml +3 -1
  70. data/spec/support/cassettes/price_quote.yml +6 -2
  71. data/spec/support/cassettes/product_date_range_bookability.yml +3 -1
  72. data/spec/support/cassettes/product_show.yml +3 -1
  73. data/spec/support/cassettes/product_show_as_agent.yml +3 -1
  74. data/spec/support/cassettes/product_type_all.yml +3 -1
  75. data/spec/support/cassettes/product_type_resource_categories.yml +3 -1
  76. data/spec/support/cassettes/product_type_resource_categories_tickets.yml +3 -1
  77. data/spec/support/cassettes/product_type_routes.yml +3 -1
  78. data/spec/support/cassettes/property.yml +3 -1
  79. data/spec/support/cassettes/property_types.yml +3 -1
  80. data/spec/support/cassettes/region_show.yml +3 -1
  81. data/spec/support/cassettes/reservation_resource.yml +3 -1
  82. data/spec/support/cassettes/reservation_with_extra_picks.yml +9 -3
  83. data/spec/support/cassettes/resource_category_all.yml +3 -1
  84. data/spec/support/cassettes/resource_category_all_for_product_type_8.yml +3 -1
  85. data/spec/support/cassettes/resource_fare_bases.yml +3 -1
  86. data/spec/support/cassettes/resource_show.yml +3 -1
  87. data/spec/support/cassettes/resource_with_price.yml +3 -1
  88. data/spec/support/cassettes/settings_basic.yml +3 -1
  89. data/spec/support/cassettes/tenant_switcher.yml +6 -2
  90. data/spec/support/cassettes/wrong_url.yml +3 -1
  91. data/spec/support/coverage_loader.rb +1 -1
  92. metadata +35 -29
  93. data/gemfiles/rails4.gemfile +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b60bf00c1c2d71210f2d1f462986abba8859b7acd216b13d68c8d4ad2c475e9d
4
- data.tar.gz: 79cb1bad6bdd5a2178577824c1196ff9e5728beda74e5474bac7f9e53e63c053
3
+ metadata.gz: 1bba05a60170b4b63f097183048b5c1bc8c73192631268c47bad3bf75999851c
4
+ data.tar.gz: 637a0c94cd80217ddbe580c77ad5ef6ac2ef99d1465c870762f1686660967fcb
5
5
  SHA512:
6
- metadata.gz: 205130ad17115f1f0491351af5b76242a8b67a1f384912708fa4ebad3211d428e83284c41aa8c024d82b1004371415848d47a7fd44bd86fcd375952c5eb68737
7
- data.tar.gz: b0cca6ec2c7b5dd695112ba71d112f7593c8bc174070c380aade2d3ddd5aed23b9a44eec3796c207b65ef3d87c6f6bd2f02ced5c3248e58a505907ddfcb2c1f4
6
+ metadata.gz: 295a00c7ed151281f4a406fea28804ad3318f55ba998350c8dd887666dc11f79038e0c381bc16e9f9f8bc2dfb07cc6fd3085ebeea02f9fa3ae4fa84f26926a38
7
+ data.tar.gz: aee796df5056e802266ebe1962ca9b882992cd8c77066ec27c97e0b36ae045ad2827485b19f8893dac75a61fe3f6a692e0d8fa4af751b4173b442c8a1e1e1ee0
@@ -1 +1 @@
1
- 2.5.0
1
+ 2.7.1
@@ -1,13 +1,14 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2
4
- - 2.3
5
- - 2.4
6
3
  - 2.5
4
+ - 2.6
5
+ - 2.7
6
+ before_install:
7
+ - gem install bundler
7
8
  script: bundle && bundle exec rake spec
8
9
  gemfile:
9
- - gemfiles/rails4.gemfile
10
10
  - gemfiles/rails5.gemfile
11
+ - gemfiles/rails6.gemfile
11
12
  notifications:
12
13
  email:
13
14
  - support@travellink.com.au
@@ -3,6 +3,36 @@ All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](http://semver.org/).
4
4
  This changelog adheres to [Keep a CHANGELOG](http://keepachangelog.com/).
5
5
 
6
+ ## Unreleased
7
+
8
+ ## [4.1.0]
9
+ ### Added
10
+ - [TT-7788] Add API namespace to vehicle_types endpoint
11
+
12
+ ## [4.0.0]
13
+ - [TT-7385] Update Money dependency, test against Ruby 2.7 / Rails 6
14
+
15
+ ## [3.9.0]
16
+ ### Added
17
+ - [DC-3115] Add customer comments method in booking
18
+ - [DC-2942] Add package class to support quantity based package
19
+
20
+ ## [3.8.1]
21
+ ### Changed
22
+ - [DC-3033] Reverse changes in checkout class to fix polipay redirection in EcomEngine
23
+
24
+ ## [3.8.0]
25
+ ### Added
26
+ - [DC-1794] Update httparty to allow caching the whole response, also add namespace to cache key
27
+ - [DC-1418] Port NRMA portal availability cache back to EcomEngine
28
+ - [TT-4850] Added client templates
29
+ - [DC-1692] Fix error that can be thrown when checking if a product configuration can be priced
30
+ - [TT-6246] Remove Booking.delete_reservations method
31
+
32
+ ### Changed
33
+ - [DC-2997] Remove API key from body, and move it to header
34
+ - [DC-1767] include long/lat changes to Stop
35
+
6
36
  ## [3.7.0]
7
37
  ### Added
8
38
  - [DC-1437] Add relationship accesssor methods
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+ gemspec :path => '../'
3
+
4
+ group :development, :test do
5
+ gem 'activesupport', '~> 6.0'
6
+ gem 'activerecord', '~> 6.0'
7
+ gem 'actionpack', '~> 6.0'
8
+ end
@@ -2,6 +2,7 @@
2
2
  #
3
3
  module QuickTravel
4
4
  require 'active_support' # for .try, etc.
5
+ require 'money_extensions'
5
6
 
6
7
  require 'quick_travel/cache'
7
8
  require 'quick_travel/config'
@@ -33,6 +34,7 @@ module QuickTravel
33
34
  require 'quick_travel/drop_off_location'
34
35
  require 'quick_travel/drop_off_option'
35
36
  require 'quick_travel/location'
37
+ require 'quick_travel/package'
36
38
  require 'quick_travel/party'
37
39
  require 'quick_travel/passenger'
38
40
  require 'quick_travel/passenger_type'
@@ -65,7 +65,7 @@ module QuickTravel
65
65
  def self.all(opts = {})
66
66
  if lookup
67
67
  cache_name = ["#{name}.all-attrs", opts.to_param].reject(&:blank?).join('?')
68
- find_all!("#{api_base}.json", opts.merge(cache: cache_name))
68
+ find_all!("#{api_base}.json", opts.merge(cache_key: cache_name, cache_options: { disable_namespacing: true }))
69
69
  else
70
70
  find_all!("#{api_base}.json", opts)
71
71
  end
@@ -80,10 +80,6 @@ module QuickTravel
80
80
  put_and_validate("#{api_base}/#{id}.json", options)
81
81
  end
82
82
 
83
- def to_hash
84
- instance_values
85
- end
86
-
87
83
  def to_s
88
84
  if defined? @to_s
89
85
  @to_s
@@ -102,20 +98,14 @@ module QuickTravel
102
98
  end
103
99
 
104
100
  def self.find_all!(request_path, opts = {})
105
- response = if opts.key? :cache
106
- QuickTravel::Cache.cache(opts[:cache], opts[:cache_options]) {
107
- get_and_validate(request_path, opts.except(:cache, :cache_options))
108
- }
109
- else
110
- get_and_validate(request_path, opts, return_response_object: true)
111
- end
112
- full_response = response.respond_to? :parsed_response
113
- parsed_response = full_response ? response.parsed_response : response
101
+ response = QuickTravel::Cache.cache(opts[:cache_key], opts[:cache_options]) {
102
+ get_and_validate(request_path, opts.except(:cache_key, :cache_options), return_response_object: true)
103
+ }
114
104
 
115
- deserializer = Deserializer.new(parsed_response)
105
+ deserializer = Deserializer.new(response.parsed_response)
116
106
  objects = Array.wrap(deserializer.extract_under_root(self))
117
107
 
118
- if full_response && response.headers['pagination'].present?
108
+ if response.headers['pagination'].present?
119
109
  pagination_headers = ::JSON.parse(response.headers['pagination'])
120
110
  WillPaginate::Collection.create(pagination_headers['current_page'], pagination_headers['per_page'], pagination_headers['total_entries']) do |pager|
121
111
  pager.replace(objects)
@@ -202,7 +192,11 @@ module QuickTravel
202
192
  end
203
193
 
204
194
  def self.call_and_validate(http_method, path, query = {}, opts = {})
205
- Api.call_and_validate(http_method, path, query, opts)
195
+ response = QuickTravel::Cache.cache(opts[:cache_key], opts[:cache_options]) {
196
+ response_object = Api.call_and_validate(http_method, path, query, opts.except(:cache_key, :cache_options))
197
+ response_object = response_object.parsed_response if !opts[:cache_key] and !opts[:return_response_object]
198
+ response_object
199
+ }
206
200
  end
207
201
 
208
202
  def self.base_uri(uri = nil)
@@ -215,12 +209,11 @@ module QuickTravel
215
209
 
216
210
  def self.call_and_validate(http_method, path, query = {}, opts = {})
217
211
  http_params = opts.clone
218
- return_response_object = http_params.delete(:return_response_object)
219
-
220
212
  # Set default token
221
213
  http_params[:query] ||= FilterQuery.new(query).call
222
214
  http_params[:headers] ||= {}
223
215
  http_params[:headers]['Content-length'] = '0' if http_params[:body].blank?
216
+ http_params[:headers]['x-api-key'] = QuickTravel.config.access_key
224
217
  expect = http_params.delete(:expect)
225
218
 
226
219
  # Use :body instead of :query for put/post.
@@ -230,7 +223,6 @@ module QuickTravel
230
223
  if [:put, :post].include?(http_method.to_sym)
231
224
  http_params[:body].merge!(http_params.delete(:query))
232
225
  end
233
- http_params[:body][:access_key] = QuickTravel.config.access_key
234
226
  http_params[:follow_redirects] = false
235
227
 
236
228
  begin
@@ -256,11 +248,7 @@ module QuickTravel
256
248
 
257
249
  validate!(response)
258
250
 
259
- if return_response_object
260
- response
261
- else
262
- response.parsed_response
263
- end
251
+ response
264
252
  end
265
253
 
266
254
  # Do standard validations on response
@@ -13,7 +13,7 @@ module QuickTravel
13
13
  end
14
14
 
15
15
  def self.find_by_reference(reference)
16
- find_all!("#{api_base}/reference/#{URI.escape(reference)}.json").first
16
+ find_all!("#{api_base}/reference/#{URI.encode_www_form_component(reference)}.json").first
17
17
  end
18
18
 
19
19
  def documents(regenerate = false)
@@ -157,10 +157,6 @@ module QuickTravel
157
157
  reserve(:packages, options)
158
158
  end
159
159
 
160
- def delete_reservations
161
- delete_and_validate("#{api_base}/#{@id}/reservations")
162
- end
163
-
164
160
  # Delete a reservation
165
161
  #
166
162
  # Returns current booking object after deleting the reservation
@@ -300,6 +296,11 @@ module QuickTravel
300
296
  Encrypt.access_key(@id.to_s)
301
297
  end
302
298
 
299
+ def customer_comments
300
+ comment = comments.detect{ |comment| comment['comment_type'] == 'customer' }
301
+ comment.presence.try(:[], 'text') || ''
302
+ end
303
+
303
304
  protected
304
305
 
305
306
  def reserve(url, options)
@@ -6,16 +6,27 @@ module QuickTravel
6
6
  end
7
7
  end
8
8
 
9
- def self.cache(key, cache_options = {})
9
+ def self.cache(key, cache_options = nil)
10
+ return yield unless key.present?
11
+ cache_options ||= {}
12
+ key = "#{@@namespace}_#{key}" unless cache_options[:disable_namespacing]
10
13
  cached_value = cache_store.read(key)
11
- return cached_value unless cached_value.nil?
14
+ return cached_value unless cache_empty?(cached_value)
12
15
  return nil unless block_given?
13
16
  cache_options ||= {}
14
17
  cache_options[:expires_in] = 1.day unless cache_options.key?(:expires_in)
15
18
  yield.tap { |value| cache_store.write(key, value, cache_options) }
16
19
  end
17
20
 
18
- def self.delete(key)
21
+ def self.cache_empty?(cached_value)
22
+ if cached_value.respond_to?(:body)
23
+ return cached_value.body.nil? || cached_value.body.empty?
24
+ end
25
+ cached_value.nil?
26
+ end
27
+
28
+ def self.delete(key, namespace = true)
29
+ key = "#{@@namespace}_#{key}" if namespace
19
30
  cache_store.delete(key)
20
31
  end
21
32
 
@@ -27,8 +38,16 @@ module QuickTravel
27
38
  @@cache_store
28
39
  end
29
40
 
30
- def self.cache_store=(store)
31
- @@cache_store = store
41
+ def self.cache_store=(session)
42
+ @@cache_store = session
43
+ end
44
+
45
+ def self.namespace
46
+ @@namespace
47
+ end
48
+
49
+ def self.namespace=(namespace)
50
+ @@namespace = namespace
32
51
  end
33
52
  end
34
53
  end
@@ -46,7 +46,7 @@ module QuickTravel
46
46
  # TODO Move to an external builder?
47
47
  def self.attributes_for
48
48
  attrs = yield
49
- attrs[:completed] = attrs['progress'] == 'completed' || attrs['successful']
49
+ attrs[:completed] = attrs['progress'] == 'completed'
50
50
  attrs[:successful] = attrs[:completed]
51
51
  attrs
52
52
  rescue AdapterError => e
@@ -1,7 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quick_travel/adapter'
1
4
  require 'quick_travel/init_from_hash'
2
5
 
3
6
  module QuickTravel
4
- class Client
7
+ class Client < Adapter
5
8
  include QuickTravel::InitFromHash
9
+ self.api_base = '/api/clients'
10
+
11
+ def templates
12
+ get_and_validate("/api/clients/#{id}/templates")
13
+ end
6
14
  end
7
15
  end
@@ -28,6 +28,10 @@ module QuickTravel
28
28
  end
29
29
  end
30
30
  end
31
+
32
+ def to_hash
33
+ instance_values
34
+ end
31
35
  end
32
36
 
33
37
  class Parser
@@ -0,0 +1,13 @@
1
+ require 'quick_travel/adapter'
2
+
3
+ module QuickTravel
4
+ class Package < Adapter
5
+ attr_reader :error_message
6
+
7
+ self.api_base = '/api/packages'
8
+
9
+ def product_type
10
+ QuickTravel::ProductType.find(product_type_id)
11
+ end
12
+ end
13
+ end
@@ -13,7 +13,7 @@ module QuickTravel
13
13
  end
14
14
 
15
15
  def self.pluralize(count, singular, plural = nil)
16
- "#{count || 0} " + ((count == 1 || count =~ /^1(\.0+)?$/) ? singular : (plural || singular.pluralize))
16
+ "#{count || 0} " + ((count == 1) ? singular : (plural || singular.pluralize))
17
17
  end
18
18
  end
19
19
  end
@@ -31,7 +31,7 @@ module QuickTravel
31
31
  end
32
32
 
33
33
  def priced?
34
- @product.pricing_details.present?
34
+ @product.try(:pricing_details).present?
35
35
  end
36
36
 
37
37
  def price
@@ -7,13 +7,13 @@ module QuickTravel
7
7
  bookable || exception_type == 'inventory'
8
8
  end
9
9
 
10
- def self.find(search_params = {})
11
- find_for_type(@reservation_for_type, search_params)
10
+ def self.find(search_params = {}, opts = {})
11
+ find_for_type(@reservation_for_type, search_params, opts)
12
12
  end
13
13
 
14
- def self.find_for_type(type, search_params = {})
14
+ def self.find_for_type(type, search_params = {}, opts = {})
15
15
  url = "/reservation_for/#{type}/find_services_for.json"
16
- product_maps = post_and_validate(url, search_params)
16
+ product_maps = post_and_validate(url, search_params, opts)
17
17
  product_maps.map { |product_map| new(product_map) }
18
18
  end
19
19
  end
@@ -42,7 +42,7 @@ module QuickTravel
42
42
  passenger_type_count = {}
43
43
  if passenger_splits.present?
44
44
  passenger_splits.each do |p|
45
- passenger = booking.find_passenger_by_id(p['consumer_id'])
45
+ passenger = booking.find_passenger_by_id(p.consumer_id)
46
46
 
47
47
  if passenger.present?
48
48
  passenger_type_count[passenger.passenger_type_id] ||= 0
@@ -13,7 +13,7 @@ module QuickTravel
13
13
 
14
14
  def self.all_with_price(opts)
15
15
  cache_key = GenerateCacheKey.new(name, opts).call
16
- find_all!("/api/resources/index_with_price.json", opts.merge(cache: cache_key))
16
+ find_all!("/api/resources/index_with_price.json", opts.merge(cache_key: cache_key))
17
17
  end
18
18
 
19
19
  def product_type
@@ -17,7 +17,7 @@ module QuickTravel
17
17
  # All routes for a given product type
18
18
  def self.all(product_type_id)
19
19
  find_all!("/product_types/#{product_type_id}/routes.json",
20
- cache: "QuickTravel::Route.all-#{product_type_id}-attrs")
20
+ cache_key: "QuickTravel::Route.all-#{product_type_id}-attrs")
21
21
  end
22
22
 
23
23
  def self.find(routes_list, route_id)
@@ -5,7 +5,14 @@ module QuickTravel
5
5
  include QuickTravel::InitFromHash
6
6
 
7
7
  def stop
8
- Stop.new({ id: stop_id, name: name, code: code, address: address })
8
+ Stop.new({
9
+ id: stop_id,
10
+ name: name,
11
+ code: code,
12
+ address: address,
13
+ longitude: longitude,
14
+ latitude: latitude
15
+ })
9
16
  end
10
17
  end
11
18
 
@@ -2,7 +2,7 @@ require 'quick_travel/adapter'
2
2
 
3
3
  module QuickTravel
4
4
  class VehicleType < Adapter
5
- self.api_base = '/vehicle_types'
5
+ self.api_base = '/api/vehicle_types'
6
6
  self.lookup = true
7
7
  end
8
8
  end
@@ -1,3 +1,3 @@
1
1
  module QuickTravel
2
- VERSION = '3.7.0'
2
+ VERSION = '4.1.0'
3
3
  end
@@ -16,14 +16,14 @@ Gem::Specification.new do |spec|
16
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
17
  spec.require_paths = ['lib']
18
18
 
19
- spec.add_dependency 'httparty', '0.15.7' # Bug in >16 relating to nested parameters
20
- spec.add_dependency 'json'
21
- spec.add_dependency 'activesupport', '>= 2.0.0'
19
+ spec.add_dependency 'httparty'
20
+ spec.add_dependency 'activesupport', '>= 5.0.0'
22
21
  spec.add_dependency 'facets'
23
- spec.add_dependency 'money', '>= 3.0', '< 6.0' # 6.0 starts to deprecate/split
22
+ spec.add_dependency 'money', '>= 6.0'
23
+ spec.add_dependency 'money_extensions', '>= 1.0'
24
24
  spec.add_dependency 'will_paginate'
25
25
 
26
- spec.add_development_dependency 'bundler', '~> 1.7'
26
+ spec.add_development_dependency 'bundler', '~> 2'
27
27
  spec.add_development_dependency 'rake'
28
28
  spec.add_development_dependency 'rspec'
29
29
  spec.add_development_dependency 'rspec-its'