pact_broker 2.87.0 → 2.88.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/docs/CONFIGURATION.md +1 -1
  4. data/docs/api/WEBHOOKS.md +789 -0
  5. data/lib/pact_broker/api/decorators/pact_decorator.rb +12 -0
  6. data/lib/pact_broker/api/resources/default_base_resource.rb +1 -1
  7. data/lib/pact_broker/api/resources/environment.rb +3 -3
  8. data/lib/pact_broker/api/resources/metadata_resource_methods.rb +33 -3
  9. data/lib/pact_broker/api/resources/pact_version.rb +4 -0
  10. data/lib/pact_broker/api/resources/previous_distinct_pact_version.rb +1 -1
  11. data/lib/pact_broker/api/resources/webhook_execution.rb +7 -3
  12. data/lib/pact_broker/api.rb +5 -0
  13. data/lib/pact_broker/deployments/environment_service.rb +3 -1
  14. data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +7 -7
  15. data/lib/pact_broker/domain/webhook.rb +2 -2
  16. data/lib/pact_broker/test/http_test_data_builder.rb +1 -0
  17. data/lib/pact_broker/test/test_data_builder.rb +6 -0
  18. data/lib/pact_broker/version.rb +1 -1
  19. data/lib/pact_broker/versions/abbreviate_number.rb +8 -4
  20. data/lib/webmachine/describe_routes.rb +62 -0
  21. data/script/data/branches.rb +1 -1
  22. data/script/docs/generate-api-docs.rb +117 -0
  23. data/script/docs/regenerate-api-docs.sh +11 -0
  24. data/spec/fixtures/approvals/docs_webhooks_executing_a_saved_webhook_options.approved.json +20 -0
  25. data/spec/fixtures/approvals/docs_webhooks_executing_a_saved_webhook_post.approved.json +43 -0
  26. data/spec/fixtures/approvals/docs_webhooks_executing_an_unsaved_webhook_options.approved.json +20 -0
  27. data/spec/fixtures/approvals/docs_webhooks_executing_an_unsaved_webhook_post.approved.json +63 -0
  28. data/spec/fixtures/approvals/docs_webhooks_logs_of_triggered_webhook_get.approved.json +20 -0
  29. data/spec/fixtures/approvals/docs_webhooks_logs_of_triggered_webhook_options.approved.json +20 -0
  30. data/spec/fixtures/approvals/docs_webhooks_pact_webhooks_get.approved.json +45 -0
  31. data/spec/fixtures/approvals/docs_webhooks_pact_webhooks_options.approved.json +20 -0
  32. data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_pact_publication_get.approved.json +52 -0
  33. data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_pact_publication_options.approved.json +20 -0
  34. data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_verification_publication_get.approved.json +32 -0
  35. data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_verification_publication_options.approved.json +20 -0
  36. data/spec/fixtures/approvals/docs_webhooks_webhook_get.approved.json +74 -0
  37. data/spec/fixtures/approvals/docs_webhooks_webhook_options.approved.json +20 -0
  38. data/spec/fixtures/approvals/docs_webhooks_webhook_put.approved.json +77 -0
  39. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_a_provider_get.approved.json +41 -0
  40. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_a_provider_options.approved.json +20 -0
  41. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_and_provider_get.approved.json +45 -0
  42. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_and_provider_options.approved.json +20 -0
  43. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_get.approved.json +41 -0
  44. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_options.approved.json +20 -0
  45. data/spec/fixtures/approvals/docs_webhooks_webhooks_get.approved.json +45 -0
  46. data/spec/fixtures/approvals/docs_webhooks_webhooks_options.approved.json +20 -0
  47. data/spec/fixtures/approvals/docs_webhooks_webhooks_post.approved.json +78 -0
  48. data/spec/fixtures/approvals/docs_webhooks_webhooks_status_get.approved.json +79 -0
  49. data/spec/fixtures/approvals/docs_webhooks_webhooks_status_options.approved.json +20 -0
  50. data/spec/fixtures/approvals/get_provider_pacts_for_verification.approved.json +1 -2
  51. data/spec/fixtures/approvals/publish_contract_no_branch.approved.json +1 -2
  52. data/spec/fixtures/approvals/publish_contract_nothing_exists.approved.json +1 -2
  53. data/spec/fixtures/approvals/publish_contract_nothing_exists_with_webhook.approved.json +1 -2
  54. data/spec/fixtures/approvals/publish_contract_verification_already_exists.approved.json +1 -2
  55. data/spec/fixtures/approvals/publish_contract_with_validation_error.approved.json +1 -2
  56. data/spec/integration/pact_metdata_spec.rb +105 -0
  57. data/spec/integration/webhooks_documentation_spec.rb +348 -0
  58. data/spec/lib/pact_broker/deployments/environment_service_spec.rb +21 -0
  59. data/spec/lib/pact_broker/domain/webhook_spec.rb +35 -0
  60. data/spec/lib/pact_broker/versions/abbreviate_number_spec.rb +2 -1
  61. data/spec/support/documentation.rb +64 -0
  62. data/spec/support/rack_helpers.rb +1 -1
  63. data/tasks/development.rake +14 -13
  64. metadata +65 -3
