pact_broker 2.100.0 → 2.101.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Gemfile +1 -0
  4. data/docs/api/PACTICIPANTS.md +290 -0
  5. data/docs/api/WEBHOOKS.md +40 -40
  6. data/lib/db.rb +1 -1
  7. data/lib/pact_broker/api/decorators/triggered_webhook_decorator.rb +1 -2
  8. data/lib/pact_broker/api/resources/all_webhooks.rb +1 -4
  9. data/lib/pact_broker/api/resources/base_resource.rb +51 -5
  10. data/lib/pact_broker/api/resources/branch_version.rb +10 -1
  11. data/lib/pact_broker/api/resources/clean.rb +11 -9
  12. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +0 -4
  13. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +0 -4
  14. data/lib/pact_broker/api/resources/deployed_version.rb +12 -14
  15. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +4 -0
  16. data/lib/pact_broker/api/resources/environment.rb +5 -5
  17. data/lib/pact_broker/api/resources/environments.rb +1 -5
  18. data/lib/pact_broker/api/resources/label.rb +4 -0
  19. data/lib/pact_broker/api/resources/pact.rb +10 -5
  20. data/lib/pact_broker/api/resources/pact_webhooks.rb +1 -4
  21. data/lib/pact_broker/api/resources/pacticipant.rb +11 -5
  22. data/lib/pact_broker/api/resources/pacticipant_webhooks.rb +1 -4
  23. data/lib/pact_broker/api/resources/pacticipants.rb +1 -4
  24. data/lib/pact_broker/api/resources/provider_pacts_for_verification.rb +19 -11
  25. data/lib/pact_broker/api/resources/publish_contracts.rb +11 -15
  26. data/lib/pact_broker/api/resources/released_version.rb +12 -6
  27. data/lib/pact_broker/api/resources/released_versions_for_version_and_environment.rb +10 -6
  28. data/lib/pact_broker/api/resources/tag.rb +7 -3
  29. data/lib/pact_broker/api/resources/verifications.rb +7 -9
  30. data/lib/pact_broker/api/resources/version.rb +8 -8
  31. data/lib/pact_broker/api/resources/webhook.rb +5 -4
  32. data/lib/pact_broker/api/resources/webhook_execution.rb +4 -6
  33. data/lib/pact_broker/doc/views/index/pacticipant-branch-version.markdown +13 -2
  34. data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +1 -1
  35. data/lib/pact_broker/domain/pacticipant.rb +1 -0
  36. data/lib/pact_broker/locale/en.yml +1 -0
  37. data/lib/pact_broker/pacticipants/repository.rb +5 -4
  38. data/lib/pact_broker/pacts/generate_sha.rb +1 -0
  39. data/lib/pact_broker/pacts/verifiable_pact_messages.rb +1 -1
  40. data/lib/pact_broker/test/test_data_builder.rb +20 -0
  41. data/lib/pact_broker/version.rb +1 -1
  42. data/lib/pact_broker/versions/branch_service.rb +7 -0
  43. data/lib/pact_broker/versions/branch_version_repository.rb +17 -0
  44. data/lib/rack/pact_broker/cascade.rb +87 -0
  45. data/lib/webmachine/describe_routes.rb +43 -9
  46. metadata +5 -4
  47. data/lib/pact_broker/api/resources/default_base_resource.rb +0 -0
@@ -22,15 +22,16 @@ module PactBroker
22
22
  ["GET", "PUT", "DELETE", "OPTIONS"]
23
23
  end
24
24
 
25
+ def put_can_create?
26
+ true
27
+ end
28
+
25
29
  def resource_exists?
26
30
  !!webhook
27
31
  end
28
32
 
29
33
  def malformed_request?
30
- if request.put?
31
- return invalid_json? || webhook_validation_errors?(parsed_webhook, uuid)
32
- end
33
- false
34
+ super || (request.put? && webhook_validation_errors?(parsed_webhook, uuid))
34
35
  end
35
36
 
36
37
  def from_json
@@ -13,10 +13,6 @@ module PactBroker
13
13
  include WebhookResourceMethods
14
14
  include WebhookExecutionMethods
15
15
 
16
- def content_types_accepted
17
- [["application/json"]]
18
- end
19
-
20
16
  def content_types_provided
21
17
  [["application/hal+json"]]
22
18
  end
@@ -37,14 +33,16 @@ module PactBroker
37
33
  end
38
34
 
39
35
  def malformed_request?
40
- if request.post?
36
+ if super
37
+ true
38
+ elsif request.post?
41
39
  if uuid
42
40
  false
43
41
  else
44
42
  webhook_validation_errors?(webhook)
