pact_broker 1.9.3 → 1.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1822a1c9b39785852ced69a22fd88a124a894e06
4
- data.tar.gz: 17276a5d6ea111fd01b5766d3246154a59ab1a60
3
+ metadata.gz: df97d13a663699e7a3eb4d7f58cc7abe8a1012d2
4
+ data.tar.gz: 319a6de81120c2bc7d627fbc2255caf51da2e742
5
5
  SHA512:
6
- metadata.gz: a74a5d53bc9b238c522190b2b6039b66c2af41ab2f8a7d900c2e12ea9e008788223d852285bf7e71c381f333679c047f48dcfbc59f1295cccd5326903fda85a3
7
- data.tar.gz: a45265e6cafa287a32fc6f31b257d4efc63bcfcf907cc9cb7808f1c35af11d342aa5caf3ff1ad766c7155af390b92e921f974b80dd53151ebcd96491213e3c4f
6
+ metadata.gz: 0130cac31b2abaebf71905c841d136a0e6d7fcf4e00186c3e33afb2fda2a8ce4fddceb5fcfe4cd5de9b0650bf6eb8d5cec1f33c422bf17af36292f01665e253b
7
+ data.tar.gz: 3a7f9c3663de5c1b1143608727321f97a40af7737d3868034e9bff1b2cfbb86ec131731667963f173484057bd118c6b84f32ca41fa22101f8f2542aaa34e1e56
data/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@ Do this to generate your change history
2
2
 
3
3
  $ git log --pretty=format:' * %h - %s (%an, %ad)' vX.Y.Z..HEAD
4
4
 
5
+ #### 1.10.0 (2016-08-01)
6
+ * efdde13 - Add ability to merge pacts via PATCH requests (Steve Pletcher, Thu Jul 28 16:29:22 2016 -0400)
7
+
5
8
  #### 1.9.3 (2016-06-27)
6
9
  * f57db36 - Clarify that pact_broker will only work with ruby >= 2.0 (Sergei Matheson, Mon Jun 27 11:06:40 2016 +1000)
7
10
  * a1742b8 - Correct release instructions (Sergei Matheson, Mon Jun 27 11:03:03 2016 +1000)
data/example/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem "pact_broker", "~>1.4"
3
+ gem "pact_broker", "~>1.9.3"
4
4
  gem "sqlite3" # Replace with your choice of database driver eg. gem "pg"
5
- gem "thin" # Keep, or replace with your choice of web server
5
+ gem "thin" # Keep, or replace with your choice of web server
@@ -6,6 +6,14 @@ require 'pact_broker/json'
6
6
  require 'pact_broker/pacts/pact_params'
7
7
  require 'pact_broker/api/contracts/put_pact_params_contract'
8
8
 
9
+ module Webmachine
10
+ class Request
11
+ def patch?
12
+ method == "PATCH"
13
+ end
14
+ end
15
+ end
16
+
9
17
  module PactBroker
10
18
 
11
19
  module Api
@@ -24,7 +32,11 @@ module PactBroker
24
32
  end
25
33
 
26
34
  def allowed_methods
27
- ["GET", "PUT", "DELETE"]
35
+ ["GET", "PUT", "DELETE", "PATCH"]
36
+ end
37
+
38
+ def known_methods
39
+ super + ['PATCH']
28
40
  end
29
41
 
30
42
  def is_conflict?
@@ -32,8 +44,8 @@ module PactBroker
32
44
  end
33
45
 
34
46
  def malformed_request?
35
- if request.put?
36
- return invalid_json? ||
47
+ if request.patch? || request.put?
48
+ invalid_json? ||
37
49
  contract_validation_errors?(Contracts::PutPactParamsContract.new(pact_params))
38
50
  else
39
51
  false
@@ -41,12 +53,18 @@ module PactBroker
41
53
  end
42
54
 
43
55
  def resource_exists?
44
- pact
56
+ !!pact
45
57
  end
46
58
 
47
59
  def from_json
48
60
  response_code = pact ? 200 : 201
49
- @pact = pact_service.create_or_update_pact(pact_params)
61
+
62
+ if request.patch? && resource_exists?
63
+ @pact = pact_service.merge_pact(pact_params)
64
+ else
65
+ @pact = pact_service.create_or_update_pact(pact_params)
66
+ end
67
+
50
68
  response.body = to_json
51
69
  response_code
52
70
  end
@@ -73,4 +91,4 @@ module PactBroker
73
91
  end
74
92
  end
