evil-client 0.2.1
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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.rubocop.yml +98 -0
- data/.travis.yml +17 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +144 -0
- data/Rakefile +6 -0
- data/docs/base_url.md +38 -0
- data/docs/documentation.md +9 -0
- data/docs/headers.md +59 -0
- data/docs/http_method.md +31 -0
- data/docs/index.md +127 -0
- data/docs/license.md +19 -0
- data/docs/model.md +173 -0
- data/docs/operation.md +0 -0
- data/docs/overview.md +0 -0
- data/docs/path.md +48 -0
- data/docs/query.md +99 -0
- data/docs/responses.md +66 -0
- data/docs/security.md +102 -0
- data/docs/settings.md +32 -0
- data/evil-client.gemspec +25 -0
- data/lib/evil/client.rb +97 -0
- data/lib/evil/client/connection.rb +35 -0
- data/lib/evil/client/connection/net_http.rb +57 -0
- data/lib/evil/client/dsl.rb +110 -0
- data/lib/evil/client/dsl/files.rb +37 -0
- data/lib/evil/client/dsl/operation.rb +102 -0
- data/lib/evil/client/dsl/operations.rb +41 -0
- data/lib/evil/client/dsl/scope.rb +34 -0
- data/lib/evil/client/dsl/security.rb +57 -0
- data/lib/evil/client/middleware.rb +81 -0
- data/lib/evil/client/middleware/base.rb +15 -0
- data/lib/evil/client/middleware/merge_security.rb +16 -0
- data/lib/evil/client/middleware/normalize_headers.rb +13 -0
- data/lib/evil/client/middleware/stringify_form.rb +36 -0
- data/lib/evil/client/middleware/stringify_json.rb +15 -0
- data/lib/evil/client/middleware/stringify_multipart.rb +32 -0
- data/lib/evil/client/middleware/stringify_multipart/part.rb +36 -0
- data/lib/evil/client/middleware/stringify_query.rb +31 -0
- data/lib/evil/client/model.rb +65 -0
- data/lib/evil/client/operation.rb +34 -0
- data/lib/evil/client/operation/request.rb +42 -0
- data/lib/evil/client/operation/response.rb +40 -0
- data/lib/evil/client/operation/response_error.rb +12 -0
- data/lib/evil/client/operation/unexpected_response_error.rb +16 -0
- data/mkdocs.yml +21 -0
- data/spec/features/instantiation_spec.rb +68 -0
- data/spec/features/middleware_spec.rb +75 -0
- data/spec/features/operation_with_documentation_spec.rb +41 -0
- data/spec/features/operation_with_files_spec.rb +40 -0
- data/spec/features/operation_with_form_body_spec.rb +158 -0
- data/spec/features/operation_with_headers_spec.rb +99 -0
- data/spec/features/operation_with_http_method_spec.rb +45 -0
- data/spec/features/operation_with_json_body_spec.rb +156 -0
- data/spec/features/operation_with_path_spec.rb +47 -0
- data/spec/features/operation_with_query_spec.rb +84 -0
- data/spec/features/operation_with_response_spec.rb +109 -0
- data/spec/features/operation_with_security_spec.rb +228 -0
- data/spec/features/scoping_spec.rb +48 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/test_client.rb +15 -0
- data/spec/unit/evil/client/connection/net_http_spec.rb +38 -0
- data/spec/unit/evil/client/dsl/files_spec.rb +37 -0
- data/spec/unit/evil/client/dsl/operation_spec.rb +233 -0
- data/spec/unit/evil/client/dsl/operations_spec.rb +27 -0
- data/spec/unit/evil/client/dsl/scope_spec.rb +30 -0
- data/spec/unit/evil/client/dsl/security_spec.rb +135 -0
- data/spec/unit/evil/client/dsl_spec.rb +57 -0
- data/spec/unit/evil/client/middleware/merge_security_spec.rb +32 -0
- data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +17 -0
- data/spec/unit/evil/client/middleware/stringify_form_spec.rb +63 -0
- data/spec/unit/evil/client/middleware/stringify_json_spec.rb +61 -0
- data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +59 -0
- data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +62 -0
- data/spec/unit/evil/client/middleware/stringify_query_spec.rb +40 -0
- data/spec/unit/evil/client/middleware_spec.rb +46 -0
- data/spec/unit/evil/client/model_spec.rb +100 -0
- data/spec/unit/evil/client/operation/request_spec.rb +49 -0
- data/spec/unit/evil/client/operation/response_spec.rb +61 -0
- metadata +271 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
class Evil::Client::Middleware
|
2
|
+
class StringifyJson < Base
|
3
|
+
private
|
4
|
+
|
5
|
+
def build(env)
|
6
|
+
return env unless env[:format] == "json"
|
7
|
+
|
8
|
+
env.dup.tap do |hash|
|
9
|
+
hash[:headers] ||= {}
|
10
|
+
hash[:headers]["content-type"] = "application/json"
|
11
|
+
hash[:body_string] = JSON.generate(env[:body].to_h)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Evil::Client::Middleware
|
2
|
+
class StringifyMultipart < Base
|
3
|
+
require_relative "stringify_multipart/part"
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def build(env)
|
8
|
+
return env unless env[:format] == "multipart"
|
9
|
+
|
10
|
+
env.dup.tap do |hash|
|
11
|
+
bound = SecureRandom.hex(10)
|
12
|
+
hash[:headers] ||= {}
|
13
|
+
hash[:headers]["content-type"] = \
|
14
|
+
"multipart/form-data; boundary=#{bound}"
|
15
|
+
hash[:body_string] = body_string(hash[:files], bound)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def body_string(list, bound)
|
20
|
+
return if list.empty?
|
21
|
+
[nil, nil, parts(list, bound), "--#{bound}--", nil].join("\r\n")
|
22
|
+
end
|
23
|
+
|
24
|
+
def parts(list, bound)
|
25
|
+
list.map.with_index { |item, index| part(bound, index + 1, item) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def part(bound, index, data)
|
29
|
+
"--#{bound}\r\n#{Part.new(name: "AttachedFile#{index}", **data)}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Evil::Client::Middleware::StringifyMultipart
|
2
|
+
# Takes a file with its options and builds a part of multipart body
|
3
|
+
class Part
|
4
|
+
extend Dry::Initializer::Mixin
|
5
|
+
option :file
|
6
|
+
option :type, default: proc { MIME::Types["text/plain"].first }
|
7
|
+
option :charset, default: proc { "utf-8" }
|
8
|
+
option :name, default: proc { "AttachedFile" }
|
9
|
+
option :filename, default: proc { default_filename }
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
[content_disposition, content_type, nil, content].join("\r\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def default_filename
|
18
|
+
return Pathname.new(file.path).basename if file.respond_to? :path
|
19
|
+
"#{SecureRandom.hex(10)}.#{type.preferred_extension}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def content_disposition
|
23
|
+
"Content-Disposition: form-data;" \
|
24
|
+
" name=\"#{name}\";" \
|
25
|
+
" filename=\"#{filename}\""
|
26
|
+
end
|
27
|
+
|
28
|
+
def content_type
|
29
|
+
"Content-Type: #{type}; charset=#{charset}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def content
|
33
|
+
file.respond_to?(:read) ? file.read : file
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Evil::Client::Middleware
|
2
|
+
class StringifyQuery < Base
|
3
|
+
private
|
4
|
+
|
5
|
+
def build(env)
|
6
|
+
return env if env&.fetch(:query, nil).to_h.empty?
|
7
|
+
string = env[:query].flat_map { |key, val| normalize(val, key) }
|
8
|
+
.flat_map { |hash| stringify(hash) }
|
9
|
+
.join("&")
|
10
|
+
|
11
|
+
env.merge(query_string: string)
|
12
|
+
end
|
13
|
+
|
14
|
+
def stringify(hash)
|
15
|
+
hash.map do |keys, val|
|
16
|
+
"#{keys.first}#{keys[1..-1].map { |key| "[#{key}]" }.join}=#{val}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def normalize(value, *keys)
|
21
|
+
case value
|
22
|
+
when Hash then
|
23
|
+
value.flat_map { |key, val| normalize(val, *keys, key) }
|
24
|
+
when Array then
|
25
|
+
value.flat_map { |val| normalize(val, *keys, nil) }
|
26
|
+
else
|
27
|
+
[{ keys.map { |key| CGI.escape(key.to_s) } => CGI.escape(value.to_s) }]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Base structure for models describing parts of requests and responses
|
2
|
+
#
|
3
|
+
# The initializer accepts a hash with symbol/string keys,
|
4
|
+
# from which it takes and validates necessary options.
|
5
|
+
#
|
6
|
+
# The method [#to_h] converts nested data to hash
|
7
|
+
# with symbolic keys at any level of nesting.
|
8
|
+
#
|
9
|
+
class Evil::Client
|
10
|
+
class Model
|
11
|
+
class << self
|
12
|
+
include Dry::Initializer::Mixin
|
13
|
+
alias_method :attribute, :option
|
14
|
+
alias_method :param, :option
|
15
|
+
|
16
|
+
def new(value)
|
17
|
+
return value if value.is_a? self
|
18
|
+
value = value.to_h.each_with_object({}) do |(key, val), obj|
|
19
|
+
obj[key.to_sym] = val
|
20
|
+
end
|
21
|
+
super value
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(value)
|
25
|
+
new(value).to_h
|
26
|
+
end
|
27
|
+
alias_method :[], :call
|
28
|
+
end
|
29
|
+
|
30
|
+
tolerant_to_unknown_options
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
return false unless other.respond_to? :to_h
|
34
|
+
to_h == other.to_h
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_h
|
38
|
+
attributes = method(:initialize)
|
39
|
+
.parameters
|
40
|
+
.map { |item| item[1] unless item[0] == :keyrest }
|
41
|
+
.compact
|
42
|
+
|
43
|
+
attributes.each_with_object({}) do |key, hash|
|
44
|
+
val = send(key)
|
45
|
+
hash[key] = hashify(val) unless val == Dry::Initializer::UNDEFINED
|
46
|
+
end
|
47
|
+
end
|
48
|
+
alias_method :[], :send
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def hashify(value)
|
53
|
+
if value.is_a? Evil::Client::Model
|
54
|
+
value.to_h
|
55
|
+
elsif value.respond_to? :to_hash
|
56
|
+
value.to_hash
|
57
|
+
.each_with_object({}) { |(key, val), obj| obj[key] = hashify(val) }
|
58
|
+
elsif value.is_a? Enumerable
|
59
|
+
value.map { |val| hashify(val) }
|
60
|
+
else
|
61
|
+
value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
# Carries a final schema for a single operation along with shared connection,
|
3
|
+
# and uses it to send requests to the server
|
4
|
+
class Operation
|
5
|
+
require_relative "operation/request"
|
6
|
+
require_relative "operation/response"
|
7
|
+
|
8
|
+
extend Dry::Initializer::Mixin
|
9
|
+
param :schema
|
10
|
+
param :connection
|
11
|
+
|
12
|
+
# Builds and sends a request and returns a response proccessed by schema
|
13
|
+
#
|
14
|
+
# @param [IO, nil] file
|
15
|
+
# @param [Hash<Symbol, Object>] options
|
16
|
+
# @return [Object]
|
17
|
+
#
|
18
|
+
def call(**options)
|
19
|
+
req = request.build(options)
|
20
|
+
array = connection.call(req)
|
21
|
+
response.handle(array)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def request
|
27
|
+
@request ||= Request.new(schema)
|
28
|
+
end
|
29
|
+
|
30
|
+
def response
|
31
|
+
@response ||= Response.new(schema)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Evil::Client::Operation
|
2
|
+
# Builds a request env from user options by applying schema validations
|
3
|
+
class Request
|
4
|
+
extend Dry::Initializer::Mixin
|
5
|
+
param :schema
|
6
|
+
|
7
|
+
# Builds an env
|
8
|
+
#
|
9
|
+
# @param [IO, nil] file (nil)
|
10
|
+
# @param [Hash<Symbol, Object>] options
|
11
|
+
# @return [Hash]
|
12
|
+
#
|
13
|
+
def build(options)
|
14
|
+
{
|
15
|
+
format: schema[:format],
|
16
|
+
http_method: http_method,
|
17
|
+
path: path.call(options),
|
18
|
+
security: schema[:security]&.call(options),
|
19
|
+
files: schema[:files]&.call(options),
|
20
|
+
query: schema[:query]&.new(options).to_h,
|
21
|
+
body: schema[:body]&.new(options).to_h,
|
22
|
+
headers: schema[:headers]&.new(options).to_h
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def key
|
29
|
+
@key ||= schema[:key]
|
30
|
+
end
|
31
|
+
|
32
|
+
def http_method
|
33
|
+
return schema[:method] if schema[:method]
|
34
|
+
fail NotImplementedError.new "No method defined for operation '#{key}'"
|
35
|
+
end
|
36
|
+
|
37
|
+
def path
|
38
|
+
return schema[:path] if schema[:path]
|
39
|
+
fail NotImplementedError.new "Path not defined for operation '#{key}'"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Evil::Client::Operation
|
2
|
+
require_relative "response_error"
|
3
|
+
require_relative "unexpected_response_error"
|
4
|
+
|
5
|
+
# Processes rack responses using an operation's schema
|
6
|
+
class Response
|
7
|
+
extend Dry::Initializer::Mixin
|
8
|
+
param :schema
|
9
|
+
|
10
|
+
# Processes rack responses returned by [Dry::Cluent::Connection]
|
11
|
+
#
|
12
|
+
# @param [Array] array Rack-compatible array of response data
|
13
|
+
# @return [Object]
|
14
|
+
# @raise [Evil::Client::ResponseError] if it is required by the schema
|
15
|
+
#
|
16
|
+
def handle(array)
|
17
|
+
status, header, body = array
|
18
|
+
response = Rack::Response.new(body, status, header)
|
19
|
+
|
20
|
+
handler = response_schema(response)
|
21
|
+
data = handler[:coercer].call response: response,
|
22
|
+
body: response.body,
|
23
|
+
header: response.header
|
24
|
+
|
25
|
+
handler[:raise] ? fail(ResponseError.new(schema, status, data)) : data
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def name
|
31
|
+
@name ||= schema[:name]
|
32
|
+
end
|
33
|
+
|
34
|
+
def response_schema(response)
|
35
|
+
schema[:responses].fetch response.status do
|
36
|
+
fail UnexpectedResponseError.new(schema, response)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Evil::Client::Operation
|
2
|
+
class ResponseError < RuntimeError
|
3
|
+
attr_reader :response
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def initialize(schema, status, response)
|
8
|
+
@response = response
|
9
|
+
super "Response to operation '#{schema[:key]}' has http status #{status}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Evil::Client::Operation
|
2
|
+
class UnexpectedResponseError < RuntimeError
|
3
|
+
attr_reader :response
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def initialize(schema, response)
|
8
|
+
@response = response
|
9
|
+
|
10
|
+
message = "Response to operation '#{schema[:key]}'" \
|
11
|
+
" has unexpected http status #{response.status}."
|
12
|
+
message << " See #{schema[:doc]} for details." if schema[:doc]
|
13
|
+
super message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/mkdocs.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
site_name: Evil::Client
|
3
|
+
site_description: Human-friendly DSL for building HTTP(s) clients in Ruby
|
4
|
+
repo_url: https://github.com/nepalez/evil-client
|
5
|
+
site_author: Andrew Kozin
|
6
|
+
theme: readthedocs
|
7
|
+
pages:
|
8
|
+
- 'Synopsis': 'index.md'
|
9
|
+
- 'Details':
|
10
|
+
- 'Overview': 'overview.md'
|
11
|
+
- 'Model': 'model.md'
|
12
|
+
- 'Settings': 'settings.md'
|
13
|
+
- 'Base URL': 'base_url.md'
|
14
|
+
- 'Operations':
|
15
|
+
- 'HTTP method': 'http_method.md'
|
16
|
+
- 'Path': 'path.md'
|
17
|
+
- 'Security Definitions': 'security.md'
|
18
|
+
- 'Headers': 'headers.md'
|
19
|
+
- 'Query': 'query.md'
|
20
|
+
- 'Responses': 'responses.md'
|
21
|
+
- 'Documentation': 'documentation.md'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
RSpec.describe "instantiation" do
|
2
|
+
# see Test::Client definition in `/spec/support/test_client.rb`
|
3
|
+
let(:client) { Test::Client.new subdomain, options }
|
4
|
+
let(:subdomain) { "foo" }
|
5
|
+
let(:options) { { version: 3, user: "bar", password: "baz", token: "qux" } }
|
6
|
+
|
7
|
+
context "with valid settings:" do
|
8
|
+
it "is accepted" do
|
9
|
+
expect(client).to be_kind_of Test::Client
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with settings that still conforms to contract:" do
|
14
|
+
let(:options) { { user: "bar" } }
|
15
|
+
|
16
|
+
it "is accepted" do
|
17
|
+
expect(client).to be_kind_of Test::Client
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with unexpected param settings:" do
|
22
|
+
let(:client) { Test::Client.new(subdomain, subdomain, **options) }
|
23
|
+
|
24
|
+
it "is rejected" do
|
25
|
+
expect { client }.to raise_error(ArgumentError)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "with missing param settings:" do
|
30
|
+
let(:client) { Test::Client.new(**options) }
|
31
|
+
|
32
|
+
it "is rejected" do
|
33
|
+
expect { client }.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with a broken contract for param:" do
|
38
|
+
let(:subdomain) { 1 }
|
39
|
+
|
40
|
+
it "is rejected" do
|
41
|
+
expect { client }.to raise_error(TypeError)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "with unexpected option settings:" do
|
46
|
+
before { options[:foo] = "bar" }
|
47
|
+
|
48
|
+
it "is rejected" do
|
49
|
+
expect { client }.to raise_error(ArgumentError)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with missing option settings:" do
|
54
|
+
before { options.delete :user }
|
55
|
+
|
56
|
+
it "is rejected" do
|
57
|
+
expect { client }.to raise_error(ArgumentError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with a broken contract for option:" do
|
62
|
+
before { options[:user] = 1 }
|
63
|
+
|
64
|
+
it "is rejected" do
|
65
|
+
expect { client }.to raise_error(TypeError)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
RSpec.describe "middleware" do
|
2
|
+
before do
|
3
|
+
class Test::UpdateRequest
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(_env)
|
9
|
+
@app.call path: "data/1",
|
10
|
+
http_method: "get",
|
11
|
+
format: "form",
|
12
|
+
headers: { "baz" => "BAZ" },
|
13
|
+
query: { "bar" => "baz" },
|
14
|
+
body: { "qux" => 2 }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Test::UpdateResponse
|
19
|
+
def initialize(app)
|
20
|
+
@app = app
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
@app.call(env).tap { |rack_response| rack_response[2] = ["Hi!"] }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Test::Client < Evil::Client
|
29
|
+
connection do |settings|
|
30
|
+
run Test::UpdateRequest
|
31
|
+
run Test::UpdateResponse if settings.version > 2
|
32
|
+
end
|
33
|
+
|
34
|
+
operation :find do
|
35
|
+
path { "some" }
|
36
|
+
http_method :post
|
37
|
+
response 200 do |body:, **|
|
38
|
+
body.first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
stub_request(:any, //)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "updates requests" do
|
47
|
+
request = a_request(:get, "https://foo.example.com/api/v3/data/1?bar=baz")
|
48
|
+
.with do |req|
|
49
|
+
expect(req.body).to eq "qux=2"
|
50
|
+
expect(req.headers).to include "Baz" => "BAZ"
|
51
|
+
end
|
52
|
+
|
53
|
+
Test::Client.new("foo", version: 3, user: "bar").operations[:find].call
|
54
|
+
|
55
|
+
expect(request).to have_been_made
|
56
|
+
end
|
57
|
+
|
58
|
+
it "updates responses" do
|
59
|
+
response = \
|
60
|
+
Test::Client.new("foo", version: 3, user: "bar")
|
61
|
+
.operations[:find]
|
62
|
+
.call
|
63
|
+
|
64
|
+
expect(response).to eq "Hi!"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "depends on settings" do
|
68
|
+
response = \
|
69
|
+
Test::Client.new("foo", version: 1, user: "bar")
|
70
|
+
.operations[:find]
|
71
|
+
.call
|
72
|
+
|
73
|
+
expect(response).to be_nil
|
74
|
+
end
|
75
|
+
end
|