pacto 0.3.0.pre → 0.3.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 (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rubocop-todo.yml +0 -27
  4. data/.rubocop.yml +9 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +4 -5
  8. data/CONTRIBUTING.md +112 -0
  9. data/Gemfile +5 -0
  10. data/Guardfile +18 -13
  11. data/README.md +157 -101
  12. data/Rakefile +3 -3
  13. data/features/configuration/strict_matchers.feature +97 -0
  14. data/features/evolve/README.md +11 -0
  15. data/features/evolve/existing_services.feature +82 -0
  16. data/features/generate/README.md +5 -0
  17. data/features/generate/generation.feature +28 -0
  18. data/features/steps/pacto_steps.rb +75 -0
  19. data/features/stub/README.md +2 -0
  20. data/features/stub/templates.feature +46 -0
  21. data/features/support/env.rb +11 -5
  22. data/features/validate/README.md +1 -0
  23. data/features/validate/body_only.feature +85 -0
  24. data/features/{journeys/validation.feature → validate/meta_validation.feature} +41 -24
  25. data/features/validate/validation.feature +36 -0
  26. data/lib/pacto.rb +61 -33
  27. data/lib/pacto/contract.rb +18 -15
  28. data/lib/pacto/contract_factory.rb +14 -11
  29. data/lib/pacto/contract_files.rb +17 -0
  30. data/lib/pacto/contract_list.rb +17 -0
  31. data/lib/pacto/contract_validator.rb +29 -0
  32. data/lib/pacto/core/configuration.rb +19 -17
  33. data/lib/pacto/core/contract_registry.rb +43 -0
  34. data/lib/pacto/core/{callback.rb → hook.rb} +3 -3
  35. data/lib/pacto/core/modes.rb +33 -0
  36. data/lib/pacto/core/validation_registry.rb +45 -0
  37. data/lib/pacto/erb_processor.rb +0 -1
  38. data/lib/pacto/extensions.rb +18 -4
  39. data/lib/pacto/generator.rb +34 -49
  40. data/lib/pacto/generator/filters.rb +41 -0
  41. data/lib/pacto/hooks/erb_hook.rb +4 -3
  42. data/lib/pacto/logger.rb +4 -2
  43. data/lib/pacto/meta_schema.rb +4 -2
  44. data/lib/pacto/rake_task.rb +28 -25
  45. data/lib/pacto/request_clause.rb +43 -0
  46. data/lib/pacto/request_pattern.rb +8 -0
  47. data/lib/pacto/response_clause.rb +15 -0
  48. data/lib/pacto/rspec.rb +102 -0
  49. data/lib/pacto/stubs/uri_pattern.rb +23 -0
  50. data/lib/pacto/stubs/webmock_adapter.rb +69 -0
  51. data/lib/pacto/stubs/webmock_helper.rb +71 -0
  52. data/lib/pacto/ui.rb +7 -0
  53. data/lib/pacto/uri.rb +9 -0
  54. data/lib/pacto/validation.rb +57 -0
  55. data/lib/pacto/validators/body_validator.rb +41 -0
  56. data/lib/pacto/validators/request_body_validator.rb +23 -0
  57. data/lib/pacto/validators/response_body_validator.rb +23 -0
  58. data/lib/pacto/validators/response_header_validator.rb +49 -0
  59. data/lib/pacto/validators/response_status_validator.rb +24 -0
  60. data/lib/pacto/version.rb +1 -1
  61. data/pacto.gemspec +33 -29
  62. data/resources/contract_schema.json +8 -176
  63. data/resources/draft-03.json +174 -0
  64. data/spec/integration/data/strict_contract.json +2 -2
  65. data/spec/integration/e2e_spec.rb +22 -31
  66. data/spec/integration/rspec_spec.rb +94 -0
  67. data/spec/integration/templating_spec.rb +9 -12
  68. data/{lib → spec}/pacto/server.rb +0 -0
  69. data/{lib → spec}/pacto/server/dummy.rb +11 -8
  70. data/{lib → spec}/pacto/server/playback_servlet.rb +1 -1
  71. data/spec/spec_helper.rb +2 -0
  72. data/spec/unit/hooks/erb_hook_spec.rb +15 -15
  73. data/spec/unit/pacto/configuration_spec.rb +2 -10
  74. data/spec/unit/pacto/contract_factory_spec.rb +16 -13
  75. data/spec/unit/pacto/contract_files_spec.rb +42 -0
  76. data/spec/unit/pacto/contract_list_spec.rb +35 -0
  77. data/spec/unit/pacto/contract_spec.rb +43 -44
  78. data/spec/unit/pacto/contract_validator_spec.rb +85 -0
  79. data/spec/unit/pacto/core/configuration_spec.rb +4 -11
  80. data/spec/unit/pacto/core/contract_registry_spec.rb +119 -0
  81. data/spec/unit/pacto/core/modes_spec.rb +18 -0
  82. data/spec/unit/pacto/core/validation_registry_spec.rb +76 -0
  83. data/spec/unit/pacto/core/validation_spec.rb +60 -0
  84. data/spec/unit/pacto/extensions_spec.rb +14 -23
  85. data/spec/unit/pacto/generator/filters_spec.rb +99 -0
  86. data/spec/unit/pacto/generator_spec.rb +34 -73
  87. data/spec/unit/pacto/meta_schema_spec.rb +46 -6
  88. data/spec/unit/pacto/pacto_spec.rb +17 -15
  89. data/spec/unit/pacto/{request_spec.rb → request_clause_spec.rb} +32 -44
  90. data/spec/unit/pacto/request_pattern_spec.rb +22 -0
  91. data/spec/unit/pacto/response_clause_spec.rb +54 -0
  92. data/spec/unit/pacto/stubs/uri_pattern_spec.rb +28 -0
  93. data/spec/unit/pacto/stubs/webmock_adapter_spec.rb +205 -0
  94. data/spec/unit/pacto/stubs/webmock_helper_spec.rb +20 -0
  95. data/spec/unit/pacto/uri_spec.rb +20 -0
  96. data/spec/unit/pacto/validators/body_validator_spec.rb +105 -0
  97. data/spec/unit/pacto/validators/response_header_validator_spec.rb +94 -0
  98. data/spec/unit/pacto/validators/response_status_validator_spec.rb +20 -0
  99. metadata +230 -146
  100. data/features/generation/generation.feature +0 -25
  101. data/lib/pacto/core/contract_repository.rb +0 -44
  102. data/lib/pacto/hash_merge_processor.rb +0 -14
  103. data/lib/pacto/request.rb +0 -57
  104. data/lib/pacto/response.rb +0 -63
  105. data/lib/pacto/response_adapter.rb +0 -24
  106. data/lib/pacto/stubs/built_in.rb +0 -57
  107. data/spec/unit/pacto/core/contract_repository_spec.rb +0 -133
  108. data/spec/unit/pacto/hash_merge_processor_spec.rb +0 -20
  109. data/spec/unit/pacto/response_adapter_spec.rb +0 -25
  110. data/spec/unit/pacto/response_spec.rb +0 -201
  111. data/spec/unit/pacto/stubs/built_in_spec.rb +0 -168
@@ -0,0 +1,43 @@
1
+ module Pacto
2
+ class ContractRegistry
3
+ def initialize
4
+ @registry = Hash.new { |hash, key| hash[key] = Set.new }
5
+ end
6
+
7
+ def [](tag)
8
+ @registry[tag]
9
+ end
10
+
11
+ def register(contract, *tags)
12
+ tags << :default if tags.empty?
13
+
14
+ tags.each do |tag|
15
+ @registry[tag] << contract
16
+ end
17
+
18
+ self
19
+ end
20
+
21
+ def use(tag, values = {})
22
+ merged_contracts = @registry[:default] + @registry[tag]
23
+
24
+ fail ArgumentError, "contract \"#{tag}\" not found" if merged_contracts.empty?
25
+
26
+ merged_contracts.each do |contract|
27
+ contract.stub_contract! values
28
+ end
29
+
30
+ self
31
+ end
32
+
33
+ def contracts_for(request_signature)
34
+ all_contracts.select { |c| c.matches? request_signature }
35
+ end
36
+
37
+ private
38
+
39
+ def all_contracts
40
+ @registry.values.inject(Set.new, :+)
41
+ end
42
+ end
43
+ end
@@ -1,11 +1,11 @@
1
1
  module Pacto
2
- class Callback
2
+ class Hook
3
3
  def initialize(&block)
4
- @callback = block
4
+ @hook = block
5
5
  end
6
6
 
7
7
  def process(contracts, request_signature, response)
8
- @callback.call contracts, request_signature, response
8
+ @hook.call contracts, request_signature, response
9
9
  end
10
10
  end
11
11
  end
@@ -0,0 +1,33 @@
1
+ module Pacto
2
+ class << self
3
+ def generate!
4
+ modes << :generate
5
+ end
6
+
7
+ def stop_generating!
8
+ modes.delete :generate
9
+ end
10
+
11
+ def generating?
12
+ modes.include? :generate
13
+ end
14
+
15
+ def validate!
16
+ modes << :validate
17
+ end
18
+
19
+ def stop_validating!
20
+ modes.delete :validate
21
+ end
22
+
23
+ def validating?
24
+ modes.include? :validate
25
+ end
26
+
27
+ private
28
+
29
+ def modes
30
+ @modes ||= []
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ module Pacto
2
+ class ValidationRegistry
3
+ include Singleton
4
+ attr_reader :validations
5
+
6
+ def initialize
7
+ @validations = []
8
+ end
9
+
10
+ def reset!
11
+ @validations.clear
12
+ end
13
+
14
+ def validated?(request_pattern)
15
+ matched_validations = @validations.select do |validation|
16
+ request_pattern.matches? validation.request
17
+ end
18
+ matched_validations unless matched_validations.empty?
19
+ end
20
+
21
+ def register_validation(validation)
22
+ @validations << validation
23
+ logger.info "Detected #{validation.summary}"
24
+ validation
25
+ end
26
+
27
+ def unmatched_validations
28
+ @validations.select do |validation|
29
+ validation.contract.nil?
30
+ end
31
+ end
32
+
33
+ def failed_validations
34
+ @validations.select do |validation|
35
+ !validation.successful?
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def logger
42
+ @logger ||= Logger.instance
43
+ end
44
+ end
45
+ end
@@ -1,7 +1,6 @@
1
1
  module Pacto
2
2
  class ERBProcessor
3
3
  def process(contract, values = {})
4
- values ||= {}
5
4
  erb = ERB.new(contract)
6
5
  erb_result = erb.result hash_binding(values)
7
6
  Logger.instance.debug "Processed contract: #{erb_result.inspect}"
@@ -1,12 +1,25 @@
1
1
  module Pacto
2
2
  module Extensions
3
- module HashSubsetOf
4
- def subset_of?(other)
5
- (to_a - other.to_a).empty?
3
+ # Adapted from Faraday
4
+ HeaderKeyMap = Hash.new do |map, key|
5
+ split_char = key.to_s.include?('-') ? '-' : '_'
6
+ map[key] = key.to_s.split(split_char). # :user_agent => %w(user agent)
7
+ each { |w| w.capitalize! }. # => %w(User Agent)
8
+ join('-') # => "User-Agent"
9
+ end
10
+ HeaderKeyMap[:etag] = 'ETag'
11
+
12
+ def self.normalize_header_keys(headers)
13
+ headers.reduce({}) do |normalized, (key, value)|
14
+ normalized[HeaderKeyMap[key]] = value
15
+ normalized
6
16
  end
17
+ end
7
18
 
19
+ module HashSubsetOf
20
+ # FIXME: Only used by HashMergeProcessor, which I'd like to deprecate
8
21
  def normalize_keys
9
- inject({}) do |normalized, (key, value)|
22
+ reduce({}) do |normalized, (key, value)|
10
23
  normalized[key.to_s.downcase] = value
11
24
  normalized
12
25
  end
@@ -15,4 +28,5 @@ module Pacto
15
28
  end
16
29
  end
17
30
 
31
+ # FIXME: Let's not extend Hash...
18
32
  Hash.send(:include, Pacto::Extensions::HashSubsetOf)
@@ -2,73 +2,58 @@ require 'json/schema_generator'
2
2
 
3
3
  module Pacto
4
4
  class Generator
5
- attr_accessor :request_headers_to_filter
6
- attr_accessor :response_headers_to_filter
7
-
8
- INFORMATIONAL_REQUEST_HEADERS =
9
- %w{
10
- content-length
11
- via
12
- }
13
-
14
- INFORMATIONAL_RESPONSE_HEADERS =
15
- %w{
16
- server
17
- date
18
- content-length
19
- connection
20
- }
21
-
22
5
  def initialize(schema_version = 'draft3',
23
6
  schema_generator = JSON::SchemaGenerator,
24
- validator = Pacto::MetaSchema.new)
7
+ validator = Pacto::MetaSchema.new,
8
+ generator_options = Pacto.configuration.generator_options,
9
+ filters = Pacto::Generator::Filters.new)
25
10
  @schema_version = schema_version
