Skip to content

Commit

Permalink
feat(pacts for verification): support querying by POST
Browse files Browse the repository at this point in the history
  • Loading branch information
bethesque committed Nov 19, 2019
1 parent b3fd3aa commit 5556b81
Show file tree
Hide file tree
Showing 15 changed files with 444 additions and 73 deletions.
38 changes: 38 additions & 0 deletions lib/pact_broker/api/contracts/dry_validation_workarounds.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module PactBroker
module Api
module Contracts
module DryValidationWorkarounds
extend self

# I just cannot seem to get the validation to stop on the first error.
# If one rule fails, they all come back failed, and it's driving me nuts.
# Why on earth would I want that behaviour?
def select_first_message(messages)
messages.each_with_object({}) do | (key, value), new_messages |
new_messages[key] = [value.first]
end
end

def flatten_array_of_hashes(array_of_hashes)
new_messages = array_of_hashes.collect do | index, hash |
hash.values.flatten.collect { | text | "#{text} at index #{index}"}
end.flatten
end

def flatten_indexed_messages(messages)
if messages.values.any?{ | value | is_indexed_structure?(value) }
messages.each_with_object({}) do | (key, value), new_messages |
new_messages[key] = is_indexed_structure?(value) ? flatten_array_of_hashes(value) : value
end
else
messages
end
end

def is_indexed_structure?(thing)
thing.is_a?(Hash) && thing.keys.first.is_a?(Integer)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'dry-validation'
require 'pact_broker/hash_refinements'
require 'pact_broker/api/contracts/dry_validation_workarounds'

module PactBroker
module Api
module Contracts
class VerifiablePactsJSONQuerySchema
extend DryValidationWorkarounds
using PactBroker::HashRefinements

SCHEMA = Dry::Validation.Schema do
optional(:providerVersionTags).maybe(:array?)
optional(:consumerVersionSelectors).each do
schema do
required(:tag).filled(:str?)
optional(:latest).filled(included_in?: [true, false])
end
end
end

def self.call(params)
select_first_message(flatten_indexed_messages(SCHEMA.call(params&.symbolize_keys).messages(full: true)))
end
end
end
end
end
24 changes: 5 additions & 19 deletions lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
require 'dry-validation'
require 'pact_broker/api/contracts/dry_validation_workarounds'

module PactBroker
module Api
module Contracts
class VerifiablePactsQuerySchema
extend DryValidationWorkarounds
using PactBroker::HashRefinements

SCHEMA = Dry::Validation.Schema do
optional(:provider_version_tags).maybe(:array?)
# optional(:exclude_other_pending).filled(included_in?: ["true", "false"])
optional(:consumer_version_selectors).each do
schema do
required(:tag).filled(:str?)
Expand All @@ -16,24 +19,7 @@ class VerifiablePactsQuerySchema
end

def self.call(params)
select_first_message(flatten_index_messages(SCHEMA.call(params).messages(full: true)))
end

def self.select_first_message(messages)
messages.each_with_object({}) do | (key, value), new_messages |
new_messages[key] = [value.first]
end
end

def self.flatten_index_messages(messages)
if messages[:consumer_version_selectors]
new_messages = messages[:consumer_version_selectors].collect do | index, value |
value.values.flatten.collect { | text | "#{text} at index #{index}"}
end.flatten
messages.merge(consumer_version_selectors: new_messages)
else
messages
end
select_first_message(flatten_indexed_messages(SCHEMA.call(params&.symbolize_keys).messages(full: true)))
end
end
end
Expand Down
22 changes: 12 additions & 10 deletions lib/pact_broker/api/decorators/verifiable_pacts_query_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
require_relative 'base_decorator'
require_relative 'verifiable_pact_decorator'
require 'pact_broker/api/pact_broker_urls'
require 'pact_broker/hash_refinements'

module PactBroker
module Api
module Decorators
class VerifiablePactsQueryDecorator < BaseDecorator
collection :provider_version_tags
using PactBroker::HashRefinements

collection :consumer_version_selectors, class: OpenStruct do
collection :provider_version_tags, default: []

collection :consumer_version_selectors, default: [], class: OpenStruct do
property :tag
property :latest, setter: ->(fragment:, represented:, **) { represented.latest = (fragment == 'true') }
property :latest,
setter: ->(fragment:, represented:, **) {
represented.latest = (fragment == 'true' || fragment == true)
}
end


