prepor-protobuf 3.5.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.
Files changed (182) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rubocop.yml +51 -0
  4. data/.rubocop_todo.yml +89 -0
  5. data/.travis.yml +25 -0
  6. data/.yardopts +5 -0
  7. data/CHANGES.md +256 -0
  8. data/CONTRIBUTING.md +16 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +33 -0
  12. data/Rakefile +64 -0
  13. data/bin/protoc-gen-ruby +16 -0
  14. data/bin/rpc_server +5 -0
  15. data/install-protobuf.sh +28 -0
  16. data/lib/protobuf.rb +100 -0
  17. data/lib/protobuf/cli.rb +242 -0
  18. data/lib/protobuf/code_generator.rb +44 -0
  19. data/lib/protobuf/decoder.rb +73 -0
  20. data/lib/protobuf/deprecation.rb +112 -0
  21. data/lib/protobuf/descriptors.rb +3 -0
  22. data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +48 -0
  23. data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +251 -0
  24. data/lib/protobuf/encoder.rb +67 -0
  25. data/lib/protobuf/enum.rb +303 -0
  26. data/lib/protobuf/exceptions.rb +9 -0
  27. data/lib/protobuf/field.rb +74 -0
  28. data/lib/protobuf/field/base_field.rb +267 -0
  29. data/lib/protobuf/field/bool_field.rb +59 -0
  30. data/lib/protobuf/field/bytes_field.rb +82 -0
  31. data/lib/protobuf/field/double_field.rb +25 -0
  32. data/lib/protobuf/field/enum_field.rb +68 -0
  33. data/lib/protobuf/field/field_array.rb +87 -0
  34. data/lib/protobuf/field/fixed32_field.rb +25 -0
  35. data/lib/protobuf/field/fixed64_field.rb +28 -0
  36. data/lib/protobuf/field/float_field.rb +41 -0
  37. data/lib/protobuf/field/int32_field.rb +21 -0
  38. data/lib/protobuf/field/int64_field.rb +21 -0
  39. data/lib/protobuf/field/integer_field.rb +23 -0
  40. data/lib/protobuf/field/message_field.rb +65 -0
  41. data/lib/protobuf/field/sfixed32_field.rb +27 -0
  42. data/lib/protobuf/field/sfixed64_field.rb +28 -0
  43. data/lib/protobuf/field/signed_integer_field.rb +29 -0
  44. data/lib/protobuf/field/sint32_field.rb +21 -0
  45. data/lib/protobuf/field/sint64_field.rb +21 -0
  46. data/lib/protobuf/field/string_field.rb +34 -0
  47. data/lib/protobuf/field/uint32_field.rb +21 -0
  48. data/lib/protobuf/field/uint64_field.rb +21 -0
  49. data/lib/protobuf/field/varint_field.rb +73 -0
  50. data/lib/protobuf/generators/base.rb +70 -0
  51. data/lib/protobuf/generators/enum_generator.rb +41 -0
  52. data/lib/protobuf/generators/extension_generator.rb +27 -0
  53. data/lib/protobuf/generators/field_generator.rb +131 -0
  54. data/lib/protobuf/generators/file_generator.rb +135 -0
  55. data/lib/protobuf/generators/group_generator.rb +112 -0
  56. data/lib/protobuf/generators/message_generator.rb +98 -0
  57. data/lib/protobuf/generators/printable.rb +160 -0
  58. data/lib/protobuf/generators/service_generator.rb +26 -0
  59. data/lib/protobuf/lifecycle.rb +33 -0
  60. data/lib/protobuf/logging.rb +39 -0
  61. data/lib/protobuf/message.rb +193 -0
  62. data/lib/protobuf/message/fields.rb +133 -0
  63. data/lib/protobuf/message/serialization.rb +89 -0
  64. data/lib/protobuf/optionable.rb +23 -0
  65. data/lib/protobuf/rpc/buffer.rb +77 -0
  66. data/lib/protobuf/rpc/client.rb +168 -0
  67. data/lib/protobuf/rpc/connector.rb +19 -0
  68. data/lib/protobuf/rpc/connectors/base.rb +55 -0
  69. data/lib/protobuf/rpc/connectors/common.rb +176 -0
  70. data/lib/protobuf/rpc/connectors/socket.rb +73 -0
  71. data/lib/protobuf/rpc/connectors/zmq.rb +322 -0
  72. data/lib/protobuf/rpc/dynamic_discovery.pb.rb +49 -0
  73. data/lib/protobuf/rpc/env.rb +58 -0
  74. data/lib/protobuf/rpc/error.rb +28 -0
  75. data/lib/protobuf/rpc/error/client_error.rb +31 -0
  76. data/lib/protobuf/rpc/error/server_error.rb +43 -0
  77. data/lib/protobuf/rpc/middleware.rb +25 -0
  78. data/lib/protobuf/rpc/middleware/exception_handler.rb +36 -0
  79. data/lib/protobuf/rpc/middleware/logger.rb +91 -0
  80. data/lib/protobuf/rpc/middleware/request_decoder.rb +83 -0
  81. data/lib/protobuf/rpc/middleware/response_encoder.rb +88 -0
  82. data/lib/protobuf/rpc/middleware/runner.rb +18 -0
  83. data/lib/protobuf/rpc/rpc.pb.rb +55 -0
  84. data/lib/protobuf/rpc/server.rb +39 -0
  85. data/lib/protobuf/rpc/servers/socket/server.rb +123 -0
  86. data/lib/protobuf/rpc/servers/socket/worker.rb +56 -0
  87. data/lib/protobuf/rpc/servers/socket_runner.rb +40 -0
  88. data/lib/protobuf/rpc/servers/zmq/broker.rb +193 -0
  89. data/lib/protobuf/rpc/servers/zmq/server.rb +320 -0
  90. data/lib/protobuf/rpc/servers/zmq/util.rb +48 -0
  91. data/lib/protobuf/rpc/servers/zmq/worker.rb +104 -0
  92. data/lib/protobuf/rpc/servers/zmq_runner.rb +62 -0
  93. data/lib/protobuf/rpc/service.rb +179 -0
  94. data/lib/protobuf/rpc/service_directory.rb +253 -0
  95. data/lib/protobuf/rpc/service_dispatcher.rb +46 -0
  96. data/lib/protobuf/rpc/service_filters.rb +272 -0
  97. data/lib/protobuf/rpc/stat.rb +97 -0
  98. data/lib/protobuf/socket.rb +21 -0
  99. data/lib/protobuf/tasks.rb +1 -0
  100. data/lib/protobuf/tasks/compile.rake +61 -0
  101. data/lib/protobuf/version.rb +3 -0
  102. data/lib/protobuf/wire_type.rb +10 -0
  103. data/lib/protobuf/zmq.rb +21 -0
  104. data/proto/dynamic_discovery.proto +44 -0
  105. data/proto/google/protobuf/compiler/plugin.proto +147 -0
  106. data/proto/google/protobuf/descriptor.proto +620 -0
  107. data/proto/rpc.proto +62 -0
  108. data/protobuf.gemspec +51 -0
  109. data/spec/benchmark/tasks.rb +139 -0
  110. data/spec/bin/protoc-gen-ruby_spec.rb +23 -0
  111. data/spec/data/data.bin +3 -0
  112. data/spec/data/types.bin +0 -0
  113. data/spec/encoding/all_types_spec.rb +105 -0
  114. data/spec/encoding/extreme_values_spec.rb +0 -0
  115. data/spec/functional/class_inheritance_spec.rb +52 -0
  116. data/spec/functional/socket_server_spec.rb +59 -0
  117. data/spec/functional/zmq_server_spec.rb +105 -0
  118. data/spec/lib/protobuf/cli_spec.rb +273 -0
  119. data/spec/lib/protobuf/code_generator_spec.rb +60 -0
  120. data/spec/lib/protobuf/enum_spec.rb +265 -0
  121. data/spec/lib/protobuf/field/bool_field_spec.rb +51 -0
  122. data/spec/lib/protobuf/field/field_array_spec.rb +69 -0
  123. data/spec/lib/protobuf/field/float_field_spec.rb +55 -0
  124. data/spec/lib/protobuf/field/int32_field_spec.rb +89 -0
  125. data/spec/lib/protobuf/field/string_field_spec.rb +45 -0
  126. data/spec/lib/protobuf/field_spec.rb +191 -0
  127. data/spec/lib/protobuf/generators/base_spec.rb +84 -0
  128. data/spec/lib/protobuf/generators/enum_generator_spec.rb +73 -0
  129. data/spec/lib/protobuf/generators/extension_generator_spec.rb +42 -0
  130. data/spec/lib/protobuf/generators/field_generator_spec.rb +102 -0
  131. data/spec/lib/protobuf/generators/file_generator_spec.rb +32 -0
  132. data/spec/lib/protobuf/generators/message_generator_spec.rb +0 -0
  133. data/spec/lib/protobuf/generators/service_generator_spec.rb +46 -0
  134. data/spec/lib/protobuf/lifecycle_spec.rb +94 -0
  135. data/spec/lib/protobuf/message_spec.rb +526 -0
  136. data/spec/lib/protobuf/optionable_spec.rb +46 -0
  137. data/spec/lib/protobuf/rpc/client_spec.rb +66 -0
  138. data/spec/lib/protobuf/rpc/connector_spec.rb +26 -0
  139. data/spec/lib/protobuf/rpc/connectors/base_spec.rb +50 -0
  140. data/spec/lib/protobuf/rpc/connectors/common_spec.rb +170 -0
  141. data/spec/lib/protobuf/rpc/connectors/socket_spec.rb +36 -0
  142. data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +117 -0
  143. data/spec/lib/protobuf/rpc/middleware/exception_handler_spec.rb +62 -0
  144. data/spec/lib/protobuf/rpc/middleware/logger_spec.rb +49 -0
  145. data/spec/lib/protobuf/rpc/middleware/request_decoder_spec.rb +115 -0
  146. data/spec/lib/protobuf/rpc/middleware/response_encoder_spec.rb +75 -0
  147. data/spec/lib/protobuf/rpc/servers/socket_server_spec.rb +38 -0
  148. data/spec/lib/protobuf/rpc/servers/zmq/server_spec.rb +43 -0
  149. data/spec/lib/protobuf/rpc/servers/zmq/util_spec.rb +55 -0
  150. data/spec/lib/protobuf/rpc/servers/zmq/worker_spec.rb +35 -0
  151. data/spec/lib/protobuf/rpc/service_directory_spec.rb +293 -0
  152. data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +52 -0
  153. data/spec/lib/protobuf/rpc/service_filters_spec.rb +517 -0
  154. data/spec/lib/protobuf/rpc/service_spec.rb +162 -0
  155. data/spec/lib/protobuf/rpc/stat_spec.rb +68 -0
  156. data/spec/lib/protobuf_spec.rb +98 -0
  157. data/spec/spec_helper.rb +44 -0
  158. data/spec/support/all.rb +6 -0
  159. data/spec/support/packed_field.rb +22 -0
  160. data/spec/support/server.rb +64 -0
  161. data/spec/support/test/all_types.data.bin +0 -0
  162. data/spec/support/test/all_types.data.txt +119 -0
  163. data/spec/support/test/defaults.pb.rb +27 -0
  164. data/spec/support/test/defaults.proto +9 -0
  165. data/spec/support/test/enum.pb.rb +55 -0
  166. data/spec/support/test/enum.proto +34 -0
  167. data/spec/support/test/extended.pb.rb +18 -0
  168. data/spec/support/test/extended.proto +10 -0
  169. data/spec/support/test/extreme_values.data.bin +0 -0
  170. data/spec/support/test/google_unittest.pb.rb +537 -0
  171. data/spec/support/test/google_unittest.proto +713 -0
  172. data/spec/support/test/google_unittest_import.pb.rb +39 -0
  173. data/spec/support/test/google_unittest_import.proto +64 -0
  174. data/spec/support/test/google_unittest_import_public.pb.rb +10 -0
  175. data/spec/support/test/google_unittest_import_public.proto +38 -0
  176. data/spec/support/test/multi_field_extensions.pb.rb +58 -0
  177. data/spec/support/test/multi_field_extensions.proto +33 -0
  178. data/spec/support/test/resource.pb.rb +119 -0
  179. data/spec/support/test/resource.proto +94 -0
  180. data/spec/support/test/resource_service.rb +23 -0
  181. data/spec/support/test_app_file.rb +2 -0
  182. metadata +502 -0
