pact_broker 1.1.0 → 1.2.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/README.md +8 -11
  4. data/lib/pact_broker/api.rb +2 -0
  5. data/lib/pact_broker/api/decorators/decorator_context.rb +8 -1
  6. data/lib/pact_broker/api/decorators/pact_decorator.rb +18 -3
  7. data/lib/pact_broker/api/decorators/pact_version_decorator.rb +39 -0
  8. data/lib/pact_broker/api/decorators/pact_versions_decorator.rb +48 -0
  9. data/lib/pact_broker/api/decorators/pacticipant_decorator.rb +3 -3
  10. data/lib/pact_broker/api/decorators/webhook_decorator.rb +1 -1
  11. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +1 -1
  12. data/lib/pact_broker/api/decorators/webhooks_decorator.rb +1 -1
  13. data/lib/pact_broker/api/pact_broker_urls.rb +16 -6
  14. data/lib/pact_broker/api/renderers/html_pact_renderer.rb +35 -6
  15. data/lib/pact_broker/api/resources/base_resource.rb +80 -33
  16. data/lib/pact_broker/api/resources/group.rb +26 -24
  17. data/lib/pact_broker/api/resources/index.rb +52 -51
  18. data/lib/pact_broker/api/resources/latest_pact.rb +22 -22
  19. data/lib/pact_broker/api/resources/latest_pacts.rb +18 -17
  20. data/lib/pact_broker/api/resources/pact.rb +34 -39
  21. data/lib/pact_broker/api/resources/pact_versions.rb +35 -0
  22. data/lib/pact_broker/api/resources/pact_webhooks.rb +54 -61
  23. data/lib/pact_broker/api/resources/pacticipant.rb +40 -39
  24. data/lib/pact_broker/api/resources/pacticipant_resource_methods.rb +19 -0
  25. data/lib/pact_broker/api/resources/pacticipants.rb +52 -17
  26. data/lib/pact_broker/api/resources/relationships.rb +18 -17
  27. data/lib/pact_broker/api/resources/tag.rb +30 -29
  28. data/lib/pact_broker/api/resources/webhook.rb +29 -28
  29. data/lib/pact_broker/api/resources/webhook_execution.rb +0 -1
  30. data/lib/pact_broker/api/resources/webhook_resource_methods.rb +24 -0
  31. data/lib/pact_broker/api/resources/webhooks.rb +18 -17
  32. data/lib/pact_broker/app.rb +1 -0
  33. data/lib/pact_broker/configuration.rb +2 -2
  34. data/lib/pact_broker/doc/views/webhooks.markdown +1 -1
  35. data/lib/pact_broker/functions/find_potential_duplicate_pacticipant_names.rb +43 -0
  36. data/lib/pact_broker/locale/en.yml +7 -0
  37. data/lib/pact_broker/messages.rb +20 -1
  38. data/lib/pact_broker/models/pact.rb +8 -0
  39. data/lib/pact_broker/models/pacticipant.rb +9 -0
  40. data/lib/pact_broker/models/version.rb +1 -0
  41. data/lib/pact_broker/repositories/pact_repository.rb +6 -0
  42. data/lib/pact_broker/repositories/pacticipant_repository.rb +4 -0
  43. data/lib/pact_broker/repositories/webhook_repository.rb +2 -3
  44. data/lib/pact_broker/services/pact_service.rb +20 -0
  45. data/lib/pact_broker/services/pacticipant_service.rb +28 -0
  46. data/lib/pact_broker/services/webhook_service.rb +7 -2
  47. data/lib/pact_broker/version.rb +1 -1
  48. data/pact_broker.gemspec +1 -1
  49. data/public/stylesheets/github.css +1 -1
  50. data/public/stylesheets/pact.css +12 -0
  51. data/spec/lib/pact_broker/api/decorators/pact_decorator_spec.rb +10 -0
  52. data/spec/lib/pact_broker/api/decorators/pact_version_decorator_spec.rb +49 -0
  53. data/spec/lib/pact_broker/api/renderers/html_pact_renderer_spec.rb +9 -1
  54. data/spec/lib/pact_broker/api/resources/group_spec.rb +1 -1
  55. data/spec/lib/pact_broker/api/resources/latest_pact_spec.rb +1 -1
  56. data/spec/lib/pact_broker/api/resources/pact_spec.rb +35 -6
  57. data/spec/lib/pact_broker/api/resources/pact_webhooks_spec.rb +6 -4
  58. data/spec/lib/pact_broker/api/resources/pacticipants_spec.rb +91 -0
  59. data/spec/lib/pact_broker/configuration_spec.rb +3 -3
  60. data/spec/lib/pact_broker/functions/find_potential_duplicate_pacticipant_names_spec.rb +82 -0
  61. data/spec/lib/pact_broker/messages_spec.rb +31 -0
  62. data/spec/lib/pact_broker/models/pacticipant_spec.rb +32 -0
  63. data/spec/lib/pact_broker/repositories/pact_repository_spec.rb +29 -0
  64. data/spec/lib/pact_broker/repositories/pacticipant_repository_spec.rb +29 -0
  65. data/spec/lib/pact_broker/repositories/webhook_repository_spec.rb +7 -18
  66. data/spec/lib/pact_broker/services/pact_service_spec.rb +20 -0
  67. data/spec/lib/pact_broker/services/pacticipant_service_spec.rb +87 -2
  68. data/spec/support/provider_state_builder.rb +7 -7
  69. data/tasks/rspec.rake +1 -1
  70. metadata +27 -2
