webmock-twirp 0.2.1 → 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: b68a561786519f4bf91828a3a01e4ae803e36e03af7fd327af7780ba5da4d838
4
- data.tar.gz: 151994b76bc7d4494be3f302f20633a19fdb681a092d4f1d489e694f89d43189
3
+ metadata.gz: f1a34362dd929abe47a175e407335cc6f94a307c00fd902742d06285550fcd78
4
+ data.tar.gz: efefa4e116a259eeab30129f7afe2b803f682daf0e0c15785e48720c5efe5d70
5
5
  SHA512:
6
- metadata.gz: b764858403de27f0b9cc28a6bddb7c827379308c1619be1b8bcbeff8d1824a3cb1c3f892644ce63c78c2bc4f1331e4fdb4a60eb88713541e0161d899a4ddaa1c
7
- data.tar.gz: 43a24c5182e1aab84cbc16897faae7ea54098be328ad07e5754b939c6b89783985c23f1d565db89fa751fb5e1edb719d72a0fef264b64ae1ceadbc877e8b92d0
6
+ metadata.gz: 04c11a624eb11547a7d0df20fcddd5b40015e75b6af94311e764484d98bc1333ffd728b085e0b5a29b08b4b039945d209a7ae74a9312bd0b65d9bebdcf87e43c
7
+ data.tar.gz: 2179804f8df26c7c65d17781cd0a4df2c5c9806697e97c1f794f2bb008a5d5febdad89522df69b853ddfd6d2a869092b8dc572a5f5ac500fb5db3a5e30995215
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ### v0.3.0 (2022-10-28)
2
+ - twirpify all the things
3
+ - improve refinements
4
+ - make_twirp_request rspec matcher
5
+
1
6
  ### v0.2.1 (2022-10-19)
2
7
  - fix client matching
3
8
 
@@ -0,0 +1,36 @@
1
+ module WebMock
2
+ module Twirp
3
+ module NetConnectNotAllowedError
4
+ def initialize(request_signature)
5
+ @request_signature = request_signature
6
+ end
7
+
8
+ def message
9
+ snippet = WebMock::RequestSignatureSnippet.new(@request_signature)
10
+
11
+ text = [
12
+ "Real Twirp connections are disabled. Unregistered request: #{@request_signature}",
13
+ snippet.stubbing_instructions,
14
+ snippet.request_stubs,
15
+ "="*60
16
+ ].compact.join("\n\n")
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ # hijack creation of Twirp errors
23
+ module WebMock
24
+ class NetConnectNotAllowedError
25
+ def self.new(request_signature)
26
+ allocate.tap do |instance|
27
+ if request_signature.is_a?(WebMock::Twirp::RequestSignature)
28
+ instance.extend(WebMock::Twirp::NetConnectNotAllowedError)
29
+ end
30
+
31
+ instance.send(:initialize, request_signature)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,46 @@
1
+ module WebMock
2
+ module Twirp
3
+ module Matchers
4
+ class MakeTwirpRequest
5
+ def initialize(*matchers)
6
+ @stub = WebMock::Twirp::RequestStub.new(*matchers)
7
+ end
8
+
9
+ def method_missing(name, *args, **kwargs, &block)
10
+ super unless respond_to?(name)
11
+ @stub.send(name, *args, **kwargs, &block)
12
+
13
+ self
14
+ end
15
+
16
+ def respond_to?(method_name, include_private = false)
17
+ super || @stub.respond_to?(method_name)
18
+ end
19
+
20
+ def matches?(block)
21
+ unless block.is_a?(Proc)
22
+ raise ArgumentError, "expected block, found: #{block}"
23
+ end
24
+
25
+ WebMock::StubRegistry.instance.register_request_stub(@stub)
26
+ block.call
27
+ WebMock::StubRegistry.instance.remove_request_stub(@stub)
28
+
29
+ RequestRegistry.instance.times_executed(@stub) > 0
30
+ end
31
+
32
+ def failure_message
33
+ "expected a Twirp request but received none"
34
+ end
35
+
36
+ def failure_message_when_negated
37
+ "did not expect a Twirp request, but received one"
38
+ end
39
+
40
+ def supports_block_expectations?
41
+ true
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ require "webmock/twirp/matchers/make_twirp_request"
2
+
3
+ module WebMock
4
+ module Twirp
5
+ module Matchers
6
+ def make_twirp_request(...)
7
+ MakeTwirpRequest.new(...)
8
+ end
9
+
10
+ alias_method :make_a_twirp_request, :make_twirp_request
11
+ end
12
+ end
13
+ end
@@ -71,6 +71,12 @@ module WebMock
71
71
  end
72
72
  end
73
73
  end