75
93
  end
76
- end
94
+ end
@@ -67,4 +67,4 @@ module PactBroker
67
67
  end
68
68
 
69
69
  end
70
- end
70
+ end
@@ -0,0 +1,53 @@
1
+ require 'json'
2
+
3
+ module PactBroker
4
+ module Pacts
5
+ module Merger
6
+
7
+ extend self
8
+
9
+ # Accepts two hashes representing pacts, outputs a merged hash
10
+ # Does not make any guarantees about order of interactions
11
+ def merge_pacts original_json, additional_json
12
+ original, additional = [original_json, additional_json].map{|str| JSON.parse(str, PACT_PARSING_OPTIONS) }
13
+
14
+ new_pact = original
15
+
16
+ additional["interactions"].each do |new_interaction|
17
+ # check to see if this interaction matches an existing interaction
18
+ overwrite_index = original["interactions"].find_index do |original_interaction|
19
+ matching_request?(original_interaction, new_interaction)
20
+ end
21
+
22
+ # overwrite existing interaction if a match is found, otherwise appends the new interaction
23
+ if overwrite_index
24
+ new_pact["interactions"][overwrite_index] = new_interaction
25
+ else
26
+ new_pact["interactions"] << new_interaction
27
+ end
28
+ end
29
+
30
+ new_pact.to_json
31
+ end
32
+
33
+ private
34
+
35
+ def matching_request? original_interaction, new_interaction
36
+ same_description_and_state?(original_interaction, new_interaction) &&
37
+ same_request_properties?(original_interaction["request"], new_interaction["request"])
38
+ end
39
+
40
+ def same_description_and_state? original, additional
41
+ original["description"] == additional["description"] &&
42
+ original["provider_state"] == additional["provider_state"]
43
+ end
44
+
45
+ def same_request_properties? original, additional
46
+ method_matches = original["method"] == additional["method"]
47
+ path_matches = original["path"] == additional["path"]
48
+
49
+ method_matches && path_matches && original["headers"] == additional["headers"]
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,6 +1,7 @@
1
1
  require 'pact_broker/repositories'
2
2
  require 'pact_broker/services'
3
3
  require 'pact_broker/logging'
4
+ require 'pact_broker/pacts/merger'
4
5
 
5
6
  module PactBroker
6
7
  module Pacts
@@ -42,6 +43,17 @@ module PactBroker
42
43
  end
43
44
  end
44
45
 
46
+ def merge_pact params
47
+ provider = pacticipant_repository.find_by_name_or_create params[:provider_name]
48
+ consumer = pacticipant_repository.find_by_name_or_create params[:consumer_name]
49
+ consumer_version = version_repository.find_by_pacticipant_id_and_number_or_create consumer.id, params[:consumer_version_number]
50
+ existing_pact = pact_repository.find_by_version_and_provider(consumer_version.id, provider.id)
51
+
52
+ params.merge!(json_content: Merger.merge_pacts(params[:json_content], existing_pact.json_content))
53
+
54
+ update_pact params, existing_pact
55
+ end
56
+
45
57
  def find_all_pact_versions_between consumer, options
46
58
  pact_repository.find_all_pact_versions_between consumer, options
47
59
  end
@@ -102,7 +114,6 @@ module PactBroker
102
114
  webhook_service.execute_webhooks pact
103
115
  end
104
116
  end
105
-
106
117
  end
107
118
  end
108
- end
119
+ end
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '1.9.3'
2
+ VERSION = '1.10.0'
3
3
  end