45
43
  end
46
44
  else
47
- super
45
+ false
48
46
  end
49
47
  end
50
48
 
@@ -1,14 +1,25 @@
1
1
  # Pacticipant branch version
2
2
 
3
- Allowed methods: `GET`, `PUT`
3
+ Allowed methods: `GET`, `PUT`, `DELETE`
4
4
 
5
5
  Path: `/pacticipants/{pacticipant}/branches/{branch}/versions/{version}`
6
6
 
7
7
  Get or add/create a pacticipant version for a branch.
8
8
 
9
- ## Example
9
+ ## Create
10
+
11
+ ### Example
10
12
 
11
13
  Add a version to a branch. The pacticipant and branch are automatically created if they do not exist.
12
14
 
13
15
  curl -XPUT http://broker/pacticipants/Bar/branches/main/versions/1e70030c6579915e5ff56b107a0fd25cf5df7464 \
14
16
  -H "Content-Type: application/json" -d "{}"
17
+
18
+
19
+ ## Delete
20
+
21
+ Removes a pacticipant version from a branch. Does not delete the actual pacticipant version.
22
+
23
+ Send a `DELETE` request to the branch version resource.
24
+
25
+ curl -XDELETE http://broker/pacticipants/Bar/branches/main/versions/1e70030c6579915e5ff56b107a0fd25cf5df7464
@@ -48,7 +48,7 @@ Example: This data structure represents the way a user might specify "I want to
48
48
 
49
49
  `consumerVersionSelectors.tag`: the tag name(s) of the consumer versions to get the pacts for. *This field is still supported but it is recommended to use the `branch` in preference now.*
50
50
 
51
- `consumerVersionSelectors.fallbackTag`: the name of the tag to fallback to if the specified `tag` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use the `fallbackBranch` in preference now.*
51
+ `consumerVersionSelectors.fallbackTag`: the name of the tag to fallback to if the specified `tag` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use two separate selectors - one with the main branch name and one with the feature branch name.*
52
52
 
53
53
  `providerVersionBranch`: the repository branch name for the provider application version that will be published with the verification results. This is used by the Broker to determine whether or not a particular pact is in pending state or not.
54
54
 
@@ -44,6 +44,7 @@ module PactBroker
44
44
  using PactBroker::StringRefinements
45
45
 
46
46
  plugin :insert_ignore, identifying_columns: [:name]
47
+ plugin :upsert, identifying_columns: [:name]
47
48
  plugin :timestamps, update_on_create: true
48
49
 
49
50
  set_primary_key :id
@@ -107,6 +107,7 @@ en:
107
107
  To disable this check, set `check_for_potential_duplicate_pacticipant_names` to false in the configuration.
108
108
  new_line_in_url_path: URL path cannot contain a new line character.
109
109
  tab_in_url_path: URL path cannot contain a tab character.
110
+ non_utf_8_char_in_request_body: "Request body has a non UTF-8 character at char %{char_number} and cannot be parsed as JSON. Fragment preceding invalid character is: '%{fragment}'"
110
111
 
111
112
  "400":
112
113
  title: 400 Malformed Request
@@ -59,22 +59,23 @@ module PactBroker
59
59
  repository_name: params[:repository_name],
60
60
  repository_namespace: params[:repository_namespace],
61
61
  main_branch: params[:main_branch]
62
- ).insert_ignore
62
+ ).insert_ignore.refresh
63
63
  end
64
64
 
65
65
  def update(pacticipant_name, pacticipant)
66
66
  pacticipant.name = pacticipant_name
67
- pacticipant.save
67
+ pacticipant.save.refresh
68
68
  end
69
69
 
70
- def replace(_pacticipant_name, open_struct_pacticipant)
70
+ def replace(pacticipant_name, open_struct_pacticipant)
71
71
  PactBroker::Domain::Pacticipant.new(
72
+ name: pacticipant_name,
72
73
  display_name: open_struct_pacticipant.display_name,
73
74
  repository_url: open_struct_pacticipant.repository_url,
74
75
  repository_name: open_struct_pacticipant.repository_name,
75
76
  repository_namespace: open_struct_pacticipant.repository_namespace,
76
77
  main_branch: open_struct_pacticipant.main_branch
77
- ).save
78
+ ).upsert
78
79
  end
79
80
 
80
81
  def delete(pacticipant)
@@ -7,6 +7,7 @@ require "pact_broker/pacts/content"
7
7
  module PactBroker
8
8
  module Pacts
9
9
  class GenerateSha
10
+ # @param [String] json_content
10
11
  def self.call json_content, _options = {}