@@ -42,6 +42,18 @@ module PactBroker
42
42
  }
43
43
  end
44
44
 
45
+ links :'pb:consumer-versions' do | options |
46
+ if options[:consumer_versions]
47
+ options[:consumer_versions].collect do | consumer_version |
48
+ {
49
+ title: "Consumer version",
50
+ name: consumer_version.number,
51
+ href: version_url(options.fetch(:base_url), consumer_version)
52
+ }
53
+ end
54
+ end
55
+ end
56
+
45
57
  link :'pb:provider' do | options |
46
58
  {
47
59
  title: "Provider",
@@ -134,7 +134,7 @@ module PactBroker
134
134
  end
135
135
 
136
136
  def pact_params
137
- @pact_params ||= PactBroker::Pacts::PactParams.from_request request, path_info
137
+ @pact_params ||= PactBroker::Pacts::PactParams.from_request request, identifier_from_path
138
138
  end
139
139
 
140
140
  def set_json_error_message message
@@ -31,7 +31,7 @@ module PactBroker
31
31
 
32
32
  def from_json
33
33
  if environment
34
- @environment = update_environment
34
+ @environment = replace_environment
35
35
  response.body = to_json
36
36
  else
37
37
  response.code = 404
@@ -63,8 +63,8 @@ module PactBroker
63
63
  identifier_from_path[:environment_uuid]
64
64
  end
65
65
 
66
- def update_environment
67
- environment_service.update(uuid, parsed_environment)
66
+ def replace_environment
67
+ environment_service.replace(uuid, parsed_environment)
68
68
  end
69
69
 
70
70
  def schema
@@ -8,16 +8,46 @@ module PactBroker
8
8
  using PactBroker::HashRefinements
9
9
 
10
10
  def pact_params
11
- @pact_params ||= PactBroker::Pacts::PactParams.from_request(request, maybe_params_with_consumer_version_number.merge(path_info))
11
+ @pact_params ||= PactBroker::Pacts::PactParams.from_request(request, maybe_consumer_version_number_param.merge(identifier_from_path))
12
12
  end
13
13
 
14
- def maybe_params_with_consumer_version_number
15
- metadata.slice(:consumer_version_number)
14
+ def maybe_consumer_version_number_param
15
+ if metadata[:consumer_version_number]
16
+ metadata.slice(:consumer_version_number)
17
+ elsif metadata_consumer_version_numbers&.any?
18
+ {
19
+ consumer_version_number: consumer_versions_from_metadata.last&.number
20
+ }
21
+ else
22
+ {}
23
+ end
16
24
  end
17
25
 
18
26
  def metadata
19
27
  @metadata ||= PactBroker::Pacts::Metadata.parse_metadata(PactBrokerUrls.decode_pact_metadata(identifier_from_path[:metadata]))
20
28
  end
29
+
30
+ def metadata_consumer_version_numbers
31
+ @metadata_consumer_version_numbers ||= begin
32
+ if metadata[:consumer_version_selectors].is_a?(Array)
33
+ metadata[:consumer_version_selectors].collect{ | selector | selector[:consumer_version_number] }.compact.uniq
34
+ elsif metadata[:consumer_version_number]
35
+ [metadata[:consumer_version_number]]
36
+ else
37
+ nil
38
+ end
39
+ end
40
+ end
41
+
42
+ def consumer_versions_from_metadata
43
+ @consumer_versions_from_metadata ||= begin
44
+ if metadata_consumer_version_numbers
45
+ metadata_consumer_version_numbers.collect do | consumer_version_number |
46
+ version_service.find_by_pacticipant_name_and_number(pacticipant_name: identifier_from_path[:consumer_name], pacticipant_version_number: consumer_version_number)
47
+ end.compact.sort_by(&:order)
48
+ end
49
+ end
50
+ end
21
51
  end
22
52
  end
23
53
  end
@@ -10,6 +10,10 @@ module PactBroker
10
10
  def allowed_methods
11
11
  ["GET", "OPTIONS"]
12
12
  end
13
+
14
+ def decorator_options(options)
15
+ super(options.merge(consumer_versions: consumer_versions_from_metadata&.reverse))
16
+ end
13
17
  end
14
18
  end
15
19
  end
@@ -29,7 +29,7 @@ module PactBroker
29
29
  end
30
30
 
31
31
  def pact_params
32
- @pact_params ||= PactBroker::Pacts::PactParams.from_request request, path_info
32
+ @pact_params ||= PactBroker::Pacts::PactParams.from_request request, identifier_from_path
33
33
  end
34
34
 
35
35
  def policy_name
@@ -37,10 +37,14 @@ module PactBroker
37
37
  end
38
38
 
39
39
  def malformed_request?
40
- if uuid
41
- false
40
+ if request.post?
41
+ if uuid
42
+ false
43
+ else
44
+ webhook_validation_errors?(webhook)
45
+ end
42
46
  else
43
- webhook_validation_errors?(webhook)
47
+ super
44
48
  end
45
49
  end
46
50
 
@@ -152,5 +152,10 @@ module PactBroker
152
152
  API ||= begin
153
153
  build_api
154
154
  end
155
+
156
+ def self.routes
157
+ require "webmachine/describe_routes"
158
+ @routes ||= Webmachine::DescribeRoutes.call([API.application])
159
+ end
155
160
  # rubocop: enable Metrics/MethodLength
156
161
  end
@@ -28,7 +28,7 @@ module PactBroker
28
28
  environment.save
29
29
  end
30
30
 
31
- def update(uuid, environment)
31
+ def replace(uuid, environment)
32
32
  environment.uuid = uuid
33
33
  if environment.display_name.blank?
34
34
  environment.display_name = PactBroker::Pacticipants::GenerateDisplayName.call(environment.name)
@@ -36,6 +36,8 @@ module PactBroker
36
36
  environment.upsert
37
37
  end
38
38
 
39
+ alias_method :update, :replace # For PF
40
+
39
41
  def find_all
40
42
  scope_for(PactBroker::Deployments::Environment).order(Sequel.function(:lower, :display_name)).all
41
43
  end
@@ -26,34 +26,34 @@ Example: This data structure represents the way a user might specify "I want to
26
26
  "includeWipPactsSince": "2020-01-01"
27
27
  }