@@ -0,0 +1,55 @@
1
+ require 'spec/support/provider_state_builder'
2
+
3
+ describe "Merging a pact" do
4
+
5
+ let(:pact_content) { load_fixture('a_consumer-a_provider.json') }
6
+ let(:path) { "/pacts/provider/A%20Provider/consumer/A%20Consumer/version/1.2.3" }
7
+ let(:response_body_json) { JSON.parse(subject.body) }
8
+
9
+ subject do
10
+ patch path, pact_content, {'CONTENT_TYPE' => 'application/json' }; last_response
11
+ end
12
+
13
+ context "when a pact for this consumer version does not exist" do
14
+ it "returns a 201 Created" do
15
+ expect(subject.status).to be 201
16
+ end
17
+
18
+ it "returns a json body" do
19
+ expect(subject.headers['Content-Type']).to eq "application/json;charset=utf-8"
20
+ end
21
+
22
+ it "returns the pact in the body" do
23
+ expect(response_body_json).to include JSON.parse(pact_content)
24
+ end
25
+ end
26
+
27
+ context "when a pact for this consumer version does exist" do
28
+ let(:existing_pact_content) { load_fixture('a_consumer-a_provider-2.json') }
29
+ let(:merged_pact_content) { load_fixture('a_consumer-a_provider-merged.json') }
30
+
31
+ before do
32
+ ProviderStateBuilder.new.create_pact_with_hierarchy "A Consumer", "1.2.3", "A Provider", existing_pact_content
33
+ end
34
+
35
+ it "returns a 200 Success" do
36
+ expect(subject.status).to be 200
37
+ end
38
+
39
+ it "returns an application/json Content-Type" do
40
+ expect(subject.headers['Content-Type']).to eq "application/json;charset=utf-8"
41
+ end
42
+
43
+ it "returns the merged pact in the response body" do
44
+ expect(response_body_json).to contain_hash JSON.parse(merged_pact_content)
45
+ end
46
+ end
47
+
48
+ context "when the pacticipant names in the path do not match those in the pact" do
49
+ let(:path) { "/pacts/provider/Another%20Provider/consumer/A%20Consumer/version/1.2.3" }
50
+
51
+ it "returns a json error response" do
52
+ expect(subject).to be_a_json_error_response "does not match"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ {
2
+ "consumer": {
3
+ "name": "A Consumer"
4
+ },
5
+ "provider": {
6
+ "name": "A Provider"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description" : "a request for something",
11
+ "provider_state": null,
12
+ "request": {
13
+ "method": "post",
14
+ "path" : "/something"
15
+ },
16
+ "response": {
17
+ "status": 200,
18
+ "body" : "something"
19
+ }
20
+ },
21
+ {
22
+ "description" : "a request for something",
23
+ "provider_state": null,
24
+ "request": {
25
+ "method": "get",
26
+ "path" : "/something"
27
+ },
28
+ "response": {
29
+ "status": 200,
30
+ "body" : "something"
31
+ }
32
+ }
33
+ ]
34
+ }
@@ -14,9 +14,8 @@ module PactBroker::Api
14
14
  let(:app) { PactBroker::API }
15
15
  let(:json) { {some: 'json'}.to_json }
16
16
 
17
- describe "PUT" do
18
-
19
- subject { put "/pacts/provider/Provider/consumer/Consumer/version/1.2", json, {'CONTENT_TYPE' => "application/json"} ; last_response }
17
+ shared_examples "an update endpoint" do |http_method|
18
+ subject { self.send http_method, "/pacts/provider/Provider/consumer/Consumer/version/1.2", json, {'CONTENT_TYPE' => "application/json"} ; last_response }
20
19
 
21
20
  let(:response) { subject; last_response }
22
21
 
@@ -76,7 +75,14 @@ module PactBroker::Api
76
75
  expect(response.body).to eq "message1\nmessage2"
77
76
  end
78
77
  end
78
+ end
79
+
80
+ describe "PUT" do
81
+ it_behaves_like "an update endpoint", :put
82
+ end
79
83
 
84
+ describe "PATCH" do
85
+ it_behaves_like "an update endpoint", :patch
80
86
  end
81
87
 