11
12
  content_for_sha = if PactBroker.configuration.base_equality_only_on_content_that_affects_verification_results
12
13
  extract_verifiable_content_for_sha(json_content)
@@ -31,7 +31,7 @@ module PactBroker
31
31
  version_text = head_consumer_tags.size == 1 || branches.size == 1 ? "version" : "versions"
32
32
  if wip?
33
33
  # WIP pacts will always have tags, because it is part of the definition of being a WIP pact
34
- "The pact at #{pact_version_url} is being verified because it is a 'work in progress' pact (ie. it is the pact for the latest #{version_text} of #{consumer_name} #{joined_head_consumer_tags_and_branches} and it has not yet been successfully verified by #{pending_provider_branch_or_tags_description("a")} when the pact's application version was explicitly specified in the consumer version selectors). #{READ_MORE_WIP}".tap { |it| puts it }
34
+ "The pact at #{pact_version_url} is being verified because it is a 'work in progress' pact (ie. it is the pact for the latest #{version_text} of #{consumer_name} #{joined_head_consumer_tags_and_branches} and it has not yet been successfully verified by #{pending_provider_branch_or_tags_description("a")} when the pact's application version was explicitly specified in the consumer version selectors). #{READ_MORE_WIP}"
35
35
  else
36
36
  criteria_or_criterion = selectors.size > 1 ? "criteria" : "criterion"
37
37
  version_or_versions = pluralize("the consumer version", selectors.size)
@@ -572,6 +572,26 @@ module PactBroker
572
572
  }.to_json
573
573
  end
574
574
 
575
+ def fixed_json_content(consumer_name, provider_name, differentiator)
576
+ {
577
+ "consumer" => {
578
+ "name" => consumer_name
579
+ },
580
+ "provider" => {
581
+ "name" => provider_name
582
+ },
583
+ "interactions" => [{
584
+ "request" => {
585
+ "method" => "GET",
586
+ "path" => "/things/#{differentiator}"
587
+ },
588
+ "response" => {
589
+ "status" => 200
590
+ }
591
+ }],
592
+ }.to_json
593
+ end
594
+
575
595
  private
576
596
 
577
597
  def create_pacticipant_version(version_number, pacticipant, params = {})
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = "2.100.0"
2
+ VERSION = "2.101.0"
3
3
  end
@@ -1,12 +1,19 @@
1
1
  require "pact_broker/logging"
2
2
  require "pact_broker/repositories"
3
3
  require "pact_broker/messages"
4
+ require "forwardable"
4
5
 
5
6
  module PactBroker
6
7
  module Versions
7
8
  class BranchService
8
9
  extend PactBroker::Repositories
9
10
 
11
+ class << self
12
+ extend Forwardable
13
+ delegate [:delete_branch_version] => :branch_version_repository
14
+ end
15
+
16
+
10
17
  def self.find_branch_version(pacticipant_name:, branch_name:, version_number:, **)