28
28
 
29
+ `consumerVersionSelectors.mainBranch`: if the key is specified, can only be set to `true`. Return the pacts for the configured `mainBranch` of each consumer. Use of this selector requires that the consumer has configured the `mainBranch` property, and has set a branch name when publishing the pacts.
30
+
29
31
  `consumerVersionSelectors.branch`: the branch name of the consumer versions to get the pacts for. Use of this selector requires that the consumer has configured a branch name when publishing the pacts.
30
32
 
31
- `consumerVersionSelectors.fallbackBranch`: the name of the branch to fallback to if the specified `branch` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features.
33
+ `consumerVersionSelectors.fallbackBranch`: the name of the branch to fallback to if the specified `branch` does not exist. Use of this property is discouraged as it may allow a pact to pass on a feature branch while breaking backwards compatibility with the main branch, which is generally not desired. It is better to use two separate consumer version selectors, one with the main branch name, and one with the feature branch name, rather than use this property.
32
34
 
33
35
  `consumerVersionSelectors.deployed`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are currently deployed to any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-deployment` CLI.
34
36
 
35
37
  `consumerVersionSelectors.released`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are released and currently supported in any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-release` CLI.
36
38
 
37
-
38
39
  `consumerVersionSelectors.deployedOrReleased`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are currently deployed or released and currently supported in any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-deployment` or `record-release` CLI.
39
40
 
40
-
41
41
  `consumerVersionSelectors.environment`: the name of the environment containing the consumer versions for which to return the pacts. Used to further qualify `{ "deployed": true }` or `{ "released": true }`. Normally, this would not be needed, as it is recommended to verify the pacts for all currently deployed/currently supported released versions.
42
42
 
43
43
  `consumerVersionSelectors.latest`: true. Used in conjuction with the `tag` and `branch` properties. When used with a `branch`, it may be `true` or the key ommitted (in which case it will be inferred to be `true`). This is because it only makes sense to verify the latest pact for a branch. If a `tag` is specified, and `latest` is `true`, then the latest pact for each of the consumers with that tag will be returned. If a `tag` is specified and the latest flag is *not* set to `true`, *all* the pacts with the specified tag will be returned. (This might seem a bit weird, but it's done this way to match the syntax used for the matrix query params. See https://docs.pact.io/selectors).
44
44
 
45
45
  `consumerVersionSelectors.consumer`: allows a selector to only be applied to a certain consumer.
46
46
 
47
+ `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.*
48
+
49
+ `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.*
50
+
47
51
  `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.
