rspec-twirp 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,169 +0,0 @@
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
@@ -1,59 +0,0 @@
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
@@ -1,54 +0,0 @@
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
data/spec/error_spec.rb DELETED
@@ -1,116 +0,0 @@
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
@@ -1,181 +0,0 @@
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
data/spec/message_spec.rb DELETED
@@ -1,83 +0,0 @@
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