Skip to content

Commit

Permalink
[DEX-2398] feat: async messages support
Browse files Browse the repository at this point in the history
  • Loading branch information
ysatarov committed Aug 22, 2024
1 parent a2846e8 commit af03588
Show file tree
Hide file tree
Showing 66 changed files with 1,755 additions and 418 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

## [0.11.0] - 2024-08-07

### Added
- async messages support
- collection matchers
- support of additional includes of proto-files for grpc plugin

## [0.10.0] - 2024-08-15

### Added
Expand Down
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ source "https://nexus.sbmt.io/repository/rubygems/"
gemspec

source "https://nexus.sbmt.io/repository/ruby-gems-sbermarket/" do
gem "pact-ffi"

group :development, :test do
gem "sbmt-app"
gem "sbmt-dev"
Expand Down
7 changes: 0 additions & 7 deletions dip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,6 @@ interaction:
service: ruby
command: bundle exec rubocop

pact:
description: Run pact tests
service: backend
environment:
RAILS_ENV: test
command: bundle exec rspec -t pact

setup:
description: Install deps
service: ruby
Expand Down
12 changes: 8 additions & 4 deletions lib/sbmt/pact/consumer/grpc_interaction_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def initialize(pact_config, description: nil)
@description = description || ""

@proto_path = nil
@proto_include_dirs = []
@service_name = nil
@method_name = nil
@request = nil
Expand All @@ -52,7 +53,7 @@ def initialize(pact_config, description: nil)
@provider_state_meta = nil
end

def with_service(proto_path, method)
def with_service(proto_path, method, include_dirs = [])
raise InteractionBuilderError.new("invalid grpc method: cannot be blank") if method.blank?

service_name, method_name = method.split("/") || []
Expand All @@ -64,6 +65,7 @@ def with_service(proto_path, method)
@proto_path = absolute_path
@service_name = service_name
@method_name = method_name
@proto_include_dirs = include_dirs.map { |dir| File.expand_path(dir) }

self
end
Expand All @@ -79,17 +81,17 @@ def upon_receiving(description)
end

def with_request(req_hash)
@request = req_hash
@request = InteractionContents.plugin(req_hash)
self
end

def with_response(resp_hash)
@response = resp_hash
@response = InteractionContents.plugin(resp_hash)
self
end

def with_response_meta(meta_hash)
@response_meta = meta_hash
@response_meta = InteractionContents.plugin(meta_hash)
self
end

Expand All @@ -101,6 +103,8 @@ def interaction_json
request: @request
}

result["pact:protobuf-config"] = {additionalIncludes: @proto_include_dirs} if @proto_include_dirs.present?

result[:response] = @response if @response.is_a?(Hash)
result[:responseMetadata] = @response_meta if @response_meta.is_a?(Hash)

Expand Down
39 changes: 17 additions & 22 deletions lib/sbmt/pact/consumer/http_interaction_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,35 +58,35 @@ def upon_receiving(description)
self
end

def with_request(method, path, query: nil, headers: nil, body: nil)
def with_request(method, path, query: {}, headers: {}, body: nil)
interaction_part = PactFfi::FfiInteractionPart["INTERACTION_PART_REQUEST"]
PactFfi.with_request(pact_interaction, method.to_s, matcher_value_or_string(path))
PactFfi.with_request(pact_interaction, method.to_s, format_value(path))

with_parameters(query) do |key, index, value_item|
PactFfi.with_query_parameter_v2(pact_interaction, key, index, value_item)
InteractionContents.basic(query).each_pair do |key, value_item|
PactFfi.with_query_parameter_v2(pact_interaction, key.to_s, 0, format_value(value_item))
end

with_parameters(headers) do |key, index, value_item|
PactFfi.with_header_v2(pact_interaction, interaction_part, key, index, value_item)
InteractionContents.basic(headers).each_pair do |key, value_item|
PactFfi.with_header_v2(pact_interaction, interaction_part, key.to_s, 0, format_value(value_item))
end

if body
PactFfi.with_body(pact_interaction, interaction_part, "application/json", JSON.dump(body))
PactFfi.with_body(pact_interaction, interaction_part, "application/json", format_value(InteractionContents.basic(body)))
end

self
end

def with_response(status, headers: nil, body: nil)
def with_response(status, headers: {}, body: nil)
interaction_part = PactFfi::FfiInteractionPart["INTERACTION_PART_RESPONSE"]
PactFfi.response_status(pact_interaction, status)

with_parameters(headers) do |key, index, value_item|
PactFfi.with_header_v2(pact_interaction, interaction_part, key, index, value_item)
InteractionContents.basic(headers).each_pair do |key, value_item|
PactFfi.with_header_v2(pact_interaction, interaction_part, key.to_s, 0, format_value(value_item))
end

if body
PactFfi.with_body(pact_interaction, interaction_part, "application/json", JSON.dump(body))
PactFfi.with_body(pact_interaction, interaction_part, "application/json", format_value(InteractionContents.basic(body)))
end

self
Expand Down Expand Up @@ -132,22 +132,17 @@ def init_pact
handle
end

def matcher_value_or_string(obj)
obj.is_a?(String) ? obj : JSON.dump(obj)
def format_value(obj)
return obj if obj.is_a?(String)

return JSON.dump({value: obj}) if obj.is_a?(Array)

JSON.dump(obj)
end

def full_description
"#{DESCRIPTION_PREFIX}#{@description}"
end

def with_parameters(parameters)
parameters&.each do |key, value|
value_arr = value.is_a?(Array) ? value : [value]
value_arr.each_with_index do |value_item, index|
yield(key.to_s, index, matcher_value_or_string(value_item))
end
end
end
end
end
end
Expand Down
47 changes: 47 additions & 0 deletions lib/sbmt/pact/consumer/interaction_contents.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module Sbmt
module Pact
module Consumer
class InteractionContents < Hash
BASIC_FORMAT = :basic
PLUGIN_FORMAT = :plugin

attr_reader :format

def self.basic(contents_hash)
new(contents_hash, BASIC_FORMAT)
end

def self.plugin(contents_hash)
new(contents_hash, PLUGIN_FORMAT)
end

def initialize(contents_hash, format)
init_hash(contents_hash, format).each_pair { |k, v| self[k] = v }
@format = format
end

private

def serialize(hash, format)
# serialize recursively
hash.each_pair do |key, value|
next serialize(value, format) if value.is_a?(Hash)
next hash[key] = value.map { |v| serialize(v, format) } if value.is_a?(Array)
if value.is_a?(Sbmt::Pact::Matchers::Base)
hash[key] = value.as_basic if format == :basic
hash[key] = value.as_plugin if format == :plugin
end
end

hash
end

def init_hash(hash, format)
serialize(hash.deep_dup, format)
end
end
end
end
end
Loading

0 comments on commit af03588

Please sign in to comment.