26
11
  @validator = validator
27
12
  @schema_generator = schema_generator
28
- @response_headers_to_filter = INFORMATIONAL_RESPONSE_HEADERS
29
- @request_headers_to_filter = INFORMATIONAL_REQUEST_HEADERS
30
- end
31
-
32
- def generate(request_file, host)
33
- contract = Pacto.build_from_file request_file, host
34
- request = contract.request
35
- response = request.execute
36
- save(request_file, request, response)
13
+ @generator_options = generator_options
14
+ @filters = filters
37
15
  end
38
16
 
39
17
  def save(source, request, response)
40
- body_schema = JSON::SchemaGenerator.generate source, response.body, @schema_version
41
- contract = {
42
- :request => {
43
- :headers => filter_request_headers(request.headers),
44
- :method => request.method,
45
- :params => request.params,
46
- :path => request.path
47
- },
48
- :response => {
49
- :headers => filter_response_headers(response.headers),
50
- :status => response.status,
51
- :body => MultiJson.load(body_schema)
52
- }
53
- }
18
+ contract = generate_contract source, request, response
54
19
  pretty_contract = MultiJson.encode(contract, :pretty => true)
55
20
  # This is because of a discrepency w/ jruby vs MRI pretty json
56
- pretty_contract.gsub! /^$\n/, ''
21
+ pretty_contract.gsub!(/^$\n/, '')
57
22
  @validator.validate pretty_contract
