proto_pharm 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rspec.yml +35 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +3 -0
  6. data/.travis.yml +7 -0
  7. data/CHANGELOG.md +12 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +113 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +238 -0
  12. data/Rakefile +8 -0
  13. data/bin/console +11 -0
  14. data/bin/regen_examples +7 -0
  15. data/bin/release +16 -0
  16. data/bin/setup +8 -0
  17. data/lib/proto_pharm.rb +47 -0
  18. data/lib/proto_pharm/action_stub.rb +57 -0
  19. data/lib/proto_pharm/adapter.rb +13 -0
  20. data/lib/proto_pharm/api.rb +31 -0
  21. data/lib/proto_pharm/configuration.rb +11 -0
  22. data/lib/proto_pharm/errors.rb +21 -0
  23. data/lib/proto_pharm/grpc_stub_adapter.rb +24 -0
  24. data/lib/proto_pharm/grpc_stub_adapter/mock_stub.rb +74 -0
  25. data/lib/proto_pharm/introspection.rb +18 -0
  26. data/lib/proto_pharm/introspection/rpc_inspector.rb +58 -0
  27. data/lib/proto_pharm/introspection/service_resolver.rb +24 -0
  28. data/lib/proto_pharm/matchers/hash_argument_matcher.rb +43 -0
  29. data/lib/proto_pharm/matchers/request_including_matcher.rb +39 -0
  30. data/lib/proto_pharm/operation_stub.rb +30 -0
  31. data/lib/proto_pharm/request_pattern.rb +29 -0
  32. data/lib/proto_pharm/request_stub.rb +67 -0
  33. data/lib/proto_pharm/response.rb +36 -0
  34. data/lib/proto_pharm/response_sequence.rb +40 -0
  35. data/lib/proto_pharm/rspec.rb +28 -0
  36. data/lib/proto_pharm/rspec/action_stub_builder.rb +37 -0
  37. data/lib/proto_pharm/rspec/action_stub_proxy.rb +58 -0
  38. data/lib/proto_pharm/rspec/dsl.rb +15 -0
  39. data/lib/proto_pharm/rspec/matchers/have_received_rpc.rb +72 -0
  40. data/lib/proto_pharm/stub_components/failure_response.rb +30 -0
  41. data/lib/proto_pharm/stub_registry.rb +36 -0
  42. data/lib/proto_pharm/version.rb +5 -0
  43. data/proto_pharm.gemspec +38 -0
  44. data/rakelib/regen_examples.rake +11 -0
  45. metadata +248 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 404f9289692becf1d216f4c8a3dbdb574394035f4fe80849565931b61ecba9ba
