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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/docs/CONFIGURATION.md +1 -1
- data/docs/api/WEBHOOKS.md +789 -0
- data/lib/pact_broker/api/decorators/pact_decorator.rb +12 -0
- data/lib/pact_broker/api/resources/default_base_resource.rb +1 -1
- data/lib/pact_broker/api/resources/environment.rb +3 -3
- data/lib/pact_broker/api/resources/metadata_resource_methods.rb +33 -3
- data/lib/pact_broker/api/resources/pact_version.rb +4 -0
- data/lib/pact_broker/api/resources/previous_distinct_pact_version.rb +1 -1
- data/lib/pact_broker/api/resources/webhook_execution.rb +7 -3
- data/lib/pact_broker/api.rb +5 -0
- data/lib/pact_broker/deployments/environment_service.rb +3 -1
- data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +7 -7
- data/lib/pact_broker/domain/webhook.rb +2 -2
- data/lib/pact_broker/test/http_test_data_builder.rb +1 -0
- data/lib/pact_broker/test/test_data_builder.rb +6 -0
- data/lib/pact_broker/version.rb +1 -1
- data/lib/pact_broker/versions/abbreviate_number.rb +8 -4
- data/lib/webmachine/describe_routes.rb +62 -0
- data/script/data/branches.rb +1 -1
- data/script/docs/generate-api-docs.rb +117 -0
- data/script/docs/regenerate-api-docs.sh +11 -0
- data/spec/fixtures/approvals/docs_webhooks_executing_a_saved_webhook_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_executing_a_saved_webhook_post.approved.json +43 -0
- data/spec/fixtures/approvals/docs_webhooks_executing_an_unsaved_webhook_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_executing_an_unsaved_webhook_post.approved.json +63 -0
- data/spec/fixtures/approvals/docs_webhooks_logs_of_triggered_webhook_get.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_logs_of_triggered_webhook_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_pact_webhooks_get.approved.json +45 -0
- data/spec/fixtures/approvals/docs_webhooks_pact_webhooks_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_pact_publication_get.approved.json +52 -0
- data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_pact_publication_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_verification_publication_get.approved.json +32 -0
- data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_verification_publication_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_webhook_get.approved.json +74 -0
- data/spec/fixtures/approvals/docs_webhooks_webhook_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_webhook_put.approved.json +77 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_for_a_provider_get.approved.json +41 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_for_a_provider_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_and_provider_get.approved.json +45 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_and_provider_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_get.approved.json +41 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_get.approved.json +45 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_options.approved.json +20 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_post.approved.json +78 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_status_get.approved.json +79 -0
- data/spec/fixtures/approvals/docs_webhooks_webhooks_status_options.approved.json +20 -0
- data/spec/fixtures/approvals/get_provider_pacts_for_verification.approved.json +1 -2
- data/spec/fixtures/approvals/publish_contract_no_branch.approved.json +1 -2
- data/spec/fixtures/approvals/publish_contract_nothing_exists.approved.json +1 -2
- data/spec/fixtures/approvals/publish_contract_nothing_exists_with_webhook.approved.json +1 -2
- data/spec/fixtures/approvals/publish_contract_verification_already_exists.approved.json +1 -2
- data/spec/fixtures/approvals/publish_contract_with_validation_error.approved.json +1 -2
- data/spec/integration/pact_metdata_spec.rb +105 -0
- data/spec/integration/webhooks_documentation_spec.rb +348 -0
- data/spec/lib/pact_broker/deployments/environment_service_spec.rb +21 -0
- data/spec/lib/pact_broker/domain/webhook_spec.rb +35 -0
- data/spec/lib/pact_broker/versions/abbreviate_number_spec.rb +2 -1
- data/spec/support/documentation.rb +64 -0
- data/spec/support/rack_helpers.rb +1 -1
- data/tasks/development.rake +14 -13
- 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,
|
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 =
|
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
|
67
|
-
environment_service.
|
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,
|
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
|
15
|
-
metadata
|
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
|
@@ -37,10 +37,14 @@ module PactBroker
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def malformed_request?
|
40
|
-
if
|
41
|
-
|
40
|
+
if request.post?
|
41
|
+
if uuid
|
42
|
+
false
|
43
|
+
else
|
44
|
+
webhook_validation_errors?(webhook)
|
45
|
+
end
|
42
46
|
else
|
43
|
-
|
47
|
+
super
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
data/lib/pact_broker/api.rb
CHANGED
@@ -28,7 +28,7 @@ module PactBroker
|
|
28
28
|
environment.save
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
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.
|
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
|
data/lib/pact_broker/version.rb
CHANGED
@@ -3,10 +3,14 @@ module PactBroker
|
|
3
3
|
class AbbreviateNumber
|
4
4
|
|
5
5
|
def self.call version_number
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
data/script/data/branches.rb
CHANGED
@@ -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
|
+
}
|