58
23
  pretty_contract
59
24
  end
60
25
 
61
26
  private
62
27
 
63
- def filter_request_headers headers
64
- headers.reject do |header|
65
- @request_headers_to_filter.include? header.downcase
66
- end
28
+ def generate_contract(source, request, response)
29
+ {
30
+ :request => generate_request(request, response, source),
31
+ :response => generate_response(request, response, source)
32
+ }
33
+ end
34
+
35
+ def generate_request(request, response, source)
36
+ {
37
+ :headers => @filters.filter_request_headers(request, response),
38
+ :method => request.method,
39
+ :params => request.params,
40
+ :path => request.path,
41
+ :body => generate_body(source, request.body)
42
+ }.delete_if { |k, v| v.nil? }
43
+ end
44
+
45
+ def generate_response(request, response, source)
46
+ {
47
+ :headers => @filters.filter_response_headers(request, response),
48
+ :status => response.status,
49
+ :body => generate_body(source, response.body)
50
+ }.delete_if { |k, v| v.nil? }
67
51
  end
68
52
 
69
- def filter_response_headers headers
70
- headers.reject do |header|
71
- @response_headers_to_filter.include? header.downcase
53
+ def generate_body(source, body)
54
+ if body && !body.empty?
55
+ body_schema = JSON::SchemaGenerator.generate source, body, @generator_options
56
+ MultiJson.load(body_schema)
72
57
  end