11
18
  BranchVersion.where(
12
19
  version: PactBroker::Domain::Version.where_pacticipant_name_and_version_number(pacticipant_name, version_number),
@@ -16,6 +16,23 @@ module PactBroker
16
16
  branch_version
17
17
  end
18
18
 
19
+ # Deletes a branch version - that is, removes a version from a branch.
20
+ # Updates the branch head if the deleted branch version was the latest for the branch.
21
+ #
22
+ # @param [PactBroker::Versions::BranchVersion] the branch version to delete
23
+ def delete_branch_version(branch_version)
24
+ latest = branch_version.latest?
25
+ branch = branch_version.latest? ? branch_version.branch : nil
26
+ deleted = branch_version.delete
27
+ if latest
28
+ new_head_branch_version = BranchVersion.find_latest_for_branch(branch)
29
+ if new_head_branch_version
30
+ PactBroker::Versions::BranchHead.new(branch: branch, branch_version: new_head_branch_version).upsert
31
+ end
32
+ end
33
+ deleted
34
+ end
35
+
19
36
  private
20
37
 
21
38
  def find_or_create_branch(pacticipant, branch_name)
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is a modified version of the Rack::Cascade class from https://github.com/rack/rack/blob/2833813/lib/rack/cascade.rb
4
+ # that short circuts the cascade if a response body is provided for a 404/403 response.
5
+ # This is to allow the UI and API to be in a cascade, but allows the UI
6
+ # to return a 404 page when the route matches, but there is no domain object found.
7
+ # If we don't do this, then the 404 from the UI causes an API response to be returned.
8
+
9
+
10
+ # This does not work. Do not use it. Have not yet worked out why.
11
+
12
+ module Rack
13
+ # Rack::Cascade tries a request on several apps, and returns the
14
+ # first response that is not 404 or 405 (or in a list of configured
15
+ # status codes). If all applications tried return one of the configured
16
+ # status codes, return the last response.
17
+
18
+ module PactBroker
19
+ class Cascade
20
+ # deprecated, no longer used
21
+ NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
22
+
23
+ # An array of applications to try in order.
24
+ attr_reader :apps
25
+
26
+ # Set the apps to send requests to, and what statuses result in
27
+ # cascading. Arguments:
28
+ #
29
+ # apps: An enumerable of rack applications.
30
+ # cascade_for: The statuses to use cascading for. If a response is received
31
+ # from an app, the next app is tried.
32
+ def initialize(apps, cascade_for = [404, 405])
33
+ @apps = []
34
+ apps.each { |app| add app }
35
+
36
+ @cascade_for = {}
37
+ [*cascade_for].each { |status| @cascade_for[status] = true }
38
+ end
39
+
40
+ # Call each app in order. If the responses uses a status that requires
41
+ # cascading, try the next app. If all responses require cascading,
42
+ # return the response from the last app.
43
+ def call(env)
44
+ return [404, { CONTENT_TYPE => "text/plain" }, []] if @apps.empty?
45
+ result = nil
46
+ last_body = nil
47
+
48
+ @apps.each_with_index do |app, i|
49
+ # The SPEC says that the body must be closed after it has been iterated
50
+ # by the server, or if it is replaced by a middleware action. Cascade
51
+ # replaces the body each time a cascade happens. It is assumed that nil
52
+ # does not respond to close, otherwise the previous application body
53
+ # will be closed. The final application body will not be closed, as it
54
+ # will be passed to the server as a result.
55
+ last_body.close if last_body.respond_to? :close
56
+ result = app.call(env)
57
+
58
+ puts result.last
59
+
60
+ # If it is a 404/403 AND the response body is empty, then try the next app
61
+ if @cascade_for.include?(result[0].to_i) && result[2].respond_to?(:empty?) && result[2].empty?
62
+ last_body = result[2]
63
+ else
64
+ puts "returned from #{i} of #{@apps.size}"
65
+ # otherwise, return the result
66
+ return result
67
+ end
68
+ end
69
+
70
+ result
71
+ end
72
+
73
+ # Append an app to the list of apps to cascade. This app will
74
+ # be tried last.
75
+ def add(app)
76
+ @apps << app
77
+ end
78
+
79
+ # Whether the given app is one of the apps to cascade to.
80
+ def include?(app)
81
+ @apps.include?(app)
82
+ end
83
+
84
+ alias_method :<<, :add
85
+ end
86
+ end
87
+ end
@@ -5,6 +5,7 @@ module Webmachine
5
5
 
6
6
  Route = Struct.new(
7
7
  :path,
8
+ :path_spec,
8
9
  :resource_class,
9
10
  :resource_name,
10
11
  :resource_class_location,
@@ -23,6 +24,38 @@ module Webmachine
23
24
  def path_include?(component)
24
25
  path.include?(component)
25
26
  end
27
+
28
+ def route_param_names
29
+ path_spec.select { | component | component.is_a?(Symbol) }
30
+ end
31
+
32
+ # Creates a Webmachine Resource for the given route for use in tests.
33
+ # @param [Hash] env the rack env from which to build the request
34
+ # @param [PactBroker::ApplicationContext] application_context the application context
35
+ # @param [Hash] path_param_values concrete parameter values from which to construct the path
36
+ # @return [Webmachine::Resource] the webmachine resource for the request
37
+ def build_resource(env, application_context, path_param_values)
38
+ path = "/" + path_spec.collect{ | part | part.is_a?(Symbol) ? (path_param_values[part] || "missing-param") : part }.join("/")
39
+
40
+ path_params = route_param_names.each_with_object({}){ | name, new_params | new_params[name] = path_param_values[name] }
41
+ path_info = {
42
+ application_context: application_context,
43
+ resource_name: resource_name
44
+ }.merge(path_params)
45
+
46
+ rack_req = ::Rack::Request.new({ "REQUEST_METHOD" => "GET", "rack.input" => StringIO.new("") }.merge(env) )
47
+ dummy_request = Webmachine::Adapters::Rack::RackRequest.new(
48
+ rack_req.env["REQUEST_METHOD"],
49
+ path,
50
+ Webmachine::Headers.from_cgi({"HTTP_HOST" => "example.org"}.merge(env)),
51
+ Webmachine::Adapters::Rack::RequestBody.new(rack_req),
52
+ {},
53
+ {},
54
+ rack_req.env
55
+ )
56
+ dummy_request.path_info = path_info
57
+ resource_class.new(dummy_request, Webmachine::Response.new)
58
+ end
26
59
  end
27
60
 
28
61
  def self.call(webmachine_applications, search_term: nil)
@@ -36,24 +69,25 @@ module Webmachine
36
69
  end
37
70
 
38
71
  def self.paths_to_resource_class_mappings(webmachine_application)
39
- webmachine_application.routes.collect do | route |
40
- resource_path_absolute = Pathname.new(source_location_for(route.resource))
72
+ webmachine_application.routes.collect do | webmachine_route |
73
+ resource_path_absolute = Pathname.new(source_location_for(webmachine_route.resource))
41
74
  Route.new({
42
- path: "/" + route.path_spec.collect{ |part| part.is_a?(Symbol) ? ":#{part}" : part }.join("/"),
43
- resource_class: route.resource,
44
- resource_name: route.instance_variable_get(:@bindings)[:resource_name],
75
+ path: "/" + webmachine_route.path_spec.collect{ |part| part.is_a?(Symbol) ? ":#{part}" : part }.join("/"),
76
+ path_spec: webmachine_route.path_spec,
77
+ resource_class: webmachine_route.resource,
78
+ resource_name: webmachine_route.instance_variable_get(:@bindings)[:resource_name],
45
79
  resource_class_location: resource_path_absolute.relative_path_from(Pathname.pwd).to_s
46
- }.merge(info_from_resource_instance(route)))
47
- end
80
+ }.merge(info_from_resource_instance(webmachine_route)))
81
+ end.reject{ | route | route.resource_class == Webmachine::Trace::TraceResource }
48
82
  end