48
52
 
49
53
  `includePendingStatus`: true|false (default false). When true, a pending boolean will be added to the verificationProperties in the response, and an extra message will appear in the notices array to indicate why this pact is/is not in pending state. This will allow your code to handle the response based on only what is present in the response, and not have to do ifs based on the user's options together with the response. As requested in the "pacts for verification" issue, please print out these messages in the tests if possible. If not possible, perhaps create a separate task which will list the pact URLs and messages for debugging purposes.
50
54
 
51
55
  `includeWipPactsSince`: Date string. The date from which to include the "work in progress" pacts. See https://docs.pact.io/wip for more information on work in progress pacts.
52
56
 
53
- `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.*
54
-
55
- `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.*
56
-
57
57
  `providerVersionTags`: the tag name(s) 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. This parameter can be specified multiple times. *This field is still supported but it is recommended to use the `providerVersionBranch` in preference now.*
58
58
 
59
59
  ### Response body
@@ -63,11 +63,11 @@ module PactBroker
63
63
  end
64
64
 
65
65
  def consumer_name
66
- consumer && (consumer.name || (consumer.label && "labeled '#{consumer.label}'"))
66
+ consumer && (consumer.name || (consumer.label && "#{provider ? 'consumers ' : ''}labeled '#{consumer.label}'"))
67
67
  end
68
68
 
69
69
  def provider_name
70
- provider && (provider.name || (provider.label && "labeled '#{provider.label}'"))
70
+ provider && (provider.name || (provider.label && "#{consumer ? 'providers ' : ''}labeled '#{provider.label}'"))
71
71
  end
72
72
 
73
73
  def trigger_on_contract_content_changed?
@@ -224,6 +224,7 @@ module PactBroker
224
224
  puts "Creating #{webhook_prefix}webhook for contract changed event with uuid #{uuid}"
225
225
  uuid ||= SecureRandom.uuid
226
226
  default_body = {
227
+ "pactUrl" => "${pactbroker.pactUrl}",
227
228
  "eventName" => "${pactbroker.eventName}",
228
229
  "consumerName" => "${pactbroker.consumerName}",
229
230
  "consumerVersionNumber" => "${pactbroker.consumerVersionNumber}",
@@ -292,6 +292,7 @@ module PactBroker
292
292
  provider: webhook_provider
293
293
  )