73
58
  end
74
59
  end
@@ -0,0 +1,41 @@
1
+ module Pacto
2
+ class Generator
3
+ class Filters
4
+ CONNECTION_CONTROL_HEADERS = %w{
5
+ Via
6
+ Server
7
+ Connection
8
+ Transfer-Encoding
9
+ Content-Length
10
+ }
11
+
12
+ FRESHNESS_HEADERS =
13
+ %w{
14
+ Date
15
+ Last-Modified
16
+ ETag
17
+ }
18
+
19
+ HEADERS_TO_FILTER = CONNECTION_CONTROL_HEADERS + FRESHNESS_HEADERS
20
+
21
+ def filter_request_headers(request, response)
22
+ # FIXME: Do we need to handle all these cases in real situations, or just because of stubbing?
23
+ vary_headers = response.headers['Vary'] || []
24
+ vary_headers = [vary_headers] if vary_headers.is_a? String
25
+ vary_headers = vary_headers.map do |h|
26
+ h.split(',').map(&:strip)
27
+ end.flatten
28
+
29
+ request.headers.select do |header|
30
+ vary_headers.map(&:downcase).include? header.downcase
31
+ end
32
+ end
33
+
34
+ def filter_response_headers(request, response)
35
+ Pacto::Extensions.normalize_header_keys(response.headers).reject do |header|
36
+ (HEADERS_TO_FILTER.include? header) || (header.start_with?('X-'))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,17 +1,18 @@
1
+ require_relative '../erb_processor'
2
+
1
3
  module Pacto
