rspec-twirp 0.2.0 → 0.3.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 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