49
83
 
50
- def self.info_from_resource_instance(route)
84
+ def self.info_from_resource_instance(webmachine_route)
51
85
  with_no_logging do
52
86
  path_info = { application_context: OpenStruct.new, pacticipant_name: "foo", pacticipant_version_number: "1", resource_name: "foo" }
53
87
  path_info.default = "1"
54
88
  dummy_request = Webmachine::Adapters::Rack::RackRequest.new("GET", "/", Webmachine::Headers["host" => "example.org"], nil, {}, {}, { "REQUEST_METHOD" => "GET" })
55
89
  dummy_request.path_info = path_info
56
- dummy_resource = route.resource.new(dummy_request, Webmachine::Response.new)
90
+ dummy_resource = webmachine_route.resource.new(dummy_request, Webmachine::Response.new)
57
91
  if dummy_resource
58
92
  {
59
93
  allowed_methods: dummy_resource.allowed_methods,
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: 2.100.0
4
+ version: 2.101.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: 2022-05-20 00:00:00.000000000 Z
13
+ date: 2022-06-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: httparty
@@ -586,6 +586,7 @@ files:
586
586
  - db/migrations/20220303_increase_consumer_version_selector_hashes_column_size.rb
587
587
  - db/migrations/migration_helper.rb
588
588
  - docs/CONFIGURATION.md
589
+ - docs/api/PACTICIPANTS.md
589
590
  - docs/api/WEBHOOKS.md
590
591
  - lib/db.rb
591
592
  - lib/pact/doc/README.md
@@ -705,7 +706,6 @@ files:
705
706
  - lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb
706
707
  - lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb
707
708
  - lib/pact_broker/api/resources/dashboard.rb
708
- - lib/pact_broker/api/resources/default_base_resource.rb
709
709
  - lib/pact_broker/api/resources/deployed_version.rb
710
710
  - lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb
711
711
  - lib/pact_broker/api/resources/environment.rb
@@ -1076,6 +1076,7 @@ files:
1076
1076
  - lib/rack/hal_browser/redirect.rb
1077
1077
  - lib/rack/pact_broker/add_pact_broker_version_header.rb
1078
1078
  - lib/rack/pact_broker/add_vary_header.rb
1079
+ - lib/rack/pact_broker/cascade.rb
1079
1080
  - lib/rack/pact_broker/configurable_make_it_later.rb
1080
1081
  - lib/rack/pact_broker/convert_404_to_hal.rb
1081
1082
  - lib/rack/pact_broker/convert_file_extension_to_accept_header.rb
@@ -1222,7 +1223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1222
1223
  - !ruby/object:Gem::Version
1223
1224
  version: '0'
1224
1225
  requirements: []
1225
- rubygems_version: 3.3.14
1226
+ rubygems_version: 3.3.15
1226
1227
  signing_key:
1227
1228
  specification_version: 4
1228
1229
  summary: See description