pact_broker 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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