webmock-twirp 0.2.1 → 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: 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