rspec-twirp 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2634a27685c93991e30afab03cf80cb5fe650047a2b91342eeadff69a7bb920e
4
- data.tar.gz: f87529044e688df0b546e65cd527989842e983576bf62a70ea7aabf72d920d09
3
+ metadata.gz: 7a9027f86450d9e045705304d1d8e590b51a733269938e83ca5ae098a90018d4
4
+ data.tar.gz: dca42845c93b814878cf24842320583ddf6dbce3b0d266cd897a58500cf31a31
5
5
  SHA512:
6
- metadata.gz: 01e6fbab6f8649f6c8dbcc8df72269699e099eb1d924d6ff6656781d5339d060c6138e465a5c43b5ec2a9320614d7924bed1c79eac088c9c10f881d99c6588d7
7
- data.tar.gz: ce566d5fd9e1e91cca7a100d205e4a1b0615c743f808383d5180bd007049aeaaaf1298bfe382ad6e0385404dd3c04fadb5980ac53fbc9de54c9aeee335b4834b
6
+ metadata.gz: 98d362457d71269cf64bffa1fe0b1fe00e173fe68df88e12cf0af20aa1030d518e031415d005050209b070095c36a6eac454e1bdba740661a090ce3be90cfa15
7
+ data.tar.gz: a0923b782b8568198fb267cd1e3bc6192bb032b0a7e746be478a4e8d66317ccc81652487f6f3b442847c6c2946b2e398f25b60682c0cc6b1c794c556837d0978
@@ -1,43 +1,59 @@
1
1
  RSpec::Matchers.define :be_a_twirp_error do |*matchers, **meta_matcher|
2
2
  match do |actual|
3
- @fail_msg = "Expected #{actual} to be a Twirp::Error, found #{actual.class}"
3
+ @fail_msg = "Expected `#{actual}` to be a Twirp::Error, found: #{actual.class}"
4
4
  return false unless actual.is_a?(Twirp::Error)
5
5
 
6
- matchers.each do |matcher|
6
+ @expected = {}
7
+ @actual = {}
8
+
9
+ matched = matchers.all? do |matcher|
7
10
  case matcher
8
11
  when Symbol
9
12
  # match code
13
+ @expected[:code] = matcher
14
+ @actual[:code] = actual.code
10
15
 
11
16
  unless Twirp::Error.valid_code?(matcher)
12
17
  raise ArgumentError, "invalid error code: #{matcher.inspect}"
13
18
  end
14
19
 
15
- @fail_msg = "Expected #{actual} to have code: #{matcher.inspect}, found #{actual.code}"
16
- return false unless actual.code == matcher
20
+ @fail_msg = "Expected #{actual} to have code `#{matcher.inspect}`, found: #{actual.code.inspect}"
21
+ actual.code == matcher
17
22
  when Integer
18
23
  # match http status code
19
24
 
20
25
  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
26
+ @expected[:code] = code
27
+ @actual[:code] = actual.code
28
+
29
+ actual_status = Twirp::ERROR_CODES_TO_HTTP_STATUS[actual.code]
30
+
31
+ @fail_msg = "Expected #{actual} to have status #{matcher} / #{code.inspect}, found: #{actual_status} / #{actual.code.inspect}"
32
+ actual.code == code
23
33
  else
24
34
  raise ArgumentError, "invalid error status code: #{matcher}"
25
35
  end
26
36
  when Twirp::Error
27
37
  # match instance
38
+ @expected = matcher.to_h
39
+ @actual = actual.to_h
28
40
 
29
41
  @fail_msg = "Expected #{actual} to equal #{matcher}"
30
- return false unless values_match?(matcher.to_h, actual.to_h)
42
+ values_match?(matcher.to_h, actual.to_h)
31
43
  else
32
44
  # match msg
33
- @fail_msg = "Expected #{actual} to have msg: #{matcher.inspect}, found #{actual.msg}"
34
- return false unless values_match?(matcher, actual.msg)
45
+ @expected[:msg] = matcher
46
+ @actual[:msg] = actual.msg
47
+
48
+ @fail_msg = "Expected #{actual} to have msg #{matcher.inspect}, found: #{actual.msg.inspect}"
49
+ values_match?(matcher, actual.msg)
35
50
  end
36
51
  end
37
52
 
38
53
  # match meta
39
54
  unless meta_matcher.empty?
40
- @cur_match = { meta: meta_matcher }
55
+ @expected[:meta] = meta_matcher
56
+ @actual[:meta] = actual.meta
41
57
 