74
+
75
+ refine ::WebMock::RequestSignature do
76
+ def proto_headers?
77
+ !!(headers&.fetch("Content-Type", nil)&.start_with?(::Twirp::Encoding::PROTO))
78
+ end
79
+ end
74
80
  end
75
81
  end
76
82
  end
@@ -0,0 +1,39 @@
1
+ using WebMock::Twirp::Refinements
2
+
3
+ module WebMock
4
+ module Twirp
5
+ module RequestBodyDiff
6
+
7
+ private
8
+
9
+ def request_signature_diffable?
10
+ !!request_signature.twirp_request
11
+ end
12
+
13
+ def request_stub_diffable?
14
+ !!request_stub.with_attrs && !request_stub.with_attrs.is_a?(Proc)
15
+ end
16
+
17
+ def request_signature_body_hash
18
+ request_signature.twirp_request.normalized_hash
19
+ end
20
+
21
+ def request_stub_body_hash
22
+ request_stub.with_attrs
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ # hijack creation of Twirp snippets
29
+ module WebMock
30
+ class RequestBodyDiff
31
+ def self.new(request_signature, request_stub)
32
+ super.tap do |instance|
33
+ if request_signature.proto_headers? && request_stub.is_a?(WebMock::Twirp::RequestStub)
34
+ instance.extend(WebMock::Twirp::RequestBodyDiff)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,55 @@
1
+ using WebMock::Twirp::Refinements
2
+
3
+ module WebMock
4
+ module Twirp
5
+ module RequestSignature
6
+ def twirp_client
7
+ @twirp_client ||= begin
8
+ service_full_name, rpc_name = uri.path.split("/").last(2)
9
+
10
+ # find matching client
11
+ client = ObjectSpace.each_object(::Twirp::Client.singleton_class).find do |obj|
12
+ next unless obj < ::Twirp::Client && obj.name
13
+ obj.service_full_name == service_full_name && obj.rpcs.key?(rpc_name)
14
+ end
15
+ end
16
+ end
17
+
18
+ def twirp_rpc
19
+ @twirp_rpc ||= begin
20
+ rpc_name = uri.path.split("/").last
21
+ client = twirp_client.rpcs[rpc_name] if twirp_client
22
+ end
23
+ end
24
+
25
+ def twirp_request
26
+ twirp_rpc[:input_class].decode(body) if twirp_rpc
27
+ end
28
+
29
+ def to_s
30
+ return super unless twirp_rpc
31
+
32
+ uri = WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)
33
+ params = twirp_request.normalized_hash.map do |k, v|
34
+ "#{k}: #{v.inspect}"
35
+ end.join(", ")
36
+
37
+ string = "#{twirp_client}(#{uri})"
38
+ string << ".#{twirp_rpc[:ruby_method]}"
39
+ string << "(#{params.empty? ? "{}" : params})"
40
+
41
+ string
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ module WebMock
48
+ class RequestSignature
49
+ def self.new(...)
50
+ super(...).tap do |instance|
51
+ instance.extend(WebMock::Twirp::RequestSignature) if instance.proto_headers?
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,40 @@
1
+ using WebMock::Twirp::Refinements
2
+
3
+ module WebMock
4
+ module Twirp
5
+ module RequestSignatureSnippet
6
+ def stubbing_instructions
7
+ return unless WebMock.show_stubbing_instructions?
8
+
9
+ client = @request_signature.twirp_client
10
+ rpc = @request_signature.twirp_rpc
11
+
12
+ return super unless client
13
+
14
+ string = "You can stub this request with the following snippet:\n\n"
15
+ string << "stub_twirp_request(#{rpc[:ruby_method].inspect})"
16
+
17
+ if request = @request_signature.twirp_request
18
+ params = request.normalized_hash.map do |k, v|
19
+ " #{k}: #{v.inspect},"
20
+ end.join("\n")
21
+
22
+ string << ".with(\n#{params}\n)" unless params.empty?
23
+ end
24
+
25
+ string << ".to_return(...)"
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ # hijack creation of Twirp snippets
32
+ module WebMock
33
+ class RequestSignatureSnippet
34
+ def self.new(request_signature)
35
+ super.tap do |instance|
36
+ instance.extend(WebMock::Twirp::RequestSignatureSnippet) if request_signature.proto_headers?
37
+ end
38
+ end
39
+ end
40
+ end
@@ -3,6 +3,8 @@ module WebMock
3
3
  class RequestStub < WebMock::RequestStub
4
4
  using Refinements
5
5
 
6
+ attr_reader :twirp_client, :rpc_name, :with_attrs
7
+
6
8
  def initialize(*filters)
7
9
  rpc_name = filters.snag { |x| x.is_a?(Symbol) }
