rspec-twirp 0.0.1 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/rspec/twirp/error_matcher.rb +62 -0
- data/lib/rspec/twirp/helpers.rb +16 -0
- data/lib/rspec/twirp/make_request_matcher.rb +169 -0
- data/lib/rspec/twirp/message_matcher.rb +61 -0
- data/lib/rspec/twirp/mock_client.rb +59 -0
- data/lib/rspec/twirp/mock_connection.rb +54 -0
- data/lib/rspec/twirp/response_matcher.rb +57 -0
- data/lib/rspec/twirp/version.rb +5 -0
- data/lib/rspec/twirp.rb +54 -0
- data/lib/rspec-twirp.rb +1 -5
- data/spec/error_spec.rb +116 -0
- data/spec/make_request_spec.rb +181 -0
- data/spec/message_spec.rb +83 -0
- data/spec/mock_connection_spec.rb +103 -0
- data/spec/response_spec.rb +124 -0
- metadata +58 -12
- data/lib/rspec-twirp/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2634a27685c93991e30afab03cf80cb5fe650047a2b91342eeadff69a7bb920e
|
4
|
+
data.tar.gz: f87529044e688df0b546e65cd527989842e983576bf62a70ea7aabf72d920d09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01e6fbab6f8649f6c8dbcc8df72269699e099eb1d924d6ff6656781d5339d060c6138e465a5c43b5ec2a9320614d7924bed1c79eac088c9c10f881d99c6588d7
|
7
|
+
data.tar.gz: ce566d5fd9e1e91cca7a100d205e4a1b0615c743f808383d5180bd007049aeaaaf1298bfe382ad6e0385404dd3c04fadb5980ac53fbc9de54c9aeee335b4834b
|
@@ -0,0 +1,62 @@
|
|
1
|
+
RSpec::Matchers.define :be_a_twirp_error do |*matchers, **meta_matcher|
|
2
|
+
match do |actual|
|
3
|
+
@fail_msg = "Expected #{actual} to be a Twirp::Error, found #{actual.class}"
|
4
|
+
return false unless actual.is_a?(Twirp::Error)
|
5
|
+
|
6
|
+
matchers.each do |matcher|
|
7
|
+
case matcher
|
8
|
+
when Symbol
|
9
|
+
# match code
|
10
|
+
|
11
|
+
unless Twirp::Error.valid_code?(matcher)
|
12
|
+
raise ArgumentError, "invalid error code: #{matcher.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
@fail_msg = "Expected #{actual} to have code: #{matcher.inspect}, found #{actual.code}"
|
16
|
+
return false unless actual.code == matcher
|
17
|
+
when Integer
|
18
|
+
# match http status code
|
19
|
+
|
20
|
+
if code = Twirp::ERROR_CODES_TO_HTTP_STATUS.key(matcher)
|
21
|
+
@fail_msg = "Expected #{actual} to have status: #{matcher}, found #{actual.code}"
|
22
|
+
return false unless actual.code == code
|
23
|
+
else
|
24
|
+
raise ArgumentError, "invalid error status code: #{matcher}"
|
25
|
+
end
|
26
|
+
when Twirp::Error
|
27
|
+
# match instance
|
28
|
+
|
29
|
+
@fail_msg = "Expected #{actual} to equal #{matcher}"
|
30
|
+
return false unless values_match?(matcher.to_h, actual.to_h)
|
31
|
+
else
|
32
|
+
# match msg
|
33
|
+
@fail_msg = "Expected #{actual} to have msg: #{matcher.inspect}, found #{actual.msg}"
|
34
|
+
return false unless values_match?(matcher, actual.msg)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# match meta
|
39
|
+
unless meta_matcher.empty?
|
40
|
+
@cur_match = { meta: meta_matcher }
|
41
|
+
|
42
|
+
# sanity check...values must be Strings or Regexp
|
43
|
+
discrete_attrs = meta_matcher.transform_values do |attr|
|
44
|
+
attr.is_a?(Regexp) ? attr.inspect : attr
|
45
|
+
end
|
46
|
+
actual.send(:validate_meta, discrete_attrs)
|
47
|
+
|
48
|
+
@fail_msg = "Expected #{actual} to have meta: #{meta_matcher.inspect}, found #{actual.meta}"
|
49
|
+
return false unless values_match?(meta_matcher, actual.meta)
|
50
|
+
end
|
51
|
+
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
description do
|
56
|
+
"a Twirp::Error"
|
57
|
+
end
|
58
|
+
|
59
|
+
failure_message { @fail_msg }
|
60
|
+
end
|
61
|
+
|
62
|
+
RSpec::Matchers.alias_matcher :a_twirp_error, :be_a_twirp_error
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rspec/twirp/mock_client"
|
2
|
+
require "rspec/twirp/mock_connection"
|
3
|
+
|
4
|
+
module RSpec
|
5
|
+
module Twirp
|
6
|
+
module Helpers
|
7
|
+
def mock_twirp_connection(...)
|
8
|
+
RSpec::Twirp.mock_connection(...)
|
9
|
+
end
|
10
|
+
|
11
|
+
def mock_twirp_client(...)
|
12
|
+
RSpec::Twirp.mock_client(...)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# expect {
|
2
|
+
# do_the_thing
|
3
|
+
# }.to make_twirp_request(client | service | rpc_method).with(request | attrs)
|
4
|
+
#
|
5
|
+
# expect(client).to make_twirp_request(rpc_method).with(...)
|
6
|
+
|
7
|
+
RSpec::Matchers.define :make_twirp_request do |*matchers|
|
8
|
+
chain :with do |input_matcher = nil|
|
9
|
+
@input_matcher = if input_matcher
|
10
|
+
if input_matcher.is_a?(Google::Protobuf::MessageExts)
|
11
|
+
defaults = input_matcher.class.new.to_h
|
12
|
+
hash_form = input_matcher.to_h.reject {|k,v| v == defaults[k] }
|
13
|
+
|
14
|
+
->(input) do
|
15
|
+
if input.is_a?(Google::Protobuf::MessageExts)
|
16
|
+
values_match?(input_matcher, input)
|
17
|
+
else
|
18
|
+
values_match?(include(**hash_form), input.to_h)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
elsif input_matcher.is_a?(Class) && input_matcher < Google::Protobuf::MessageExts
|
22
|
+
->(input) { values_match?(be_a(input_matcher), input) }
|
23
|
+
elsif input_matcher.is_a?(Hash)
|
24
|
+
->(input) { values_match?(include(**input_matcher), input.to_h) }
|
25
|
+
else
|
26
|
+
raise TypeError, "Expected an input_matcher of type `Google::Protobuf::MessageExts`, found #{input_matcher.class}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
chain(:and_call_original) { @and_call_original = true }
|
32
|
+
chain(:and_return) do |arg|
|
33
|
+
@and_return = case arg
|
34
|
+
when Google::Protobuf::MessageExts
|
35
|
+
Twirp::ClientResp.new(arg, nil)
|
36
|
+
when Twirp::Error
|
37
|
+
Twirp::ClientResp.new(nil, arg)
|
38
|
+
when Class
|
39
|
+
if arg < Google::Protobuf::MessageExts
|
40
|
+
Twirp::ClientResp.new(arg.new, nil)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
unless @and_return
|
45
|
+
raise TypeError, "Expected type `Google::Protobuf::MessageExts`, found #{arg}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
supports_block_expectations
|
50
|
+
|
51
|
+
match do |client_or_block|
|
52
|
+
@input_matcher ||= ->(*){ true }
|
53
|
+
|
54
|
+
if @and_call_original && @and_return
|
55
|
+
raise ArgumentError, "use `and_call_original` or `and_return`, but not both"
|
56
|
+
end
|
57
|
+
|
58
|
+
if client_or_block.is_a? Proc
|
59
|
+
RSpec::Mocks.with_temporary_scope do
|
60
|
+
match_block_request(client_or_block, matchers)
|
61
|
+
end
|
62
|
+
elsif client_or_block.is_a?(Twirp::Client)
|
63
|
+
match_client_request(client_or_block, matchers)
|
64
|
+
else
|
65
|
+
raise ArgumentError, "Expected Twirp::Client or block, found: #{client_or_block}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def match_block_request(block, matchers)
|
70
|
+
expected_client = be_a(Twirp::Client)
|
71
|
+
expected_service = anything
|
72
|
+
expected_rpc_name = anything
|
73
|
+
|
74
|
+
matchers.each do |matcher|
|
75
|
+
case matcher
|
76
|
+
when Twirp::Client
|
77
|
+
expected_client = be(matcher)
|
78
|
+
when Class
|
79
|
+
if matcher <= Twirp::Client
|
80
|
+
expected_client = be_a(matcher)
|
81
|
+
elsif matcher <= Twirp::Service
|
82
|
+
expected_service = matcher.service_full_name
|
83
|
+
else
|
84
|
+
raise TypeError
|
85
|
+
end
|
86
|
+
when String
|
87
|
+
expected_rpc_name = be(matcher.to_sym)
|
88
|
+
when Symbol
|
89
|
+
expected_rpc_name = be(matcher)
|
90
|
+
when Regexp
|
91
|
+
expected_rpc_name = matcher
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@twirp_request_made = false
|
96
|
+
|
97
|
+
# stub pre-existing client instances
|
98
|
+
ObjectSpace.each_object(Twirp::Client) do |client|
|
99
|
+
if expected_client === client && (expected_service === client.class.service_full_name)
|
100
|
+
stub_client(client, expected_rpc_name)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# stub future client instances
|
105
|
+
ObjectSpace.each_object(Twirp::Client.singleton_class).select do |obj|
|
106
|
+
obj < Twirp::Client && obj != Twirp::ClientJSON
|
107
|
+
end.each do |client_type|
|
108
|
+
next unless client_type.name && expected_service === client_type.service_full_name
|
109
|
+
|
110
|
+
allow(client_type).to receive(:new).and_wrap_original do |orig, *args, **kwargs|
|
111
|
+
orig.call(*args, **kwargs).tap do |client|
|
112
|
+
if expected_client === client
|
113
|
+
stub_client(client, expected_rpc_name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
block.call
|
120
|
+
|
121
|
+
@twirp_request_made
|
122
|
+
end
|
123
|
+
|
124
|
+
def stub_client(client, rpc_matcher)
|
125
|
+
allow(client).to receive(:rpc).and_wrap_original do |orig, rpc_name, input, req_opts|
|
126
|
+
if values_match?(rpc_matcher, rpc_name) && @input_matcher.call(input)
|
127
|
+
@twirp_request_made = true
|
128
|
+
end
|
129
|
+
|
130
|
+
if @and_call_original
|
131
|
+
orig.call(rpc_name, input, req_opts)
|
132
|
+
elsif @and_return
|
133
|
+
@and_return
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def match_client_request(client, matchers)
|
139
|
+
rpc_name = matchers.first.to_sym if matchers.any?
|
140
|
+
|
141
|
+
if rpc_name
|
142
|
+
rpc_info = client.class.rpcs.values.find do |x|
|
143
|
+
x[:rpc_method] == rpc_name || x[:ruby_method] == rpc_name
|
144
|
+
end
|
145
|
+
|
146
|
+
raise ArgumentError, "invalid rpc method: #{rpc_name}" unless rpc_info
|
147
|
+
|
148
|
+
msg = "Expected #{client} to make a twirp request to #{rpc_name}"
|
149
|
+
rpc_matcher = eq(rpc_info[:rpc_method])
|
150
|
+
else
|
151
|
+
rpc_info = nil
|
152
|
+
msg = "Expected #{client} to make a twirp request"
|
153
|
+
rpc_matcher = anything
|
154
|
+
end
|
155
|
+
|
156
|
+
expect(client).to receive(:rpc) do |rpc_name, input, req_opts|
|
157
|
+
expect(rpc_name).to match(rpc_matcher), msg
|
158
|
+
expect(@input_matcher.call(input)).to be(true), msg
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
description do
|
163
|
+
"make a Twirp request"
|
164
|
+
end
|
165
|
+
|
166
|
+
failure_message { @fail_msg || super() }
|
167
|
+
end
|
168
|
+
|
169
|
+
RSpec::Matchers.alias_matcher :make_a_twirp_request, :make_twirp_request
|
@@ -0,0 +1,61 @@
|
|
1
|
+
RSpec::Matchers.define :be_a_twirp_message do |type = nil, **attrs|
|
2
|
+
match do |actual|
|
3
|
+
# ensure type is a valid twirp message type
|
4
|
+
if type && !(type < Google::Protobuf::MessageExts)
|
5
|
+
raise TypeError, "Expected `type` to be a Google::Protobuf::MessageExts, found: #{type}"
|
6
|
+
end
|
7
|
+
|
8
|
+
@fail_msg = "Expected a Twirp message, found #{actual}"
|
9
|
+
return false unless actual.is_a?(Google::Protobuf::MessageExts)
|
10
|
+
|
11
|
+
# match expected message type
|
12
|
+
@fail_msg = "Expected a Twirp message of type #{type}, found #{actual.class}"
|
13
|
+
return false if type && actual.class != type
|
14
|
+
|
15
|
+
return true if attrs.empty?
|
16
|
+
|
17
|
+
# sanity check inputs
|
18
|
+
validate_types(attrs, actual.class)
|
19
|
+
|
20
|
+
# match attributes which are present
|
21
|
+
attrs.each do |attr_name, expected_attr|
|
22
|
+
actual_attr = actual.send(attr_name)
|
23
|
+
|
24
|
+
@fail_msg = "Expected #{actual} to have #{attr_name}: #{expected_attr.inspect}, found #{actual_attr}"
|
25
|
+
return false unless values_match?(expected_attr, actual_attr)
|
26
|
+
end
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
description do
|
32
|
+
type ? "a #{type} Twirp message" : "a Twirp message"
|
33
|
+
end
|
34
|
+
|
35
|
+
failure_message { @fail_msg }
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def validate_types(attrs, klass)
|
40
|
+
# check names and types of attrs by constructing an actual proto object
|
41
|
+
discrete_attrs = attrs.transform_values do |attr|
|
42
|
+
case attr
|
43
|
+
when Regexp
|
44
|
+
attr.inspect
|
45
|
+
when Range
|
46
|
+
attr.first
|
47
|
+
when RSpec::Matchers::BuiltIn::BaseMatcher
|
48
|
+
# no good substitute, so skip attr and hope for the best
|
49
|
+
nil
|
50
|
+
else
|
51
|
+
attr
|
52
|
+
end
|
53
|
+
end.compact
|
54
|
+
|
55
|
+
klass.new(**discrete_attrs)
|
56
|
+
rescue Google::Protobuf::TypeError => e
|
57
|
+
raise TypeError, e.message
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
RSpec::Matchers.alias_matcher :a_twirp_message, :be_a_twirp_message
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# mock_twirp_client(Twirp::Client, { rpc => response } )
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Twirp
|
5
|
+
def mock_client(client, **responses)
|
6
|
+
unless client.is_a?(Class) && client < ::Twirp::Client
|
7
|
+
raise ArgumentError, "Expected Twirp::Client, found: #{client.class}"
|
8
|
+
end
|
9
|
+
|
10
|
+
rpcs = client.rpcs.values
|
11
|
+
|
12
|
+
client_instance = client.new(mock_connection)
|
13
|
+
|
14
|
+
unless responses.empty?
|
15
|
+
# validate input
|
16
|
+
responses.transform_keys! do |rpc_name|
|
17
|
+
rpc_info = rpcs.find do |x|
|
18
|
+
x[:rpc_method] == rpc_name || x[:ruby_method] == rpc_name
|
19
|
+
end
|
20
|
+
|
21
|
+
unless rpc_info
|
22
|
+
raise ArgumentError, "invalid rpc method: #{rpc_name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
rpc_info[:rpc_method]
|
26
|
+
end
|
27
|
+
|
28
|
+
# repackage responses
|
29
|
+
client_responses = {}
|
30
|
+
responses.each do |rpc_method, response|
|
31
|
+
if response.is_a?(Hash)
|
32
|
+
response = client.rpcs[rpc_method.to_s][:output_class].new(**response)
|
33
|
+
end
|
34
|
+
|
35
|
+
client_responses[rpc_method] = RSpec::Twirp.generate_client_response(response)
|
36
|
+
end
|
37
|
+
|
38
|
+
# mock
|
39
|
+
rpcs.each do |info|
|
40
|
+
response = client_responses[info[:rpc_method]]
|
41
|
+
|
42
|
+
if response
|
43
|
+
allow(client_instance).to receive(:rpc).with(
|
44
|
+
info[:rpc_method],
|
45
|
+
any_args,
|
46
|
+
).and_return(response)
|
47
|
+
else
|
48
|
+
allow(client_instance).to receive(:rpc).with(
|
49
|
+
info[:rpc_method],
|
50
|
+
any_args,
|
51
|
+
).and_raise(::RSpec::Mocks::MockExpectationError)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
client_instance
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# GoodbyeClient.new(mock_twirp_connection)
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Twirp
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def mock_connection(response = nil)
|
8
|
+
if block_given? && response
|
9
|
+
raise ArgumentError, "can not specify both block and response"
|
10
|
+
end
|
11
|
+
|
12
|
+
Faraday.new do |conn|
|
13
|
+
conn.adapter :test do |stub|
|
14
|
+
stub.post(/.*/) do |env|
|
15
|
+
response = yield(env) if block_given?
|
16
|
+
response ||= {}
|
17
|
+
|
18
|
+
if response.is_a?(Hash)
|
19
|
+
# create default response
|
20
|
+
|
21
|
+
# determine which client would make this rpc call
|
22
|
+
service_full_name, rpc_method = env.url.path.split("/").last(2)
|
23
|
+
client = ObjectSpace.each_object(::Twirp::Client.singleton_class).find do |client|
|
24
|
+
next unless client.name
|
25
|
+
|
26
|
+
client.service_full_name == service_full_name && client.rpcs.key?(rpc_method)
|
27
|
+
end
|
28
|
+
|
29
|
+
unless client
|
30
|
+
raise TypeError, "could not determine Twirp::Client for: #{env.url.path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
response = client.rpcs[rpc_method][:output_class].new(**response)
|
34
|
+
end
|
35
|
+
|
36
|
+
res = RSpec::Twirp.generate_client_response(response)
|
37
|
+
|
38
|
+
if res.data
|
39
|
+
status = 200
|
40
|
+
headers = { "Content-Type" => ::Twirp::Encoding::PROTO }
|
41
|
+
body = res.data.to_proto
|
42
|
+
else
|
43
|
+
status = ::Twirp::ERROR_CODES_TO_HTTP_STATUS[res.error.code]
|
44
|
+
headers = { "Content-Type" => ::Twirp::Encoding::JSON } # errors are always JSON
|
45
|
+
body = ::Twirp::Encoding.encode_json(res.error.to_h)
|
46
|
+
end
|
47
|
+
|
48
|
+
[ status, headers, body ]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
RSpec::Matchers.define :be_a_twirp_response do |type = nil, **attrs|
|
2
|
+
chain :with_error do |*matchers, **meta_matchers|
|
3
|
+
# code, msg, meta
|
4
|
+
@with_error = [ matchers, meta_matchers ]
|
5
|
+
end
|
6
|
+
|
7
|
+
match do |actual|
|
8
|
+
# ensure type is a valid twirp request type
|
9
|
+
if type
|
10
|
+
if type.is_a?(Google::Protobuf::MessageExts)
|
11
|
+
unless attrs.empty?
|
12
|
+
raise ArgumentError, "Expected Google::Protobuf::MessageExts instance or attrs, but not both"
|
13
|
+
end
|
14
|
+
|
15
|
+
attrs = type.to_h
|
16
|
+
type = type.class
|
17
|
+
elsif !(type < Google::Protobuf::MessageExts)
|
18
|
+
raise ArgumentError, "Expected `type` to be a Google::Protobuf::MessageExts, found: #{type}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
@fail_msg = "Expected a Twirp::ClientResp, found #{actual}"
|
23
|
+
return false unless actual.is_a?(Twirp::ClientResp)
|
24
|
+
|
25
|
+
# match expected response type
|
26
|
+
@fail_msg = "Expected a Twirp::ClientResp of type #{type}, found #{actual.data&.class}"
|
27
|
+
return false if type && actual.data&.class != type
|
28
|
+
|
29
|
+
if @with_error
|
30
|
+
unless attrs.empty?
|
31
|
+
raise ArgumentError, "match data attributes or error, but not both"
|
32
|
+
end
|
33
|
+
|
34
|
+
@fail_msg = "Expected #{actual} to have an error, but found none"
|
35
|
+
return false unless actual.error
|
36
|
+
|
37
|
+
matchers, meta_matchers = @with_error
|
38
|
+
expect(actual.error).to be_a_twirp_error(*matchers, **meta_matchers)
|
39
|
+
else
|
40
|
+
@fail_msg = "Expected #{actual} to have data, but found none"
|
41
|
+
return false unless actual.data
|
42
|
+
|
43
|
+
expect(actual.data).to be_a_twirp_message(**attrs)
|
44
|
+
end
|
45
|
+
rescue RSpec::Expectations::ExpectationNotMetError => err
|
46
|
+
@fail_msg = err.message
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
description do
|
51
|
+
type ? "a #{type} Twirp response" : "a Twirp response"
|
52
|
+
end
|
53
|
+
|
54
|
+
failure_message { @fail_msg }
|
55
|
+
end
|
56
|
+
|
57
|
+
RSpec::Matchers.alias_matcher :a_twirp_response, :be_a_twirp_response
|
data/lib/rspec/twirp.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require "google/protobuf"
|
2
|
+
require "rspec"
|
3
|
+
require "twirp"
|
4
|
+
|
5
|
+
require "rspec/twirp/helpers"
|
6
|
+
require "rspec/twirp/make_request_matcher"
|
7
|
+
require "rspec/twirp/error_matcher"
|
8
|
+
require "rspec/twirp/message_matcher"
|
9
|
+
require "rspec/twirp/response_matcher"
|
10
|
+
|
11
|
+
module RSpec
|
12
|
+
module Twirp
|
13
|
+
extend RSpec::Mocks::ExampleMethods
|
14
|
+
|
15
|
+
# private
|
16
|
+
|
17
|
+
def generate_client_response(arg)
|
18
|
+
res = case arg
|
19
|
+
when Google::Protobuf::MessageExts, ::Twirp::Error
|
20
|
+
arg
|
21
|
+
when Class
|
22
|
+
if arg < Google::Protobuf::MessageExts
|
23
|
+
arg.new
|
24
|
+
else
|
25
|
+
raise TypeError, "Expected type `Google::Protobuf::MessageExts`, found: #{arg}"
|
26
|
+
end
|
27
|
+
when Symbol
|
28
|
+
if ::Twirp::Error.valid_code?(arg)
|
29
|
+
::Twirp::Error.new(arg, arg)
|
30
|
+
else
|
31
|
+
raise ArgumentError, "invalid error code: #{arg}"
|
32
|
+
end
|
33
|
+
when Integer
|
34
|
+
if code = ::Twirp::ERROR_CODES_TO_HTTP_STATUS.key(arg)
|
35
|
+
::Twirp::Error.new(code, code)
|
36
|
+
else
|
37
|
+
raise ArgumentError, "invalid error code: #{arg}"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
if res.is_a?(Google::Protobuf::MessageExts)
|
44
|
+
::Twirp::ClientResp.new(res, nil)
|
45
|
+
else
|
46
|
+
::Twirp::ClientResp.new(nil, res)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
RSpec.configure do |config|
|
53
|
+
config.include(RSpec::Twirp::Helpers)
|
54
|
+
end
|
data/lib/rspec-twirp.rb
CHANGED
data/spec/error_spec.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
describe "be_a_twirp_error" do
|
2
|
+
subject { Twirp::Error.new(code, msg, meta) }
|
3
|
+
|
4
|
+
let(:code) { :not_found }
|
5
|
+
let(:msg) { "Not Found" }
|
6
|
+
let(:meta) { { is_meta: "true" } }
|
7
|
+
|
8
|
+
it { is_expected.to be_a_twirp_error }
|
9
|
+
|
10
|
+
it "catches type mismatches" do
|
11
|
+
expect {
|
12
|
+
expect(Object).to be_a_twirp_error
|
13
|
+
}.to fail_with /to be a Twirp::Error/
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "status matches" do
|
17
|
+
it { is_expected.to be_a_twirp_error(404) }
|
18
|
+
|
19
|
+
it { expect { is_expected.to be_a_twirp_error(400) }.to fail }
|
20
|
+
|
21
|
+
it "catches erroneous codes" do
|
22
|
+
expect {
|
23
|
+
is_expected.to be_a_twirp_error(123)
|
24
|
+
}.to raise_error(ArgumentError, /invalid error/)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "code matches" do
|
29
|
+
it { is_expected.to be_a_twirp_error(:not_found) }
|
30
|
+
|
31
|
+
it { expect { is_expected.to be_a_twirp_error(:unknown) }.to fail }
|
32
|
+
|
33
|
+
it "catches erroneous codes" do
|
34
|
+
expect {
|
35
|
+
is_expected.to be_a_twirp_error(:not_a_valid_code)
|
36
|
+
}.to raise_error(ArgumentError, /:not_a_valid_code/)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "msg matches" do
|
41
|
+
it { is_expected.to be_a_twirp_error("Not Found") }
|
42
|
+
|
43
|
+
it "supports Regex matches" do
|
44
|
+
is_expected.to be_a_twirp_error(/Not/)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "catches mismatches" do
|
48
|
+
expect {
|
49
|
+
is_expected.to be_a_twirp_error("Not")
|
50
|
+
}.to fail_with /to have msg: "Not"/
|
51
|
+
|
52
|
+
expect {
|
53
|
+
is_expected.to be_a_twirp_error(/Nope/)
|
54
|
+
}.to fail_with /to have msg: \/Nope\//
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "meta matches" do
|
59
|
+
it { is_expected.to be_a_twirp_error(is_meta: "true") }
|
60
|
+
it { is_expected.to be_a_twirp_error(is_meta: /^t/) }
|
61
|
+
|
62
|
+
it "catches mismatches" do
|
63
|
+
expect {
|
64
|
+
is_expected.to be_a_twirp_error(is_meta: "false")
|
65
|
+
}.to fail_with /to have meta.*is_meta/
|
66
|
+
|
67
|
+
expect {
|
68
|
+
is_expected.to be_a_twirp_error(not_meta: "")
|
69
|
+
}.to fail_with /to have meta.*not_meta/
|
70
|
+
end
|
71
|
+
|
72
|
+
it "catches type errors" do
|
73
|
+
expect {
|
74
|
+
is_expected.to be_a_twirp_error(is_meta: true)
|
75
|
+
}.to raise_error(ArgumentError, /meta values must be Strings/)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "instance matches" do
|
80
|
+
it "matches similar looking instances" do
|
81
|
+
error = Twirp::Error.new(code, msg, meta)
|
82
|
+
is_expected.to be_a_twirp_error(error)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "catches mismatches" do
|
86
|
+
# no meta
|
87
|
+
expect {
|
88
|
+
is_expected.to be_a_twirp_error(Twirp::Error.not_found("Not Found"))
|
89
|
+
}.to fail
|
90
|
+
|
91
|
+
expect {
|
92
|
+
is_expected.to be_a_twirp_error(Twirp::Error.internal("boom"))
|
93
|
+
}.to fail
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "multi matches" do
|
98
|
+
it { is_expected.to be_a_twirp_error(:not_found, "Not Found") }
|
99
|
+
it { is_expected.to be_a_twirp_error(:not_found, /Not/) }
|
100
|
+
it { is_expected.to be_a_twirp_error(:not_found, is_meta: "true") }
|
101
|
+
|
102
|
+
it "catches mismatches" do
|
103
|
+
expect {
|
104
|
+
is_expected.to be_a_twirp_error(:unknown, "Not Found")
|
105
|
+
}.to fail_with /unknown/
|
106
|
+
|
107
|
+
expect {
|
108
|
+
is_expected.to be_a_twirp_error(:not_found, "Nope")
|
109
|
+
}.to fail_with /Nope/
|
110
|
+
|
111
|
+
expect {
|
112
|
+
is_expected.to be_a_twirp_error(:not_found, is_meta: "false")
|
113
|
+
}.to fail_with /false/
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
describe "make_twirp_request" do
|
2
|
+
let(:client) { HelloWorldClient.new(conn) }
|
3
|
+
let(:conn) { mock_twirp_connection(response) }
|
4
|
+
let!(:other_client) { HelloWorldClient.new(conn) }
|
5
|
+
let(:request) { HelloRequest.new(name: "World", count: 3) }
|
6
|
+
let(:response) { HelloResponse.new(message: ["Hello", "Hello", "Hello World"]) }
|
7
|
+
|
8
|
+
def hello
|
9
|
+
client.hello(request)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "block mode" do
|
13
|
+
it { expect { hello }.to make_twirp_request }
|
14
|
+
|
15
|
+
it "returns nil" do
|
16
|
+
expect {
|
17
|
+
expect(hello).to be_nil
|
18
|
+
}.to make_twirp_request
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with client instance matcher" do
|
22
|
+
it "matches a client instance" do
|
23
|
+
expect { hello }.to make_twirp_request(client)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "matches a pre-existing client instance" do
|
27
|
+
expect {
|
28
|
+
other_client.hello(HelloRequest.new)
|
29
|
+
}.to make_twirp_request(other_client)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "catches client mismaches" do
|
33
|
+
expect {
|
34
|
+
expect { hello }.to make_twirp_request(other_client)
|
35
|
+
}.to fail
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with client class matcher" do
|
40
|
+
it "matches a client class" do
|
41
|
+
expect { hello }.to make_twirp_request(HelloWorldClient)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "matches subclasses" do
|
45
|
+
expect { hello }.to make_twirp_request(Twirp::Client)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "catches mismatches" do
|
49
|
+
expect {
|
50
|
+
expect { hello }.to make_twirp_request(GoodbyeClient)
|
51
|
+
}.to fail
|
52
|
+
end
|
53
|
+
|
54
|
+
it "catches type errors" do
|
55
|
+
expect {
|
56
|
+
expect { hello }.to make_twirp_request(Object)
|
57
|
+
}.to raise_error(TypeError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with rpc name matcher" do
|
62
|
+
it { expect { hello }.to make_twirp_request(:Hello) }
|
63
|
+
it { expect { hello }.to make_twirp_request("Hello") }
|
64
|
+
it { expect { hello }.to make_twirp_request(/^He/) }
|
65
|
+
|
66
|
+
it "catches mismatches" do
|
67
|
+
expect {
|
68
|
+
expect { hello }.to make_twirp_request(:Bye)
|
69
|
+
}.to fail
|
70
|
+
|
71
|
+
expect {
|
72
|
+
expect { hello }.to make_twirp_request(/B/)
|
73
|
+
}.to fail
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with input matcher" do
|
78
|
+
it "matches messages" do
|
79
|
+
expect { hello }.to make_twirp_request.with(request)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "matches message type" do
|
83
|
+
expect { hello }.to make_twirp_request.with(HelloRequest)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "matches with attrs" do
|
87
|
+
expect { hello }.to make_twirp_request.with(count: 3)
|
88
|
+
end
|
89
|
+
|
90
|
+
context "with json request" do
|
91
|
+
it "matches messages" do
|
92
|
+
expect {
|
93
|
+
client.hello(name: "World")
|
94
|
+
}.to make_twirp_request.with(HelloRequest.new(name: "World"))
|
95
|
+
end
|
96
|
+
|
97
|
+
it "matches attrs" do
|
98
|
+
expect {
|
99
|
+
client.hello(name: "World")
|
100
|
+
}.to make_twirp_request.with(name: "World")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "catches mismatches" do
|
105
|
+
expect {
|
106
|
+
expect { hello }.to make_twirp_request.with(count: 1)
|
107
|
+
}.to fail
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "with service matcher" do
|
112
|
+
it { expect { hello }.to make_twirp_request(HelloWorldService) }
|
113
|
+
|
114
|
+
it "catches mismatches" do
|
115
|
+
expect {
|
116
|
+
expect { hello }.to make_twirp_request(GoodbyeService)
|
117
|
+
}.to fail
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe ".and_call_original" do
|
122
|
+
it "calls original" do
|
123
|
+
expect {
|
124
|
+
expect(hello).to be_a_twirp_response(response)
|
125
|
+
}.to make_twirp_request.and_call_original
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe ".and_return" do
|
130
|
+
it "returns the specified value" do
|
131
|
+
expect {
|
132
|
+
expect(hello).to be_a_twirp_response(HelloRequest.new)
|
133
|
+
}.to make_twirp_request.and_return(HelloRequest.new)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns the specified value" do
|
137
|
+
expect {
|
138
|
+
expect(hello).to be_a_twirp_response(HelloRequest.new)
|
139
|
+
}.to make_twirp_request.and_return(HelloRequest)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "inline mode" do
|
145
|
+
after { hello }
|
146
|
+
|
147
|
+
it { expect(client).to make_twirp_request }
|
148
|
+
|
149
|
+
it "matches a specific rpc method" do
|
150
|
+
expect(client).to make_twirp_request(:hello)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "matches a specific rpc method by rpc name" do
|
154
|
+
expect(client).to make_twirp_request(:Hello)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "fails when client is not called within scope" do
|
158
|
+
expect_expectation_failure {
|
159
|
+
expect(client).to make_twirp_request
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
describe ".with" do
|
164
|
+
let(:request) { HelloRequest.new(name: "Daniel") }
|
165
|
+
|
166
|
+
it "matches the request message" do
|
167
|
+
expect(client).to make_twirp_request(:hello).with(request)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "matches the request arguments" do
|
171
|
+
expect(client).to make_twirp_request(:hello).with(name: "Daniel")
|
172
|
+
end
|
173
|
+
|
174
|
+
it "fails when params don't match" do
|
175
|
+
expect_expectation_failure {
|
176
|
+
expect(client).to make_twirp_request.with(name: "Bob")
|
177
|
+
}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
describe "be_a_twirp_message" do
|
2
|
+
subject { HelloRequest.new(**attrs) }
|
3
|
+
|
4
|
+
let(:attrs) { {} }
|
5
|
+
|
6
|
+
it { is_expected.to be_a_twirp_message }
|
7
|
+
|
8
|
+
it "works with responses also" do
|
9
|
+
expect(HelloResponse.new).to be_a_twirp_message
|
10
|
+
end
|
11
|
+
|
12
|
+
it "supports compound matchers" do
|
13
|
+
expect([ subject ]).to include(a_twirp_message)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "catches non-twirp subjects" do
|
17
|
+
expect {
|
18
|
+
expect(Object).to be_a_twirp_message
|
19
|
+
}.to fail_with /Expected a Twirp message, found Object/
|
20
|
+
end
|
21
|
+
|
22
|
+
it "matches a specific message type" do
|
23
|
+
is_expected.to be_a_twirp_message(HelloRequest)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "catches type mismatches" do
|
27
|
+
expect {
|
28
|
+
is_expected.to be_a_twirp_message(GoodbyeRequest)
|
29
|
+
}.to fail_with /message of type GoodbyeRequest/
|
30
|
+
end
|
31
|
+
|
32
|
+
it "catches erroneous message types" do
|
33
|
+
expect {
|
34
|
+
is_expected.to be_a_twirp_message(Object)
|
35
|
+
}.to raise_error(TypeError, /Object/)
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with attributes" do
|
39
|
+
let(:attrs) { { name: "Bob", count: 3 } }
|
40
|
+
|
41
|
+
it "can match attributes" do
|
42
|
+
is_expected.to be_a_twirp_message(HelloRequest, **attrs)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "supports regex matches" do
|
46
|
+
is_expected.to be_a_twirp_message(name: /^B/)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "supports range matches" do
|
50
|
+
is_expected.to be_a_twirp_message(count: 1..5)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "catches mismatches" do
|
54
|
+
expect {
|
55
|
+
is_expected.to be_a_twirp_message(GoodbyeRequest, name: "Bob")
|
56
|
+
}.to fail_with /message of type/
|
57
|
+
|
58
|
+
expect {
|
59
|
+
is_expected.to be_a_twirp_message(name: "nope")
|
60
|
+
}.to fail_with /to have name: "nope"/
|
61
|
+
|
62
|
+
expect {
|
63
|
+
is_expected.to be_a_twirp_message(name: /no/)
|
64
|
+
}.to fail_with /to have name: \/no\//
|
65
|
+
|
66
|
+
expect {
|
67
|
+
is_expected.to be_a_twirp_message(count: 1)
|
68
|
+
}.to fail_with /to have count: 1/
|
69
|
+
end
|
70
|
+
|
71
|
+
it "catches the erroneous attribute matches" do
|
72
|
+
expect {
|
73
|
+
is_expected.to be_a_twirp_message(namezzz: "Bob")
|
74
|
+
}.to raise_error(ArgumentError, /namezzz/)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "catches type mismatches" do
|
78
|
+
expect {
|
79
|
+
is_expected.to be_a_twirp_message(name: 123)
|
80
|
+
}.to raise_error(TypeError, /string field.*given Integer/)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
describe :mock_twirp_connection do
|
2
|
+
subject { client.bye(request) }
|
3
|
+
|
4
|
+
let(:client) { GoodbyeClient.new(conn) }
|
5
|
+
let(:request) { GoodbyeRequest.new }
|
6
|
+
let(:response) { GoodbyeResponse.new(**response_attrs) }
|
7
|
+
let(:response_attrs) { { message: "bye" } }
|
8
|
+
|
9
|
+
context "without a mock" do
|
10
|
+
let(:conn) { "http://localhost:3000/twirp/Goodbye/Bye" }
|
11
|
+
|
12
|
+
it { expect { subject }.to raise_error(Faraday::Error) }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "with a response instance" do
|
16
|
+
let(:conn) { mock_twirp_connection(response) }
|
17
|
+
|
18
|
+
it { is_expected.to be_a(Twirp::ClientResp) }
|
19
|
+
it { is_expected.to be_a_twirp_response }
|
20
|
+
it { is_expected.to be_a_twirp_response(response) }
|
21
|
+
it { is_expected.to be_a_twirp_response(message: "bye") }
|
22
|
+
|
23
|
+
it "catches mismatches" do
|
24
|
+
expect {
|
25
|
+
is_expected.to be_a_twirp_response(message: "nope")
|
26
|
+
}.to fail
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "with a response type" do
|
31
|
+
let(:conn) { mock_twirp_connection(GoodbyeResponse) }
|
32
|
+
|
33
|
+
it { is_expected.to be_a_twirp_response(GoodbyeResponse) }
|
34
|
+
|
35
|
+
it "catches mismatches" do
|
36
|
+
expect {
|
37
|
+
is_expected.to be_a_twirp_response(HelloResponse)
|
38
|
+
}.to fail
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "with default response" do
|
43
|
+
let(:conn) { mock_twirp_connection }
|
44
|
+
|
45
|
+
it { is_expected.to be_a_twirp_response(GoodbyeResponse) }
|
46
|
+
|
47
|
+
context "and with attrs" do
|
48
|
+
let(:conn) { mock_twirp_connection(message: "adios") }
|
49
|
+
|
50
|
+
it { is_expected.to be_a_twirp_response(message: "adios") }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "with an error" do
|
55
|
+
let(:conn) { mock_twirp_connection(error) }
|
56
|
+
let(:error) { Twirp::Error.not_found("nope") }
|
57
|
+
|
58
|
+
it { is_expected.to be_a_twirp_response.with_error(error) }
|
59
|
+
|
60
|
+
context "when error is symbol" do
|
61
|
+
let(:error) { :not_found }
|
62
|
+
|
63
|
+
it { is_expected.to be_a_twirp_response.with_error(404) }
|
64
|
+
it { is_expected.to be_a_twirp_response.with_error(:not_found) }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when error is integer" do
|
68
|
+
let(:error) { 500 }
|
69
|
+
|
70
|
+
it { is_expected.to be_a_twirp_response.with_error(500) }
|
71
|
+
it { is_expected.to be_a_twirp_response.with_error(:internal) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "with a block" do
|
76
|
+
context "with a response object" do
|
77
|
+
let(:conn) { mock_twirp_connection { response } }
|
78
|
+
|
79
|
+
it { is_expected.to be_a_twirp_response(response) }
|
80
|
+
end
|
81
|
+
|
82
|
+
context "with response attrs" do
|
83
|
+
let(:conn) { mock_twirp_connection { response_attrs } }
|
84
|
+
|
85
|
+
it "determines the correct response type and incorporates the attrs" do
|
86
|
+
is_expected.to be_a_twirp_response(GoodbyeResponse, **response_attrs)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "with error" do
|
91
|
+
let(:conn) { mock_twirp_connection { error } }
|
92
|
+
let(:error) { Twirp::Error.not_found("nope") }
|
93
|
+
|
94
|
+
it { is_expected.to be_a_twirp_response.with_error(error) }
|
95
|
+
end
|
96
|
+
|
97
|
+
context "with error code" do
|
98
|
+
let(:conn) { mock_twirp_connection { :not_found } }
|
99
|
+
|
100
|
+
it { is_expected.to be_a_twirp_response.with_error(:not_found) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
describe "be_a_twirp_response" do
|
2
|
+
context "with a response" do
|
3
|
+
subject { Twirp::ClientResp.new(response, nil) }
|
4
|
+
|
5
|
+
let(:response) { GoodbyeResponse.new }
|
6
|
+
|
7
|
+
it { is_expected.to be_a_twirp_response }
|
8
|
+
|
9
|
+
it "catches non-twirp response" do
|
10
|
+
expect {
|
11
|
+
expect(Object).to be_a_twirp_response
|
12
|
+
}.to fail_with /found Object/
|
13
|
+
end
|
14
|
+
|
15
|
+
it "matches a specific response type" do
|
16
|
+
is_expected.to be_a_twirp_response(GoodbyeResponse)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "matches a specific response" do
|
20
|
+
is_expected.to be_a_twirp_response(response)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "catches type mismatches" do
|
24
|
+
expect {
|
25
|
+
is_expected.to be_a_twirp_response(HelloResponse)
|
26
|
+
}.to fail_with /of type HelloResponse/
|
27
|
+
end
|
28
|
+
|
29
|
+
it "catches erroneous response types" do
|
30
|
+
expect {
|
31
|
+
is_expected.to be_a_twirp_response(Object)
|
32
|
+
}.to raise_error(ArgumentError, /Object/)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "with attributes" do
|
36
|
+
subject { Twirp::ClientResp.new(GoodbyeResponse.new(**attrs), nil) }
|
37
|
+
|
38
|
+
let(:attrs) { { message: "bye", name: "Bob" } }
|
39
|
+
|
40
|
+
it "can match attributes" do
|
41
|
+
is_expected.to be_a_twirp_response(GoodbyeResponse, **attrs)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "supports regex matches" do
|
45
|
+
is_expected.to be_a_twirp_response(name: /^B/)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "catches mismatches" do
|
49
|
+
expect {
|
50
|
+
is_expected.to be_a_twirp_response(name: "nope")
|
51
|
+
}.to fail_with /to have name: "nope"/
|
52
|
+
|
53
|
+
expect {
|
54
|
+
is_expected.to be_a_twirp_response(name: /no/)
|
55
|
+
}.to fail_with /to have name: \/no\//
|
56
|
+
end
|
57
|
+
|
58
|
+
it "catches the erroneous attributes" do
|
59
|
+
expect {
|
60
|
+
is_expected.to be_a_twirp_response(namezzz: "Bob")
|
61
|
+
}.to raise_error(ArgumentError, /namezzz/)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "catches type mismatches" do
|
65
|
+
expect {
|
66
|
+
is_expected.to be_a_twirp_response(name: 123)
|
67
|
+
}.to raise_error(TypeError, /string field.*given Integer/)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "can't also match a specific response" do
|
71
|
+
expect {
|
72
|
+
is_expected.to be_a_twirp_response(response, name: "Bob")
|
73
|
+
}.to raise_error(ArgumentError, /but not both/)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "with error" do
|
79
|
+
subject { Twirp::ClientResp.new(nil, error) }
|
80
|
+
|
81
|
+
let(:error) { Twirp::Error.new(code, msg, meta) }
|
82
|
+
let(:code) { :not_found }
|
83
|
+
let(:msg) { "Not Found" }
|
84
|
+
let(:meta) { { is_meta: "true" } }
|
85
|
+
|
86
|
+
it { is_expected.to be_a_twirp_response.with_error }
|
87
|
+
it { is_expected.to be_a_twirp_response.with_error(code) }
|
88
|
+
it { is_expected.to be_a_twirp_response.with_error(msg) }
|
89
|
+
it { is_expected.to be_a_twirp_response.with_error(**meta) }
|
90
|
+
it { is_expected.to be_a_twirp_response.with_error(/Not/) }
|
91
|
+
|
92
|
+
it "catches mismatches" do
|
93
|
+
expect {
|
94
|
+
is_expected.to be_a_twirp_response.with_error(:internal)
|
95
|
+
}.to fail_with /code: :internal/
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with neither response nor error" do
|
100
|
+
subject { Twirp::ClientResp.new(nil, nil) }
|
101
|
+
|
102
|
+
it "fails the response match" do
|
103
|
+
expect {
|
104
|
+
is_expected.to be_a_twirp_response
|
105
|
+
}.to fail_with /to have data/
|
106
|
+
end
|
107
|
+
|
108
|
+
it "fails the error match" do
|
109
|
+
expect {
|
110
|
+
is_expected.to be_a_twirp_response.with_error
|
111
|
+
}.to fail_with /to have an error/
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with both response and error" do
|
116
|
+
subject { Twirp::ClientResp.new(GoodbyeResponse.new, Twirp::Error.not_found("Not Found")) }
|
117
|
+
|
118
|
+
it "fails" do
|
119
|
+
expect {
|
120
|
+
is_expected.to be_a_twirp_response(name: "Bob").with_error
|
121
|
+
}.to raise_error(ArgumentError, /but not both/)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
metadata
CHANGED
@@ -1,31 +1,59 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-twirp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Pepper
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: google-protobuf
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
type: :
|
19
|
+
version: '3'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: twirp
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: byebug
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
30
58
|
requirements:
|
31
59
|
- - ">="
|
@@ -39,7 +67,7 @@ dependencies:
|
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
70
|
+
name: codecov
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
73
|
- - ">="
|
@@ -73,7 +101,20 @@ extensions: []
|
|
73
101
|
extra_rdoc_files: []
|
74
102
|
files:
|
75
103
|
- lib/rspec-twirp.rb
|
76
|
-
- lib/rspec
|
104
|
+
- lib/rspec/twirp.rb
|
105
|
+
- lib/rspec/twirp/error_matcher.rb
|
106
|
+
- lib/rspec/twirp/helpers.rb
|
107
|
+
- lib/rspec/twirp/make_request_matcher.rb
|
108
|
+
- lib/rspec/twirp/message_matcher.rb
|
109
|
+
- lib/rspec/twirp/mock_client.rb
|
110
|
+
- lib/rspec/twirp/mock_connection.rb
|
111
|
+
- lib/rspec/twirp/response_matcher.rb
|
112
|
+
- lib/rspec/twirp/version.rb
|
113
|
+
- spec/error_spec.rb
|
114
|
+
- spec/make_request_spec.rb
|
115
|
+
- spec/message_spec.rb
|
116
|
+
- spec/mock_connection_spec.rb
|
117
|
+
- spec/response_spec.rb
|
77
118
|
homepage: https://github.com/dpep/rspec-twirp_rb
|
78
119
|
licenses:
|
79
120
|
- MIT
|
@@ -93,8 +134,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
134
|
- !ruby/object:Gem::Version
|
94
135
|
version: '0'
|
95
136
|
requirements: []
|
96
|
-
rubygems_version: 3.
|
137
|
+
rubygems_version: 3.3.7
|
97
138
|
signing_key:
|
98
139
|
specification_version: 4
|
99
|
-
summary:
|
100
|
-
test_files:
|
140
|
+
summary: Gem::Specification::RSpec::Twirp
|
141
|
+
test_files:
|
142
|
+
- spec/error_spec.rb
|
143
|
+
- spec/make_request_spec.rb
|
144
|
+
- spec/message_spec.rb
|
145
|
+
- spec/mock_connection_spec.rb
|
146
|
+
- spec/response_spec.rb
|
data/lib/rspec-twirp/version.rb
DELETED