diff --git a/lib/pact_broker/api/contracts/dry_validation_predicates.rb b/lib/pact_broker/api/contracts/dry_validation_predicates.rb new file mode 100644 index 000000000..a9904a2c9 --- /dev/null +++ b/lib/pact_broker/api/contracts/dry_validation_predicates.rb @@ -0,0 +1,15 @@ +require 'dry-validation' + +module PactBroker + module Api + module Contracts + module DryValidationPredicates + include Dry::Logic::Predicates + + predicate(:date?) do |value| + DateTime.parse(value) rescue false + end + end + end + end +end diff --git a/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb b/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb index 46c8f27b6..0f19cfddd 100644 --- a/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb +++ b/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb @@ -1,6 +1,7 @@ require 'dry-validation' require 'pact_broker/hash_refinements' require 'pact_broker/api/contracts/dry_validation_workarounds' +require 'pact_broker/api/contracts/dry_validation_predicates' module PactBroker module Api @@ -10,6 +11,9 @@ class VerifiablePactsJSONQuerySchema using PactBroker::HashRefinements SCHEMA = Dry::Validation.Schema do + configure do + predicates(DryValidationPredicates) + end optional(:providerVersionTags).maybe(:array?) optional(:consumerVersionSelectors).each do schema do @@ -18,6 +22,7 @@ class VerifiablePactsJSONQuerySchema end end optional(:includePendingStatus).filled(included_in?: [true, false]) + optional(:includeWipPactsSince).filled(:date?) end def self.call(params) diff --git a/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb b/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb index a3b07918e..55e48823f 100644 --- a/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb +++ b/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb @@ -1,5 +1,6 @@ require 'dry-validation' require 'pact_broker/api/contracts/dry_validation_workarounds' +require 'pact_broker/api/contracts/dry_validation_predicates' module PactBroker module Api @@ -9,6 +10,9 @@ class VerifiablePactsQuerySchema using PactBroker::HashRefinements SCHEMA = Dry::Validation.Schema do + configure do + predicates(DryValidationPredicates) + end optional(:provider_version_tags).maybe(:array?) optional(:consumer_version_selectors).each do schema do @@ -17,6 +21,7 @@ class VerifiablePactsQuerySchema end end optional(:include_pending_status).filled(included_in?: ["true", "false"]) + optional(:include_wip_pacts_since).filled(:date?) end def self.call(params) diff --git a/lib/pact_broker/api/decorators/verifiable_pact_decorator.rb b/lib/pact_broker/api/decorators/verifiable_pact_decorator.rb index 79d416c79..f075620b2 100644 --- a/lib/pact_broker/api/decorators/verifiable_pact_decorator.rb +++ b/lib/pact_broker/api/decorators/verifiable_pact_decorator.rb @@ -21,11 +21,12 @@ def initialize(verifiable_pact) end property :verification_properties, as: :verificationProperties do - property :pending, - if: ->(context) { context[:options][:user_options][:include_pending_status] } - property :pending_reason, as: :pendingReason, exec_context: :decorator, - if: ->(context) { context[:options][:user_options][:include_pending_status] } - property :inclusion_reason, as: :inclusionReason, exec_context: :decorator + property :pending, + if: ->(context) { context[:options][:user_options][:include_pending_status] } + property :wip, if: -> (context) { context[:represented].wip } + property :inclusion_reason, as: :inclusionReason, exec_context: :decorator + property :pending_reason, as: :pendingReason, exec_context: :decorator, + if: ->(context) { context[:options][:user_options][:include_pending_status] } def inclusion_reason PactBroker::Pacts::VerifiablePactMessages.new(represented).inclusion_reason diff --git a/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator.rb b/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator.rb index fc3509492..b93415d69 100644 --- a/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator.rb +++ b/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator.rb @@ -24,6 +24,11 @@ class VerifiablePactsQueryDecorator < BaseDecorator represented.include_pending_status = (fragment == 'true' || fragment == true) } + property :include_wip_pacts_since, default: nil, + setter: ->(fragment:, represented:, **) { + represented.include_wip_pacts_since = fragment ? DateTime.parse(fragment) : nil + } + def from_hash(hash) # This handles both the snakecase keys from the GET query and the camelcase JSON POST body super(hash&.snakecase_keys) diff --git a/lib/pact_broker/api/resources/provider_pacts_for_verification.rb b/lib/pact_broker/api/resources/provider_pacts_for_verification.rb index b2f586007..d2298f09d 100644 --- a/lib/pact_broker/api/resources/provider_pacts_for_verification.rb +++ b/lib/pact_broker/api/resources/provider_pacts_for_verification.rb @@ -39,7 +39,8 @@ def pacts pact_service.find_for_verification( provider_name, parsed_query_params.provider_version_tags, - parsed_query_params.consumer_version_selectors + parsed_query_params.consumer_version_selectors, + { include_wip_pacts_since: parsed_query_params.include_wip_pacts_since } ) end diff --git a/lib/pact_broker/pacts/repository.rb b/lib/pact_broker/pacts/repository.rb index 2d95145e5..da901a748 100644 --- a/lib/pact_broker/pacts/repository.rb +++ b/lib/pact_broker/pacts/repository.rb @@ -125,29 +125,21 @@ def find_latest_pact_versions_for_provider provider_name, tag = nil end end - def find_wip_pact_versions_for_provider provider_name, provider_tags = [] + def find_wip_pact_versions_for_provider provider_name, provider_tags = [], options = {} return [] if provider_tags.empty? - successfully_verified_pact_publication_ids_for_each_tag = provider_tags.collect do | provider_tag | - ids = LatestTaggedPactPublications - .join(:verifications, { pact_version_id: :pact_version_id }) - .join(:tags, { Sequel[:verifications][:provider_version_id] => Sequel[:provider_tags][:version_id] }, {table_alias: :provider_tags}) - .where(Sequel[:provider_tags][:name] => provider_tag) - .provider(provider_name) - .where(Sequel[:verifications][:success] => true) - .select(Sequel[:latest_tagged_pact_publications][:id].as(:id)) - .collect(&:id) - [provider_tag, ids] - end - successfully_verified_pact_publication_ids_for_all_tags = successfully_verified_pact_publication_ids_for_each_tag.collect(&:last).reduce(:&) - pact_publication_ids = LatestTaggedPactPublications.provider(provider_name).exclude(id: successfully_verified_pact_publication_ids_for_all_tags).select_for_subquery(:id) + # Hash of provider tag names => list of pact_publication_ids + successfully_verified_head_pact_publication_ids_for_each_provider_tag = find_successfully_verified_head_pacts_by_provider_tag(provider_name, provider_tags, options) + + pact_publication_ids = find_head_pacts_that_have_not_been_successfully_verified_by_all_provider_tags( + provider_name, + successfully_verified_head_pact_publication_ids_for_each_provider_tag.values.reduce(:&), + options) pacts = AllPactPublications.where(id: pact_publication_ids).order_ignore_case(:consumer_name).order_append(:consumer_version_order).collect(&:to_domain) pacts.collect do | pact| - pending_tags = successfully_verified_pact_publication_ids_for_each_tag.select do | (provider_tag, pact_publication_ids) | - !pact_publication_ids.include?(pact.id) - end.collect(&:first) - VerifiablePact.new(pact, true, pending_tags, [], pact.consumer_version_tag_names) + pending_tags = find_provider_tags_for_which_pact_publication_id_is_pending(pact.id, successfully_verified_head_pact_publication_ids_for_each_provider_tag) + VerifiablePact.new(pact, true, pending_tags, [], pact.consumer_version_tag_names, nil, true) end end @@ -340,6 +332,37 @@ def find_all_database_versions_between(consumer_name, options, base_class = Late query = query.tag(options[:tag]) if options[:tag] query end + + def find_provider_tags_for_which_pact_publication_id_is_pending(pact_publication_id, successfully_verified_head_pact_publication_ids_for_each_provider_tag) + successfully_verified_head_pact_publication_ids_for_each_provider_tag + .select do | provider_tag, pact_publication_ids | + !pact_publication_ids.include?(pact_publication_id) + end.keys + end + + def find_head_pacts_that_have_not_been_successfully_verified_by_all_provider_tags(provider_name, pact_publication_ids_successfully_verified_by_all_provider_tags, options) + # Exclude the head pacts that have been successfully verified by all the specified provider tags + pact_publication_ids = LatestTaggedPactPublications + .provider(provider_name) + .exclude(id: pact_publication_ids_successfully_verified_by_all_provider_tags) + .where(Sequel.lit('latest_tagged_pact_publications.created_at > ?', options.fetch(:include_wip_pacts_since))) + .select_for_subquery(:id) + end + + def find_successfully_verified_head_pacts_by_provider_tag(provider_name, provider_tags, options) + provider_tags.compact.each_with_object({}) do | provider_tag, tag_to_ids_hash | + ids = LatestTaggedPactPublications + .join(:verifications, { pact_version_id: :pact_version_id }) + .join(:tags, { Sequel[:verifications][:provider_version_id] => Sequel[:provider_tags][:version_id] }, {table_alias: :provider_tags}) + .where(Sequel[:provider_tags][:name] => provider_tag) + .provider(provider_name) + .where(Sequel[:verifications][:success] => true) + .where(Sequel.lit('latest_tagged_pact_publications.created_at > ?', options.fetch(:include_wip_pacts_since))) + .select(Sequel[:latest_tagged_pact_publications][:id].as(:id)) + .collect(&:id) + tag_to_ids_hash[provider_tag] = ids + end + end end end end diff --git a/lib/pact_broker/pacts/service.rb b/lib/pact_broker/pacts/service.rb index c62329f8a..6279e5efd 100644 --- a/lib/pact_broker/pacts/service.rb +++ b/lib/pact_broker/pacts/service.rb @@ -115,18 +115,36 @@ def find_distinct_pacts_between consumer, options distinct end - def find_for_verification(provider_name, provider_version_tags, consumer_version_selectors) - pact_repository + def find_for_verification(provider_name, provider_version_tags, consumer_version_selectors, options) + verifiable_pacts_specified_in_request = pact_repository .find_for_verification(provider_name, consumer_version_selectors) .group_by(&:pact_version_sha) .values .collect do | head_pacts | squash_pacts_for_verification(provider_version_tags, head_pacts) end + + verifiable_wip_pacts = if options[:include_wip_pacts_since] + exclude_specified_pacts( + pact_repository.find_wip_pact_versions_for_provider(provider_name, provider_version_tags, options), + verifiable_pacts_specified_in_request) + else + [] + end + + verifiable_pacts_specified_in_request + verifiable_wip_pacts end private + def exclude_specified_pacts(wip_pacts, specified_pacts) + wip_pacts.select do | wip_pact | + !specified_pacts.any? do | specified_pacts | + wip_pact.pact_version_sha == specified_pacts.pact_version_sha + end + end + end + # Overwriting an existing pact with the same consumer/provider/consumer version number def update_pact params, existing_pact, webhook_options logger.info "Updating existing pact publication with params #{params.reject{ |k, v| k == :json_content}}" diff --git a/lib/pact_broker/pacts/verifiable_pact.rb b/lib/pact_broker/pacts/verifiable_pact.rb index bebc7f1a5..5145ed6d1 100644 --- a/lib/pact_broker/pacts/verifiable_pact.rb +++ b/lib/pact_broker/pacts/verifiable_pact.rb @@ -3,15 +3,17 @@ module PactBroker module Pacts class VerifiablePact < SimpleDelegator - attr_reader :pending, :pending_provider_tags, :non_pending_provider_tags, :head_consumer_tags + attr_reader :pending, :pending_provider_tags, :non_pending_provider_tags, :head_consumer_tags, :wip - def initialize(pact, pending, pending_provider_tags = [], non_pending_provider_tags = [], head_consumer_tags = [], overall_latest = false) + # TODO refactor this constructor + def initialize(pact, pending, pending_provider_tags = [], non_pending_provider_tags = [], head_consumer_tags = [], overall_latest = false, wip = false) super(pact) @pending = pending @pending_provider_tags = pending_provider_tags @non_pending_provider_tags = non_pending_provider_tags @head_consumer_tags = head_consumer_tags @overall_latest = overall_latest + @wip = wip end def consumer_tags @@ -25,6 +27,10 @@ def overall_latest? def pending? pending end + + def wip? + wip + end end end end diff --git a/lib/pact_broker/pacts/verifiable_pact_messages.rb b/lib/pact_broker/pacts/verifiable_pact_messages.rb index 8d595bddc..c3c9efffe 100644 --- a/lib/pact_broker/pacts/verifiable_pact_messages.rb +++ b/lib/pact_broker/pacts/verifiable_pact_messages.rb @@ -3,28 +3,34 @@ module Pacts class VerifiablePactMessages extend Forwardable - READ_MORE = "Read more at https://pact.io/pending" + READ_MORE_PENDING = "Read more at https://pact.io/pending" + READ_MORE_WIP = "Read more at https://pact.io/wip" - delegate [:consumer_name, :provider_name, :head_consumer_tags, :pending_provider_tags, :non_pending_provider_tags, :pending?] => :verifiable_pact + delegate [:consumer_name, :provider_name, :head_consumer_tags, :pending_provider_tags, :non_pending_provider_tags, :pending?, :wip?] => :verifiable_pact def initialize(verifiable_pact) @verifiable_pact = verifiable_pact end def inclusion_reason - if head_consumer_tags.any? - version_text = head_consumer_tags.size == 1 ? "version" : "versions" - "This pact is being verified because it is the pact for the latest #{version_text} of Foo tagged with #{joined_head_consumer_tags}" + version_text = head_consumer_tags.size == 1 ? "version" : "versions" + if wip? + # WIP pacts will always have tags, because it is part of the definition of being a WIP pact + "This pact is being verified because it is a 'work in progress' pact (ie. it is the pact for the latest #{version_text} of Foo tagged with #{joined_head_consumer_tags} and is still in pending state). #{READ_MORE_WIP}" else - "This pact is being verified because it is the latest pact between #{consumer_name} and #{provider_name}." + if head_consumer_tags.any? + "This pact is being verified because it is the pact for the latest #{version_text} of Foo tagged with #{joined_head_consumer_tags}" + else + "This pact is being verified because it is the latest pact between #{consumer_name} and #{provider_name}." + end end end def pending_reason if pending? - "This pact is in pending state because it has not yet been successfully verified by #{pending_provider_tags_description}. If this verification fails, it will not cause the overall build to fail. #{READ_MORE}" + "This pact is in pending state because it has not yet been successfully verified by #{pending_provider_tags_description}. If this verification fails, it will not cause the overall build to fail. #{READ_MORE_PENDING}" else - "This pact has previously been successfully verified by #{non_pending_provider_tags_description}. If this verification fails, it will fail the build. #{READ_MORE}" + "This pact has previously been successfully verified by #{non_pending_provider_tags_description}. If this verification fails, it will fail the build. #{READ_MORE_PENDING}" end end diff --git a/spec/features/wip_pacts_spec.rb b/spec/features/wip_pacts_spec.rb new file mode 100644 index 000000000..1c5265d4c --- /dev/null +++ b/spec/features/wip_pacts_spec.rb @@ -0,0 +1,138 @@ +RSpec.describe "the lifecycle of a WIP pact" do + let(:pact_content_1) { { interactions: [{ some: 'interaction'}] }.to_json } + let(:pact_content_2) { { interactions: [{ some: 'other interaction'}] }.to_json } + let(:request_headers) { { "CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/hal+json"} } + let(:provider_version_number) { "2" } + let(:failed_verification_results) do + { + providerApplicationVersion: provider_version_number, + success: false + }.to_json + end + let(:successful_verification_results) do + { + providerApplicationVersion: provider_version_number, + success: true + }.to_json + end + let(:pacts_for_verification_request_body) do + { + consumerVersionSelectors: [ { tag: "master", latest: true } ], + providerVersionTags: ["master"], + includeWipPactsSince: start_date + }.to_json + end + let(:start_date) { (Date.today - 1).to_datetime } + + def publish_pact_with_master_tag + put("/pacts/provider/Bar/consumer/Foo/version/1", pact_content_1, request_headers) + put("/pacticipants/Foo/versions/1/tags/master", nil, request_headers) + end + + def publish_pact_with_feature_tag + put("/pacts/provider/Bar/consumer/Foo/version/2", pact_content_2, request_headers) + put("/pacticipants/Foo/versions/2/tags/feat-x", nil, request_headers) + end + + def get_pacts_for_verification + post("/pacts/provider/Bar/for-verification", pacts_for_verification_request_body, request_headers) + end + + def wip_pact_url_from(pacts_for_verification_response) + wip_pacts_from(pacts_for_verification_response).first["_links"]["self"]["href"] + end + + def get_pact(pact_url) + get pact_url, nil, request_headers + end + + def verification_results_url_from(pact_response) + JSON.parse(pact_response.body)["_links"]["pb:publish-verification-results"]["href"] + end + + def publish_verification_results_with_tag_master(verification_results_url, results) + post(verification_results_url, results, request_headers) + put("/pacticipants/Bar/versions/#{provider_version_number}/tags/master", nil, request_headers) + end + + def pending_status_from(pacts_for_verification_response) + JSON.parse(pacts_for_verification_response.body)["_embedded"]["pacts"][0]["verificationProperties"]["pending"] + end + + def wip_pacts_from(pacts_for_verification_response) + JSON.parse(pacts_for_verification_response.body)["_embedded"]["pacts"].select do | pact | + pact["verificationProperties"]["wip"] + end + end + + context "when the includeWipPactsSince date is specified" do + context "a pact published afer the specified date, with a tag that is not specified explictly in the 'pacts for verification' request" do + describe "when it is first published" do + it "is included in the list of pacts to verify as a WIP pact" do + publish_pact_with_master_tag + publish_pact_with_feature_tag + + pacts_for_verification_response = get_pacts_for_verification + expect(wip_pacts_from(pacts_for_verification_response).size).to eq 1 + end + end + + describe "when it is verified unsuccessfully" do + it "is still included as a WIP pact" do + # CONSUMER BUILD + # publish pact + publish_pact_with_master_tag + publish_pact_with_feature_tag + + # PROVIDER BUILD + # fetch pacts to verify + pacts_for_verification_response = get_pacts_for_verification + pact_url = wip_pact_url_from(pacts_for_verification_response) + pact_response = get_pact(pact_url) + + # verify pact... failure... + + # publish failure verification results + verification_results_url = verification_results_url_from(pact_response) + publish_verification_results_with_tag_master(verification_results_url, failed_verification_results) + + # ANOTHER PROVIDER BUILD + # get pacts for verification + pacts_for_verification_response = get_pacts_for_verification + # still pending + expect(wip_pacts_from(pacts_for_verification_response).size).to eq 1 + end + end + + describe "when it is verified successfully" do + it "is no longer included in the list of pacts to verify" do + # CONSUMER BUILD + publish_pact_with_master_tag + publish_pact_with_feature_tag + + # PROVIDER BUILD + # fetch pacts to verify + pacts_for_verification_response = get_pacts_for_verification + pact_url = wip_pact_url_from(pacts_for_verification_response) + pact_response = get_pact(pact_url) + + # verify pact... success! + + # publish failure verification results + verification_results_url = verification_results_url_from(pact_response) + publish_verification_results_with_tag_master(verification_results_url, successful_verification_results) + + + # ANOTHER PROVIDER BUILD 2 + # get pacts for verification + # publish successful verification results + pacts_for_verification_response = get_pacts_for_verification + # not wip any more + expect(wip_pacts_from(pacts_for_verification_response).size).to eq 0 + end + end + + end + + end +end diff --git a/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb b/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb index c867554cc..4aa61002f 100644 --- a/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb +++ b/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb @@ -61,6 +61,31 @@ module Contracts expect(subject).to eq({}) end end + + context "when includeWipPactsSince key exists" do + let(:include_wip_pacts_since) { nil } + let(:params) do + { + includeWipPactsSince: include_wip_pacts_since + } + end + + context "when it is nil" do + it { is_expected.to have_key(:includeWipPactsSince) } + end + + context "when it is not a date" do + let(:include_wip_pacts_since) { "foo" } + + it { is_expected.to have_key(:includeWipPactsSince) } + end + + context "when it is a valid date" do + let(:include_wip_pacts_since) { "2013-02-13T20:04:45.000+11:00" } + + it { is_expected.to_not have_key(:includeWipPactsSince) } + end + end end end end diff --git a/spec/lib/pact_broker/api/contracts/verifiable_pacts_query_schema_spec.rb b/spec/lib/pact_broker/api/contracts/verifiable_pacts_query_schema_spec.rb index 1f2eb2240..ac24119f2 100644 --- a/spec/lib/pact_broker/api/contracts/verifiable_pacts_query_schema_spec.rb +++ b/spec/lib/pact_broker/api/contracts/verifiable_pacts_query_schema_spec.rb @@ -44,7 +44,7 @@ module Contracts end end - context "whne the consumer_version_selectors is missing the latest" do + context "when the consumer_version_selectors is missing the latest" do let(:consumer_version_selectors) do [{ tag: "master" @@ -55,6 +55,31 @@ module Contracts expect(subject).to eq({}) end end + + context "when include_wip_pacts_since key exists" do + let(:include_wip_pacts_since) { nil } + let(:params) do + { + include_wip_pacts_since: include_wip_pacts_since + } + end + + context "when it is nil" do + it { is_expected.to have_key(:include_wip_pacts_since) } + end + + context "when it is not a date" do + let(:include_wip_pacts_since) { "foo" } + + it { is_expected.to have_key(:include_wip_pacts_since) } + end + + context "when it is a valid date" do + let(:include_wip_pacts_since) { "2013-02-13T20:04:45.000+11:00" } + + it { is_expected.to_not have_key(:include_wip_pacts_since) } + end + end end end end diff --git a/spec/lib/pact_broker/api/decorators/verifiable_pact_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/verifiable_pact_decorator_spec.rb index 9560371f2..ee4f469f6 100644 --- a/spec/lib/pact_broker/api/decorators/verifiable_pact_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/verifiable_pact_decorator_spec.rb @@ -29,6 +29,7 @@ module Decorators let(:pact) do double('pact', pending: true, + wip: wip, name: "name", provider_name: "Bar", pending_provider_tags: pending_provider_tags, @@ -39,6 +40,7 @@ module Decorators let(:json) { decorator.to_json(options) } let(:options) { { user_options: { base_url: 'http://example.org', include_pending_status: include_pending_status } } } let(:include_pending_status) { true } + let(:wip){ false } subject { JSON.parse(json) } @@ -62,6 +64,14 @@ module Decorators expect(subject['verificationProperties']).to_not have_key('pendingReason') end end + + context "when wip is true" do + let(:wip) { true } + + it "includes the wip flag" do + expect(subject['verificationProperties']['wip']).to be true + end + end end end end diff --git a/spec/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator_spec.rb index 387706cac..1cc13f6fa 100644 --- a/spec/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator_spec.rb @@ -72,6 +72,18 @@ module Decorators expect(subject.consumer_version_selectors.first.latest).to be true end end + + context "when specifying include_wip_pacts_since" do + let(:params) do + { + "include_wip_pacts_since" => "2013-02-13T20:04:45.000+11:00" + } + end + + it "parses the date" do + expect(subject.include_wip_pacts_since).to eq DateTime.parse("2013-02-13T20:04:45.000+11:00") + end + end end end end diff --git a/spec/lib/pact_broker/api/resources/provider_pacts_for_verification_spec.rb b/spec/lib/pact_broker/api/resources/provider_pacts_for_verification_spec.rb index 7829cfba5..7e463bc3d 100644 --- a/spec/lib/pact_broker/api/resources/provider_pacts_for_verification_spec.rb +++ b/spec/lib/pact_broker/api/resources/provider_pacts_for_verification_spec.rb @@ -17,8 +17,9 @@ module Resources let(:query) do { provider_version_tags: ['master'], - consumer_version_selectors: [ { tag: "dev", latest: "true" }], - include_pending_status: false + consumer_version_selectors: [ { tag: 'dev', latest: 'true' }], + include_pending_status: false, + include_wip_pacts_since: '2018-01-01' } end @@ -30,7 +31,8 @@ module Resources expect(PactBroker::Pacts::Service).to receive(:find_for_verification).with( "Bar", ["master"], - [OpenStruct.new(tag: "dev", latest: true)]) + [OpenStruct.new(tag: "dev", latest: true)], + { include_wip_pacts_since: DateTime.parse('2018-01-01') }) subject end @@ -51,8 +53,9 @@ module Resources let(:request_body) do { providerVersionTags: ['master'], - consumerVersionSelectors: [ { tag: "dev", latest: true }], - includePendingStatus: true + consumerVersionSelectors: [ { tag: 'dev', latest: true }], + includePendingStatus: false, + includeWipPactsSince: '2018-01-01' } end @@ -70,14 +73,15 @@ module Resources expect(PactBroker::Pacts::Service).to receive(:find_for_verification).with( "Bar", ["master"], - [OpenStruct.new(tag: "dev", latest: true)]) + [OpenStruct.new(tag: "dev", latest: true)], + { include_wip_pacts_since: DateTime.parse('2018-01-01') }) subject end context "when there are validation errors" do let(:request_body) do { - providerVersionTags: true, + providerVersionTags: true } end diff --git a/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb b/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb index da17a7852..83c137332 100644 --- a/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb +++ b/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb @@ -6,9 +6,11 @@ module Pacts let(:td) { TestDataBuilder.new } describe "find_wip_pact_versions_for_provider" do - let(:provider_tags) { %w[dev] } - subject { Repository.new.find_wip_pact_versions_for_provider("bar", provider_tags) } + let(:options) { { include_wip_pacts_since: include_wip_pacts_since } } + let(:include_wip_pacts_since) { (Date.today - 1).to_datetime } + + subject { Repository.new.find_wip_pact_versions_for_provider("bar", provider_tags, options) } context "when there are no tags" do let(:provider_tags) { [] } @@ -118,6 +120,19 @@ module Pacts expect(subject.size).to be 0 end end + + context "when the pact was published before the specified include_wip_pacts_since" do + before do + td.create_pact_with_hierarchy("foo", "1", "bar") + .create_consumer_version_tag("prod") + end + + let(:include_wip_pacts_since) { (Date.today + 3).to_datetime } + + it "is not included" do + expect(subject.size).to be 0 + end + end end end end diff --git a/spec/lib/pact_broker/pacts/service_spec.rb b/spec/lib/pact_broker/pacts/service_spec.rb index 3ab668999..7dc622c7b 100644 --- a/spec/lib/pact_broker/pacts/service_spec.rb +++ b/spec/lib/pact_broker/pacts/service_spec.rb @@ -155,6 +155,62 @@ module Pacts }.by(-1) end end + + describe "find_for_verification integration test" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_consumer_version_tag("feat-1", comment: "latest for feat-1") + .create_consumer_version("2") + .create_pact(comment: "overall latest") + .create_consumer_version("3") + end + + let(:options) { { } } + + subject { Service.find_for_verification("Bar", ["master"], [], options) } + + + context "when the consumer version tags are empty" do + it "returns the latest overall pact for the consumer" do + expect(subject.first.consumer_version_number).to eq "2" + expect(subject.first.wip).to be false + end + end + + context "when include_wip_pacts_since is not specified" do + it "does not include the WIP pacts" do + expect(subject.size).to eq 1 + end + end + + context "when WIP pacts are included" do + let(:options) do + { + include_wip_pacts_since: (Date.today - 1).to_datetime + } + end + it "returns the WIP pacts as well as the specified pacts" do + expect(subject.size).to eq 2 + expect(subject.last.consumer_version_number).to eq "1" + expect(subject.last.wip).to be true + end + + context "when the WIP pact has the same content as a specified pact" do + before do + td.create_pact_with_hierarchy("Blah", "1", "Wiffle") + .create_consumer_version_tag("feat-1", comment: "latest for feat-1") + .create_consumer_version("2") + .republish_same_pact(comment: "overall latest") + end + + subject { Service.find_for_verification("Wiffle", ["master"], [], options) } + + it "is not included" do + expect(subject.size).to eq 1 + end + end + end + end end end end diff --git a/spec/lib/pact_broker/pacts/verifiable_pact_messages_spec.rb b/spec/lib/pact_broker/pacts/verifiable_pact_messages_spec.rb index d5382cb13..c63c84740 100644 --- a/spec/lib/pact_broker/pacts/verifiable_pact_messages_spec.rb +++ b/spec/lib/pact_broker/pacts/verifiable_pact_messages_spec.rb @@ -8,6 +8,7 @@ module Pacts let(:pending_provider_tags) { [] } let(:non_pending_provider_tags) { [] } let(:pending) { false } + let(:wip) { false } let(:verifiable_pact) do double(VerifiablePact, head_consumer_tags: head_consumer_tags, @@ -15,7 +16,8 @@ module Pacts provider_name: "Bar", pending_provider_tags: pending_provider_tags, non_pending_provider_tags: non_pending_provider_tags, - pending?: pending + pending?: pending, + wip?: wip ) end @@ -45,6 +47,15 @@ module Pacts let(:head_consumer_tags) { %w[dev prod feat-x feat-y] } its(:inclusion_reason) { is_expected.to include "'dev', 'prod', 'feat-x' and 'feat-y'" } end + + context "when the pact is a WIP pact" do + let(:wip) { true } + let(:pending) { true } + let(:head_consumer_tags) { %w[feat-x] } + let(:pending_provider_tags) { %w[dev] } + + its(:inclusion_reason) { is_expected.to include "This pact is being verified because it is a 'work in progress' pact (ie. it is the pact for the latest version of Foo tagged with 'feat-x' and is still in pending state)."} + end end describe "#pending_reason" do