8
10
 
@@ -25,15 +27,11 @@ module WebMock
25
27
  end
26
28
 
27
29
  if klass
28
- @rpcs = klass.rpcs
30
+ @twirp_client = klass
29
31
  uri += "/#{klass.service_full_name}"
30
- else
31
- uri += "/[^/]+"
32
- end
33
32
 
34
- if rpc_name
35
- if klass
36
- # kindly convert ruby method to rpc method name
33
+ if rpc_name
34
+ # type check and kindly convert ruby method to rpc method name
37
35
  rpc_info = klass.rpcs.values.find do |x|
38
36
  x[:rpc_method] == rpc_name || x[:ruby_method] == rpc_name
39
37
  end
@@ -41,19 +39,29 @@ module WebMock
41
39
  raise ArgumentError, "invalid rpc method: #{rpc_name}" unless rpc_info
42
40
 
43
41
  uri += "/#{rpc_info[:rpc_method]}"
44
- else
45
- uri += "/#{rpc_name}"
46
42
  end
47
- else
48
- uri += "/[^/]+"
49
43
  end
50
44
 
51
- super(:post, /#{uri}$/)
45
+ super(:post, /#{uri}/)
52
46
 
53
47
  # filter on Twirp header
54
48
  @request_pattern.with(
55
49
  headers: { "Content-Type" => ::Twirp::Encoding::PROTO },
56
50
  )
51
+
52
+ if rpc_name
53
+ # match rpc dynamically after client resolves
54
+ @rpc_name = rpc_name
55
+
56
+ @request_pattern.with do |request|
57
+ rpc_info = request.twirp_rpc
58
+
59
+ !!rpc_info && (
60
+ rpc_info[:rpc_method] == rpc_name ||
61
+ rpc_info[:ruby_method] == rpc_name
62
+ )
63
+ end
64
+ end
57
65
  end
58
66
 
59
67
  def with(request = nil, **attrs, &block)
@@ -69,16 +77,31 @@ module WebMock
69
77
  { body: request.to_proto }
70
78
  end
71
79
 
72
- decoder = ->(request) do
73
- input_class = rpc_from_request(request)[:input_class]
74
- decoded_request = input_class.decode(request.body)
80
+ # save for diffing
81
+ @with_attrs = if block_given?
82
+ block
83
+ elsif request
84
+ request
85
+ elsif attrs.any?
86
+ attrs
87
+ end
75
88
 
89
+ decoder = ->(request_signature) do
76
90
  matched = true
77
- matched &= decoded_request.include?(attrs) if attrs.any?
78
- matched &= block.call(decoded_request) if block
91
+
92
+ request = request_signature.twirp_request
93
+ matched &&= !!request
94
+
95
+ # match request attributes
96
+ if attrs.any?
97
+ matched &&= request.include?(attrs)
98
+ end
99
+
100
+ # match block
101
+ matched &&= block.call(request) if block_given?
79
102
 
80
103
  matched
81
- end if attrs.any? || block_given?
104
+ end
82
105
 
83
106
  super(request_matcher || {}, &decoder)
84
107
  end
@@ -93,17 +116,15 @@ module WebMock
93
116
 
94
117
  response_hashes = responses.map do |response|
95
118
  ->(request) do
96
- # determine msg type and package response
97
- output_class = rpc_from_request(request)[:output_class]
98
- generate_http_response(output_class, response)
119
+ generate_http_response(request, response)
99
120
  end
100
121
  end
101
122
 
102
123
  decoder = ->(request) do
103
- # determine msg type and call provided block
104
- rpc = rpc_from_request(request)
105
- res = block.call(rpc[:input_class].decode(request.body))
106
- generate_http_response(rpc[:output_class], res)
124
+ generate_http_response(
125
+ request,
126
+ block.call(request.twirp_request),
127
+ )
107
128
  end if block_given?
108
129
 
109
130
  super(*response_hashes, &decoder)
@@ -116,27 +137,13 @@ module WebMock
116
137
 
117
138
  private
118
139
 
119
- def rpc_from_request(request_signature)
120
- service_full_name, rpc_name = request_signature.uri.path.split("/").last(2)
140
+ def generate_http_response(request, obj)
141
+ msg_class = request.twirp_rpc&.fetch(:output_class)
121
142
 
122
- rpcs = @rpcs || begin
123
- # find matching client instance
124
- client = ObjectSpace.each_object(::Twirp::Client).find do |client|
125
- service_full_name == client.class.service_full_name && \
126
- client.class.rpcs.key?(rpc_name)
127
- end
128
-
129
- unless client
130
- raise "could not determine Twirp::Client for call to: #{service_full_name}/#{rpc_name}"
131
- end
132
-
133
- client.class.rpcs
143
+ if msg_class.nil?
144
+ raise "could not determine Twirp::Client for request: #{request}"
134
145
  end
135
146
 
136
- rpcs[rpc_name]
137
- end
138
-
139
- def generate_http_response(msg_class, obj)
140
147
  res = case obj
141
148
  when nil
142
149
  msg_class.new
@@ -0,0 +1,46 @@
1
+ module WebMock
2
+ module Twirp
3
+ module StubRequestSnippet
4
+ def to_s(with_response = true)
5
+ string = "stub_twirp_request"
6
+
7
+ filters = [
8
+ @request_stub.twirp_client,
9
+ @request_stub.rpc_name&.inspect,
10
+ ].compact.join(", ")
11
+ string << "(#{filters})" unless filters.empty?
12
+
13
+ if attrs = @request_stub.with_attrs
14
+ string << ".with(\n"
15
+
16
+ if attrs.is_a?(Hash)
17
+ string << attrs.map do |k, v|
18
+ " #{k}: #{v.inspect},"
19
+ end.join("\n")
20
+ elsif attrs.is_a?(Proc)
21
+ string << " { ... }"
22
+ else
23
+ string << " #{attrs}"
24
+ end
25
+
26
+ string << "\n)"
27
+ end
28
+
29
+ string
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # hijack creation of Twirp snippets
36
+ module WebMock
37
+ class StubRequestSnippet
38
+ def self.new(request_stub)
39
+ super.tap do |instance|
40
+ if request_stub.is_a?(WebMock::Twirp::RequestStub)
41
+ instance.extend(WebMock::Twirp::StubRequestSnippet)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  module WebMock
2
2
  module Twirp
3
- VERSION = "0.2.1"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/lib/webmock/twirp.rb CHANGED
@@ -1,11 +1,16 @@
1
1
  require "google/protobuf"
2
2
  require "twirp"
3
3
  require "webmock"
4
+ require "webmock/twirp/error"
5
+ require "webmock/twirp/matchers"
4
6
  require "webmock/twirp/refinements"
7
+ require "webmock/twirp/request_body_diff"
8
+ require "webmock/twirp/request_signature"
9
+ require "webmock/twirp/request_signature_snippet"
5
10
  require "webmock/twirp/request_stub"
11
+ require "webmock/twirp/stub_request_snippet"
6
12
  require "webmock/twirp/version"
7
13
 
8
-
9
14
  module WebMock
10
15
  module Twirp
11
16
  extend self
@@ -26,15 +31,22 @@ module WebMock
26
31
  end
27
32
  end
28
33
 
34
+ # patch WebMock to export Twirp helpers
35
+ module WebMock
36
+ module API
37
+ include WebMock::Twirp::API
38
+ end
29
39
 
30
- if $LOADED_FEATURES.find { |x| x =~ %r{/webmock/rspec.rb} }
31
- # require "webmock/rspec"
32
- RSpec.configure { |c| c.include WebMock::Twirp::API }
33
- else
34
- # patch WebMock to also export stub_twirp_request
35
- module WebMock
36
- module API
37
- include WebMock::Twirp::API
38
- end
40
+ module Matchers
41
+ include WebMock::Twirp::Matchers
42
+ end
43
+ end
44
+
45
+ if $LOADED_FEATURES.find { |x| x =~ %r{/webmock/rspec.rb$} }
46
+ # require "webmock/rspec" was already called, so load helpers
47
+
48
+ RSpec.configure do |conf|
49
+ conf.include WebMock::Twirp::API
50
+ conf.include WebMock::Twirp::Matchers
39
51
  end
40
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webmock-twirp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
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-20 00:00:00.000000000 Z
11
+ date: 2022-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: webmock
@@ -133,8 +133,15 @@ files:
133
133
  - README.md
134
134
  - lib/webmock-twirp.rb
135
135
  - lib/webmock/twirp.rb
136
+ - lib/webmock/twirp/error.rb
137
+ - lib/webmock/twirp/matchers.rb
138
+ - lib/webmock/twirp/matchers/make_twirp_request.rb
136
139
  - lib/webmock/twirp/refinements.rb
140
+ - lib/webmock/twirp/request_body_diff.rb
141
+ - lib/webmock/twirp/request_signature.rb
142
+ - lib/webmock/twirp/request_signature_snippet.rb
137
143
  - lib/webmock/twirp/request_stub.rb
144
+ - lib/webmock/twirp/stub_request_snippet.rb
138
145
  - lib/webmock/twirp/version.rb
139
146
  - webmock-twirp.gemspec
140
147
  homepage: https://github.com/dpep/webmock-twirp