82
88
  describe "DELETE" do
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+ require 'spec/support/provider_state_builder'
3
+ require 'pact_broker/pacts/merger'
4
+ require 'json'
5
+
6
+ module PactBroker
7
+ module Pacts
8
+ describe Merger do
9
+ let(:example_pact) { load_json_fixture('consumer-provider.json') }
10
+ let(:example_interaction) do
11
+ {
12
+ "description" => "some description",
13
+ "provider_state" => nil,
14
+ "request" => {
15
+ "method" => "get",
16
+ "path" => "/cash_money",
17
+ "headers" => {
18
+ "Content-Type" => "application/json"
19
+ }
20
+ },
21
+ "response" => {
22
+ "status" => 200,
23
+ "body" => "$$$$$$$$",
24
+ "headers" => {
25
+ "Content-Type" => "application/json"
26
+ }
27
+ }
28
+ }
29
+ end
30
+
31
+ before :each do
32
+ @pact_to_merge = load_json_fixture('consumer-provider.json')
33
+ end
34
+
35
+ it "merges two pacts" do
36
+ @pact_to_merge["interactions"] << example_interaction
37
+ result = merge_pacts(example_pact, @pact_to_merge)
38
+ expect(result["interactions"]).to match_array(example_pact["interactions"].push(example_interaction))
39
+ end
40
+
41
+ it "is idempotent" do
42
+ @pact_to_merge["interactions"] << example_interaction
43
+ first_result = merge_pacts(example_pact, @pact_to_merge)
44
+ second_result = merge_pacts(first_result, @pact_to_merge)
45
+ expect(first_result).to contain_hash second_result
46
+ end
47
+
48
+ it "overwrites identical interactions" do
49
+ @pact_to_merge["interactions"][0]["response"]["body"] = "changed!"
50
+ result = merge_pacts(example_pact, @pact_to_merge)
51
+
52
+ expect(result["interactions"].length).to eq example_pact["interactions"].length
53
+ expect(result["interactions"].first["response"]["body"]).to eq "changed!"
54
+ end
55
+
56
+ it "appends interactions with a different provider state" do
57
+ @pact_to_merge["interactions"][0]["provider_state"] = "upside down"
58
+
59
+ result = merge_pacts(example_pact, @pact_to_merge)
60
+ expect(result["interactions"].length).to eq example_pact["interactions"].length + 1
61
+ end
62
+
63
+ it "appends interactions with a different description" do
64
+ @pact_to_merge["interactions"][0]["description"] = "getting $$$"
65
+
66
+ result = merge_pacts(example_pact, @pact_to_merge)
67
+ expect(result["interactions"].length).to eq example_pact["interactions"].length + 1
68
+ end
69
+
70
+ it "appends interactions with a different request method" do
71
+ @pact_to_merge["interactions"][0]["request"]["method"] = "delete"
72
+
73
+ result = merge_pacts(example_pact, @pact_to_merge)
74
+ expect(result["interactions"].length).to eq example_pact["interactions"].length + 1
75
+ end
76
+
77
+ it "appends interactions with a different request path" do
78
+ @pact_to_merge["interactions"][0]["request"]["path"] = "/decrypt_all_passwords"
79
+
80
+ result = merge_pacts(example_pact, @pact_to_merge)
81
+ expect(result["interactions"].length).to eq example_pact["interactions"].length + 1
82
+ end
83
+
84
+ it "appends interactions which have additional request headers in the new pact" do
85
+ @pact_to_merge["interactions"][0]["request"]["headers"] = { "Accept" => "script/javascript" }
86
+
87
+ result = merge_pacts(example_pact, @pact_to_merge)
88
+ expect(result["interactions"].length).to eq example_pact["interactions"].length + 1
89
+ end
90
+
91
+ it "appends interactions with different request headers" do
92
+ example_pact["interactions"][0]["request"]["headers"] = { "Content-Type" => "script/javascript" }
93
+ @pact_to_merge["interactions"][0]["request"]["headers"] = { "Content-Type" => "ayy/lmao" }
94
+
95
+ result = merge_pacts(example_pact, @pact_to_merge)
96
+ expect(result["interactions"].length).to eq example_pact["interactions"].length + 1
97
+ end
98
+
99
+ it "appends interactions with fewer request headers" do
100
+ example_pact["interactions"][0]["request"]["headers"] = { "Content-Type" => "script/javascript" }
101
+
102
+ result = merge_pacts(example_pact, @pact_to_merge)
103
+ expect(result["interactions"].length).to eq example_pact["interactions"].length + 1
104
+ end
105
+
106
+ # helper that lets these specs deal with hashes instead of JSON strings
107
+ def merge_pacts(a, b, return_hash = true)
108
+ result = PactBroker::Pacts::Merger.merge_pacts(a.to_json, b.to_json)
109
+
110
+ return_hash ? JSON.parse(result) : result
111
+ end
112
+ end
113
+ end
114
+ end
@@ -66,4 +66,4 @@ module PactBroker
66
66
 
67
67
  end
68
68
  end
69
- end
69
+ end
@@ -0,0 +1,25 @@
1
+ # checks if actual contains all the key-value pairs that expected does,
2
+ # ignoring order for any child arrays
3
+ RSpec::Matchers.define :contain_hash do |expected|
4
+ match do |actual|
5
+ contains_hash?(expected, actual)
6
+ end
7
+ end
8
+
9
+
10
+ def contains_hash?(expected, actual)
11
+ expected.all? do |key, value|
12
+ unordered_match(actual[key], value)
13
+ end
14
+ end
15
+
16
+ def unordered_match(expected, actual)
17
+ case
18
+ when [expected, actual].all?{|val| val.is_a? Array }
19
+ expected.all?{|el| actual.include? el }
20
+ when [expected, actual].all?{|val| val.is_a? Hash }
21
+ contains_hash?(expected, actual)
22
+ else
23
+ expected == actual
24
+ end
25
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pact_broker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.3
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bethany Skurrie
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-06-27 00:00:00.000000000 Z
13
+ date: 2016-08-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: httparty
@@ -498,6 +498,7 @@ files:
498
498
  - lib/pact_broker/pacts/diff.rb