4
+ data.tar.gz: 6bb014799feda5517ed91d065a92c3feb5955c253779d5bf312fd6abc81f7882
5
+ SHA512:
6
+ metadata.gz: 84fc28f8e009deb5dee062d15b8eff4091219bb3af1b9ad1d83dd90399c171e1c19b435eb271258fdfb449e60d7210ef4a57a6d61ab5a9d23c22282ef9df476e
7
+ data.tar.gz: ed36363f230916dfcab696c78f517b78256e7d3e954e02665485d7a0faf8aa0eb518be92daba1f30c58d4f85b92b5d1556cade1ac1e986e4441433f288d74cb0
@@ -0,0 +1,35 @@
1
+ name: RSpec
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby:
11
+ - "2.6.x"
12
+ - "2.7.x"
13
+
14
+ steps:
15
+ - uses: actions/checkout@v1
16
+ - uses: actions/cache@v1
17
+ with:
18
+ path: vendor/bundle
19
+ key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ hashFiles('**/Gemfile.lock') }}
20
+ restore-keys: |
21
+ ${{ runner.os }}-gems-${{ matrix.ruby }}
22
+ - name: Set up Ruby ${{ matrix.ruby }}
23
+ uses: actions/setup-ruby@v1
24
+ with:
25
+ ruby-version: ${{ matrix.ruby }}
26
+ - name: Set up dependencies
27
+ env:
28
+ BUNDLE_GEM__FURY__IO: ${{ secrets.GEMFURY_DEPLOY_TOKEN }}
29
+ run: |
30
+ gem install bundler
31
+ bundle config path vendor/bundle
32
+ bundle install --jobs 4 --retry 3
33
+ - name: Run RSpec
34
+ run: |
35
+ bundle exec rspec
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,3 @@
1
+ inherit_gem:
2
+ spicerack-styleguide:
3
+ - rubocop.yml
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.6.3
5
+ - 2.5.3
6
+ - 2.4.5
7
+ before_install: gem install bundler -v 1.16.1
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ # 0.6.0
4
+ *Release Date: 2020-12-07*
5
+
6
+ - Rename gem to `proto_pharm`
7
+ - Add support for `return_op` option([#1](https://github.com/Freshly/proto_pharm_old/pull/1), [#2](https://github.com/Freshly/proto_pharm_old/pull/2))
8
+ - Fix load order bug ([#3](https://github.com/Freshly/proto_pharm_old/pull/3))
9
+ - Add `stub_grpc_action` convenience method ([#9](https://github.com/Freshly/proto_pharm_old/pull/9), [#12](https://github.com/Freshly/proto_pharm_old/pull/12))
10
+ - Add `to_fail_with` stub method ([#14](https://github.com/Freshly/proto_pharm_old/pull/14), [#18](https://github.com/Freshly/proto_pharm_old/pull/18))
11
+ - Add `have_received_rpc` matcher ([#19](https://github.com/Freshly/proto_pharm_old/pull/19))
12
+ - Add RSpec DSL ([#10](https://github.com/Freshly/proto_pharm_old/pull/10), [#26](https://github.com/Freshly/proto_pharm_old/pull/26))
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in proto_pharm.gemspec
8
+ gemspec
@@ -0,0 +1,113 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ proto_pharm (0.6.0)
5
+ activesupport (>= 5.2.0)
6
+ grpc (>= 1.12.0, < 2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (6.0.3.4)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 0.7, < 2)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
16
+ zeitwerk (~> 2.2, >= 2.2.2)
17
+ ast (2.4.1)
18
+ byebug (11.1.1)
19
+ coderay (1.1.2)
20
+ concurrent-ruby (1.1.7)
21
+ diff-lcs (1.4.4)
22
+ faker (2.14.0)
23
+ i18n (>= 1.6, < 2)
24
+ google-protobuf (3.14.0)
25
+ googleapis-common-protos-types (1.0.5)
26
+ google-protobuf (~> 3.11)
27
+ grpc (1.34.0)
28
+ google-protobuf (~> 3.13)
29
+ googleapis-common-protos-types (~> 1.0)
30
+ grpc-tools (1.32.0)
31
+ i18n (1.8.5)
32
+ concurrent-ruby (~> 1.0)
33
+ method_source (0.9.2)
34
+ minitest (5.14.2)
35
+ parallel (1.20.1)
36
+ parser (2.7.2.0)
37
+ ast (~> 2.4.1)
38
+ pry (0.12.2)
39
+ coderay (~> 1.1.0)
40
+ method_source (~> 0.9.0)
41
+ pry-byebug (3.7.0)
42
+ byebug (~> 11.0)
43
+ pry (~> 0.10)
44
+ rack (2.2.3)
45
+ rainbow (3.0.0)
46
+ rake (13.0.1)
47
+ regexp_parser (1.8.2)
48
+ rexml (3.2.4)
49
+ rspec (3.9.0)
50
+ rspec-core (~> 3.9.0)
51
+ rspec-expectations (~> 3.9.0)
52
+ rspec-mocks (~> 3.9.0)
53
+ rspec-core (3.9.3)
54
+ rspec-support (~> 3.9.3)
55
+ rspec-expectations (3.9.4)
56
+ diff-lcs (>= 1.2.0, < 2.0)
57
+ rspec-support (~> 3.9.0)
58
+ rspec-mocks (3.9.1)
59
+ diff-lcs (>= 1.2.0, < 2.0)
60
+ rspec-support (~> 3.9.0)
61
+ rspec-support (3.9.4)
62
+ rspice (0.25.2)
63
+ faker (>= 1.8, < 3.0)
64
+ rspec (~> 3.0)
65
+ rubocop (0.92.0)
66
+ parallel (~> 1.10)
67
+ parser (>= 2.7.1.5)
68
+ rainbow (>= 2.2.2, < 4.0)
69
+ regexp_parser (>= 1.7)
70
+ rexml
71
+ rubocop-ast (>= 0.5.0)
72
+ ruby-progressbar (~> 1.7)
73
+ unicode-display_width (>= 1.4.0, < 2.0)
74
+ rubocop-ast (1.1.1)
75
+ parser (>= 2.7.1.5)
76
+ rubocop-performance (1.8.1)
77
+ rubocop (>= 0.87.0)
78
+ rubocop-ast (>= 0.4.0)
79
+ rubocop-rails (2.8.1)
80
+ activesupport (>= 4.2.0)
81
+ rack (>= 1.1)
82
+ rubocop (>= 0.87.0)
83
+ rubocop-rspec (1.43.2)
84
+ rubocop (~> 0.87)
85
+ ruby-progressbar (1.10.1)
86
+ spicerack-styleguide (0.25.2)
87
+ rubocop (~> 0.92.0)
88
+ rubocop-performance (~> 1.8.0)
89
+ rubocop-rails (~> 2.8.0)
90
+ rubocop-rspec (~> 1.43.0)
91
+ thread_safe (0.3.6)
92
+ tzinfo (1.2.8)
93
+ thread_safe (~> 0.1)
94
+ unicode-display_width (1.7.0)
95
+ zeitwerk (2.4.2)
96
+
97
+ PLATFORMS
98
+ ruby
99
+
100
+ DEPENDENCIES
101
+ bundler
102
+ faker
103
+ grpc-tools
104
+ proto_pharm!
105
+ pry-byebug
106
+ rake (>= 12.3.3)
107
+ rspec (~> 3.0)
108
+ rspice
109
+ rubocop
110
+ spicerack-styleguide
111
+
112
+ BUNDLED WITH
113
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Yuta Iwama
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,238 @@
1
+ # ProtoPharm
2
+
3
+ Stub your gRPCs with lab-grown proto objects. Life is better on the pharm.
4
+
5
+ Built on a great foundation by @ganmacs at [ganmacs/grpc_mock](https://github.com/ganmacs/grpc_mock).
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile in the development/test group:
10
+
11
+ ```ruby
12
+ gem 'proto_pharm'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install proto_pharm
22
+
23
+ ## Let's go Pharming!
24
+
25
+ Before we dive into the code - all examples below will refer to a gRPC service called `hello.hello` with (among others) an rpc endpoint `Hello` which receives the proto `hello.HelloRequest` and responds with the proto `hello.HelloResponse`.
26
+
27
+ The local variable `client` is defined as follows:
28
+ ```ruby
29
+ client = Hello::Hello::Stub.new('localhost:8000', :this_channel_is_insecure)
30
+ ```
31
+
32
+ See full definition of protocol buffers and gRPC generated code in [spec/examples/hello](https://github.com/Freshly/proto_pharm/tree/master/spec/examples/hello).
33
+
34
+ ## RSpec Usage
35
+
36
+ To take full advantage of the helpers listed here, make sure to add the following to your `spec_helper.rb` or `rails_helper.rb`:
37
+ ```ruby
38
+ require 'proto_pharm/rspec'
39
+ ```
40
+
41
+ ### Stubbing service responses
42
+
43
+ For the simplest use case, stub a response value for a given rpc endpoint like so:
44
+ ```ruby
45
+ allow_grpc_service(Hello::Hello)
46
+ .to receive_rpc(:hello)
47
+ .and_return(msg: 'Hello!')
48
+
49
+ client.hello(Hello::HelloRequest.new(msg: 'Hello?')) # => <Hello::HelloResponse: msg: "Hello!">
50
+ ```
51
+
52
+ To stub a response for a specific request received:
53
+ ```ruby
54
+ allow_grpc_service(Hello::Hello)
55
+ .to receive_rpc(:hello)
56
+ .with(msg: 'Hola?')
57
+ .and_return(msg: 'Bienvenidos!')
58
+
59
+ client.hello(Hello::HelloRequest.new(msg: 'Hello?')) # => Sent to network
60
+ client.hello(Hello::HelloRequest.new(msg: 'Hola?')) # => <Hello::HelloResponse: msg: "Bienvenidos!">
61
+ ```
62
+
63
+ Stub a failure response:
64
+ ```ruby
65
+ allow_grpc_service(Hello::Hello)
66
+ .to receive_rpc(:hello)
67
+ .and_fail_with(:not_found, "No one's here...")
68
+
69
+ client.hello(Hello::HelloRequest.new(msg: 'Hello?')) # => <GRPC::NotFound: 5:No one's here...>
70
+ ```
71
+
72
+ Stub failure metadata:
73
+ ```ruby
74
+ allow_grpc_service(Hello::Hello)
75
+ .to receive_rpc(:hello)
76
+ .and_fail_with(:not_found, metadata: { people_here: :none })
77
+
78
+ begin
79
+ client.hello(Hello::HelloRequest.new(msg: 'Hello?'))
80
+ rescue => e
81
+ e # => <GRPC::NotFound: 5:>
82
+ e.metadata # => {:people_here=>"none"}
83
+ end
84
+ ```
85
+ Note here that the `"none"` value is a string - all metadata values will be cast as strings on response to emulate actual gRPC behavior.
86
+
87
+ Or, if you just want the call to fail and don't care about the failure type, it defaults to `:invalid_argument`:
88
+ ```ruby
89
+ allow_grpc_service(Hello::Hello).to receive_rpc(:hello).and_fail
90
+ client.hello(Hello::HelloRequest.new(msg: 'Hello?')) # => <GRPC::InvalidArgument: 3:>
91
+
92
+ # Or with some metadata...
93
+
94
+ allow_grpc_service(Hello::Hello).to receive_rpc(:hello).and_fail_with(metadata: { some: :meta_here })
95
+ client.hello(Hello::HelloRequest.new(msg: 'Hello?')) # => You get the picture
96
+ ```
97
+
98
+ #### Asserting RPC reception
99
+
100
+ ProtoPharm also adds a matcher to assert rpc reception. For example:
101
+ ```ruby
102
+ allow_grpc_service(Hello::Hello)
103
+ .to receive_rpc(:hello)
104
+ .and_return(msg: 'Hello!')
105
+
106
+ client.hello(Hello::HelloRequest.new(msg: 'Hello?'))
107
+ expect(Hello::Hello).to have_received_rpc(:hello)
108
+ ```
109
+
110
+ You can also assert the arguments received:
111
+ ```ruby
112
+ expect(Hello::Hello).to have_received_rpc(:hello).with(msg: 'Hello?')
113
+ ```
114
+
115
+ ### Argument Flexibility
116
+ You may have noticed that the above examples stub proto objects without specifying the proto type (for example, `.and_return(msg: 'Hello!')`). No `Hello::HelloRequest.new`s in sight! Both `with` and `and_return` will happily accept protos, hashes or keyword args. If you pass an invalid key to a stub method, you'll get an error:
117
+ ```ruby
118
+ allow_grpc_service(Hello::Hello).to receive_rpc(:hello).and_return(message: "Is this thing on?")
119
+ # => ArgumentError: Unknown field name 'message' in initialization map entry.
120
+ ```
121
+
122
+ Happy stubbing!
123
+
124
+ ## Usage for Minitest etc.
125
+
126
+ ### Stubbed request based on path and with the default response
127
+
128
+ ```ruby
129
+ ProtoPharm.stub_request("/hello.hello/Hello").to_return(Hello::HelloResponse.new(msg: 'test'))
130
+
131
+ client.hello(Hello::HelloRequest.new(msg: 'hi')) # => Hello::HelloResponse.new(msg: 'test')
132
+ ```
133
+
134
+ ### Stubbing requests based on path and request
135
+
136
+ ```ruby
137
+ ProtoPharm.stub_request("/hello.hello/Hello").with(Hello::HelloRequest.new(msg: 'hi')).to_return(Hello::HelloResponse.new(msg: 'test'))
138
+
139
+ client.hello(Hello::HelloRequest.new(msg: 'hello')) # => send a request to server
140
+ client.hello(Hello::HelloRequest.new(msg: 'hi')) # => Hello::HelloResponse.new(msg: 'test') (without any requests to server)
141
+ ```
142
+
143
+ ### Stubbing per-action requests based on parametrized request
144
+
145
+ ```ruby
146
+ ProtoPharm.stub_grpc_action(Hello::Hello::Service, :Hello).with(msg: 'hi').to_return(msg: 'test')
147
+
148
+ client.hello(Hello::HelloRequest.new(msg: 'hello')) # => send a request to server
149
+ client.hello(Hello::HelloRequest.new(msg: 'hi')) # => Hello::HelloResponse.new(msg: 'test') (without any requests to server)
150
+
151
+ ```
152
+
153
+ ### You can user either proto objects or hash for stubbing requests
154
+
155
+ ```ruby
156
+ ProtoPharm.stub_grpc_action(Hello::Hello::Service, :Hello).with(Hello::HelloRequest.new(msg: 'hi')).to_return(msg: 'test')
157
+ # or
158
+ ProtoPharm.stub_grpc_action(Hello::Hello::Service, :Hello).with(msg: 'hi').to_return(Hello::HelloResponse.new(msg: 'test'))
159
+
160
+ client.hello(Hello::HelloRequest.new(msg: 'hello')) # => send a request to server
161
+ client.hello(Hello::HelloRequest.new(msg: 'hi')) # => Hello::HelloResponse.new(msg: 'test') (without any requests to server)
162
+ ```
163
+
164
+ ### Real requests to network can be allowed or disabled
165
+
166
+ ```ruby
167
+ client = Hello::Hello::Stub.new('localhost:8000', :this_channel_is_insecure)
168
+
169
+ ProtoPharm.disable_net_connect!
170
+ client.hello(Hello::HelloRequest.new(msg: 'hello')) # => Raise NetConnectNotAllowedError error
171
+
172
+ ProtoPharm.allow_net_connect!
173
+ Hello::Hello::Stub.new('localhost:8000', :this_channel_is_insecure) # => send a request to server
174
+ ```
175
+
176
+ ### Stubbing Failures
177
+
178
+ Specific gRPC failure codes can be stubbed with metadata
179
+ ```ruby
180
+ ProtoPharm.
181
+ stub_grpc_action(Hello::Hello::Service, :Hello).
182
+ with(Hello::HelloRequest.new(msg: 'hi')).
183
+ to_fail_with(:invalid_argument, "This message is optional", metadata: { put: :your, metadata: :here })
184
+
185
+ begin
186
+ client.hello(Hello::HelloRequest.new(msg: 'hi'))
187
+ rescue => e
188
+ e # => #<GRPC::InvalidArgument: 3:This message is optional>
189
+ e.metadata # => { :put => :your, :metadata => here }
190
+ ```
191
+
192
+ By default, The failure code is `invalid_argument` and the message is optional - so if the code under test doesn't rely on those for any downstream behavior, you can simplify the stubbing by passing only metadata:
193
+ ```ruby
194
+ stub_grpc_action(Hello::Hello::Service, :Hello).
195
+ to_fail_with(metadata: { important_things: [:in, :here] })
196
+ client.hello(Hello::HelloRequest.new(msg: 'hi'))
197
+ # => #<GRPC::InvalidArgument: 3:>
198
+ exception.metadata
199
+ # => { :important_things => [:in, :here] }
200
+
201
+ ```
202
+ ...or by passing nothing at all:
203
+ ```ruby
204
+ stub_grpc_action(Hello::Hello::Service, :Hello).to_fail
205
+ client.hello(Hello::HelloRequest.new(msg: 'hi'))
206
+ # => #<GRPC::InvalidArgument: 3:>
207
+ ```
208
+
209
+ ### Raising errors
210
+
211
+ **Exception declared by class**
212
+
213
+ ```ruby
214
+ ProtoPharm.stub_request("/hello.hello/Hello").to_raise(StandardError)
215
+
216
+ client = Hello::Hello::Stub.new('localhost:8000', :this_channel_is_insecure)
217
+ client.hello(Hello::HelloRequest.new(msg: 'hi')) # => Raise StandardError
218
+ ```
219
+
220
+ **or by exception instance**
221
+
222
+ ```ruby
223
+ ProtoPharm.stub_request("/hello.hello/Hello").to_raise(StandardError.new("Some error"))
224
+ ```
225
+
226
+ **or by string**
227
+
228
+ ```ruby
229
+ ProtoPharm.stub_request("/hello.hello/Hello").to_raise("Some error")
230
+ ```
231
+
232
+ ## Contributing
233
+
234
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Freshly/proto_pharm. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
235
+
236
+ ## License
237
+
238
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).