lib/gruf/rspec.rb
# frozen_string_literal: true
# Copyright (c) 2018-present, BigCommerce Pty. Ltd. All rights reserved
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
require 'rspec/core'
require 'rspec/expectations'
# use Zeitwerk to lazily autoload all the files in the lib directory
require 'zeitwerk'
lib_path = File.dirname(__dir__)
loader = ::Zeitwerk::Loader.new
loader.tag = 'gruf-rspec'
loader.inflector = ::Zeitwerk::GemInflector.new(__FILE__)
loader.ignore("#{lib_path}/gruf/rspec/railtie.rb")
loader.push_dir(lib_path)
loader.setup
##
# Base gruf module
#
module Gruf
##
# Base gruf-rspec module
#
module Rspec
extend Gruf::Rspec::Configuration
end
end
Gruf::Rspec.reset # initial reset
# Attempt to load railtie if we're in a rails environment. This assists with autoloading in a rails rspec context
begin
require 'rails'
rescue LoadError
nil
end
require_relative 'rspec/railtie' if defined?(::Rails::Railtie)
RSpec.configure do |config|
config.include Gruf::Rspec::Helpers
config.define_derived_metadata(file_path: Regexp.new(Gruf::Rspec.rpc_spec_path)) do |metadata|
metadata[:type] = :gruf_controller
end
config.before(:each, type: :gruf_controller) do
define_singleton_method :run_rpc do |method_name, request, active_call_options: {}, &block|
@gruf_controller = described_class.new(
method_key: method_name.to_s.underscore.to_sym,
service: grpc_bound_service,
rpc_desc: grpc_bound_service.rpc_descs[method_name.to_sym],
active_call: grpc_active_call(active_call_options),
message: request
)
resp = @gruf_controller.call(@gruf_controller.request.method_key)
block.call(resp) if block.is_a?(Proc)
resp
end
define_singleton_method :grpc_bound_service do
described_class.bound_service
end
define_singleton_method :gruf_controller do
@gruf_controller
end
end
end
RSpec::Matchers.define :raise_rpc_error do |expected_error_class|
supports_block_expectations
def with_serialized(&block)
@serialized_block = block
self
end
match do |rpc_call_proc|
@error_matcher = Gruf::Rspec::ErrorMatcher.new(
rpc_call_proc: rpc_call_proc,
expected_error_class: expected_error_class,
serialized_block: @serialized_block
)
@error_matcher.valid?
end
failure_message do |_|
@error_matcher.error_message
end
end
RSpec::Matchers.define :be_a_successful_rpc do |_|
match do |actual|
if !gruf_controller || actual.is_a?(GRPC::BadStatus) || actual.is_a?(GRPC::Core::CallError)
false
else
method_key = gruf_controller.request.method_key.to_s.camelcase.to_sym
expected_class = gruf_controller.class.bound_service.rpc_descs[method_key].output
expected_class == actual.class
end
end
end