499
499
  - lib/pact_broker/pacts/latest_pacts.rb
500
500
  - lib/pact_broker/pacts/latest_tagged_pacts.rb
501
+ - lib/pact_broker/pacts/merger.rb
501
502
  - lib/pact_broker/pacts/pact_params.rb
502
503
  - lib/pact_broker/pacts/pact_version_content.rb
503
504
  - lib/pact_broker/pacts/repository.rb
@@ -575,10 +576,12 @@ files:
575
576
  - spec/features/get_previous_distinct_version.rb
576
577
  - spec/features/get_provider_pacts.rb
577
578
  - spec/features/get_version.rb
579
+ - spec/features/merge_pact_spec.rb
578
580
  - spec/features/publish_not_a_pact_spec.rb
579
581
  - spec/features/publish_pact_spec.rb
580
582
  - spec/features/update_pacticipant_spec.rb
581
583
  - spec/fixtures/a_consumer-a_provider-2.json
584
+ - spec/fixtures/a_consumer-a_provider-merged.json
582
585
  - spec/fixtures/a_consumer-a_provider.json
583
586
  - spec/fixtures/consumer-provider.json
584
587
  - spec/fixtures/renderer_pact.json
@@ -630,6 +633,7 @@ files:
630
633
  - spec/lib/pact_broker/pacticipants/find_potential_duplicate_pacticipant_names_spec.rb
631
634
  - spec/lib/pact_broker/pacts/create_formatted_diff_spec.rb
632
635
  - spec/lib/pact_broker/pacts/diff_spec.rb
636
+ - spec/lib/pact_broker/pacts/merger_spec.rb
633
637
  - spec/lib/pact_broker/pacts/pact_params_spec.rb
634
638
  - spec/lib/pact_broker/pacts/repository_spec.rb
635
639
  - spec/lib/pact_broker/pacts/service_spec.rb
@@ -653,6 +657,7 @@ files:
653
657
  - spec/support/database_cleaner.rb
654
658
  - spec/support/fixture_helpers.rb
655
659
  - spec/support/provider_state_builder.rb
660
+ - spec/support/rspec_match_hash.rb
656
661
  - spec/support/shared_examples_for_responses.rb
657
662
  - tasks/database.rb
658
663
  - tasks/db.rake
@@ -728,10 +733,12 @@ test_files:
728
733
  - spec/features/get_previous_distinct_version.rb
729
734
  - spec/features/get_provider_pacts.rb
730
735
  - spec/features/get_version.rb
736
+ - spec/features/merge_pact_spec.rb
731
737
  - spec/features/publish_not_a_pact_spec.rb
732
738
  - spec/features/publish_pact_spec.rb
733
739
  - spec/features/update_pacticipant_spec.rb
734
740
  - spec/fixtures/a_consumer-a_provider-2.json
741
+ - spec/fixtures/a_consumer-a_provider-merged.json
735
742
  - spec/fixtures/a_consumer-a_provider.json
736
743
  - spec/fixtures/consumer-provider.json
737
744
  - spec/fixtures/renderer_pact.json
@@ -783,6 +790,7 @@ test_files:
783
790
  - spec/lib/pact_broker/pacticipants/find_potential_duplicate_pacticipant_names_spec.rb
784
791
  - spec/lib/pact_broker/pacts/create_formatted_diff_spec.rb
785
792
  - spec/lib/pact_broker/pacts/diff_spec.rb
793
+ - spec/lib/pact_broker/pacts/merger_spec.rb
786
794
  - spec/lib/pact_broker/pacts/pact_params_spec.rb
787
795
  - spec/lib/pact_broker/pacts/repository_spec.rb
788
796
  - spec/lib/pact_broker/pacts/service_spec.rb
@@ -806,4 +814,5 @@ test_files:
806
814
  - spec/support/database_cleaner.rb
807
815
  - spec/support/fixture_helpers.rb
808
816
  - spec/support/provider_state_builder.rb
817
+ - spec/support/rspec_match_hash.rb
809
818
  - spec/support/shared_examples_for_responses.rb