@@ -29,6 +29,7 @@ module PactBroker
29
29
  def post_configure
30
30
  PactBroker.logger = configuration.logger
31
31
  PactBroker::DB.connection = configuration.database_connection
32
+ PactBroker::DB.connection.timezone = :utc
32
33
 
33
34
  if configuration.auto_migrate_db
34
35
  logger.info "Migrating database"
@@ -23,9 +23,9 @@ module PactBroker
23
23
  end
24
24
 
25
25
  def self.default_html_pact_render
26
- lambda { |json_content|
26
+ lambda { |pact|
27
27
  require 'pact_broker/api/renderers/html_pact_renderer'
28
- PactBroker::Api::Renderers::HtmlPactRenderer.call json_content
28
+ PactBroker::Api::Renderers::HtmlPactRenderer.call pact
29
29
  }
30
30
  end
31
31
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ### Creating
4
4
 
5
- 1. To create a webhook, navigate to the pact you want to create the webhook for
5
+ 1. To create a webhook, in the HAL Browser, navigate to the pact you want to create the webhook for
6
6
  (Click "Go to Entry Point", then select "latest-pacts", then select the pact you want to create the webhook for.)
7
7
  2. Click the "NON-GET" button for the "pact-webhooks" relation.
8
8
  3. Paste in the webhook JSON (example shown below) in the body section and click "Make Request".
@@ -0,0 +1,43 @@
1
+ require 'pact_broker/models/group'
2
+
3
+ =begin
4
+
5
+ =end
6
+
7
+ module PactBroker
8
+
9
+ module Functions
10
+
11
+ class FindPotentialDuplicatePacticipantNames
12
+
13
+ attr_reader :new_name, :existing_names
14
+
15
+ def initialize new_name, existing_names
16
+ @new_name = new_name
17
+ @existing_names = existing_names
18
+ end
19
+
20
+ def self.call new_name, existing_names
21
+ new(new_name, existing_names).call
22
+ end
23
+
24
+ def call
25
+ return [] if existing_names.include?(new_name)
26
+
27
+ existing_names.select do | existing_name |
28
+ similar?(clean(new_name), clean(existing_name))
29
+ end
30
+ end
31
+
32
+ def similar?(new_name, existing_name)
33
+ existing_name.include?(new_name) || new_name.include?(existing_name)
34
+ end
35
+
36
+ def clean name #TODO uppercase S
37
+ name.gsub(/s\b/,'').gsub(/s([A-Z])/,'\1').gsub(/[^A-Za-z0-9]/,'').downcase
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -5,6 +5,13 @@ en:
5
5
  attribute_missing: "Missing required attribute '%{attribute}'"
6
6
  invalid_http_method: "Invalid HTTP method '%{method}'"
7
7
  invalid_url: "Invalid URL '%{url}'. Expected format: http://example.org"
8
+ duplicate_pacticipant: |
9
+ This is the first time a pact has been published for "%{new_name}".
10
+ The name "%{new_name}" is very similar to the following existing consumers/providers:
11
+ %{existing_names}
12
+ If you meant to specify one of the above names, please correct the pact configuration, and re-publish the pact.
13
+ If the pact is intended to be for a new consumer or provider, please manually create "%{new_name}" using the following command, and then re-publish the pact:
14
+ $ curl -v -XPOST -H "Content-Type: application/json" -d "{\"name\": \"%{new_name}\"}" %{create_pacticipant_url}
8
15
  "400":
9
16
  title: 400 Malformed Request
10
17
  message: The request was malformed and could not be processed.
@@ -1,11 +1,15 @@
1
1
  require 'i18n'
2
+ require 'pact_broker/api/pact_broker_urls'
2
3
 
3
4
  I18n.config.load_path << File.expand_path("../locale/en.yml", __FILE__)
4
5
 
5
6
  module PactBroker
6
7
  # Provides an interface to the I18n library specifically for
7
- # {Webmachine}'s messages.
8
+ # the PactBroker's messages.
8
9
  module Messages
10
+
11
+ extend self
12
+
9
13
  # Interpolates an internationalized string.
10
14
  # @param [String] key the name of the string to interpolate
11
15
  # @param [Hash] options options to pass to I18n, including
@@ -14,5 +18,20 @@ module PactBroker
14
18
  def message(key, options={})
15
19
  ::I18n.t(key, options.merge(:scope => :pact_broker))
16
20
  end
21
+
22
+ def potential_duplicate_pacticipant_message new_name, potential_duplicate_pacticipants, base_url
23
+ existing_names = potential_duplicate_pacticipants.
24
+ collect{ | p | "* #{p.name}" }.join("\n")
25
+ message('errors.duplicate_pacticipant',
26
+ new_name: new_name,
27
+ existing_names: existing_names,
28
+ create_pacticipant_url: pacticipants_url(base_url))
29
+ end
30
+
31
+ private
32
+
33
+ def pacticipants_url base_url
34
+ PactBroker::Api::PactBrokerUrls.pacticipants_url base_url
35
+ end
17
36
  end
18
37
  end
@@ -25,6 +25,14 @@ module PactBroker
25
25
  def to_json options = {}
26
26
  json_content
27
27
  end
28
+
29
+ def name
30
+ "Pact between #{consumer.name} (v#{consumer_version_number}) and #{provider.name}"
31
+ end
32
+
33
+ def version_and_updated_date
34
+ "Version #{consumer_version_number} - #{updated_at.to_time.localtime.strftime("%d/%m/%Y")}"
35
+ end
28
36
  end
29
37
 
30
38
  Pact.plugin :timestamps, :update_on_create=>true
@@ -1,4 +1,5 @@
1
1
  require 'pact_broker/db'
2
+ require 'pact_broker/messages'
2
3
 
3
4
  module PactBroker
4
5
 
@@ -6,6 +7,8 @@ module PactBroker
6
7
 
7
8
  class Pacticipant < Sequel::Model
8
9
 
10
+ include Messages
11
+
9
12
  set_primary_key :id
10
13
 
11
14
  one_to_many :versions, :order => :order, :reciprocal => :pacticipant
@@ -18,6 +21,12 @@ module PactBroker
18
21
  def to_s
19
22
  "Pacticipant: id=#{id}, name=#{name}"
20
23
  end
24
+
25
+ def validate
26
+ messages = []
27
+ messages << message('errors.validation.attribute_missing', attribute: 'name') unless name
28
+ messages
29
+ end
21
30
  end
22
31
 
23
32
  Pacticipant.plugin :timestamps, :update_on_create=>true
@@ -10,6 +10,7 @@ module PactBroker
10
10
  set_primary_key :id
11
11
  one_to_many :pacts
12
12
  associate(:many_to_one, :pacticipant, :class => "PactBroker::Models::Pacticipant", :key => :pacticipant_id, :primary_key => :id)
13
+ one_to_many :tags, :reciprocal => :version
13
14
 
14
15
  def after_create
15
16
  OrderVersions.(self.pacticipant_id)
@@ -8,6 +8,12 @@ module PactBroker
8
8
 
9
9
  include PactBroker::Logging
10
10
 
11
+ def find_all_pacts_between consumer_name, options
12
+ pact_finder(consumer_name, options.fetch(:and))
13
+ .left_outer_join(:tags, {:version_id => :id}, {implicit_qualifier: :versions})
14
+ .reverse_order(:order).all
15
+ end
16
+
11
17
  def find_by_version_and_provider version_id, provider_id
12
18
  PactBroker::Models::Pact.where(version_id: version_id, provider_id: provider_id).single_record
13
19
  end
@@ -29,6 +29,10 @@ module PactBroker
29
29
  PactBroker::Models::Pacticipant.new(name: args[:name], repository_url: args[:repository_url]).save(raise_on_save_failure: true)
30
30
  end
31
31
 
32
+ def pacticipant_names
33
+ PactBroker::Models::Pacticipant.select(:name).order(:name).collect{ | pacticipant| pacticipant.name }
34
+ end
35
+
32
36
  def find_latest_version name
33
37
 
34
38
  end
@@ -2,7 +2,6 @@ require 'sequel'
2
2
  require 'pact_broker/models/webhook'
3
3
  require 'pact_broker/models/pacticipant'
4
4
  require 'pact_broker/db'
5
- require 'base64'
6
5
 
7
6
  module PactBroker
8
7
  module Repositories
@@ -14,9 +13,9 @@ module PactBroker
14
13
 
15
14
  include Repositories
16
15
 
17
- def create webhook, consumer, provider
16
+ def create uuid, webhook, consumer, provider
18
17
  db_webhook = Webhook.from_model webhook, consumer, provider
19
- db_webhook.uuid = SecureRandom.urlsafe_base64
18
+ db_webhook.uuid = uuid
20
19
  db_webhook.save
21
20
  webhook.request.headers.each_pair do | name, value |
22
21
  db_webhook.add_header WebhookHeader.from_model(name, value, db_webhook.id)
@@ -36,6 +36,26 @@ module PactBroker
36
36
 
37
37
  end
38
38
 
39
+ def find_all_pacts_between consumer, options
40
+ pact_repository.find_all_pacts_between consumer, options
41
+ end
42
+
43
+ def find_distinct_pacts_between consumer, options
44
+ # Assumes pacts are sorted from newest to oldest
45
+ all = pact_repository.find_all_pacts_between consumer, options
46
+ distinct = []
47
+ (0...all.size).each do | i |
48
+ if i == all.size - 1
49
+ distinct << all[i]
50
+ else
51
+ if all[i].json_content != all[i+1].json_content
52
+ distinct << all[i]
53
+ end
54
+ end
55
+ end
56
+ distinct
57
+ end
58
+
39
59
  def pact_has_changed_since_previous_version? pact
40
60
  previous_pact = pact_repository.find_previous_pact pact
41
61
  previous_pact && pact.json_content != previous_pact.json_content
@@ -1,5 +1,8 @@
1
1
  require 'pact_broker/repositories'
2
+ require 'pact_broker/logging'
3
+ require 'pact_broker/messages'
2
4
  require 'pact_broker/models/relationship'
5
+ require 'pact_broker/functions/find_potential_duplicate_pacticipant_names'
3
6
 
4
7
  module PactBroker
5
8
 
@@ -8,6 +11,27 @@ module PactBroker
8
11
 
9
12
  extend PactBroker::Repositories
10
13
  extend PactBroker::Services
14
+ extend PactBroker::Logging
15
+
16
+ def self.messages_for_potential_duplicate_pacticipants pacticipant_names, base_url
17
+ messages = []
18
+ pacticipant_names.each do | name |
19
+ potential_duplicate_pacticipants = find_potential_duplicate_pacticipants(name)
20
+ if potential_duplicate_pacticipants.any?
21
+ messages << Messages.potential_duplicate_pacticipant_message(name, potential_duplicate_pacticipants, base_url)
22
+ end
23
+ end
24
+ messages
25
+ end
26
+
27
+ def self.find_potential_duplicate_pacticipants pacticipant_name
28
+ PactBroker::Functions::FindPotentialDuplicatePacticipantNames
29
+ .call(pacticipant_name, pacticipant_names).tap { | names|
30
+ if names.any?
31
+ logger.info "The following potential duplicate pacticipants were found for #{pacticipant_name}: #{names.join(", ")}"
32
+ end
33
+ } .collect{ | name | pacticipant_repository.find_by_name(name) }
34
+ end
11
35
 
12
36
  def self.find_all_pacticipants
13
37
  pacticipant_repository.find_all
@@ -51,6 +75,10 @@ module PactBroker
51
75
  connection.run("delete from pacticipants where name = '#{name}'")
52
76
  end
53
77
 
78
+ def self.pacticipant_names
79
+ pacticipant_repository.pacticipant_names
80
+ end
81
+
54
82
  end
55
83
  end
56
84
  end
@@ -1,5 +1,6 @@
1
1
  require 'pact_broker/repositories'
2
2
  require 'pact_broker/logging'
3
+ require 'base64'
3
4
 
4
5
  module PactBroker
5
6
 
@@ -9,8 +10,12 @@ module PactBroker
9
10
  extend Repositories
10
11
  include Logging
11
12
 
12
- def self.create webhook, consumer, provider
13
- webhook_repository.create webhook, consumer, provider
13
+ def self.next_uuid
14
+ SecureRandom.urlsafe_base64
15
+ end
16
+
17
+ def self.create uuid, webhook, consumer, provider
18
+ webhook_repository.create uuid, webhook, consumer, provider
14
19
  end
15
20
 
16
21
  def self.find_by_uuid uuid
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
data/pact_broker.gemspec CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |gem|
28
28
  gem.add_runtime_dependency 'versionomy'
29
29
  gem.add_runtime_dependency 'rack'
30
30
  gem.add_runtime_dependency 'redcarpet', '~>3.1'
31
- gem.add_runtime_dependency 'pact', '~>1.3'
31
+ gem.add_runtime_dependency 'pact', '~>1.3', '>=1.3.2'
32
32
  gem.add_runtime_dependency 'padrino', '~>0.12'
33
33
  gem.add_runtime_dependency 'haml'
34
34
 
@@ -43,7 +43,7 @@ h1, h2, h3, h4, h5, h6 {
43
43
  font-weight: bold;
44
44
  -webkit-font-smoothing: antialiased;
45
45
  cursor: text;
46
- position: relative;
46
+ /* position: relative; Beth: this makes the View In HAL Browser link unclickable */
47
47
  }
48
48
 
49
49
  h2:first-child, h1:first-child, h1:first-child + h2, h3:first-child, h4:first-child, h5:first-child, h6:first-child {
@@ -0,0 +1,12 @@
1
+ div.pact-metadata {
2
+ float: right;
3
+ }
4
+
5
+ div.pact-metadata li {
6
+ list-style-type: none ;
7
+ text-align: right
8
+ }
9
+
10
+ div.pact-metadata span.name {
11
+ font-weight: bold ;
12
+ }
@@ -41,6 +41,16 @@ module PactBroker
41
41
  it "includes a link to the webhooks for this pact" do
42
42
  expect(subject[:_links][:'pact-webhooks'][:href]).to eq "http://example.org/webhooks/provider/Provider/consumer/Consumer"
43
43
  end
44
+
45
+ it "includes a link to the latest pact" do
46
+ expect(subject[:_links][:'latest-pact'][:title]).to eq "Latest version of the pact between Consumer and Provider"
47
+ expect(subject[:_links][:'latest-pact'][:href]).to eq "http://example.org/pacts/provider/Provider/consumer/Consumer/latest"
48
+ end
49
+
50
+ it "includes a link to the pact versions" do
51
+ expect(subject[:_links][:'pact-versions'][:title]).to eq "All versions of the pact between Consumer and Provider"
52
+ expect(subject[:_links][:'pact-versions'][:href]).to eq "http://example.org/pacts/provider/Provider/consumer/Consumer/versions"
53
+ end
44
54
  end
45
55
 
46
56
  end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'pact_broker/api/decorators/pact_version_decorator'
3
+
4
+ module PactBroker
5
+
6
+ module Api
7
+
8
+ module Decorators
9
+
10
+ describe PactVersionDecorator do
11
+
12
+ let(:json_content) {
13
+ {
14
+ 'consumer' => {'name' => 'Consumer'},
15
+ 'provider' => {'name' => 'Provider'},
16
+ 'interactions' => [],
17
+ 'metadata' => {}
18
+ }.to_json
19
+ }
20
+
21
+ let(:base_url) { 'http://example.org' }
22
+ let(:created_at) { Time.new(2014, 3, 4) }
23
+ let(:updated_at) { Time.new(2014, 3, 5) }
24
+ let(:pact) { double('pact', json_content: json_content, created_at: created_at, updated_at: updated_at, consumer: consumer, provider: provider, consumer_version: consumer_version, name: 'pact_name')}
25
+ let(:consumer) { instance_double(PactBroker::Models::Pacticipant, name: 'Consumer')}
26
+ let(:provider) { instance_double(PactBroker::Models::Pacticipant, name: 'Provider')}
27
+ let(:consumer_version) { instance_double(PactBroker::Models::Version, number: '1234', pacticipant: consumer)}
28
+ let(:decorator_context) { DecoratorContext.new(base_url, '') }
29
+
30
+ let(:json) { PactVersionDecorator.new(pact).to_json(decorator_context) }
31
+
32
+ subject { JSON.parse(json, symbolize_names: true) }
33
+
34
+ it "includes a link to the pact" do
35
+ expect(subject[:_links][:self][:href]).to eq 'http://example.org/pacts/provider/Provider/consumer/Consumer/version/1234'
36
+ end
37
+
38
+ it "includes the consumer version number" do
39
+ expect(subject[:_embedded][:consumerVersion][:number]).to eq "1234"
40
+ end
41
+
42
+ it "includes a link to the version" do
43
+ expect(subject[:_embedded][:consumerVersion][:_links][:self][:href]).to eq "http://example.org/pacticipants/Consumer/versions/1234"
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -6,9 +6,17 @@ module PactBroker
6
6
  module Renderers
7
7
  describe HtmlPactRenderer do
8
8
 
9
+ let(:consumer) { double('consumer', name: 'Consumer')}
10
+ let(:created_at) { DateTime.now }
9
11
  let(:json_content) { load_fixture('renderer_pact.json') }
12
+ let(:pact) { double('pact', json_content: json_content, updated_at: created_at, consumer_version_number: '1.2.3', consumer: consumer)}
13
+ let(:pact_url) { '/pact/url' }
10
14
 
11
- subject { HtmlPactRenderer.call json_content }
15
+ before do
16
+ allow(PactBroker::Api::PactBrokerUrls).to receive(:pact_url).with('', pact).and_return(pact_url)
17
+ end
18
+
19
+ subject { HtmlPactRenderer.call pact }
12
20
 
13
21
  describe ".call" do
14
22
  it "renders the pact as HTML" do