proto_pharm 0.6.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 +7 -0
- data/.github/workflows/rspec.yml +35 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +113 -0
- data/LICENSE.txt +21 -0
- data/README.md +238 -0
- data/Rakefile +8 -0
- data/bin/console +11 -0
- data/bin/regen_examples +7 -0
- data/bin/release +16 -0
- data/bin/setup +8 -0
- data/lib/proto_pharm.rb +47 -0
- data/lib/proto_pharm/action_stub.rb +57 -0
- data/lib/proto_pharm/adapter.rb +13 -0
- data/lib/proto_pharm/api.rb +31 -0
- data/lib/proto_pharm/configuration.rb +11 -0
- data/lib/proto_pharm/errors.rb +21 -0
- data/lib/proto_pharm/grpc_stub_adapter.rb +24 -0
- data/lib/proto_pharm/grpc_stub_adapter/mock_stub.rb +74 -0
- data/lib/proto_pharm/introspection.rb +18 -0
- data/lib/proto_pharm/introspection/rpc_inspector.rb +58 -0
- data/lib/proto_pharm/introspection/service_resolver.rb +24 -0
- data/lib/proto_pharm/matchers/hash_argument_matcher.rb +43 -0
- data/lib/proto_pharm/matchers/request_including_matcher.rb +39 -0
- data/lib/proto_pharm/operation_stub.rb +30 -0
- data/lib/proto_pharm/request_pattern.rb +29 -0
- data/lib/proto_pharm/request_stub.rb +67 -0
- data/lib/proto_pharm/response.rb +36 -0
- data/lib/proto_pharm/response_sequence.rb +40 -0
- data/lib/proto_pharm/rspec.rb +28 -0
- data/lib/proto_pharm/rspec/action_stub_builder.rb +37 -0
- data/lib/proto_pharm/rspec/action_stub_proxy.rb +58 -0
- data/lib/proto_pharm/rspec/dsl.rb +15 -0
- data/lib/proto_pharm/rspec/matchers/have_received_rpc.rb +72 -0
- data/lib/proto_pharm/stub_components/failure_response.rb +30 -0
- data/lib/proto_pharm/stub_registry.rb +36 -0
- data/lib/proto_pharm/version.rb +5 -0
- data/proto_pharm.gemspec +38 -0
- data/rakelib/regen_examples.rake +11 -0
- metadata +248 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require_relative "../lib/proto_pharm"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
data/bin/regen_examples
ADDED
data/bin/release
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "../lib/proto_pharm/version"
|
4
|
+
|
5
|
+
current_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
6
|
+
|
7
|
+
unless current_branch == "master" || ProtoPharm::VERSION =~ %r{\.pre\d*\z}
|
8
|
+
puts "Can only release from the master branch 😿"
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
12
|
+
system "git tag v#{ProtoPharm::VERSION}"
|
13
|
+
|
14
|
+
system "git push --tag"
|
15
|
+
|
16
|
+
system "git push fury #{current_branch}:master"
|
data/bin/setup
ADDED
data/lib/proto_pharm.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module"
|
4
|
+
require "grpc"
|
5
|
+
|
6
|
+
require_relative "proto_pharm/version"
|
7
|
+
require_relative "proto_pharm/configuration"
|
8
|
+
|
9
|
+
require_relative "proto_pharm/introspection"
|
10
|
+
require_relative "proto_pharm/stub_components/failure_response"
|
11
|
+
|
12
|
+
require_relative "proto_pharm/adapter"
|
13
|
+
require_relative "proto_pharm/grpc_stub_adapter"
|
14
|
+
require_relative "proto_pharm/grpc_stub_adapter/mock_stub"
|
15
|
+
|
16
|
+
require_relative "proto_pharm/stub_registry"
|
17
|
+
require_relative "proto_pharm/api"
|
18
|
+
|
19
|
+
module ProtoPharm
|
20
|
+
extend ProtoPharm::Api
|
21
|
+
|
22
|
+
class << self
|
23
|
+
delegate :enable!, :disable!, :enabled?, to: :adapter
|
24
|
+
|
25
|
+
def reset!
|
26
|
+
ProtoPharm.stub_registry.reset!
|
27
|
+
end
|
28
|
+
|
29
|
+
def stub_registry
|
30
|
+
@stub_registry ||= ProtoPharm::StubRegistry.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def adapter
|
34
|
+
@adapter ||= Adapter.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def config
|
38
|
+
@config ||= Configuration.new
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Hook into GRPC::ClientStub
|
43
|
+
# https://github.com/grpc/grpc/blob/bec3b5ada2c5e5d782dff0b7b5018df646b65cb0/src/ruby/lib/grpc/generic/service.rb#L150-L186
|
44
|
+
GRPC::ClientStub.prepend GrpcStubAdapter::MockStub
|
45
|
+
end
|
46
|
+
|
47
|
+
GrpcMock = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("GrpcMock", "ProtoPharm")
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
|
5
|
+
module ProtoPharm
|
6
|
+
class ActionStub < RequestStub
|
7
|
+
include Introspection
|
8
|
+
include StubComponents::FailureResponse
|
9
|
+
|
10
|
+
attr_reader :service, :action
|
11
|
+
|
12
|
+
# @param service [GRPC::GenericService] gRPC service class representing the the service being stubbed
|
13
|
+
# @param action [String, Symbol] name of the endpoint being stubbed
|
14
|
+
def initialize(service, action)
|
15
|
+
@service = service
|
16
|
+
@action = action
|
17
|
+
|
18
|
+
super(grpc_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param proto [Object] request proto object
|
22
|
+
# @param request_kwargs [Hash] parameters for request
|
23
|
+
def with(proto = nil, **request_kwargs)
|
24
|
+
super(endpoint.normalize_request_proto(proto, **request_kwargs))
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param proto [Object] response proto object
|
28
|
+
# @param request_kwargs [Hash] parameters to respond with
|
29
|
+
def to_return(proto = nil, **request_kwargs)
|
30
|
+
super(endpoint.normalize_response_proto(proto, **request_kwargs))
|
31
|
+
end
|
32
|
+
|
33
|
+
def received!(request)
|
34
|
+
@received_requests << request
|
35
|
+
end
|
36
|
+
|
37
|
+
def received_count
|
38
|
+
received_requests.size
|
39
|
+
end
|
40
|
+
|
41
|
+
def match?(match_path, match_request)
|
42
|
+
# If paths don't match, don't try to cast the request object
|
43
|
+
super unless grpc_path == match_path
|
44
|
+
|
45
|
+
# If paths match, cast the given request object to the expected proto
|
46
|
+
super(match_path, endpoint.normalize_request_proto(match_request))
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
delegate :grpc_path, :input_type, :output_type, to: :endpoint
|
52
|
+
|
53
|
+
def endpoint
|
54
|
+
@endpoint ||= inspect_rpc(service, action)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "proto_pharm/request_stub"
|
4
|
+
require "proto_pharm/action_stub"
|
5
|
+
require "proto_pharm/matchers/request_including_matcher"
|
6
|
+
|
7
|
+
module ProtoPharm
|
8
|
+
module Api
|
9
|
+
# @param path [String]
|
10
|
+
def stub_request(path)
|
11
|
+
ProtoPharm.stub_registry.register_request_stub(ProtoPharm::RequestStub.new(path))
|
12
|
+
end
|
13
|
+
|
14
|
+
def stub_grpc_action(service, rpc_action)
|
15
|
+
ProtoPharm.stub_registry.register_request_stub(ProtoPharm::ActionStub.new(service, rpc_action))
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param values [Hash]
|
19
|
+
def request_including(values)
|
20
|
+
ProtoPharm::Matchers::RequestIncludingMatcher.new(values)
|
21
|
+
end
|
22
|
+
|
23
|
+
def disable_net_connect!
|
24
|
+
ProtoPharm.config.allow_net_connect = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def allow_net_connect!
|
28
|
+
ProtoPharm.config.allow_net_connect = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProtoPharm
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class NetConnectNotAllowedError < Error
|
7
|
+
def initialize(sigunature)
|
8
|
+
super("Real gRPC connections are disabled. #{sigunature} is requested")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class NoResponseError < Error
|
13
|
+
def initialize(msg)
|
14
|
+
super("There is no response: #{msg}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvalidProtoType < Error; end
|
19
|
+
class RpcNotFoundError < Error; end
|
20
|
+
class RpcNotStubbedError < Error; end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "errors"
|
4
|
+
require_relative "operation_stub"
|
5
|
+
|
6
|
+
module ProtoPharm
|
7
|
+
class GrpcStubAdapter
|
8
|
+
delegate :enable!, :disable!, :enabled?, to: :class
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def disable!
|
12
|
+
@enabled = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def enable!
|
16
|
+
@enabled = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def enabled?
|
20
|
+
@enabled
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProtoPharm
|
4
|
+
class GrpcStubAdapter
|
5
|
+
module MockStub
|
6
|
+
def request_response(method, request, *args, return_op: false, **opts)
|
7
|
+
return super unless ProtoPharm.enabled?
|
8
|
+
|
9
|
+
request_stub = ProtoPharm.stub_registry.find_request_matching(method, request)
|
10
|
+
|
11
|
+
if request_stub
|
12
|
+
operation = OperationStub.new(metadata: opts[:metadata]) do
|
13
|
+
request_stub.received!(request)
|
14
|
+
request_stub.response.evaluate
|
15
|
+
end
|
16
|
+
|
17
|
+
return_op ? operation : operation.execute
|
18
|
+
elsif ProtoPharm.config.allow_net_connect
|
19
|
+
super
|
20
|
+
else
|
21
|
+
raise NetConnectNotAllowedError, method
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO
|
26
|
+
def client_streamer(method, requests, *args)
|
27
|
+
return super unless ProtoPharm.enabled?
|
28
|
+
|
29
|
+
r = requests.to_a # FIXME: this may not work
|
30
|
+
request_stub = ProtoPharm.stub_registry.find_request_matching(method, r)
|
31
|
+
|
32
|
+
if request_stub
|
33
|
+
request_stub.received!(requests)
|
34
|
+
request_stub.response.evaluate
|
35
|
+
elsif ProtoPharm.config.allow_net_connect
|
36
|
+
super
|
37
|
+
else
|
38
|
+
raise NetConnectNotAllowedError, method
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def server_streamer(method, request, *args)
|
43
|
+
return super unless ProtoPharm.enabled?
|
44
|
+
|
45
|
+
request_stub = ProtoPharm.stub_registry.find_request_matching(method, request)
|
46
|
+
|
47
|
+
if request_stub
|
48
|
+
request_stub.received!(request)
|
49
|
+
request_stub.response.evaluate
|
50
|
+
elsif ProtoPharm.config.allow_net_connect
|
51
|
+
super
|
52
|
+
else
|
53
|
+
raise NetConnectNotAllowedError, method
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def bidi_streamer(method, requests, *args)
|
58
|
+
return super unless ProtoPharm.enabled?
|
59
|
+
|
60
|
+
r = requests.to_a # FIXME: this may not work
|
61
|
+
request_stub = ProtoPharm.stub_registry.find_request_matching(method, r)
|
62
|
+
|
63
|
+
if request_stub
|
64
|
+
request_stub.received!(requests)
|
65
|
+
request_stub.response.evaluate
|
66
|
+
elsif ProtoPharm.config.allow_net_connect
|
67
|
+
super
|
68
|
+
else
|
69
|
+
raise NetConnectNotAllowedError, method
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "introspection/rpc_inspector"
|
4
|
+
require_relative "introspection/service_resolver"
|
5
|
+
|
6
|
+
module ProtoPharm
|
7
|
+
module Introspection
|
8
|
+
private
|
9
|
+
|
10
|
+
def resolve_service(service)
|
11
|
+
Introspection::ServiceResolver.resolve(service)
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect_rpc(service, endpoint)
|
15
|
+
Introspection::RpcInspector.new(service, endpoint)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProtoPharm
|
4
|
+
module Introspection
|
5
|
+
class RpcInspector
|
6
|
+
attr_reader :grpc_service, :endpoint_name
|
7
|
+
|
8
|
+
delegate :service_name, :rpc_descs, to: :grpc_service
|
9
|
+
|
10
|
+
def initialize(service, endpoint_name)
|
11
|
+
@grpc_service = ServiceResolver.resolve(service)
|
12
|
+
|
13
|
+
@endpoint_name = endpoint_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def normalize_request_proto(proto = nil, **kwargs)
|
17
|
+
cast_proto(input_type, proto, **kwargs)
|
18
|
+
end
|
19
|
+
|
20
|
+
def normalize_response_proto(proto = nil, **kwargs)
|
21
|
+
cast_proto(output_type, proto, **kwargs)
|
22
|
+
end
|
23
|
+
|
24
|
+
def normalized_rpc_name
|
25
|
+
@normalized_rpc_name ||= endpoint_name.to_s.camelize.to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
def rpc_desc
|
29
|
+
@rpc_desc ||= rpc_descs[normalized_rpc_name].tap do |endpoint|
|
30
|
+
raise RpcNotFoundError, "Service #{service_name} does not implement '#{normalized_rpc_name}'" if endpoint.blank?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def grpc_path
|
35
|
+
@grpc_path ||= "/#{service_name}/#{normalized_rpc_name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def input_type
|
39
|
+
rpc_desc.input
|
40
|
+
end
|
41
|
+
|
42
|
+
def output_type
|
43
|
+
rpc_desc.output
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def cast_proto(proto_class, proto = nil, **kwargs)
|
49
|
+
return proto_class.new(**kwargs) if proto.blank?
|
50
|
+
return proto_class.new(proto) if proto.respond_to?(:to_hash)
|
51
|
+
|
52
|
+
raise InvalidProtoType, "Invalid proto type #{proto.class} for #{grpc_path}, expected #{proto_class}" unless proto.class == proto_class
|
53
|
+
|
54
|
+
proto
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProtoPharm
|
4
|
+
module Introspection
|
5
|
+
module ServiceResolver
|
6
|
+
class InvalidGRPCServiceError < StandardError; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def resolve(service)
|
10
|
+
raise InvalidGRPCServiceError, "Not a valid gRPC service module: #{service.inspect}" unless service.respond_to?(:const_defined?)
|
11
|
+
|
12
|
+
service.const_defined?(:Service) ? service::Service : service
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# We'll need this later
|
17
|
+
# attr_reader :service
|
18
|
+
|
19
|
+
# def initialize(service)
|
20
|
+
# @service = service
|
21
|
+
# end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|