@@ -0,0 +1,293 @@
1
+ require 'spec_helper'
2
+
3
+ require 'protobuf/rpc/service_directory'
4
+
5
+ RSpec.describe ::Protobuf::Rpc::ServiceDirectory do
6
+ subject { described_class.instance }
7
+
8
+ let(:echo_server) do
9
+ ::Protobuf::Rpc::DynamicDiscovery::Server.new(
10
+ :uuid => 'echo',
11
+ :address => '127.0.0.1',
12
+ :port => '1111',
13
+ :ttl => 10,
14
+ :services => %w(EchoService),
15
+ )
16
+ end
17
+
18
+ let(:hello_server) do
19
+ ::Protobuf::Rpc::DynamicDiscovery::Server.new(
20
+ :uuid => "hello",
21
+ :address => '127.0.0.1',
22
+ :port => "1112",
23
+ :ttl => 10,
24
+ :services => %w(HelloService),
25
+ )
26
+ end
27
+
28
+ let(:hello_server_with_short_ttl) do
29
+ ::Protobuf::Rpc::DynamicDiscovery::Server.new(
30
+ :uuid => "hello_server_with_short_ttl",
31
+ :address => '127.0.0.1',
32
+ :port => '1113',
33
+ :ttl => 1,
34
+ :services => %w(HelloService),
35
+ )
36
+ end
37
+
38
+ let(:combo_server) do
39
+ ::Protobuf::Rpc::DynamicDiscovery::Server.new(
40
+ :uuid => "combo",
41
+ :address => '127.0.0.1',
42
+ :port => '1114',
43
+ :ttl => 10,
44
+ :services => %w(HelloService EchoService),
45
+ )
46
+ end
47
+
48
+ before(:all) do
49
+ @address = "127.0.0.1"
50
+ @port = 33333
51
+ @socket = UDPSocket.new
52
+ EchoService = Class.new
53
+
54
+ described_class.address = @address
55
+ described_class.port = @port
56
+ end
57
+
58
+ def expect_event_trigger(event)
59
+ expect(::ActiveSupport::Notifications).to receive(:instrument)
60
+ .with(event, hash_including(:listing => an_instance_of(::Protobuf::Rpc::ServiceDirectory::Listing))).once
61
+ end
62
+
63
+ def send_beacon(type, server)
64
+ type = type.to_s.upcase
65
+ beacon = ::Protobuf::Rpc::DynamicDiscovery::Beacon.new(
66
+ :server => server,
67
+ :beacon_type => ::Protobuf::Rpc::DynamicDiscovery::BeaconType.fetch(type),
68
+ )
69
+
70
+ @socket.send(beacon.encode, 0, @address, @port)
71
+ sleep 0.01 # give the service directory time to process the beacon
72
+ end
73
+
74
+ it "should be a singleton" do
75
+ expect(subject).to be_a_kind_of(Singleton)
76
+ end
77
+
78
+ it "should be configured to listen to address 127.0.0.1" do
79
+ expect(described_class.address).to eq '127.0.0.1'
80
+ end
81
+
82
+ it "should be configured to listen to port 33333" do
83
+ expect(described_class.port).to eq 33333
84
+ end
85
+
86
+ it "should defer .start to the instance#start" do
87
+ expect(described_class.instance).to receive(:start)
88
+ described_class.start
89
+ end
90
+
91
+ it "should yeild itself to blocks passed to .start" do
92
+ allow(described_class.instance).to receive(:start)
93
+ expect { |b| described_class.start(&b) }.to yield_with_args(described_class)
94
+ end
95
+
96
+ it "should defer .stop to the instance#stop" do
97
+ expect(described_class.instance).to receive(:stop)
98
+ described_class.stop
99
+ end
100
+
101
+ context "stopped" do
102
+ before { subject.stop }
103
+
104
+ describe "#lookup" do
105
+ it "should return nil" do
106
+ send_beacon(:heartbeat, echo_server)
107
+ expect(subject.lookup("EchoService")).to be_nil
108
+ end
109
+ end
110
+
111
+ describe "#restart" do
112
+ it "should start the service" do
113
+ subject.restart
114
+ expect(subject).to be_running
115
+ end
116
+ end
117
+
118
+ describe "#running" do
119
+ it "should be false" do
120
+ expect(subject).to_not be_running
121
+ end
122
+ end
123
+
124
+ describe "#stop" do
125
+ it "has no effect" do
126
+ subject.stop
127
+ end
128
+ end
129
+ end
130
+
131
+ context "started" do
132
+ before { subject.start }
133
+ after { subject.stop }
134
+
135
+ specify { expect(subject).to be_running }
136
+
137
+ it "should trigger added events" do
138
+ expect_event_trigger("directory.listing.added")
139
+ send_beacon(:heartbeat, echo_server)
140
+ end
141
+
142
+ it "should trigger updated events" do
143
+ send_beacon(:heartbeat, echo_server)
144
+ expect_event_trigger("directory.listing.updated")
145
+ send_beacon(:heartbeat, echo_server)
146
+ end
147
+
148
+ it "should trigger removed events" do
149
+ send_beacon(:heartbeat, echo_server)
150
+ expect_event_trigger("directory.listing.removed")
151
+ send_beacon(:flatline, echo_server)
152
+ end
153
+
154
+ describe "#all_listings_for" do
155
+ context "when listings are present" do
156
+ it "returns all listings for a given service" do
157
+ send_beacon(:heartbeat, hello_server)
158
+ send_beacon(:heartbeat, combo_server)
159
+
160
+ expect(subject.all_listings_for("HelloService").size).to eq(2)
161
+ end
162
+ end
163
+
164
+ context "when no listings are present" do
165
+ it "returns and empty array" do
166
+ expect(subject.all_listings_for("HelloService").size).to eq(0)
167
+ end
168
+ end
169
+ end
170
+
171
+ describe "#each_listing" do
172
+ it "should yield to a block for each listing" do
173
+ send_beacon(:heartbeat, hello_server)
174
+ send_beacon(:heartbeat, echo_server)
175
+ send_beacon(:heartbeat, combo_server)
176
+
177
+ expect do |block|
178
+ subject.each_listing(&block)
179
+ end.to yield_control.exactly(3).times
180
+ end
181
+ end
182
+
183
+ describe "#lookup" do
184
+ it "should provide listings by service" do
185
+ send_beacon(:heartbeat, hello_server)
186
+ expect(subject.lookup("HelloService").to_hash).to eq hello_server.to_hash
187
+ end
188
+
189
+ it "should return random listings" do
190
+ send_beacon(:heartbeat, hello_server)
191
+ send_beacon(:heartbeat, combo_server)
192
+
193
+ uuids = 100.times.map { subject.lookup("HelloService").uuid }
194
+ expect(uuids.count("hello")).to be_within(25).of(50)
195
+ expect(uuids.count("combo")).to be_within(25).of(50)
196
+ end
197
+
198
+ it "should not return expired listings" do
199
+ send_beacon(:heartbeat, hello_server_with_short_ttl)
200
+ sleep 5
201
+ expect(subject.lookup("HelloService")).to be_nil
202
+ end
203
+
204
+ it "should not return flatlined servers" do
205
+ send_beacon(:heartbeat, echo_server)
206
+ send_beacon(:heartbeat, combo_server)
207
+ send_beacon(:flatline, echo_server)
208
+
209
+ uuids = 100.times.map { subject.lookup("EchoService").uuid }
210
+ expect(uuids.count("combo")).to eq 100
211
+ end
212
+
213
+ it "should return up-to-date listings" do
214
+ send_beacon(:heartbeat, echo_server)
215
+ echo_server.port = "7777"
216
+ send_beacon(:heartbeat, echo_server)
217
+
218
+ expect(subject.lookup("EchoService").port).to eq "7777"
219
+ end
220
+
221
+ context 'when given service identifier is a class name' do
222
+ it 'returns the listing corresponding to the class name' do
223
+ send_beacon(:heartbeat, echo_server)
224
+ expect(subject.lookup(EchoService).uuid).to eq echo_server.uuid
225
+ end
226
+ end
227
+ end
228
+
229
+ describe "#restart" do
230
+ it "should clear all listings" do
231
+ send_beacon(:heartbeat, echo_server)
232
+ send_beacon(:heartbeat, combo_server)
233
+ subject.restart
234
+ expect(subject.lookup("EchoService")).to be_nil
235
+ end
236
+ end
237
+
238
+ describe "#running" do
239
+ it "should be true" do
240
+ expect(subject).to be_running
241
+ end
242
+ end
243
+
244
+ describe "#stop" do
245
+ it "should clear all listings" do
246
+ send_beacon(:heartbeat, echo_server)
247
+ send_beacon(:heartbeat, combo_server)
248
+ subject.stop
249
+ expect(subject.lookup("EchoService")).to be_nil
250
+ end
251
+
252
+ it "should stop the server" do
253
+ subject.stop
254
+ expect(subject).to_not be_running
255
+ end
256
+ end
257
+ end
258
+
259
+ if ENV.key?("BENCH")
260
+ context "performance" do
261
+ let(:servers) do
262
+ 100.times.map do |x|
263
+ ::Protobuf::Rpc::DynamicDiscovery::Server.new(
264
+ :uuid => "performance_server#{x + 1}",
265
+ :address => '127.0.0.1',
266
+ :port => (5555 + x).to_s,
267
+ :ttl => rand(1..5),
268
+ :services => 10.times.map { |y| "PerformanceService#{y}" },
269
+ )
270
+ end
271
+ end
272
+
273
+ before do
274
+ require 'benchmark'
275
+ subject.start
276
+ servers.each { |server| send_beacon(:heartbeat, server) }
277
+ end
278
+
279
+ after do
280
+ subject.stop
281
+ end
282
+
283
+ it "should perform lookups in constant time" do
284
+ print "\n\n"
285
+ Benchmark.bm(17) do |x|
286
+ x.report(" 1_000 lookups:") { 1_000.times { subject.lookup("PerformanceService#{rand(0..9)}") } }
287
+ x.report(" 10_000 lookups:") { 10_000.times { subject.lookup("PerformanceService#{rand(0..9)}") } }
288
+ x.report("100_000 lookups:") { 100_000.times { subject.lookup("PerformanceService#{rand(0..9)}") } }
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'protobuf/rpc/service_dispatcher'
3
+
4
+ RSpec.describe Protobuf::Rpc::ServiceDispatcher do
5
+ let(:app) { proc { |env| env } }
6
+ let(:env) do
7
+ Protobuf::Rpc::Env.new(
8
+ 'method_name' => method_name,
9
+ 'request' => request,
10
+ 'rpc_service' => service_class,
11
+ 'service_name' => service_name,
12
+ )
13
+ end
14
+ let(:method_name) { :find }
15
+ let(:request) { request_type.new(:name => 'required') }
16
+ let(:request_type) { service_class.rpcs[method_name].request_type }
17
+ let(:response) { response_type.new(:name => 'required') }
18
+ let(:response_type) { service_class.rpcs[method_name].response_type }
19
+
20
+ let(:rpc_service) { service_class.new(env) }
21
+ let(:service_class) { Test::ResourceService }
22
+ let(:service_name) { service_class.to_s }
23
+
24
+ subject { described_class.new(app) }
25
+
26
+ before { allow(subject).to receive(:rpc_service).and_return(rpc_service) }
27
+
28
+ describe '#call' do
29
+ before { allow(rpc_service).to receive(:response).and_return(response) }
30
+
31
+ it "dispatches the request" do
32
+ stack_env = subject.call(env)
33
+ expect(stack_env.response).to eq response
34
+ end
35
+
36
+ context "when the given RPC method is not implemented" do
37
+ let(:method_name) { :find_not_implemented }
38
+
39
+ it "raises a method not found exception" do
40
+ expect { subject.call(env) }.to raise_exception(Protobuf::Rpc::MethodNotFound)
41
+ end
42
+ end
43
+
44
+ context "when the given RPC method is implemented and a NoMethodError is raised" do
45
+ before { allow(rpc_service).to receive(:callable_rpc_method).and_return(-> { rpc_service.__send__(:foo) }) }
46
+
47
+ it "raises the exeception" do
48
+ expect { subject.call(env) }.to raise_exception(NoMethodError)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,517 @@
1
+ require 'spec_helper'
2
+
3
+ class FilterTest
4
+ include Protobuf::Rpc::ServiceFilters
5
+
6
+ attr_accessor :called
7
+
8
+ # Initialize the hash keys as instance vars
9
+ def initialize(ivar_hash)
10
+ @called = []
11
+ ivar_hash.each_pair do |key, value|
12
+ self.class.class_eval do
13
+ attr_accessor key
14
+ end
15
+ __send__("#{key}=", value)
16
+ end
17
+ end
18
+
19
+ def endpoint
20
+ @called << :endpoint
21
+ end
22
+
23
+ def self.clear_filters!
24
+ @defined_filters = nil
25
+ @filters = nil
26
+ @rescue_filters = nil
27
+ end
28
+ end
29
+
30
+ RSpec.describe Protobuf::Rpc::ServiceFilters do
31
+ let(:params) { {} }
32
+ subject { FilterTest.new(params) }
33
+ after(:each) { FilterTest.clear_filters! }
34
+
35
+ describe '#before_filter' do
36
+ let(:params) { { :before_filter_calls => 0 } }
37
+
38
+ before(:all) do
39
+ class FilterTest
40
+ private
41
+
42
+ def verify_before
43
+ @called << :verify_before
44
+ @before_filter_calls += 1
45
+ end
46
+
47
+ def foo
48
+ @called << :foo
49
+ end
50
+ end
51
+ end
52
+
53
+ before do
54
+ FilterTest.before_filter(:verify_before)
55
+ FilterTest.before_filter(:verify_before)
56
+ FilterTest.before_filter(:foo)
57
+ end
58
+
59
+ specify { expect(subject.class).to respond_to(:before_filter) }
60
+ specify { expect(subject.class).to respond_to(:before_action) }
61
+
62
+ it 'calls filters in the order they were defined' do
63
+ subject.__send__(:run_filters, :endpoint)
64
+ expect(subject.called).to eq [:verify_before, :foo, :endpoint]
65
+ expect(subject.before_filter_calls).to eq 1
66
+ end
67
+
68
+ context 'when filter is configured with "only"' do
69
+ before(:all) do
70
+ class FilterTest
71
+ private
72
+
73
+ def endpoint_with_verify
74
+ @called << :endpoint_with_verify
75
+ end
76
+ end
77
+ end
78
+
79
+ before do
80
+ FilterTest.clear_filters!
81
+ FilterTest.before_filter(:verify_before, :only => :endpoint_with_verify)
82
+ end
83
+
84
+ context 'when invoking a method defined in "only" option' do
85
+ it 'invokes the filter' do
86
+ subject.__send__(:run_filters, :endpoint_with_verify)
87
+ expect(subject.called).to eq [:verify_before, :endpoint_with_verify]
88
+ end
89
+ end
90
+
91
+ context 'when invoking a method not defined by "only" option' do
92
+ it 'does not invoke the filter' do
93
+ subject.__send__(:run_filters, :endpoint)
94
+ expect(subject.called).to eq [:endpoint]
95
+ end
96
+ end
97
+ end
98
+
99
+ context 'when filter is configured with "except"' do
100
+ before(:all) do
101
+ class FilterTest
102
+ private
103
+
104
+ def endpoint_without_verify
105
+ @called << :endpoint_without_verify
106
+ end
107
+ end
108
+ end
109
+
110
+ before do
111
+ FilterTest.clear_filters!
112
+ FilterTest.before_filter(:verify_before, :except => :endpoint_without_verify)
113
+ end
114
+
115
+ context 'when invoking a method not defined in "except" option' do
116
+ it 'invokes the filter' do
117
+ subject.__send__(:run_filters, :endpoint)
118
+ expect(subject.called).to eq [:verify_before, :endpoint]
119
+ end
120
+ end
121
+
122
+ context 'when invoking a method defined by "except" option' do
123
+ it 'does not invoke the filter' do
124
+ subject.__send__(:run_filters, :endpoint_without_verify)
125
+ expect(subject.called).to eq [:endpoint_without_verify]
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'when filter is configured with "if"' do
131
+ before(:all) do
132
+ class FilterTest
133
+ private
134
+
135
+ def check_true
136
+ true
137
+ end
138
+
139
+ def check_false
140
+ false
141
+ end
142
+
143
+ def verify_before
144
+ @called << :verify_before
145
+ end
146
+ end
147
+ end
148
+
149
+ context 'when "if" option is a method that returns true' do
150
+ before do
151
+ FilterTest.clear_filters!
152
+ FilterTest.before_filter(:verify_before, :if => :check_true)
153
+ end
154
+
155
+ it 'invokes the filter' do
156
+ subject.__send__(:run_filters, :endpoint)
157
+ expect(subject.called).to eq [:verify_before, :endpoint]
158
+ end
159
+ end
160
+
161
+ context 'when "if" option is a callable that returns true' do
162
+ before do
163
+ FilterTest.clear_filters!
164
+ FilterTest.before_filter(:verify_before, :if => ->(_service) { true })
165
+ end
166
+
167
+ it 'invokes the filter' do
168
+ subject.__send__(:run_filters, :endpoint)
169
+ expect(subject.called).to eq [:verify_before, :endpoint]
170
+ end
171
+ end
172
+
173
+ context 'when "if" option is a method that returns false' do
174
+ before do
175
+ FilterTest.clear_filters!
176
+ FilterTest.before_filter(:verify_before, :if => :check_false)
177
+ end
178
+
179
+ it 'skips the filter' do
180
+ subject.__send__(:run_filters, :endpoint)
181
+ expect(subject.called).to eq [:endpoint]
182
+ end
183
+ end
184
+
185
+ context 'when "if" option is a callable that returns false' do
186
+ before do
187
+ FilterTest.clear_filters!
188
+ FilterTest.before_filter(:verify_before, :if => ->(_service) { false })
189
+ end
190
+
191
+ it 'skips the filter' do
192
+ subject.__send__(:run_filters, :endpoint)
193
+ expect(subject.called).to eq [:endpoint]
194
+ end
195
+ end
196
+ end
197
+
198
+ context 'when filter is configured with "unless"' do
199
+ before(:all) do
200
+ class FilterTest
201
+ private
202
+
203
+ def check_true
204
+ true
205
+ end
206
+
207
+ def check_false
208
+ false
209
+ end
210
+
211
+ def verify_before
212
+ @called << :verify_before
213
+ end
214
+ end
215
+ end
216
+
217
+ context 'when "unless" option is a method that returns false' do
218
+ before do
219
+ FilterTest.clear_filters!
220
+ FilterTest.before_filter(:verify_before, :unless => :check_false)
221
+ end
222
+
223
+ it 'invokes the filter' do
224
+ subject.__send__(:run_filters, :endpoint)
225
+ expect(subject.called).to eq [:verify_before, :endpoint]
226
+ end
227
+ end
228
+
229
+ context 'when "unless" option is a callable that returns true' do
230
+ before do
231
+ FilterTest.clear_filters!
232
+ FilterTest.before_filter(:verify_before, :unless => ->(_service) { false })
233
+ end
234
+
235
+ it 'invokes the filter' do
236
+ subject.__send__(:run_filters, :endpoint)
237
+ expect(subject.called).to eq [:verify_before, :endpoint]
238
+ end
239
+ end
240
+
241
+ context 'when "unless" option is a method that returns false' do
242
+ before do
243
+ FilterTest.clear_filters!
244
+ FilterTest.before_filter(:verify_before, :unless => :check_true)
245
+ end
246
+
247
+ it 'skips the filter' do
248
+ subject.__send__(:run_filters, :endpoint)
249
+ expect(subject.called).to eq [:endpoint]
250
+ end
251
+ end
252
+
253
+ context 'when "unless" option is a callable that returns false' do
254
+ before do
255
+ FilterTest.clear_filters!
256
+ FilterTest.before_filter(:verify_before, :unless => ->(_service) { true })
257
+ end
258
+
259
+ it 'skips the filter' do
260
+ subject.__send__(:run_filters, :endpoint)
261
+ expect(subject.called).to eq [:endpoint]
262
+ end
263
+ end
264
+ end
265
+
266
+ context 'when filter returns false' do
267
+ before(:all) do
268
+ class FilterTest
269
+ private
270
+
271
+ def short_circuit_filter
272
+ @called << :short_circuit_filter
273
+ false
274
+ end
275
+ end
276
+ end
277
+
278
+ before do
279
+ FilterTest.clear_filters!
280
+ FilterTest.before_filter(:short_circuit_filter)
281
+ end
282
+
283
+ it 'does not invoke the rpc method' do
284
+ expect(subject).not_to receive(:endpoint)
285
+ subject.__send__(:run_filters, :endpoint)
286
+ expect(subject.called).to eq [:short_circuit_filter]
287
+ end
288
+ end
289
+ end
290
+
291
+ describe '#after_filter' do
292
+ let(:params) { { :after_filter_calls => 0 } }
293
+
294
+ before(:all) do
295
+ class FilterTest
296
+ private
297
+
298
+ def verify_after
299
+ @called << :verify_after
300
+ @after_filter_calls += 1
301
+ end
302
+
303
+ def foo
304
+ @called << :foo
305
+ end
306
+ end
307
+ end
308
+
309
+ before do
310
+ FilterTest.after_filter(:verify_after)
311
+ FilterTest.after_filter(:verify_after)
312
+ FilterTest.after_filter(:foo)
313
+ end
314
+
315
+ specify { expect(subject.class).to respond_to(:after_filter) }
316
+ specify { expect(subject.class).to respond_to(:after_action) }
317
+
318
+ it 'calls filters in the order they were defined' do
319
+ subject.__send__(:run_filters, :endpoint)
320
+ expect(subject.called).to eq [:endpoint, :verify_after, :foo]
321
+ expect(subject.after_filter_calls).to eq 1
322
+ end
323
+ end
324
+
325
+ describe '#around_filter' do
326
+ let(:params) { {} }
327
+
328
+ before(:all) do
329
+ class FilterTest
330
+ private
331
+
332
+ def outer_around
333
+ @called << :outer_around_top
334
+ yield
335
+ @called << :outer_around_bottom
336
+ end
337
+
338
+ def inner_around
339
+ @called << :inner_around_top
340
+ yield
341
+ @called << :inner_around_bottom
342
+ end
343
+ end
344
+ end
345
+
346
+ before do
347
+ FilterTest.around_filter(:outer_around)
348
+ FilterTest.around_filter(:inner_around)
349
+ FilterTest.around_filter(:outer_around)
350
+ FilterTest.around_filter(:inner_around)
351
+ end
352
+
353
+ specify { expect(subject.class).to respond_to(:around_filter) }
354
+ specify { expect(subject.class).to respond_to(:around_action) }
355
+
356
+ it 'calls filters in the order they were defined' do
357
+ subject.__send__(:run_filters, :endpoint)
358
+ expect(subject.called).to eq(
359
+ [
360
+ :outer_around_top,
361
+ :inner_around_top,
362
+ :endpoint,
363
+ :inner_around_bottom,
364
+ :outer_around_bottom,
365
+ ],
366
+ )
367
+ end
368
+
369
+ context 'when around_filter does not yield' do
370
+ before do
371
+ class FilterTest
372
+ private
373
+
374
+ def inner_around
375
+ @called << :inner_around
376
+ end
377
+ end
378
+ end
379
+
380
+ before do
381
+ FilterTest.around_filter(:outer_around)
382
+ FilterTest.around_filter(:inner_around)
383
+ end
384
+
385
+ it 'cancels calling the rest of the filters and the endpoint' do
386
+ expect(subject).not_to receive(:endpoint)
387
+ subject.__send__(:run_filters, :endpoint)
388
+ expect(subject.called).to eq(
389
+ [
390
+ :outer_around_top,
391
+ :inner_around,
392
+ :outer_around_bottom,
393
+ ],
394
+ )
395
+ end
396
+
397
+ end
398
+ end
399
+
400
+ describe '#rescue_from' do
401
+ before do
402
+ class CustomError1 < StandardError; end
403
+ class CustomError2 < StandardError; end
404
+ class CustomError3 < StandardError; end
405
+ end
406
+
407
+ before do
408
+ class FilterTest
409
+ private
410
+
411
+ def filter_with_error1
412
+ @called << :filter_with_error1
413
+ fail CustomError1, 'Filter 1 failed'
414
+ end
415
+
416
+ def filter_with_error2
417
+ @called << :filter_with_error2
418
+ fail CustomError1, 'Filter 2 failed'
419
+ end
420
+
421
+ def filter_with_error3
422
+ @called << :filter_with_error3
423
+ fail CustomError3, 'Filter 3 failed'
424
+ end
425
+
426
+ def filter_with_runtime_error
427
+ @called << :filter_with_runtime_error
428
+ fail 'Filter with runtime error failed'
429
+ end
430
+
431
+ def custom_error_occurred(ex)
432
+ @ex_class = ex.class
433
+ @called << :custom_error_occurred
434
+ end
435
+ end
436
+ end
437
+
438
+ let(:params) { { :ex_class => nil } }
439
+
440
+ context 'when defining multiple errors with a given callback' do
441
+ before do
442
+ FilterTest.rescue_from(CustomError1, CustomError2, CustomError3, :with => :custom_error_occurred)
443
+ end
444
+ before { FilterTest.before_filter(:filter_with_error3) }
445
+
446
+ it 'short-circuits the call stack' do
447
+ expect do
448
+ expect(subject).not_to receive(:endpoint)
449
+ subject.__send__(:run_filters, :endpoint)
450
+ expect(subject.called).to eq([:filter_with_error3, :custom_error_occurred])
451
+ expect(subject.ex_class).to eq CustomError3
452
+ end.not_to raise_error
453
+ end
454
+ end
455
+
456
+ context 'when defined with options' do
457
+ context 'when :with option is not given' do
458
+ specify do
459
+ expect { FilterTest.rescue_from(CustomError1) }.to raise_error(ArgumentError, /with/)
460
+ end
461
+ end
462
+
463
+ context 'when error occurs inside filter' do
464
+ before { FilterTest.rescue_from(CustomError1, :with => :custom_error_occurred) }
465
+ before { FilterTest.before_filter(:filter_with_error1) }
466
+
467
+ it 'short-circuits the call stack' do
468
+ expect do
469
+ expect(subject).not_to receive(:endpoint)
470
+ subject.__send__(:run_filters, :endpoint)
471
+ expect(subject.called).to eq([:filter_with_error1, :custom_error_occurred])
472
+ expect(subject.ex_class).to eq CustomError1
473
+ end.not_to raise_error
474
+ end
475
+ end
476
+ end
477
+
478
+ context 'when defined with block' do
479
+ before do
480
+ FilterTest.rescue_from(CustomError1) do |service, ex|
481
+ service.ex_class = ex.class
482
+ service.called << :block_rescue_handler
483
+ end
484
+ end
485
+ before { FilterTest.before_filter(:filter_with_error1) }
486
+
487
+ it 'short-circuits the call stack' do
488
+ expect do
489
+ expect(subject).not_to receive(:endpoint)
490
+ subject.__send__(:run_filters, :endpoint)
491
+ expect(subject.called).to eq([:filter_with_error1, :block_rescue_handler])
492
+ expect(subject.ex_class).to eq CustomError1
493
+ end.not_to raise_error
494
+ end
495
+ end
496
+
497
+ context 'when thrown exception inherits from a mapped exception' do
498
+ before do
499
+ FilterTest.rescue_from(StandardError) do |service, ex|
500
+ service.ex_class = ex.class
501
+ service.called << :standard_error_rescue_handler
502
+ end
503
+ end
504
+ before { FilterTest.before_filter(:filter_with_runtime_error) }
505
+
506
+ it 'rescues with the given callable' do
507
+ expect do
508
+ expect(subject).not_to receive(:endpoint)
509
+ subject.__send__(:run_filters, :endpoint)
510
+ expect(subject.called).to eq([:filter_with_runtime_error, :standard_error_rescue_handler])
511
+ expect(subject.ex_class).to eq RuntimeError
512
+ end.not_to raise_error
513
+ end
514
+ end
515
+ end
516
+
517
+ end