42
58
  # sanity check...values must be Strings or Regexp
43
59
  discrete_attrs = meta_matcher.transform_values do |attr|
@@ -46,10 +62,10 @@ RSpec::Matchers.define :be_a_twirp_error do |*matchers, **meta_matcher|
46
62
  actual.send(:validate_meta, discrete_attrs)
47
63
 
48
64
  @fail_msg = "Expected #{actual} to have meta: #{meta_matcher.inspect}, found #{actual.meta}"
49
- return false unless values_match?(meta_matcher, actual.meta)
65
+ matched &= values_match?(meta_matcher, actual.meta)
50
66
  end
51
67
 
52
- true
68
+ matched
53
69
  end
54
70
 
55
71
  description do
@@ -57,6 +73,12 @@ RSpec::Matchers.define :be_a_twirp_error do |*matchers, **meta_matcher|
57
73
  end
58
74
 
59
75
  failure_message { @fail_msg }
76
+
77
+ diffable
78
+
79
+ def expected
80
+ @expected
81
+ end
60
82
  end
61
83
 
62
84
  RSpec::Matchers.alias_matcher :a_twirp_error, :be_a_twirp_error
@@ -1,61 +1,2 @@
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
1
+ RSpec::Matchers.alias_matcher :be_a_twirp_message, :be_a_protobuf
2
+ RSpec::Matchers.alias_matcher :a_twirp_message, :a_protobuf
@@ -1,31 +1,13 @@
1
- RSpec::Matchers.define :be_a_twirp_response do |type = nil, **attrs|
1
+ RSpec::Matchers.define :be_a_twirp_response do |**attrs|
2
2
  chain :with_error do |*matchers, **meta_matchers|
3
3
  # code, msg, meta
4
4
  @with_error = [ matchers, meta_matchers ]
5
5
  end
6
6
 
7
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
8
  @fail_msg = "Expected a Twirp::ClientResp, found #{actual}"
23
9
  return false unless actual.is_a?(Twirp::ClientResp)
24
10
 
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
11
  if @with_error
30
12
  unless attrs.empty?
31
13
  raise ArgumentError, "match data attributes or error, but not both"
@@ -48,7 +30,7 @@ RSpec::Matchers.define :be_a_twirp_response do |type = nil, **attrs|
48
30
  end
49
31
 
50
32
  description do
51
- type ? "a #{type} Twirp response" : "a Twirp response"
33
+ "a Twirp response"
52
34
  end
53
35
 
54
36
  failure_message { @fail_msg }
@@ -1,5 +1,5 @@
1
1
  module RSpec
2
2
  module Twirp
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/lib/rspec/twirp.rb CHANGED
@@ -1,54 +1,9 @@
1
1
  require "google/protobuf"
2
2
  require "rspec"
3
+ require "rspec/protobuf"
3
4
  require "twirp"
4
5
 
5
- require "rspec/twirp/helpers"
6
- require "rspec/twirp/make_request_matcher"
7
6
  require "rspec/twirp/error_matcher"
8
7
  require "rspec/twirp/message_matcher"
9
8
  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
9
+ require "rspec/twirp/version"
data/spec/error_spec.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  describe "be_a_twirp_error" do
2
- subject { Twirp::Error.new(code, msg, meta) }
2
+ subject(:error) { Twirp::Error.new(code, msg, meta) }
3
3
 
4
4
  let(:code) { :not_found }
5
5
  let(:msg) { "Not Found" }
@@ -7,6 +7,10 @@ describe "be_a_twirp_error" do
7
7
 
8
8
  it { is_expected.to be_a_twirp_error }
9
9
 
10
+ it "is composable" do
11
+ expect(e: error).to include(e: a_twirp_error)
12
+ end
13
+
10
14
  it "catches type mismatches" do
