webmock-twirp 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 165be6973cc98a0dcfb538746766610afa45c02a344bf3c79f0567c36ab92d4c
4
+ data.tar.gz: 65a3021683eff819b8a5e1ca6e54d8d4eed63ed9364fd2631cb5308772243ade
5
+ SHA512:
6
+ metadata.gz: e5c02e2bfbe249a15af9aa36b1701e894550a330a72da318bec451a142e2d053ce40bc7c112e43057f1f216c8a0429dc3a670b8c112d136cd167ab482dbc9585
7
+ data.tar.gz: 6e1839bcd0cf51cb664cdda48408e8bcb89aec4dfbe60ba142cdd33f19b3462257ed56b5096fd9f338996257ec76b5121a2a8028f9e2ae848d2b1e79efd2d433
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ### v0.0.1 (2022-10-07)
2
+ - stub_twirp_request
3
+
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Daniel Pepper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ WebMock::Twirp
2
+ ======
3
+ Twirp support for [WebMock](https://github.com/bblimke/webmock). All our favorite http request stubbing for Twirp RPCs - message and error serialization done automatically.
4
+
5
+
6
+ ```ruby
7
+ require "webmock-twirp"
8
+
9
+ stub_twirp_request(MyTwirpClient, :optional_rpc_method)
10
+
11
+ stub_twirp_request(...).with(my_request_message: /^foo/)
12
+
13
+ # or use block mode
14
+ stub_twirp_request(...).with do |request|
15
+ request # the Twirp request, aka. proto message, used to initiate the request
16
+ request.my_request_message == "hello"
17
+ end
18
+
19
+
20
+ stub_twirp_request(...).and_return(return_message: "yo yo")
21
+ stub_twirp_request(...).and_return(404) # results in a Twirp::Error.not_found
22
+
23
+ # or use block mode
24
+ stub_twirp_request(...).and_return do |request|
25
+ { response_message: "oh hi" } # will get properly packaged up automagically
26
+ end
27
+ ```
28
+
29
+
30
+ ----
31
+ ## Contributing
32
+
33
+ Yes please :)
34
+
35
+ 1. Fork it
36
+ 1. Create your feature branch (`git checkout -b my-feature`)
37
+ 1. Ensure the tests pass (`bundle exec rspec`)
38
+ 1. Commit your changes (`git commit -am 'awesome new feature'`)
39
+ 1. Push your branch (`git push origin my-feature`)
40
+ 1. Create a Pull Request
41
+
42
+
43
+ ----
44
+ ![Gem](https://img.shields.io/gem/dt/webmock-twirp?style=plastic)
45
+ [![codecov](https://codecov.io/gh/dpep/webmock-twirp/branch/main/graph/badge.svg)](https://codecov.io/gh/dpep/webmock-twirp)
@@ -0,0 +1,149 @@
1
+ module WebMock
2
+ module Twirp
3
+ class RequestStub < WebMock::RequestStub
4
+ def initialize(client_or_service, rpc_name = nil)
5
+ klass = client_or_service.is_a?(Class) ? client_or_service : client_or_service.class
6
+
7
+ unless klass < ::Twirp::Client || klass < ::Twirp::Service
8
+ raise TypeError, "expected Twirp Client or Service, found: #{client_or_service}"
9
+ end
10
+
11
+ @rpcs = klass.rpcs
12
+ uri = "/#{klass.service_full_name}"
13
+
14
+ if rpc_name
15
+ rpc_info = rpcs.values.find do |x|
16
+ x[:rpc_method] == rpc_name.to_sym || x[:ruby_method] == rpc_name.to_sym
17
+ end
18
+
19
+ raise ArgumentError, "invalid rpc method: #{rpc_name}" unless rpc_info
20
+
21
+ uri += "/#{rpc_info[:rpc_method]}"
22
+ else
23
+ uri += "/[^/]+"
24
+ end
25
+
26
+ super(:post, /#{uri}$/)
27
+ end
28
+
29
+ def with(request = nil, **attrs, &block)
30
+ unless request.nil? || attrs.empty?
31
+ raise ArgumentError, "specify request or attrs, but not both"
32
+ end
33
+
34
+ request_matcher = if request
35
+ unless request.is_a?(Google::Protobuf::MessageExts)
36
+ raise TypeError, "Expected request to be Protobuf::MessageExts, found: #{request}"
37
+ end
38
+
39
+ { body: request.to_proto }
40
+ end
41
+
42
+ decoder = ->(request) do
43
+ input_class = rpc_from_request(request)[:input_class]
44
+
45
+ matched = true
46
+ decoded_request = input_class.decode(request.body)
47
+
48
+ if attrs.any?
49
+ attr_matcher = Matchers::HashIncludingMatcher.new(**attrs)
50
+ attr_hash = WebMock::Util::HashKeysStringifier.stringify_keys!(decoded_request.to_h, deep: true)
51
+
52
+ matched &= attr_matcher === attr_hash
53
+ end
54
+
55
+ if block
56
+ matched &= block.call(decoded_request)
57
+ end
58
+
59
+ matched
60
+ end if attrs.any? || block_given?
61
+
62
+ super(request_matcher || {}, &decoder)
63
+ end
64
+
65
+ def to_return(*responses, &block)
66
+ unless responses.empty? || block.nil?
67
+ raise ArgumentError, "specify responses or block, but not both"
68
+ end
69
+
70
+ # if no args, provide default response
71
+ responses << nil if responses.empty? && block.nil?
72
+
73
+ response_hashes = responses.map do |response|
74
+ ->(request) do
75
+ # determine msg type and package response
76
+ output_class = rpc_from_request(request)[:output_class]
77
+ generate_http_response(output_class, response)
78
+ end
79
+ end
80
+
81
+ decoder = ->(request) do
82
+ # determine msg type and call provided block
83
+ rpc = rpc_from_request(request)
84
+ res = block.call(rpc[:input_class].decode(request.body))
85
+ generate_http_response(rpc[:output_class], res)
86
+ end if block_given?
87
+
88
+ super(*response_hashes, &decoder)
89
+ end
90
+
91
+ def to_return_json(*)
92
+ raise NotImplementedError
93
+ end
94
+
95
+ private
96
+
97
+ attr_reader :rpcs
98
+
99
+ def rpc_from_request(request_signature)
100
+ rpcs[request_signature.uri.path.split("/").last]
101
+ end
102
+
103
+ def generate_http_response(msg_class, obj)
104
+ res = case obj
105
+ when nil
106
+ msg_class.new
107
+ when Hash
108
+ msg_class.new(**obj)
109
+ when Google::Protobuf::MessageExts
110
+ unless obj.is_a?(msg_class)
111
+ raise TypeError, "Expected type #{msg_class}, found #{obj}"
112
+ end
113
+
114
+ obj
115
+ when ::Twirp::Error
116
+ obj
117
+ when Symbol
118
+ if ::Twirp::Error.valid_code?(obj)
119
+ ::Twirp::Error.new(obj, obj)
120
+ else
121
+ raise ArgumentError, "invalid error code: #{obj}"
122
+ end
123
+ when Integer
124
+ if code = ::Twirp::ERROR_CODES_TO_HTTP_STATUS.key(obj)
125
+ ::Twirp::Error.new(code, code)
126
+ else
127
+ raise ArgumentError, "invalid error code: #{obj}"
128
+ end
129
+ else
130
+ raise NotImplementedError
131
+ end
132
+
133
+ if res.is_a?(Google::Protobuf::MessageExts)
134
+ {
135
+ status: 200,
136
+ headers: { "Content-Type" => ::Twirp::Encoding::PROTO },
137
+ body: res.to_proto,
138
+ }
139
+ else # Twirp::Error
140
+ {
141
+ status: ::Twirp::ERROR_CODES_TO_HTTP_STATUS[res.code],
142
+ headers: { "Content-Type" => ::Twirp::Encoding::JSON },
143
+ body: ::Twirp::Encoding.encode_json(res.to_h),
144
+ }
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,5 @@
1
+ module WebMock
2
+ module Twirp
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,40 @@
1
+ require "google/protobuf"
2
+ require "twirp"
3
+ require "webmock"
4
+ require "webmock/twirp/request_stub"
5
+ require "webmock/twirp/version"
6
+
7
+
8
+ module WebMock
9
+ module Twirp
10
+ extend self
11
+
12
+ private
13
+
14
+ module API
15
+ def stub_twirp_request(...)
16
+ WebMock::StubRegistry.instance.register_request_stub(
17
+ WebMock::Twirp::RequestStub.new(...),
18
+ )
19
+ end
20
+
21
+ # def a_twirp_request(uri)
22
+ # WebMock::RequestPattern.new(:post, uri)
23
+ # end
24
+ end
25
+ end
26
+ end
27
+
28
+
29
+ if $LOADED_FEATURES.find { |x| x =~ %r{/webmock/rspec.rb} }
30
+ # require "webmock/rspec"
31
+ RSpec.configure { |c| c.include WebMock::Twirp::API }
32
+ else
33
+ # patch Webmock to also export stub_twirp_request
34
+
35
+ module WebMock
36
+ module API
37
+ include WebMock::Twirp::API
38
+ end
39
+ end
40
+ end
@@ -0,0 +1 @@
1
+ require "webmock/twirp"
@@ -0,0 +1,31 @@
1
+ package_name = File.basename(__FILE__).split(".")[0]
2
+ load Dir.glob("lib/**/version.rb")[0]
3
+
4
+ package = WebMock::Twirp
5
+
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = package_name
9
+ s.version = package.const_get "VERSION"
10
+ s.authors = ["Daniel Pepper"]
11
+ s.summary = package.to_s
12
+ s.description = "Twirp support for WebMock"
13
+ s.homepage = "https://github.com/dpep/#{package_name}"
14
+ s.license = "MIT"
15
+
16
+ s.files = Dir[
17
+ __FILE__,
18
+ 'lib/**/*',
19
+ 'CHANGELOG*',
20
+ 'LICENSE*',
21
+ 'README*',
22
+ ]
23
+
24
+ s.add_dependency "webmock", ">= 3"
25
+ s.add_dependency "twirp", ">= 1"
26
+
27
+ s.add_development_dependency "byebug"
28
+ s.add_development_dependency "codecov"
29
+ s.add_development_dependency "rspec"
30
+ s.add_development_dependency "simplecov"
31
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: webmock-twirp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Pepper
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-10-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: webmock
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: twirp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: codecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Twirp support for WebMock
98
+ email:
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - LICENSE.txt
105
+ - README.md
106
+ - lib/webmock-twirp.rb
107
+ - lib/webmock/twirp.rb
108
+ - lib/webmock/twirp/request_stub.rb
109
+ - lib/webmock/twirp/version.rb
110
+ - webmock-twirp.gemspec
111
+ homepage: https://github.com/dpep/webmock-twirp
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubygems_version: 3.3.7
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Gem::Specification::WebMock::Twirp
134
+ test_files: []