Comparing changes
v0.2.16
→
v0.3.0
30 commits
24 files changed
Commits
Changed files (24)
lib
spec
lib/scim/kit/v2/attributable.rb
@@ -7,14 +7,21 @@ module Scim
module Attributable
include Enumerable
+ # Returns a hash of the generated dynamic attributes
+ # @return [Hash] the dynamic attributes keys by their name
def dynamic_attributes
@dynamic_attributes ||= {}.with_indifferent_access
end
+ # Defines dynamic attributes on the resource for the types provided
+ # @param resource [Scim::Kit::V2::Resource] the resource to attach dynamic attributes to.
+ # @param types [Array<Scim::Kit::V2::AttributeType>] the array of types
def define_attributes_for(resource, types)
types.each { |x| attribute(x, resource) }
end
+ # Assigns attribute values via the provided hash.
+ # @param attributes [Hash] The name/values to assign.
def assign_attributes(attributes = {})
attributes.each do |key, value|
next if key.to_sym == :schemas
@@ -27,10 +34,16 @@ module Scim
end
end
+ # Returns the attribute identified by the name.
+ # @param name [String] the name of the attribute to return
+ # @return [Scim::Kit::V2::Attribute] the attribute or {Scim::Kit::V2::UnknownAttribute}
def attribute_for(name)
dynamic_attributes[name.to_s.underscore] || UnknownAttribute.new(name)
end
+ # Returns the value associated with the attribute name
+ # @param name [String] the name of the attribute
+ # @return [Object] the value assigned to the attribute
def read_attribute(name)
attribute = attribute_for(name)
return attribute._value if attribute._type.multi_valued
@@ -38,6 +51,9 @@ module Scim
attribute._type.complex? ? attribute : attribute._value
end
+ # Assigns the value to the attribute with the given name
+ # @param name [String] the name of the attribute
+ # @param value [Object] the value to assign to the attribute
def write_attribute(name, value)
if value.is_a?(Hash)
attribute_for(name)&.assign_attributes(value)
@@ -46,6 +62,8 @@ module Scim
end
end
+ # yields each attribute to the provided block
+ # @param [Block] the block to yield each attribute to.
def each
dynamic_attributes.each do |_name, attribute|
yield attribute
lib/scim/kit/v2/attribute_type.rb
@@ -6,20 +6,14 @@ module Scim
# Represents a scim Attribute type
class AttributeType
include Templatable
- attr_accessor :canonical_values
- attr_accessor :case_exact
- attr_accessor :description
- attr_accessor :multi_valued
- attr_accessor :required
- attr_reader :mutability
- attr_reader :name, :type
- attr_reader :reference_types
- attr_reader :returned
- attr_reader :uniqueness
+ attr_accessor :canonical_values, :case_exact, :description
+ attr_accessor :multi_valued, :required
+ attr_reader :mutability, :name, :type, :attributes
+ attr_reader :reference_types, :returned, :uniqueness
def initialize(name:, type: :string)
@name = name.to_s.underscore
- @type = type.to_sym
+ @type = DATATYPES[type.to_sym] ? type.to_sym : (raise TYPE_ERROR)
@description = name
@multi_valued = false
@required = false
@@ -27,7 +21,7 @@ module Scim
@mutability = Mutability::READ_WRITE
@returned = Returned::DEFAULT
@uniqueness = Uniqueness::NONE
- raise ArgumentError, :type unless DATATYPES[@type]
+ @attributes = []
end
def mutability=(value)
@@ -54,10 +48,6 @@ module Scim
@reference_types = value
end
- def attributes
- @attributes ||= []
- end
-
def complex?
type_is?(:complex)
end
@@ -85,6 +75,19 @@ module Scim
complex? ? valid_complex?(value) : valid_simple?(value)
end
+ class << self
+ def from(hash)
+ x = new(name: hash[:name], type: hash[:type])
+ %i[
+ canonicalValues caseExact description multiValued mutability
+ referenceTypes required returned uniqueness
+ ].each do |y|
+ x.public_send("#{y.to_s.underscore}=", hash[y]) if hash.key?(y)
+ end
+ x
+ end
+ end
+
private
def coerce_single(value)
lib/scim/kit/v2/authentication_scheme.rb
@@ -32,15 +32,26 @@ module Scim
yield self if block_given?
end
- def self.build_for(type, primary: nil)
- defaults = DEFAULTS[type.to_sym] || {}
- new do |x|
- x.type = type
- x.primary = primary
- x.description = defaults[:description]
- x.documentation_uri = defaults[:documentation_uri]
- x.name = defaults[:name]
- x.spec_uri = defaults[:spec_uri]
+ class << self
+ def build_for(type, primary: nil)
+ defaults = DEFAULTS[type.to_sym] || {}
+ new do |x|
+ x.type = type
+ x.primary = primary
+ x.description = defaults[:description]
+ x.documentation_uri = defaults[:documentation_uri]
+ x.name = defaults[:name]
+ x.spec_uri = defaults[:spec_uri]
+ end
+ end
+
+ def from(hash)
+ x = build_for(hash[:type], primary: hash[:primary])
+ x.description = hash[:description]
+ x.documentation_uri = hash[:documentationUri]
+ x.name = hash[:name]
+ x.spec_uri = hash[:specUri]
+ x
end
end
end
lib/scim/kit/v2/configuration.rb
@@ -7,31 +7,32 @@ module Scim
class Configuration
# @private
class Builder
- def initialize
- @resource_types = {}
- @schemas = {}
+ attr_reader :configuration
+
+ def initialize(configuration)
+ @configuration = configuration
end
def service_provider_configuration(location:)
- @sp_config = ServiceProviderConfiguration.new(location: location)
- yield @sp_config
+ configuration.service_provider_configuration =
+ ServiceProviderConfiguration.new(location: location)
+ yield configuration.service_provider_configuration
end
def resource_type(id:, location:)
- @resource_types[id] ||= ResourceType.new(location: location)
- @resource_types[id].id = id
- yield @resource_types[id]
+ configuration.resource_types[id] ||=
+ ResourceType.new(location: location)
+ configuration.resource_types[id].id = id
+ yield configuration.resource_types[id]
end
def schema(id:, name:, location:)
- @schemas[id] ||= Schema.new(id: id, name: name, location: location)
- yield @schemas[id]
- end
-
- def apply_to(configuration)
- configuration.service_provider_configuration = @sp_config
- configuration.resource_types = @resource_types
- configuration.schemas = @schemas
+ configuration.schemas[id] ||= Schema.new(
+ id: id,
+ name: name,
+ location: location
+ )
+ yield configuration.schemas[id]
end
end
@@ -40,9 +41,41 @@ module Scim
attr_accessor :schemas
def initialize
- builder = Builder.new
- yield builder if block_given?
- builder.apply_to(self)
+ @resource_types = {}
+ @schemas = {}
+
+ yield Builder.new(self) if block_given?
+ end
+
+ def load_from(base_url)
+ uri = URI.join(base_url, 'ServiceProviderConfig')
+ self.service_provider_configuration =
+ ServiceProviderConfiguration.parse(client.get(uri).body)
+
+ load_items(base_url, 'Schemas', Schema, schemas)
+ load_items(base_url, 'ResourceTypes', ResourceType, resource_types)
+ end
+
+ private
+
+ def load_items(base_url, path, type, items)
+ response = client.get(URI.join(base_url, path), headers: headers)
+ hashes = JSON.parse(response.body, symbolize_names: true)
+ hashes.each do |hash|
+ item = type.from(hash)
+ items[item.id] = item
+ end
+ end
+
+ def client
+ @client ||= Net::Hippie::Client.new
+ end
+
+ def headers
+ {
+ 'Accept' => 'application/scim+json',
+ 'Content-Type' => 'application/scim+json'
+ }
end
end
end
lib/scim/kit/v2/meta.rb
@@ -21,6 +21,20 @@ module Scim
def disable_timestamps
@version = @created = @last_modified = nil
end
+
+ def self.from(hash)
+ meta = Meta.new(hash[:resourceType], hash[:location])
+ meta.created = parse_date(hash[:created])
+ meta.last_modified = parse_date(hash[:lastModified])
+ meta.version = hash[:version]
+ meta
+ end
+
+ def self.parse_date(date)
+ DateTime.parse(date).to_time
+ rescue StandardError
+ nil
+ end
end
end
end
lib/scim/kit/v2/mutability.rb
@@ -11,10 +11,13 @@ module Scim
WRITE_ONLY = 'writeOnly'
VALID = {
immutable: IMMUTABLE,
+ readOnly: READ_ONLY,
+ readWrite: READ_WRITE,
read_only: READ_ONLY,
read_write: READ_WRITE,
readonly: READ_ONLY,
readwrite: READ_WRITE,
+ writeOnly: WRITE_ONLY,
write_only: WRITE_ONLY,
writeonly: WRITE_ONLY
}.freeze
lib/scim/kit/v2/resource.rb
@@ -27,6 +27,10 @@ module Scim
yield self if block_given?
end
+ # Returns the current mode.
+ #
+ # @param type [Symbol] The mode `:server` or `:client`.
+ # @return [Boolean] Returns true if the resource matches the # type of mode
def mode?(type)
case type.to_sym
when :server
@@ -36,6 +40,8 @@ module Scim
end
end
+ # Returns the name of the jbuilder template file.
+ # @return [String] the name of the jbuilder template.
def template_name
'resource.json.jbuilder'
end
lib/scim/kit/v2/resource_type.rb
@@ -13,7 +13,7 @@ module Scim
attr_accessor :endpoint
attr_accessor :schema
attr_reader :schema_extensions
- attr_reader :meta
+ attr_accessor :meta
def initialize(location:)
@meta = Meta.new('ResourceType', location)
@@ -25,10 +25,28 @@ module Scim
@schema_extensions.push(schema: schema, required: required)
end
- def self.build(*args)
- item = new(*args)
- yield item
- item
+ class << self
+ def build(*args)
+ item = new(*args)
+ yield item
+ item
+ end
+
+ def from(hash)
+ x = new(location: hash[:location])
+ x.meta = Meta.from(hash[:meta])
+ %i[id name description endpoint schema].each do |key|
+ x.public_send("#{key}=", hash[key])
+ end
+ hash[:schemaExtensions].each do |y|
+ x.add_schema_extension(schema: y[:schema], required: y[:required])
+ end
+ x
+ end
+
+ def parse(json)
+ from(JSON.parse(json, symbolize_names: true))
+ end
end
end
end
lib/scim/kit/v2/schema.rb
@@ -7,8 +7,8 @@ module Scim
class Schema
include Templatable
- attr_reader :id, :name, :attributes, :meta
- attr_accessor :description
+ attr_reader :id, :name, :attributes
+ attr_accessor :meta, :description
def initialize(id:, name:, location:)
@id = id
@@ -30,10 +30,29 @@ module Scim
id.include?(Schemas::CORE) || id.include?(Messages::CORE)
end
- def self.build(*args)
- item = new(*args)
- yield item
- item
+ class << self
+ def build(*args)
+ item = new(*args)
+ yield item
+ item
+ end
+
+ def from(hash)
+ Schema.new(
+ id: hash[:id],
+ name: hash[:name],
+ location: hash[:location]
+ ) do |x|
+ x.meta = Meta.from(hash[:meta])
+ hash[:attributes].each do |y|
+ x.attributes << AttributeType.from(y)
+ end
+ end
+ end
+
+ def parse(json)
+ from(JSON.parse(json, symbolize_names: true))
+ end
end
end
end
lib/scim/kit/v2/service_provider_configuration.rb
@@ -6,14 +6,16 @@ module Scim
# Represents a scim Service Provider Configuration
class ServiceProviderConfiguration
include Templatable
- attr_reader :location
- attr_accessor :documentation_uri
- attr_reader :authentication_schemes
- attr_reader :etag, :sort, :change_password, :patch
- attr_reader :bulk, :filter, :meta
+ attr_accessor :bulk, :filter
+ attr_accessor :etag, :sort, :change_password, :patch
+ attr_accessor :meta, :documentation_uri
+ attr_accessor :authentication_schemes
- def initialize(location:)
- @meta = Meta.new('ServiceProviderConfig', location)
+ def initialize(
+ location:,
+ meta: Meta.new('ServiceProviderConfig', location)
+ )
+ @meta = meta
@authentication_schemes = []
@etag = Supportable.new
@sort = Supportable.new
@@ -28,6 +30,21 @@ module Scim
yield scheme if block_given?
@authentication_schemes << scheme
end
+
+ class << self
+ def parse(json, hash = JSON.parse(json, symbolize_names: true))
+ x = new(location: hash[:location], meta: Meta.from(hash[:meta]))
+ x.documentation_uri = hash[:documentationUri]
+ %i[patch changePassword sort etag filter bulk].each do |key|
+ x.send("#{key.to_s.underscore}=", Supportable.from(hash[key]))
+ end
+ schemes = hash[:authenticationSchemes]
+ x.authentication_schemes = schemes&.map do |auth|
+ AuthenticationScheme.from(auth)
+ end
+ x
+ end
+ end
end
end
end
lib/scim/kit/v2/supportable.rb
@@ -11,11 +11,22 @@ module Scim
attr_accessor :supported
def initialize(*dynamic_attributes)
+ dynamic_attributes.delete(:supported)
@dynamic_attributes = Hash[
dynamic_attributes.map { |x| ["#{x}=".to_sym, nil] }
]
@supported = false
end
+
+ class << self
+ def from(hash)
+ x = new(*hash.keys)
+ hash.each do |key, value|
+ x.public_send("#{key}=", value)
+ end
+ x
+ end
+ end
end
end
end
lib/scim/kit/templatable.rb
@@ -4,22 +4,35 @@ module Scim
module Kit
# Implement methods necessary to generate json from jbuilder templates.
module Templatable
+ # Returns the JSON representation of the item.
+ # @param options [Hash] the hash of options to forward to jbuilder
+ # return [String] the json string
def to_json(options = {})
render(self, options)
end
+ # Returns the hash representation of the JSON
+ # @return [Hash] the hash representation of the items JSON.
def as_json(_options = nil)
to_h
end
+ # Returns the hash representation of the JSON
+ # @return [Hash] the hash representation of the items JSON.
def to_h
JSON.parse(to_json, symbolize_names: true).with_indifferent_access
end
+ # Renders the model to JSON.
+ # @param model [Object] the model to render.
+ # @param options [Hash] the hash of options to pass to jbuilder.
+ # @return [String] the JSON.
def render(model, options)
Template.new(model).to_json(options)
end
+ # Returns the file name of the jbuilder template.
+ # @return [String] name of the jbuilder template.
def template_name
"#{self.class.name.split('::').last.underscore}.json.jbuilder"
end
lib/scim/kit/version.rb
@@ -2,6 +2,6 @@
module Scim
module Kit
- VERSION = '0.2.16'
+ VERSION = '0.3.0'
end
end
lib/scim/kit.rb
@@ -4,6 +4,7 @@ require 'active_model'
require 'active_support/core_ext/hash/indifferent_access'
require 'json'
require 'logger'
+require 'net/hippie'
require 'pathname'
require 'tilt'
require 'tilt/jbuilder'
@@ -19,6 +20,7 @@ module Scim
module Kit
class Error < StandardError; end
class UnknownAttributeError < Error; end
+ TYPE_ERROR = ArgumentError.new(:type)
def self.logger
@logger ||= Logger.new(STDOUT)
spec/scim/kit/v2/configuration_spec.rb
@@ -37,4 +37,36 @@ RSpec.describe Scim::Kit::V2::Configuration do
specify { expect(subject.schemas['User'].name).to eql('User') }
specify { expect(subject.schemas['User'].meta.location).to eql(user_schema_location) }
specify { expect(subject.schemas['User'].attributes[0].name).to eql('user_name') }
+
+ describe '#load_from' do
+ let(:base_url) { FFaker::Internet.uri('https') }
+ let(:service_provider_configuration) do
+ Scim::Kit::V2::ServiceProviderConfiguration.new(location: FFaker::Internet.uri('https'))
+ end
+ let(:schema) do
+ Scim::Kit::V2::Schema.new(id: 'User', name: 'User', location: FFaker::Internet.uri('https'))
+ end
+ let(:resource_type) do
+ x = Scim::Kit::V2::ResourceType.new(location: FFaker::Internet.uri('https'))
+ x.id = 'User'
+ x
+ end
+
+ before do
+ stub_request(:get, "#{base_url}/ServiceProviderConfig")
+ .to_return(status: 200, body: service_provider_configuration.to_json)
+
+ stub_request(:get, "#{base_url}/Schemas")
+ .to_return(status: 200, body: [schema.to_h].to_json)
+
+ stub_request(:get, "#{base_url}/ResourceTypes")
+ .to_return(status: 200, body: [resource_type.to_h].to_json)
+
+ subject.load_from(base_url)
+ end
+
+ specify { expect(subject.service_provider_configuration.to_h).to eql(service_provider_configuration.to_h) }
+ specify { expect(subject.schemas[schema.id].to_h).to eql(schema.to_h) }
+ specify { expect(subject.resource_types[resource_type.id].to_h).to eql(resource_type.to_h) }
+ end
end
spec/scim/kit/v2/resource_type_spec.rb
@@ -30,4 +30,20 @@ RSpec.describe Scim::Kit::V2::ResourceType do
specify { expect(subject.to_h[:schemaExtensions]).to match_array([{ schema: extension, required: false }]) }
end
+
+ describe '.parse' do
+ let(:extension) { 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' }
+ let(:result) { described_class.parse(subject.to_json) }
+
+ before { subject.add_schema_extension(schema: extension, required: false) }
+
+ specify { expect(result.id).to eql(subject.id) }
+ specify { expect(result.name).to eql(subject.name) }
+ specify { expect(result.description).to eql(subject.description) }
+ specify { expect(result.endpoint).to eql(subject.endpoint) }
+ specify { expect(result.schema).to eql(subject.schema) }
+ specify { expect(result.schema_extensions).to eql(subject.schema_extensions) }
+ specify { expect(result.to_h).to eql(subject.to_h) }
+ specify { expect(result.to_json).to eql(subject.to_json) }
+ end
end
spec/scim/kit/v2/schema_spec.rb
@@ -122,4 +122,32 @@ RSpec.describe Scim::Kit::V2::Schema do
specify { expect(result[:meta][:resourceType]).to eql('Schema') }
specify { expect(result[:meta][:location]).to eql(location) }
end
+
+ describe '.parse' do
+ let(:result) { described_class.parse(subject.to_json) }
+
+ before do
+ subject.add_attribute(name: :display_name) do |x|
+ x.multi_valued = true
+ x.required = true
+ x.case_exact = true
+ x.mutability = :read_only
+ x.returned = :never
+ x.uniqueness = :server
+ x.canonical_values = ['honerva']
+ x.reference_types = %w[User Group]
+ end
+ end
+
+ specify { expect(result.id).to eql(subject.id) }
+ specify { expect(result.name).to eql(subject.name) }
+ specify { expect(result.description).to eql(subject.description) }
+ specify { expect(result.meta.created).to eql(subject.meta.created) }
+ specify { expect(result.meta.last_modified).to eql(subject.meta.last_modified) }
+ specify { expect(result.meta.version).to eql(subject.meta.version) }
+ specify { expect(result.meta.location).to eql(subject.meta.location) }
+ specify { expect(result.meta.resource_type).to eql(subject.meta.resource_type) }
+ specify { expect(result.to_json).to eql(subject.to_json) }
+ specify { expect(result.to_h).to eql(subject.to_h) }
+ end
end
spec/scim/kit/v2/service_provider_configuration_spec.rb
@@ -135,4 +135,30 @@ RSpec.describe Scim::Kit::V2::ServiceProviderConfiguration do
specify { expect(result[:filter][:maxResults]).to be(200) }
end
end
+
+ describe '.parse' do
+ let(:result) { described_class.parse(subject.to_json) }
+
+ before do
+ subject.add_authentication(:oauthbearertoken)
+ subject.bulk.max_operations = 1000
+ subject.bulk.max_payload_size = 1_048_576
+ subject.bulk.supported = true
+ subject.change_password.supported = true
+ subject.documentation_uri = FFaker::Internet.uri('https')
+ subject.etag.supported = true
+ subject.filter.max_results = 200
+ subject.filter.supported = true
+ subject.patch.supported = true
+ subject.sort.supported = true
+ end
+
+ specify { expect(result.meta.created.to_i).to eql(subject.meta.created.to_i) }
+ specify { expect(result.meta.last_modified.to_i).to eql(subject.meta.last_modified.to_i) }
+ specify { expect(result.meta.version).to eql(subject.meta.version) }
+ specify { expect(result.meta.location).to eql(subject.meta.location) }
+ specify { expect(result.meta.resource_type).to eql(subject.meta.resource_type) }
+ specify { expect(result.to_json).to eql(subject.to_json) }
+ specify { expect(result.to_h).to eql(subject.to_h) }
+ end
end
spec/spec_helper.rb
@@ -5,6 +5,7 @@ require 'scim/kit'
require 'ffaker'
require 'json'
require 'byebug'
+require 'webmock/rspec'
Scim::Kit.logger = Logger.new('/dev/null')
.rubocop.yml
@@ -20,6 +20,8 @@ Metrics/BlockLength:
Metrics/LineLength:
Exclude:
- 'spec/**/*.rb'
+ IgnoredPatterns:
+ - '^#*'
Naming/FileName:
Exclude:
.travis.yml
@@ -4,4 +4,5 @@ language: ruby
cache: bundler
rvm:
- 2.5.3
+ - 2.6.1
before_install: gem install bundler -v 1.17.1
CHANGELOG.md
@@ -1,4 +1,4 @@
-Version 0.2.16
+Version 0.3.0
# Changelog
All notable changes to this project will be documented in this file.
@@ -6,8 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
-### Changed
-- nil
+- NA
+
+## [0.3.0] - 2019-02-21
+### Added
+- add ServiceProviderConfiguration JSON parsing
+- add Schema JSON parsing
+- add Resource Type JSON parsing
## [0.2.16] - 2019-02-03
### Added
@@ -22,7 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- \_assign does not coerce values by default.
- errors are merged together instead of overwritten during attribute validation.
-[Unreleased]: https://github.com/mokhan/scim-kit/compare/v0.2.16...HEAD
+[Unreleased]: https://github.com/mokhan/scim-kit/compare/v0.3.0...HEAD
+[0.3.0]: https://github.com/mokhan/scim-kit/compare/v0.2.16...v0.3.0
[0.2.16]: https://github.com/mokhan/scim-kit/compare/v0.2.15...v0.2.16
[0.2.15]: https://github.com/mokhan/scim-kit/compare/v0.2.14...v0.2.15
[0.2.14]: https://github.com/mokhan/scim-kit/compare/v0.2.13...v0.2.14
README.md
@@ -1,5 +1,9 @@
# Scim::Kit
+[](https://travis-ci.org/mokhan/scim-kit)
+[](https://codeclimate.com/github/mokhan/scim-kit)
+[](https://rubygems.org/gems/scim-kit)
+
Scim::Kit is a library with the purpose of simplifying the generation
and consumption of SCIM Schema. https://tools.ietf.org/html/rfc7643#section-2
@@ -77,13 +81,13 @@ puts user_schema.to_json
## Development
-After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
+After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
-Bug reports and pull requests are welcome on GitHub at https://github.com/mokha/scim-kit.
+Bug reports and pull requests are welcome on GitHub at https://github.com/mokhan/scim-kit.
## License
scim-kit.gemspec
@@ -28,8 +28,10 @@ Gem::Specification.new do |spec|
end
spec.require_paths = ['lib']
spec.required_ruby_version = '>= 2.5.0'
+ spec.metadata['yard.run'] = 'yri'
spec.add_dependency 'activemodel', '>= 5.2.0'
+ spec.add_dependency 'net-hippie', '~> 0.2'
spec.add_dependency 'tilt', '~> 2.0'
spec.add_dependency 'tilt-jbuilder', '~> 0.7'
spec.add_development_dependency 'bundler', '~> 1.17'
@@ -40,4 +42,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'rubocop', '~> 0.52'
spec.add_development_dependency 'rubocop-rspec', '~> 1.22'
+ spec.add_development_dependency 'webmock', '~> 3.5'
end