11
15
  expect {
12
16
  expect(Object).to be_a_twirp_error
@@ -16,7 +20,18 @@ describe "be_a_twirp_error" do
16
20
  describe "status matches" do
17
21
  it { is_expected.to be_a_twirp_error(404) }
18
22
 
19
- it { expect { is_expected.to be_a_twirp_error(400) }.to fail }
23
+ it "catches mismatches" do
24
+ expect {
25
+ is_expected.to be_a_twirp_error(400)
26
+ }.to fail_including(
27
+ "to have status 400",
28
+ "found: 404",
29
+
30
+ # diff
31
+ "-:code => :invalid_argument,",
32
+ "+:code => :not_found,",
33
+ )
34
+ end
20
35
 
21
36
  it "catches erroneous codes" do
22
37
  expect {
@@ -28,7 +43,18 @@ describe "be_a_twirp_error" do
28
43
  describe "code matches" do
29
44
  it { is_expected.to be_a_twirp_error(:not_found) }
30
45
 
31
- it { expect { is_expected.to be_a_twirp_error(:unknown) }.to fail }
46
+ it "catches mismatches" do
47
+ expect {
48
+ is_expected.to be_a_twirp_error(:unknown)
49
+ }.to fail_including(
50
+ "to have code `:unknown`",
51
+ "found: :not_found",
52
+
53
+ # diff
54
+ "-:code => :unknown,",
55
+ "+:code => :not_found,",
56
+ )
57
+ end
32
58
 
33
59
  it "catches erroneous codes" do
34
60
  expect {
@@ -47,11 +73,22 @@ describe "be_a_twirp_error" do
47
73
  it "catches mismatches" do
48
74
  expect {
49
75
  is_expected.to be_a_twirp_error("Not")
50
- }.to fail_with /to have msg: "Not"/
76
+ }.to fail_including(
77
+ 'to have msg "Not"',
78
+ 'found: "Not Found"',
79
+
80
+ # diff
81
+ '-:msg => "Not",',
82
+ '+:msg => "Not Found",',
83
+ )
51
84
 
52
85
  expect {
53
86
  is_expected.to be_a_twirp_error(/Nope/)
54
- }.to fail_with /to have msg: \/Nope\//
87
+ }.to fail_including(
88
+ # diff
89
+ '-:msg => /Nope/,',
90
+ '+:msg => "Not Found",',
91
+ )
55
92
  end
56
93
  end
57
94
 
@@ -62,7 +99,13 @@ describe "be_a_twirp_error" do
62
99
  it "catches mismatches" do
63
100
  expect {
64
101
  is_expected.to be_a_twirp_error(is_meta: "false")
65
- }.to fail_with /to have meta.*is_meta/
102
+ }.to fail_including(
103
+ "to have meta",
104
+
105
+ # diff
106
+ '-:meta => {:is_meta=>"false"},',
107
+ '+:meta => {:is_meta=>"true"},',
108
+ )
66
109
 
67
110
  expect {
68
111
  is_expected.to be_a_twirp_error(not_meta: "")
data/spec/message_spec.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  describe "be_a_twirp_message" do
2
- subject { HelloRequest.new(**attrs) }
2
+ subject(:request) { HelloRequest.new(**attrs) }
3
3
 
4
4
  let(:attrs) { {} }
5
5
 
@@ -10,13 +10,11 @@ describe "be_a_twirp_message" do
10
10
  end
11
11
 
12
12
  it "supports compound matchers" do
13
- expect([ subject ]).to include(a_twirp_message)
13
+ expect([ request ]).to include(a_twirp_message)
14
14
  end
15
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/
16
+ it "does not match non-twirp subjects" do
17
+ expect(Object).not_to be_a_twirp_message
20
18
  end
21
19
 
22
20
  it "matches a specific message type" do
@@ -24,9 +22,7 @@ describe "be_a_twirp_message" do
24
22
  end
25
23
 
26
24
  it "catches type mismatches" do
27
- expect {
28
- is_expected.to be_a_twirp_message(GoodbyeRequest)
29
- }.to fail_with /message of type GoodbyeRequest/
25
+ is_expected.not_to be_a_twirp_message(GoodbyeRequest)
30
26
  end
31
27
 
32
28
  it "catches erroneous message types" do
@@ -55,29 +51,19 @@ describe "be_a_twirp_message" do
55
51
  is_expected.to be_a_twirp_message(GoodbyeRequest, name: "Bob")
56
52
  }.to fail_with /message of type/
57
53
 
58
- expect {
59
- is_expected.to be_a_twirp_message(name: "nope")
60
- }.to fail_with /to have name: "nope"/
54
+ is_expected.not_to be_a_twirp_message(name: "nope")
61
55
 
62
- expect {
63
- is_expected.to be_a_twirp_message(name: /no/)
64
- }.to fail_with /to have name: \/no\//
56
+ is_expected.not_to be_a_twirp_message(name: /no/)
65
57
 
66
- expect {
67
- is_expected.to be_a_twirp_message(count: 1)
68
- }.to fail_with /to have count: 1/
58
+ is_expected.not_to be_a_twirp_message(count: 1)
69
59
  end