294
294
  @webhook = PactBroker::Webhooks::Repository.new.create uuid, new_webhook, consumer, provider
295
+ set_created_at_if_set(params[:created_at], :webhooks, uuid: @webhook.uuid)
295
296
  self
296
297
  end
297
298
  # rubocop: enable Metrics/CyclomaticComplexity
@@ -472,6 +473,11 @@ module PactBroker
472
473
  self
473
474
  end
474
475
 
476
+ def set_now_date_time date_time
477
+ @now = date_time
478
+ self
479
+ end
480
+
475
481
  def add_day
476
482
  @now = @now + 1
477
483
  self
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = "2.87.0"
2
+ VERSION = "2.88.0"
3
3
  end
@@ -3,10 +3,14 @@ module PactBroker
3
3
  class AbbreviateNumber
4
4
 
5
5
  def self.call version_number
6
- if version_number
7
- version_number.gsub(/[A-Za-z0-9]{40}/) do | val |
8
- val[0..6]
9
- end
6
+ return version_number unless version_number
7
+
8
+ # hard limit of max 50 characters
9
+ version_length = version_number.length
10
+ return version_number[0...39] + "…" + version_number[version_length - 10...version_length] if version_length > 50
11
+
12
+ version_number.gsub(/[A-Za-z0-9]{40}/) do | val |
13
+ val[0..6]
10
14
  end
11
15
  end
12
16
  end
@@ -0,0 +1,62 @@
1
+ require "webmachine/adapters/rack_mapped"
2
+
3
+ module Webmachine
4
+ class DescribeRoutes
5
+
6
+ def self.call(webmachine_applications, search_term: nil)
7
+ path_mappings = webmachine_applications.flat_map { | webmachine_application | paths_to_resource_class_mappings(webmachine_application) }
8
+
9
+ if search_term
10
+ path_mappings = path_mappings.select{ |(route, _)| route[:path].include?(search_term) }
11
+ end
12
+
13
+ path_mappings.sort_by{ | mapping | mapping[:path] }
14
+ end
15
+
16
+ def self.paths_to_resource_class_mappings(webmachine_application)
17
+ webmachine_application.routes.collect do | route |
18
+ resource_path_absolute = Pathname.new(source_location_for(route.resource))
19
+ {
20
+ path: "/" + route.path_spec.collect{ |part| part.is_a?(Symbol) ? ":#{part}" : part }.join("/"),
21
+ resource_class: route.resource,
22
+ resource_name: route.instance_variable_get(:@bindings)[:resource_name],
23
+ resource_class_location: resource_path_absolute.relative_path_from(Pathname.pwd).to_s
24
+ }.merge(info_from_resource_instance(route))
25
+ end
26
+ end
27
+
28
+ def self.info_from_resource_instance(route)
29
+ with_no_logging do
30
+ path_info = { application_context: OpenStruct.new, pacticipant_name: "foo", pacticipant_version_number: "1", resource_name: "foo" }
31
+ path_info.default = "1"
32
+ dummy_request = Webmachine::Adapters::Rack::RackRequest.new("GET", "/", Webmachine::Headers["host" => "example.org"], nil, {}, {}, { "REQUEST_METHOD" => "GET" })
33
+ dummy_request.path_info = path_info
34
+ dummy_resource = route.resource.new(dummy_request, Webmachine::Response.new)
35
+ if dummy_resource
36
+ {
37
+ allowed_methods: dummy_resource.allowed_methods,
38
+ }
39
+ else
40
+ {}
41
+ end
42
+ end
43
+ rescue StandardError => e
44
+ puts "Could not determine instance info for #{route.resource}. #{e.class} - #{e.message}"
45
+ {}
46
+ end
47
+
48
+ def self.source_location_for(clazz)
49
+ first_instance_method_name = (clazz.instance_methods(false) + clazz.private_instance_methods(false)).first
50
+ clazz.instance_method(first_instance_method_name).source_location.first
51
+ end
52
+
53
+ # If we don't turn off the logging, we get metrics logging due to the instantiation of the Webmachine::RackRequest class
54
+ def self.with_no_logging
55
+ original_default_level = SemanticLogger.default_level
56
+ SemanticLogger.default_level = :fatal
57
+ yield
58
+ ensure
59
+ SemanticLogger.default_level = original_default_level
60
+ end
61
+ end
62
+ end
@@ -11,7 +11,7 @@ begin
11
11
  .publish_contract(consumer: "branch-consumer", provider: "branch-provider", consumer_version: "1", content_id: "1111", branch: "main")