2
4
  module Hooks
3
- class ERBHook < Pacto::Callback
5
+ class ERBHook < Pacto::Hook
4
6
  def initialize
5
7
  @processor = ERBProcessor.new
6
8
  end
7
9
 
8
10
  def process(contracts, request_signature, response)
9
11
  bound_values = contracts.empty? ? {} : contracts.first.values
10
- bound_values.merge!({:req => { 'HEADERS' => request_signature.headers}})
12
+ bound_values.merge!(:req => { 'HEADERS' => request_signature.headers})
11
13
  response.body = @processor.process response.body, bound_values
12
14
  response.body
13
15
  end
14
-
15
16
  end
16
17
  end
17
18
  end
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module Pacto
2
4
  class Logger
3
5
  include Singleton
@@ -9,13 +11,13 @@ module Pacto
9
11
  log ::Logger.new STDOUT
10
12
  end
11
13
 
12
- def log log
14
+ def log(log)
13
15
  @log = log
14
16
  @log.level = default_level
15
17
  @log.progname = 'Pacto'
16
18
  end
17
19
 
18
- def level= level
20
+ def level=(level)
19
21
  @log.level = log_levels.fetch(level, default_level)
20
22
  end
21
23
 
@@ -4,13 +4,15 @@ module Pacto
4
4
 
5
5
  def initialize(engine = JSON::Validator)
6
6
  @schema = File.join(File.dirname(File.expand_path(__FILE__)), '../../resources/contract_schema.json')
7
+ @base_schema = File.join(File.dirname(File.expand_path(__FILE__)), '../../resources/draft-03.json')
8
+ JSON::Validator.validate!(@base_schema, @schema)
7
9
  @engine = engine
8
10
  end
9
11
 
10
- def validate definition
12
+ def validate(definition)
11
13
  errors = engine.fully_validate(schema, definition, :version => :draft3)
12
14
  unless errors.empty?
13
- raise InvalidContract.new(errors)
15
+ fail InvalidContract, errors
14
16
  end
15
17
  end
16
18
  end
@@ -1,13 +1,7 @@
1
1
  require 'pacto'
2
2
 
3
- unless String.respond_to?(:colors)
4
- class String
5
- def colorize(*args)
6
- self
7
- end
8
- end
9
- end
10
-
3
+ # FIXME: RakeTask is a huge class, refactor this please
4
+ # rubocop:disable ClassLength
11
5
  module Pacto
12
6
  class RakeTask
13
7
  include Rake::DSL
@@ -29,7 +23,7 @@ module Pacto
29
23
  desc 'Validates all contracts in a given directory against a given host'
30
24
  task :validate, :host, :dir do |t, args|
31
25
  if args.to_a.size < 2
32
- fail 'USAGE: rake pacto:validate[<host>, <contract_dir>]'.colorize(:yellow)
26
+ fail Pacto::UI.yellow('USAGE: rake pacto:validate[<host>, <contract_dir>]')
33
27
  end
34
28
 
35
29
  validate_contracts(args[:host], args[:dir])
@@ -40,24 +34,26 @@ module Pacto
40
34
  desc 'Generates contracts from partial contracts'
41
35
  task :generate, :input_dir, :output_dir, :host do |t, args|
42
36
  if args.to_a.size < 3
43
- fail 'USAGE: rake pacto:generate[<request_contract_dir>, <output_dir>, <record_host>]'.colorize(:yellow)
37
+ fail Pacto::UI.yellow('USAGE: rake pacto:generate[<request_contract_dir>, <output_dir>, <record_host>]')
44
38
  end
45
39
 
46
40
  generate_contracts(args[:input_dir], args[:output_dir], args[:host])
47
41
  end
48
42
  end
49
43
 
44
+ # FIXME: meta_validate is a big method =(. Needs refactoring
45
+ # rubocop:disable MethodLength
50
46
  def meta_validate
51
47
  desc 'Validates a directory of contract definitions'
52
48
  task :meta_validate, :dir do |t, args|
53
49
  if args.to_a.size < 1