def from_hash(*args)
# Should remember how to do this via Representable...
result = super
result.consumer_version_selectors = [] if result.consumer_version_selectors.nil?
result.provider_version_tags = [] if result.provider_version_tags.nil?
result
def from_hash(hash)
# This handles both the snakecase keys from the GET query and the camelcase JSON POST body
super(hash&.snakecase_keys)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/pact_broker/api/resources/base_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def params
end

def params_with_string_keys
JSON.parse(request.body.to_s, {symbolize_names: false}.merge(PACT_PARSING_OPTIONS))
@params_with_string_keys ||= JSON.parse(request.body.to_s, {symbolize_names: false}.merge(PACT_PARSING_OPTIONS))
end

def pact_params
Expand Down
33 changes: 27 additions & 6 deletions lib/pact_broker/api/resources/provider_pacts_for_verification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
require 'pact_broker/api/decorators/verifiable_pacts_decorator'
require 'pact_broker/api/contracts/verifiable_pacts_query_schema'
require 'pact_broker/api/decorators/verifiable_pacts_query_decorator'
require 'pact_broker/api/contracts/verifiable_pacts_json_query_schema'

module PactBroker
module Api
module Resources
class ProviderPactsForVerification < ProviderPacts
def initialize
super
@query = Rack::Utils.parse_nested_query(request.uri.query)
def allowed_methods
["GET", "POST", "OPTIONS"]
end

def content_types_accepted
[["application/json"]]
end

def malformed_request?
Expand All @@ -21,9 +25,12 @@ def malformed_request?
end
end

private
def process_post
response.body = to_json
true
end

attr_reader :query
private

def pacts
pact_service.find_for_verification(
Expand All @@ -43,12 +50,26 @@ def to_json


def query_schema
PactBroker::Api::Contracts::VerifiablePactsQuerySchema
if request.get?
PactBroker::Api::Contracts::VerifiablePactsQuerySchema
else
PactBroker::Api::Contracts::VerifiablePactsJSONQuerySchema
end
end

def parsed_query_params
@parsed_query_params ||= PactBroker::Api::Decorators::VerifiablePactsQueryDecorator.new(OpenStruct.new).from_hash(query)
end

def query
@query ||= begin
if request.get?
Rack::Utils.parse_nested_query(request.uri.query)
elsif request.post?
params_with_string_keys
end
end
end
end
end
end
Expand Down
48 changes: 48 additions & 0 deletions lib/pact_broker/hash_refinements.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,61 @@
require 'pact_broker/string_refinements'

module PactBroker
module HashRefinements

refine Hash do
using PactBroker::StringRefinements

def deep_merge(other_hash, &block)
block_actual = Proc.new {|key, oldval, newval|
newval = block.call(key, oldval, newval) if block_given?
[oldval, newval].all? {|v| v.is_a?(Hash)} ? oldval.merge(newval, &block_actual) : newval
}
merge(other_hash, &block_actual)
end

def symbolize_keys
symbolize_keys_private(self)
end

def snakecase_keys
snakecase_keys_private(self)
end

private

def snakecase_keys_private(params)
case params
when Hash
params.inject({}) do |result, (key, value)|
snake_key = case key
when String then key.snakecase
when Symbol then key.to_s.snakecase.to_sym
else
key
end
result.merge(snake_key => snakecase_keys_private(value))
end
when Array
params.collect { |value| snakecase_keys_private(value) }
else
params
end

end

def symbolize_keys_private(params)
case params
when Hash
params.inject({}) do |result, (key, value)|
result.merge(key.to_sym => symbolize_keys_private(value))
end
when Array
params.collect { |value| symbolize_keys_private(value) }
else
params
end
end
end
end
end
37 changes: 36 additions & 1 deletion lib/pact_broker/string_refinements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,41 @@ def not_blank?
def blank?
self.strip.size == 0
end

# ripped from rubyworks/facets, thank you
def snakecase
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
.tr('-', '_')
.gsub(/\s/, '_')
.gsub(/__+/, '_')
.downcase
end

# ripped from rubyworks/facets, thank you
def camelcase(*separators)
case separators.first
when Symbol, TrueClass, FalseClass, NilClass
first_letter = separators.shift
end

separators = ['_', '\s'] if separators.empty?

str = self.dup

separators.each do |s|
str = str.gsub(/(?:#{s}+)([a-z])/){ $1.upcase }
end

case first_letter
when :upper, true
str = str.gsub(/(\A|\s)([a-z])/){ $1 + $2.upcase }
when :lower, false
str = str.gsub(/(\A|\s)([A-Z])/){ $1 + $2.downcase }
end

str
end
end
end
end
end
Loading

0 comments on commit 5556b81

Please sign in to comment.