12
12
  .publish_contract(consumer: "branch-consumer", provider: "branch-provider", consumer_version: "1", content_id: "1111", branch: "feat/x")
13
13
  .publish_contract(consumer: "branch-consumer", provider: "branch-provider", consumer_version: "2", content_id: "1111", branch: "feat/x")
14
- .get_pacts_for_verification(provider: "branch-provider", enable_pending: false)
14
+ .get_pacts_for_verification(provider: "branch-provider", enable_pending: false, consumer_version_selectors: [ { branch: "main" }, { branch: "feat/x" }])
15
15
  .verify_pact(
16
16
  provider_version_branch: "main",
17
17
  provider_version: "1",
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env ruby
2
+ require "json"
3
+ require "pathname"
4
+ require "fileutils"
5
+
6
+ EXAMPLES_FILE_PATTERN = "spec/fixtures/approvals/docs_*"
7
+ API_DOCS_DIR = Pathname.new("docs/api")
8
+
9
+
10
+ class Category
11
+ attr_reader :name, :examples, :not_options_examples
12
+
13
+ def initialize(name, examples)
14
+ @name = name
15
+ @examples = examples
16
+ @options_example = examples.find { | example | example[:request][:method] == "OPTIONS" }
17
+ @not_options_examples = examples.select { | example | example[:request][:method] != "OPTIONS" }
18
+ end
19
+
20
+ def path_template
21
+ not_options_examples.first[:request][:path_template]
22
+ end
23
+
24
+ def allowed_methods
25
+ if options_example
26
+ options_example[:response][:headers][:'Access-Control-Allow-Methods'].split(",").collect(&:strip).reject { |m| m == "OPTIONS" }
27
+ else
28
+ []
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :other_examples, :options_example
35
+
36
+ end
37
+
38
+ def generate_example_markdown_for_examples(name, examples)
39
+ category = Category.new(name, examples)
40
+
41
+
42
+ not_options_docs = category.not_options_examples.collect { | example | generate_example_markdown(example) }
43
+
44
+ allowed_methods = category.allowed_methods.collect{ | meth| "`#{meth}`"}.join(", ")
45
+
46
+ "
47
+ ## #{name}
48
+
49
+ Path: `#{category.path_template}`<br/>
50
+ Allowed methods: #{allowed_methods}<br/>
51
+ #{not_options_docs.join("\n")}
52
+ "
53
+ end
54
+
55
+ def generate_example_markdown(hash)
56
+ body = nil
57
+ if hash[:request][:body]
58
+ body = "Body:
59
+
60
+ ```
61
+ #{JSON.pretty_generate(hash[:request][:body])}
62
+ ```
63
+ "
64
+ end
65
+
66
+ "
67
+ ### #{hash[:request][:method]}
68
+
69
+ #### Request
70
+
71
+ Headers: `#{hash[:request][:headers]&.to_json}`<br/>
72
+ #{body}
73
+
74
+ #### Response
75
+
76
+ Status: `#{hash[:response][:status]}`<br/>
77
+ Headers: `#{hash[:response][:headers]&.to_json}`<br/>
78
+ Body:
79
+
80
+ ```
81
+ #{body_markdown(hash[:response][:body])}
82
+ ```
83
+ "
84
+ end
85
+
86
+ def body_markdown(body)
87
+ body.is_a?(Hash) ? JSON.pretty_generate(body) : body
88
+ end
89
+
90
+ file_names = Dir.glob(EXAMPLES_FILE_PATTERN)
91
+
92
+ examples = file_names.collect do | file_name |
93
+ JSON.parse(File.read(file_name), symbolize_names: true)
94
+ end
95
+
96
+ examples_by_category = examples.group_by { | hash | hash[:category] }
97
+
98
+ FileUtils.rm_rf(API_DOCS_DIR)
99
+ FileUtils.mkdir_p(API_DOCS_DIR)
100
+
101
+ examples_by_category.each do | category, category_examples |
102
+
103
+ examples_by_name = category_examples.sort_by{ |hash| hash[:order] }.group_by { | hash | hash[:name] }
104
+
105
+ docs = examples_by_name.collect do | name, examples_for_name |
106
+ generate_example_markdown_for_examples(name, examples_for_name)
107
+ end
108
+
109
+ file_name = (API_DOCS_DIR / category.upcase).to_s + ".md"
110
+ contents = "
111
+ # #{category}
112
+
113
+ #{docs.join("\n")}
114
+ "
115
+
116
+ File.open(file_name, "w") { |file| file << contents }
117
+ end
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ rm spec/fixtures/approvals/docs_webhooks*
3
+ bundle exec rspec spec/integration/webhooks_documentation_spec.rb
4
+ script/test/approval-all.sh
5
+ bundle exec rspec spec/integration/webhooks_documentation_spec.rb
6
+ script/docs/generate-api-docs.rb
7
+
8
+ git add spec/integration/webhooks_documentation_spec.rb
9
+ git add spec/fixtures/approvals
10
+ git add docs/api
11
+ git add script/docs/generate-api-docs.rb
@@ -0,0 +1,20 @@
1
+ {
2
+ "category": "Webhooks",
3
+ "name": "Executing a saved webhook",
4
+ "order": 33,
5
+ "request": {
6
+ "method": "OPTIONS",
7
+ "path_template": "/webhooks/:uuid/execute",
8
+ "path": "/webhooks/d2181b32-8b03-4daf-8cc0-d9168b2f6fac/execute",
9
+ "headers": {
10
+ "Accept": "application/hal+json"
11
+ }
12
+ },
13
+ "response": {
14
+ "status": 200,
15
+ "headers": {
16
+ "Access-Control-Allow-Methods": "POST, OPTIONS"
17
+ },
18
+ "body": ""
19
+ }
20
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "category": "Webhooks",
3
+ "name": "Executing a saved webhook",
4
+ "order": 35,
5
+ "request": {
6
+ "method": "POST",
7
+ "path_template": "/webhooks/:uuid/execute",
8
+ "path": "/webhooks/d2181b32-8b03-4daf-8cc0-d9168b2f6fac/execute",
9
+ "headers": {
10
+ "Content-Type": "application/json",
11
+ "Accept": "application/hal+json"
12
+ }
13
+ },
14
+ "response": {
15
+ "status": 200,
16
+ "headers": {
17
+ "Content-Type": "application/hal+json;charset=utf-8"
18
+ },
19
+ "body": {
20
+ "request": {
21
+ "headers": {
22
+ "accept": "*/*",
23
+ "user-agent": "Pact Broker v2.87.0",
24
+ "content-type": "application/json"
25
+ },
26
+ "body": {
27
+ "pactUrl": "http://example.org/pacts/provider/Bar/consumer/Foo/pact-version/3e193ecb37ad04b43ce974a38352c704b2e0ed6b/metadata/Y3ZuPTImdz10cnVl"
28
+ },
29
+ "url": "/example"
30
+ },
31
+ "response": {
32
+ "status": 200,
33
+ "headers": {
34
+ },
35
+ "body": ""
36
+ },
37
+ "logs": "[2021-09-01T10:07:21Z] DEBUG: Webhook context {\"base_url\":\"http://example.org\",\"event_name\":\"test\"}\n[2021-09-01T10:07:21Z] INFO: HTTP/1.1 POST https://example.org/example\n[2021-09-01T10:07:21Z] INFO: accept: */*\n[2021-09-01T10:07:21Z] INFO: user-agent: Pact Broker v2.87.0\n[2021-09-01T10:07:21Z] INFO: content-type: application/json\n[2021-09-01T10:07:21Z] INFO: {\"pactUrl\":\"http://example.org/pacts/provider/Bar/consumer/Foo/pact-version/3e193ecb37ad04b43ce974a38352c704b2e0ed6b/metadata/Y3ZuPTImdz10cnVl\"}\n[2021-09-01T10:07:21Z] INFO: HTTP/1.0 200 \n[2021-09-01T10:07:21Z] INFO: \n",
38
+ "success": true,
39
+ "_links": {
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "category": "Webhooks",
3
+ "name": "Executing an unsaved webhook",
4
+ "order": 37,
5
+ "request": {
6
+ "method": "OPTIONS",
7
+ "path_template": "/webhooks/execute",
8
+ "path": "/webhooks/execute",
9
+ "headers": {
10
+ "Accept": "application/hal+json"
11
+ }
12
+ },
13
+ "response": {
14
+ "status": 200,
15
+ "headers": {
16
+ "Access-Control-Allow-Methods": "POST, OPTIONS"
17
+ },
18
+ "body": ""
19
+ }
20
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "category": "Webhooks",
3
+ "name": "Executing an unsaved webhook",
4
+ "order": 39,
5
+ "request": {
6
+ "method": "POST",
7
+ "path_template": "/webhooks/execute",
8
+ "path": "/webhooks/execute",
9
+ "headers": {
10
+ "Content-Type": "application/json",
11
+ "Accept": "application/hal+json"
12
+ },
13
+ "body": {
14
+ "description": "an example webhook",
15
+ "events": [
16
+ {
17
+ "name": "contract_content_changed"
18
+ }
19
+ ],
20
+ "request": {
21
+ "method": "POST",
22
+ "url": "https://example.org/example",
23
+ "username": "username",
24
+ "password": "password",
25
+ "headers": {
26
+ "Accept": "application/json"
27
+ },
28
+ "body": {
29
+ "pactUrl": "${pactbroker.pactUrl}"
30
+ }
31
+ }
32
+ }
33
+ },
34
+ "response": {
35
+ "status": 200,
36
+ "headers": {
37
+ "Content-Type": "application/hal+json;charset=utf-8"
38
+ },
39
+ "body": {
40
+ "request": {
41
+ "headers": {
42
+ "accept": "application/json",
43
+ "user-agent": "Pact Broker v2.87.0",
44
+ "authorization": "**********"
45
+ },
46
+ "body": {
47
+ "pactUrl": "http://example.org/pacts/provider/Bar/consumer/Foo/pact-version/3e193ecb37ad04b43ce974a38352c704b2e0ed6b/metadata/Y3ZuPTImdz10cnVl"
48
+ },
49
+ "url": "/example"
50
+ },
51
+ "response": {
52
+ "status": 200,
53
+ "headers": {
54
+ },
55
+ "body": ""
56
+ },
57
+ "logs": "[2021-09-01T10:07:21Z] DEBUG: Webhook context {\"base_url\":\"http://example.org\",\"event_name\":\"test\"}\n[2021-09-01T10:07:21Z] INFO: HTTP/1.1 POST https://example.org/example\n[2021-09-01T10:07:21Z] INFO: accept: application/json\n[2021-09-01T10:07:21Z] INFO: user-agent: Pact Broker v2.87.0\n[2021-09-01T10:07:21Z] INFO: authorization: **********\n[2021-09-01T10:07:21Z] INFO: {\"pactUrl\":\"http://example.org/pacts/provider/Bar/consumer/Foo/pact-version/3e193ecb37ad04b43ce974a38352c704b2e0ed6b/metadata/Y3ZuPTImdz10cnVl\"}\n[2021-09-01T10:07:21Z] INFO: HTTP/1.0 200 \n[2021-09-01T10:07:21Z] INFO: \n",
58
+ "success": true,
59
+ "_links": {
60
+ }
61
+ }
62
+ }
63
+ }