54
- fail 'USAGE: rake pacto:meta_validate[<contract_dir>]'.colorize(:yellow)
50
+ fail Pacto::UI.yellow('USAGE: rake pacto:meta_validate[<contract_dir>]')
55
51
  end
56
52
 
57
53
  each_contract(args[:dir]) do |contract_file|
58
- puts "Validating #{contract_file}"
59
54
  fail unless Pacto.validate_contract contract_file
60
55
  end
56
+ puts 'All contracts successfully meta-validated'
61
57
  end
62
58
  end
63
59
 
@@ -66,31 +62,36 @@ module Pacto
66
62
  puts "Validating contracts in directory #{dir} against host #{host}\n\n"
67
63
 
68
64
  total_failed = 0
69
- each_contract(dir) do |contact_file|
65
+ contracts = []
66
+ each_contract(dir) do |contract_file|
67
+ contracts << contract_file
70
68
  print "#{contract_file.split('/').last}:"
71
- contract = Pacto.build_from_file(contract_file, host)
72
- errors = contract.validate
69
+ contract = Pacto.load_contract(contract_file, host)
70
+ errors = contract.validate_provider
73
71
 
74
72
  if errors.empty?
75
- puts ' OK!'.colorize(:green)
73
+ puts Pacto::UI.green(' OK!')
76
74
  else
77
75
  @exit_with_error = true
78
76
  total_failed += 1
79
- puts ' FAILED!'.colorize(:red)
77
+ puts Pacto::UI.red(' FAILED!')
80
78
  errors.each do |error|
81
- puts "\t* #{error}".colorize(:light_red)
79
+ puts Pacto::UI.red("\t* #{error}")
82
80
  end
83
81
  puts ''
84
82
  end
85
83
  end
86
84
 
87
85
  if @exit_with_error
88
- fail "#{total_failed} of #{contracts.size} failed. Check output for detailed error messages.".colorize(:red)
86
+ fail Pacto::UI.red("#{total_failed} of #{contracts.size} failed. Check output for detailed error messages.")
89
87
  else
90
- puts "#{contracts.size} valid contract#{contracts.size > 1 ? 's' : nil}".colorize(:green)
88
+ puts Pacto::UI.green("#{contracts.size} valid contract#{contracts.size > 1 ? 's' : nil}")
91
89
  end
92
90
  end
91
+ # rubocop:enable MethodLength
93
92
 
93
+ # FIXME: generate_contracts is a big method =(. Needs refactoring
94
+ # rubocop:disable MethodLength
94
95
  def generate_contracts(input_dir, output_dir, host)
95
96
  WebMock.allow_net_connect!
96
97
  generator = Pacto::Generator.new
@@ -107,16 +108,17 @@ module Pacto
107
108
  output_file.close
108
109
  rescue InvalidContract => e
109
110
  failed_contracts << contract_file
110
- puts e.message.colorize(:red)
111
+ puts Pacto::UI.red(e.message)
111
112
  end
112
113
  end
113
114
 
114
115
  if failed_contracts.empty?
115
- puts 'Successfully generated all contracts'.colorize(:green)
116
+ puts Pacto::UI.green('Successfully generated all contracts')
116
117
  else
117
- fail "The following contracts could not be generated: #{failed_contracts.join ','}".colorize(:red)
118
+ fail Pacto::UI.red("The following contracts could not be generated: #{failed_contracts.join ','}")
118
119
  end
119
120
  end
121
+ # rubocop:enable MethodLength
120
122
 
121
123
  private
122
124
 
@@ -124,8 +126,8 @@ module Pacto
124
126
  if File.file? dir
125
127
  yield dir
126
128
  else
127
- contracts = Dir[File.join(dir, '*{.json.erb,.json}')]
128
- fail "No contracts found in directory #{dir}".colorize(:yellow) if contracts.empty?
129
+ contracts = Dir[File.join(dir, '**/*{.json.erb,.json}')]
130
+ fail Pacto::UI.yellow("No contracts found in directory #{dir}") if contracts.empty?
129
131
 
130
132
  contracts.sort.each do |contract_file|
131
133
  yield contract_file
@@ -134,5 +136,6 @@ module Pacto
134
136
  end
135
137
  end
136
138
  end
139
+ # rubocop:enable ClassLength
137
140
 
138
141
  Pacto::RakeTask.new.install