pacto 0.3.1 → 0.4.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +29 -7
- data/.travis.yml +8 -1
- data/CONTRIBUTING.md +3 -6
- data/Gemfile +13 -2
- data/Guardfile +4 -4
- data/Procfile +1 -0
- data/README.md +47 -13
- data/Rakefile +66 -19
- data/TODO.md +33 -10
- data/bin/pacto +4 -0
- data/changelog.md +30 -0
- data/docs/configuration.md +69 -0
- data/docs/consumer.md +18 -0
- data/docs/cops.md +39 -0
- data/docs/forensics.md +66 -0
- data/docs/generation.md +65 -0
- data/docs/rake_tasks.md +10 -0
- data/docs/rspec.md +0 -0
- data/docs/samples.md +133 -0
- data/docs/server.md +34 -0
- data/docs/server_cli.md +18 -0
- data/docs/stenographer.md +20 -0
- data/features/configuration/strict_matchers.feature +10 -10
- data/features/evolve/existing_services.feature +12 -10
- data/features/generate/generation.feature +11 -11
- data/features/steps/pacto_steps.rb +17 -12
- data/features/stub/templates.feature +4 -4
- data/features/support/env.rb +21 -9
- data/features/validate/meta_validation.feature +9 -17
- data/features/validate/validation.feature +5 -6
- data/lib/pacto.rb +41 -33
- data/lib/pacto/actor.rb +5 -0
- data/lib/pacto/actors/from_examples.rb +67 -0
- data/lib/pacto/actors/json_generator.rb +20 -0
- data/lib/pacto/cli.rb +75 -0
- data/lib/pacto/cli/helpers.rb +20 -0
- data/lib/pacto/consumer.rb +80 -0
- data/lib/pacto/consumer/faraday_driver.rb +34 -0
- data/lib/pacto/contract.rb +48 -20
- data/lib/pacto/contract_builder.rb +125 -0
- data/lib/pacto/contract_factory.rb +31 -12
- data/lib/pacto/contract_files.rb +1 -0
- data/lib/pacto/contract_set.rb +12 -0
- data/lib/pacto/cops.rb +46 -0
- data/lib/pacto/cops/body_cop.rb +23 -0
- data/lib/pacto/cops/request_body_cop.rb +10 -0
- data/lib/pacto/cops/response_body_cop.rb +10 -0
- data/lib/pacto/{validators/response_header_validator.rb → cops/response_header_cop.rb} +9 -15
- data/lib/pacto/cops/response_status_cop.rb +18 -0
- data/lib/pacto/core/configuration.rb +16 -5
- data/lib/pacto/core/contract_registry.rb +13 -32
- data/lib/pacto/core/hook.rb +1 -0
- data/lib/pacto/core/http_middleware.rb +23 -0
- data/lib/pacto/core/investigation_registry.rb +60 -0
- data/lib/pacto/core/modes.rb +1 -0
- data/lib/pacto/core/pacto_request.rb +59 -0
- data/lib/pacto/core/pacto_response.rb +41 -0
- data/lib/pacto/dash.rb +9 -0
- data/lib/pacto/erb_processor.rb +1 -0
- data/lib/pacto/exceptions/invalid_contract.rb +1 -0
- data/lib/pacto/extensions.rb +3 -16
- data/lib/pacto/forensics/investigation_filter.rb +90 -0
- data/lib/pacto/forensics/investigation_matcher.rb +80 -0
- data/lib/pacto/generator.rb +31 -53
- data/lib/pacto/generator/filters.rb +8 -7
- data/lib/pacto/generator/hint.rb +26 -0
- data/lib/pacto/generator/native_contract_generator.rb +74 -0
- data/lib/pacto/hooks/erb_hook.rb +2 -1
- data/lib/pacto/investigation.rb +49 -0
- data/lib/pacto/logger.rb +1 -0
- data/lib/pacto/meta_schema.rb +12 -6
- data/lib/pacto/native_contract_factory.rb +60 -0
- data/lib/pacto/observers/stenographer.rb +42 -0
- data/lib/pacto/provider.rb +27 -0
- data/lib/pacto/rake_task.rb +25 -70
- data/lib/pacto/request_clause.rb +31 -29
- data/lib/pacto/request_pattern.rb +20 -3
- data/lib/pacto/resettable.rb +22 -0
- data/lib/pacto/response_clause.rb +5 -12
- data/lib/pacto/rspec.rb +38 -31
- data/lib/pacto/server.rb +4 -0
- data/lib/pacto/stubs/uri_pattern.rb +21 -11
- data/lib/pacto/stubs/webmock_adapter.rb +69 -34
- data/lib/pacto/swagger_contract_factory.rb +90 -0
- data/lib/pacto/test_helper.rb +37 -0
- data/lib/pacto/ui.rb +32 -2
- data/lib/pacto/uri.rb +2 -1
- data/lib/pacto/version.rb +2 -1
- data/pacto-server.gemspec +24 -0
- data/pacto.gemspec +13 -9
- data/resources/contract_schema.json +46 -18
- data/resources/draft-04.json +150 -0
- data/sample_apis/album/cover_api.rb +12 -0
- data/sample_apis/config.ru +25 -0
- data/sample_apis/echo_api.rb +26 -0
- data/sample_apis/files_api.rb +50 -0
- data/sample_apis/hello_api.rb +14 -0
- data/sample_apis/ping_api.rb +11 -0
- data/sample_apis/reverse_api.rb +20 -0
- data/samples/README.md +11 -0
- data/samples/Rakefile +2 -0
- data/samples/configuration.rb +33 -0
- data/samples/consumer.rb +15 -0
- data/samples/contracts/README.md +1 -0
- data/samples/contracts/contract.js +93 -0
- data/samples/contracts/get_album_cover.json +48 -0
- data/samples/contracts/localhost/api/echo.json +37 -0
- data/samples/contracts/localhost/api/ping.json +38 -0
- data/samples/cops.rb +30 -0
- data/samples/forensics.rb +54 -0
- data/samples/generation.rb +48 -0
- data/samples/rake_tasks.sh +7 -0
- data/samples/rspec.rb +1 -0
- data/samples/samples.rb +92 -0
- data/samples/scripts/bootstrap +2 -0
- data/samples/scripts/wrapper +11 -0
- data/samples/server.rb +24 -0
- data/samples/server_cli.sh +12 -0
- data/samples/stenographer.rb +17 -0
- data/spec/coveralls_helper.rb +1 -0
- data/spec/fabricators/contract_fabricator.rb +94 -0
- data/spec/fabricators/http_fabricator.rb +48 -0
- data/spec/fabricators/webmock_fabricator.rb +24 -0
- data/spec/{unit/data → fixtures/contracts}/contract.json +2 -2
- data/spec/fixtures/contracts/contract_with_examples.json +58 -0
- data/spec/{unit/data → fixtures/contracts}/simple_contract.json +5 -3
- data/spec/{integration/data → fixtures/contracts}/strict_contract.json +5 -3
- data/spec/{integration/data → fixtures/contracts}/templating_contract.json +3 -2
- data/spec/{integration/data/simple_contract.json → fixtures/deprecated_contracts/deprecated_contract.json} +2 -1
- data/spec/fixtures/swagger/petstore.yaml +101 -0
- data/spec/integration/e2e_spec.rb +19 -20
- data/spec/integration/forensics/integration_matcher_spec.rb +90 -0
- data/spec/integration/rspec_spec.rb +22 -25
- data/spec/integration/templating_spec.rb +7 -6
- data/spec/pacto/dummy_server.rb +4 -0
- data/spec/pacto/{server → dummy_server}/dummy.rb +7 -6
- data/spec/pacto/dummy_server/jruby_workaround_helper.rb +23 -0
- data/spec/pacto/{server → dummy_server}/playback_servlet.rb +3 -2
- data/spec/spec_helper.rb +16 -7
- data/spec/unit/actors/from_examples_spec.rb +70 -0
- data/spec/unit/actors/json_generator_spec.rb +105 -0
- data/spec/unit/pacto/actor_spec.rb +23 -0
- data/spec/unit/pacto/configuration_spec.rb +7 -6
- data/spec/unit/pacto/consumer/faraday_driver_spec.rb +40 -0
- data/spec/unit/pacto/contract_builder_spec.rb +89 -0
- data/spec/unit/pacto/contract_factory_spec.rb +62 -11
- data/spec/unit/pacto/contract_files_spec.rb +1 -0
- data/spec/unit/pacto/contract_set_spec.rb +36 -0
- data/spec/unit/pacto/contract_spec.rb +51 -39
- data/spec/unit/pacto/cops/body_cop_spec.rb +107 -0
- data/spec/unit/pacto/{validators/response_header_validator_spec.rb → cops/response_header_cop_spec.rb} +30 -19
- data/spec/unit/pacto/cops/response_status_cop_spec.rb +26 -0
- data/spec/unit/pacto/cops_spec.rb +75 -0
- data/spec/unit/pacto/core/configuration_spec.rb +6 -5
- data/spec/unit/pacto/core/contract_registry_spec.rb +16 -83
- data/spec/unit/pacto/core/http_middleware_spec.rb +36 -0
- data/spec/unit/pacto/core/investigation_spec.rb +62 -0
- data/spec/unit/pacto/core/modes_spec.rb +5 -4
- data/spec/unit/pacto/erb_processor_spec.rb +3 -2
- data/spec/unit/pacto/extensions_spec.rb +10 -20
- data/spec/unit/pacto/generator/filters_spec.rb +11 -10
- data/spec/unit/pacto/generator/native_contract_generator_spec.rb +171 -0
- data/spec/unit/{hooks → pacto/hooks}/erb_hook_spec.rb +18 -11
- data/spec/unit/pacto/investigation_registry_spec.rb +77 -0
- data/spec/unit/pacto/logger_spec.rb +6 -5
- data/spec/unit/pacto/meta_schema_spec.rb +5 -4
- data/spec/unit/pacto/native_contract_factory_spec.rb +26 -0
- data/spec/unit/pacto/pacto_spec.rb +13 -28
- data/spec/unit/pacto/request_clause_spec.rb +16 -51
- data/spec/unit/pacto/request_pattern_spec.rb +6 -5
- data/spec/unit/pacto/response_clause_spec.rb +6 -19
- data/spec/unit/pacto/server/playback_servlet_spec.rb +21 -18
- data/spec/unit/pacto/stubs/observers/stenographer_spec.rb +33 -0
- data/spec/unit/pacto/stubs/uri_pattern_spec.rb +39 -11
- data/spec/unit/pacto/stubs/webmock_adapter_spec.rb +67 -117
- data/spec/unit/pacto/swagger_contract_factory_spec.rb +56 -0
- data/spec/unit/pacto/uri_spec.rb +1 -0
- data/tasks/release.rake +57 -0
- metadata +247 -76
- data/.rubocop-todo.yml +0 -24
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/CHANGELOG +0 -12
- data/features/validate/body_only.feature +0 -85
- data/lib/pacto/contract_list.rb +0 -17
- data/lib/pacto/contract_validator.rb +0 -29
- data/lib/pacto/core/validation_registry.rb +0 -40
- data/lib/pacto/stubs/webmock_helper.rb +0 -69
- data/lib/pacto/validation.rb +0 -54
- data/lib/pacto/validators/body_validator.rb +0 -49
- data/lib/pacto/validators/request_body_validator.rb +0 -26
- data/lib/pacto/validators/response_body_validator.rb +0 -26
- data/lib/pacto/validators/response_status_validator.rb +0 -24
- data/spec/pacto/server.rb +0 -2
- data/spec/unit/pacto/contract_list_spec.rb +0 -35
- data/spec/unit/pacto/contract_validator_spec.rb +0 -85
- data/spec/unit/pacto/core/validation_registry_spec.rb +0 -76
- data/spec/unit/pacto/core/validation_spec.rb +0 -60
- data/spec/unit/pacto/generator_spec.rb +0 -132
- data/spec/unit/pacto/stubs/webmock_helper_spec.rb +0 -20
- data/spec/unit/pacto/validators/body_validator_spec.rb +0 -118
- data/spec/unit/pacto/validators/response_status_validator_spec.rb +0 -20
@@ -1,43 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
module Pacto
|
2
|
-
class
|
3
|
-
def initialize
|
4
|
-
@registry = Hash.new { |hash, key| hash[key] = Set.new }
|
5
|
-
end
|
6
|
-
|
7
|
-
def [](tag)
|
8
|
-
@registry[tag]
|
9
|
-
end
|
3
|
+
class ContractNotFound < StandardError; end
|
10
4
|
|
11
|
-
|
12
|
-
|
5
|
+
class ContractRegistry < Set
|
6
|
+
include Logger
|
13
7
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
self
|
8
|
+
def register(contract)
|
9
|
+
fail ArgumentError, 'expected a Pacto::Contract' unless contract.is_a? Contract
|
10
|
+
logger.debug "Registering #{contract.request_pattern} as #{contract.name}"
|
11
|
+
add contract
|
19
12
|
end
|
20
13
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
merged_contracts.each do |contract|
|
27
|
-
contract.stub_contract! values
|
28
|
-
end
|
29
|
-
|
30
|
-
self
|
14
|
+
def find_by_name(name)
|
15
|
+
contract = select { |c| c.name == name }.first
|
16
|
+
fail ContractNotFound, "No contract found for #{name}" unless contract
|
17
|
+
contract
|
31
18
|
end
|
32
19
|
|
33
20
|
def contracts_for(request_signature)
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def all_contracts
|
40
|
-
@registry.values.inject(Set.new, :+)
|
21
|
+
select { |c| c.matches? request_signature }
|
41
22
|
end
|
42
23
|
end
|
43
24
|
end
|
data/lib/pacto/core/hook.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
module Pacto
|
5
|
+
module Core
|
6
|
+
class HTTPMiddleware
|
7
|
+
include Logger
|
8
|
+
include Observable
|
9
|
+
|
10
|
+
def process(request, response)
|
11
|
+
contracts = Pacto.contracts_for request
|
12
|
+
Pacto.configuration.hook.process contracts, request, response
|
13
|
+
|
14
|
+
changed
|
15
|
+
begin
|
16
|
+
notify_observers request, response
|
17
|
+
rescue StandardError => e
|
18
|
+
logger.error(e)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Pacto
|
3
|
+
class InvestigationRegistry
|
4
|
+
include Singleton
|
5
|
+
include Logger
|
6
|
+
include Resettable
|
7
|
+
attr_reader :investigations
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@investigations = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.reset!
|
14
|
+
instance.reset!
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset!
|
18
|
+
@investigations.clear
|
19
|
+
@stenographer = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def validated?(request_pattern)
|
23
|
+
matched_investigations = @investigations.select do |investigation|
|
24
|
+
request_pattern.matches? investigation.request
|
25
|
+
end
|
26
|
+
matched_investigations unless matched_investigations.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
def register_investigation(investigation)
|
30
|
+
@investigations << investigation
|
31
|
+
stenographer.log_investigation investigation
|
32
|
+
logger.info "Detected #{investigation.summary}"
|
33
|
+
logger.debug(investigation.to_s) unless investigation.successful?
|
34
|
+
investigation
|
35
|
+
end
|
36
|
+
|
37
|
+
def unmatched_investigations
|
38
|
+
@investigations.select do |investigation|
|
39
|
+
investigation.contract.nil?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def failed_investigations
|
44
|
+
@investigations.select do |investigation|
|
45
|
+
!investigation.successful?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def stenographer
|
52
|
+
@stenographer ||= create_stenographer
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_stenographer
|
56
|
+
stenographer_log = File.open(Pacto.configuration.stenographer_log_file, 'a+')
|
57
|
+
Pacto::Observers::Stenographer.new stenographer_log
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/pacto/core/modes.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'hashie/mash'
|
3
|
+
|
4
|
+
module Pacto
|
5
|
+
class PactoRequest
|
6
|
+
# FIXME: Need case insensitive header lookup, but case-sensitive storage
|
7
|
+
attr_accessor :headers, :body, :method, :uri
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
mash = Hashie::Mash.new data
|
11
|
+
@headers = mash.headers.nil? ? {} : mash.headers
|
12
|
+
@body = mash.body
|
13
|
+
@method = mash[:method]
|
14
|
+
@uri = mash.uri
|
15
|
+
normalize
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_hash
|
19
|
+
{
|
20
|
+
method: method,
|
21
|
+
uri: uri,
|
22
|
+
headers: headers,
|
23
|
+
body: body
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
string = Pacto::UI.colorize_method(method)
|
29
|
+
string << " #{relative_uri}"
|
30
|
+
string << " with body (#{body.bytesize} bytes)" if body
|
31
|
+
string
|
32
|
+
end
|
33
|
+
|
34
|
+
def relative_uri
|
35
|
+
uri.to_s.tap do |s|
|
36
|
+
s.slice!(uri.normalized_site)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parsed_body
|
41
|
+
if body.is_a?(String) && content_type == 'application/json'
|
42
|
+
JSON.parse(body)
|
43
|
+
else
|
44
|
+
body
|
45
|
+
end
|
46
|
+
rescue
|
47
|
+
body
|
48
|
+
end
|
49
|
+
|
50
|
+
def content_type
|
51
|
+
headers['Content-Type']
|
52
|
+
end
|
53
|
+
|
54
|
+
def normalize
|
55
|
+
@method = @method.to_s.downcase.to_sym
|
56
|
+
@uri = @uri.normalize if @uri
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Pacto
|
3
|
+
class PactoResponse
|
4
|
+
# FIXME: Need case insensitive header lookup, but case-sensitive storage
|
5
|
+
attr_accessor :headers, :body, :status, :parsed_body
|
6
|
+
attr_reader :parsed_body
|
7
|
+
|
8
|
+
def initialize(data)
|
9
|
+
mash = Hashie::Mash.new data
|
10
|
+
@headers = mash.headers.nil? ? {} : mash.headers
|
11
|
+
@body = mash.body
|
12
|
+
@status = mash.status.to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_hash
|
16
|
+
{
|
17
|
+
status: status,
|
18
|
+
headers: headers,
|
19
|
+
body: body
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
string = "STATUS: #{status}"
|
25
|
+
string << " with body (#{body.bytesize} bytes)" if body
|
26
|
+
string
|
27
|
+
end
|
28
|
+
|
29
|
+
def parsed_body
|
30
|
+
if body.is_a?(String) && content_type == 'application/json'
|
31
|
+
JSON.parse(body)
|
32
|
+
else
|
33
|
+
body
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def content_type
|
38
|
+
headers['Content-Type']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/pacto/dash.rb
ADDED
data/lib/pacto/erb_processor.rb
CHANGED
data/lib/pacto/extensions.rb
CHANGED
@@ -1,32 +1,19 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
module Pacto
|
2
3
|
module Extensions
|
3
4
|
# Adapted from Faraday
|
4
5
|
HeaderKeyMap = Hash.new do |map, key|
|
5
6
|
split_char = key.to_s.include?('-') ? '-' : '_'
|
6
7
|
map[key] = key.to_s.split(split_char). # :user_agent => %w(user agent)
|
7
|
-
each
|
8
|
+
each(&:capitalize!). # => %w(User Agent)
|
8
9
|
join('-') # => "User-Agent"
|
9
10
|
end
|
10
11
|
HeaderKeyMap[:etag] = 'ETag'
|
11
12
|
|
12
13
|
def self.normalize_header_keys(headers)
|
13
|
-
headers.
|
14
|
+
headers.each_with_object({}) do |(key, value), normalized|
|
14
15
|
normalized[HeaderKeyMap[key]] = value
|
15
|
-
normalized
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
module HashSubsetOf
|
20
|
-
# FIXME: Only used by HashMergeProcessor, which I'd like to deprecate
|
21
|
-
def normalize_keys
|
22
|
-
reduce({}) do |normalized, (key, value)|
|
23
|
-
normalized[key.to_s.downcase] = value
|
24
|
-
normalized
|
25
|
-
end
|
26
16
|
end
|
27
17
|
end
|
28
18
|
end
|
29
19
|
end
|
30
|
-
|
31
|
-
# FIXME: Let's not extend Hash...
|
32
|
-
Hash.send(:include, Pacto::Extensions::HashSubsetOf)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Pacto
|
3
|
+
module Forensics
|
4
|
+
class FilterExhaustedError < StandardError
|
5
|
+
attr_reader :suspects
|
6
|
+
|
7
|
+
def initialize(msg, filter, suspects = [])
|
8
|
+
@suspects = suspects
|
9
|
+
if filter.respond_to? :description
|
10
|
+
msg = "#{msg} #{filter.description}"
|
11
|
+
else
|
12
|
+
msg = "#{msg} #{filter}"
|
13
|
+
end
|
14
|
+
super(msg)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvestigationFilter
|
19
|
+
# CaseEquality makes sense for some of the rspec matchers and compound matching behavior
|
20
|
+
# rubocop:disable Style/CaseEquality
|
21
|
+
attr_reader :investigations, :filtered_investigations
|
22
|
+
|
23
|
+
def initialize(investigations, track_suspects = true)
|
24
|
+
investigations ||= []
|
25
|
+
@investigations = investigations.dup
|
26
|
+
@filtered_investigations = @investigations.dup
|
27
|
+
@track_suspects = track_suspects
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_name(contract_name)
|
31
|
+
@filtered_investigations.keep_if do |investigation|
|
32
|
+
return false if investigation.contract.nil?
|
33
|
+
|
34
|
+
contract_name === investigation.contract.name
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_request(request_constraints)
|
40
|
+
return self if request_constraints.nil?
|
41
|
+
[:headers, :body].each do |section|
|
42
|
+
filter_request_section(section, request_constraints[section])
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_response(response_constraints)
|
48
|
+
return self if response_constraints.nil?
|
49
|
+
[:headers, :body].each do |section|
|
50
|
+
filter_response_section(section, response_constraints[section])
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def successful_investigations
|
56
|
+
@filtered_investigations.select(&:successful?)
|
57
|
+
end
|
58
|
+
|
59
|
+
def unsuccessful_investigations
|
60
|
+
@filtered_investigations - successful_investigations
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def filter_request_section(section, filter)
|
66
|
+
suspects = []
|
67
|
+
section = :parsed_body if section == :body
|
68
|
+
@filtered_investigations.keep_if do |investigation|
|
69
|
+
candidate = investigation.request.send(section)
|
70
|
+
suspects << candidate if @track_suspects
|
71
|
+
filter === candidate
|
72
|
+
end if filter
|
73
|
+
fail FilterExhaustedError.new("no requests matched #{section}", filter, suspects) if @filtered_investigations.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
def filter_response_section(section, filter)
|
77
|
+
section = :parsed_body if section == :body
|
78
|
+
suspects = []
|
79
|
+
@filtered_investigations.keep_if do |investigation|
|
80
|
+
candidate = investigation.response.send(section)
|
81
|
+
suspects << candidate if @track_suspects
|
82
|
+
filter === candidate
|
83
|
+
end if filter
|
84
|
+
fail FilterExhaustedError.new("no responses matched #{section}", filter, suspects) if @filtered_investigations.empty?
|
85
|
+
end
|
86
|
+
|
87
|
+
# rubocop:enable Style/CaseEquality
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
RSpec::Matchers.define :have_investigated do |service_name|
|
3
|
+
match do
|
4
|
+
investigations = Pacto::InvestigationRegistry.instance.investigations
|
5
|
+
@service_name = service_name
|
6
|
+
|
7
|
+
begin
|
8
|
+
@investigation_filter = Pacto::Forensics::InvestigationFilter.new(investigations)
|
9
|
+
@investigation_filter.with_name(@service_name)
|
10
|
+
.with_request(@request_constraints)
|
11
|
+
.with_response(@response_constraints)
|
12
|
+
|
13
|
+
@matched_investigations = @investigation_filter.filtered_investigations
|
14
|
+
@unsuccessful_investigations = @investigation_filter.unsuccessful_investigations
|
15
|
+
|
16
|
+
!@matched_investigations.empty? && (@allow_citations || @unsuccessful_investigations.empty?)
|
17
|
+
rescue Pacto::Forensics::FilterExhaustedError => e
|
18
|
+
@filter_error = e
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def describe(obj)
|
24
|
+
obj.respond_to?(:description) ? obj.description : obj.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
description do
|
28
|
+
buffer = StringIO.new
|
29
|
+
buffer.puts "to have investigated #{@service_name}"
|
30
|
+
if @request_constraints
|
31
|
+
buffer.puts ' with request matching'
|
32
|
+
@request_constraints.each do |k, v|
|
33
|
+
buffer.puts " #{k}: #{describe(v)}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
buffer.puts ' and' if @request_constraints && @response_constraints
|
37
|
+
if @response_constraint
|
38
|
+
buffer.puts ' with response matching'
|
39
|
+
@request_constraints.each do |k, v|
|
40
|
+
buffer.puts " #{k}: #{describe(v)}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
buffer.string
|
44
|
+
end
|
45
|
+
|
46
|
+
chain :with_request do |request_constraints|
|
47
|
+
@request_constraints = request_constraints
|
48
|
+
end
|
49
|
+
|
50
|
+
chain :with_response do |response_constraints|
|
51
|
+
@response_constraints = response_constraints
|
52
|
+
end
|
53
|
+
|
54
|
+
chain :allow_citations do
|
55
|
+
@allow_citations = true
|
56
|
+
end
|
57
|
+
|
58
|
+
failure_message do | group |
|
59
|
+
buffer = StringIO.new
|
60
|
+
buffer.puts "expected #{group} " + description
|
61
|
+
if @filter_error
|
62
|
+
buffer.puts "but #{@filter_error.message}"
|
63
|
+
unless @filter_error.suspects.empty?
|
64
|
+
buffer.puts ' suspects:'
|
65
|
+
@filter_error.suspects.each do |suspect|
|
66
|
+
buffer.puts " #{suspect}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
elsif @matched_investigations.empty?
|
70
|
+
investigated_services = @investigation_filter.investigations.map(&:contract).compact.map(&:name).uniq
|
71
|
+
buffer.puts "but it was not among the services investigated: #{investigated_services}"
|
72
|
+
elsif @unsuccessful_investigations
|
73
|
+
buffer.puts 'but investigation errors were found:'
|
74
|
+
@unsuccessful_investigations.each do |investigation|
|
75
|
+
buffer.puts " #{investigation}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
buffer.string
|
79
|
+
end
|
80
|
+
end
|