70
60
 
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/)
61
+ it "catches erroneous attribute matches" do
62
+ is_expected.not_to be_a_twirp_message(namezzz: "Bob")
75
63
  end
76
64
 
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/)
65
+ it "handles type mismatches" do
66
+ is_expected.not_to be_a_twirp_message(name: 123)
81
67
  end
82
68
  end
83
69
  end
@@ -6,39 +6,16 @@ describe "be_a_twirp_response" do
6
6
 
7
7
  it { is_expected.to be_a_twirp_response }
8
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/)
9
+ it "handles non-twirp response" do
10
+ expect(Object).not_to be_a_twirp_response
33
11
  end
34
12
 
35
13
  context "with attributes" do
36
- subject { Twirp::ClientResp.new(GoodbyeResponse.new(**attrs), nil) }
37
-
14
+ let(:response) { GoodbyeResponse.new(**attrs) }
38
15
  let(:attrs) { { message: "bye", name: "Bob" } }
39
16
 
40
17
  it "can match attributes" do
41
- is_expected.to be_a_twirp_response(GoodbyeResponse, **attrs)
18
+ is_expected.to be_a_twirp_response(**attrs)
42
19
  end
43
20
 
44
21
  it "supports regex matches" do
@@ -48,29 +25,17 @@ describe "be_a_twirp_response" do
48
25
  it "catches mismatches" do
49
26
  expect {
50
27
  is_expected.to be_a_twirp_response(name: "nope")
51
- }.to fail_with /to have name: "nope"/
28
+ }.to fail_including(
29
+ '-:name => "nope",',
30
+ '+:name => "Bob",',
31
+ )
52
32
 
53
33
  expect {
54
34
  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/)
35
+ }.to fail_including(
36
+ '-:name => /no/,',
37
+ '+:name => "Bob",',
38
+ )
74
39
  end
75
40
  end
76
41
  end
@@ -92,7 +57,7 @@ describe "be_a_twirp_response" do
92
57
  it "catches mismatches" do
93
58
  expect {
94
59
  is_expected.to be_a_twirp_response.with_error(:internal)
95
- }.to fail_with /code: :internal/
60
+ }.to fail_with /to have code `:internal`/
96
61
  end
97
62
  end
98
63
 
@@ -115,7 +80,7 @@ describe "be_a_twirp_response" do
115
80
  context "with both response and error" do
116
81
  subject { Twirp::ClientResp.new(GoodbyeResponse.new, Twirp::Error.not_found("Not Found")) }
117
82
 
118
- it "fails" do
83
+ it "does not permit both attr and error matching" do
119
84
  expect {
120
85
  is_expected.to be_a_twirp_response(name: "Bob").with_error
121
86
  }.to raise_error(ArgumentError, /but not both/)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-twirp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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-10-06 00:00:00.000000000 Z
11
+ date: 2022-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-protobuf
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: twirp
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -94,7 +108,7 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
- description: "..."
111
+ description: Twirp RSpec matchers
98
112
  email:
99
113
  executables: []
100
114
  extensions: []
@@ -103,17 +117,11 @@ files:
103
117
  - lib/rspec-twirp.rb
104
118
  - lib/rspec/twirp.rb
105
119
  - lib/rspec/twirp/error_matcher.rb
106
- - lib/rspec/twirp/helpers.rb
107
- - lib/rspec/twirp/make_request_matcher.rb
108
120
  - lib/rspec/twirp/message_matcher.rb
109
- - lib/rspec/twirp/mock_client.rb
110
- - lib/rspec/twirp/mock_connection.rb
111
121
  - lib/rspec/twirp/response_matcher.rb
112
122
  - lib/rspec/twirp/version.rb
113
123
  - spec/error_spec.rb
114
- - spec/make_request_spec.rb
115
124
  - spec/message_spec.rb
116
- - spec/mock_connection_spec.rb
117
125
  - spec/response_spec.rb
118
126
  homepage: https://github.com/dpep/rspec-twirp_rb
119
127
  licenses:
@@ -140,7 +148,5 @@ specification_version: 4
140
148
  summary: Gem::Specification::RSpec::Twirp
141
149
  test_files:
142
150
  - spec/error_spec.rb
143
- - spec/make_request_spec.rb
144
151
  - spec/message_spec.rb
145
- - spec/mock_connection_spec.rb
146
152
  - spec/response_spec.rb
@@ -1,16 +0,0 @@
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
@@ -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
@@ -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
@@ -1,103 +0,0 @@
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