pacto 0.3.0.pre → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,25 +0,0 @@
1
- @needs_server
2
- Feature: Contract Generation
3
- Scenario: Generating a contract from a partial contract
4
- Given a directory named "contracts"
5
- Given a file named "requests/my_contract.json" with:
6
- """
7
- {
8
- "request": {
9
- "method": "GET",
10
- "path": "/hello",
11
- "headers": {
12
- "Accept": "application/json"
13
- },
14
- "params": {}
15
- },
16
- "response": {
17
- "status": 200,
18
- "body": {
19
- "required": true
20
- }
21
- }
22
- }
23
- """
24
- When I successfully run `bundle exec rake --trace pacto:generate['tmp/aruba/requests','tmp/aruba/contracts','http://localhost:8000']`
25
- Then the output should contain "Successfully generated all contracts"
@@ -1,44 +0,0 @@
1
- module Pacto
2
- class << self
3
-
4
- def register_contract(contract = nil, *tags)
5
- tags << :default if tags.empty?
6
- start_count = registered.count
7
- tags.uniq.each do |tag|
8
- registered[tag] << contract
9
- end
10
- registered.count - start_count
11
- end
12
-
13
- def use(tag, values = {})
14
- merged_contracts = registered[:default] + registered[tag]
15
-
16
- raise ArgumentError, "contract \"#{tag}\" not found" if merged_contracts.empty?
17
-
18
- merged_contracts.each do |contract|
19
- contract.stub_contract! values
20
- end
21
- merged_contracts.count
22
- end
23
-
24
- def registered
25
- @registered ||= Hash.new { |hash, key| hash[key] = Set.new }
26
- end
27
-
28
- def unregister_all!
29
- registered.clear
30
- end
31
-
32
- def contract_for(request_signature)
33
- matches = Set.new
34
- registered.values.each do |contract_set|
35
- contract_set.each do |contract|
36
- if contract.matches? request_signature
37
- matches.add contract
38
- end
39
- end
40
- end
41
- matches
42
- end
43
- end
44
- end
@@ -1,14 +0,0 @@
1
- module Pacto
2
- class HashMergeProcessor
3
- def process(response_body, values = {})
4
- unless values.nil? || values.empty?
5
- if response_body.respond_to?(:normalize_keys)
6
- response_body = response_body.normalize_keys.deep_merge(values.normalize_keys)
7
- else
8
- response_body = values
9
- end
10
- end
11
- response_body.to_s
12
- end
13
- end
14
- end
@@ -1,57 +0,0 @@
1
- module Pacto
2
- class Request
3
- attr_reader :host
4
-
5
- def initialize(host, definition)
6
- @host = host
7
- @definition = definition
8
- end
9
-
10
- def method
11
- @definition['method'].to_s.downcase.to_sym
12
- end
13
-
14
- def path
15
- @definition['path']
16
- end
17
-
18
- def headers
19
- @definition['headers']
20
- end
21
-
22
- def params
23
- @definition['params']
24
- end
25
-
26
- def absolute_uri
27
- @host + path
28
- end
29
-
30
- def full_uri
31
- return absolute_uri if params.empty?
32
-
33
- uri = Addressable::URI.new
34
- uri.query_values = params
35
-
36
- absolute_uri + '?' + uri.query
37
- end
38
-
39
- def execute
40
- response = HTTParty.send(method, @host + path, {
41
- httparty_params_key => normalized_params,
42
- :headers => headers
43
- })
44
- ResponseAdapter.new(response)
45
- end
46
-
47
- private
48
-
49
- def httparty_params_key
50
- method == :get ? :query : :body
51
- end
52
-
53
- def normalized_params
54
- method == :get ? params : params.to_json
55
- end
56
- end
57
- end
@@ -1,63 +0,0 @@
1
- module Pacto
2
- class Response
3
- attr_reader :status, :headers, :schema
4
-
5
- def initialize(definition)
6
- @definition = definition
7
- @status = @definition['status']
8
- @headers = @definition['headers']
9
- @schema = @definition['body']
10
- end
11
-
12
- def instantiate
13
- OpenStruct.new({
14
- 'status' => @status,
15
- 'headers' => @headers,
16
- 'body' => JSON::Generator.generate(@schema)
17
- })
18
- end
19
-
20
- def validate(response, opt = {})
21
-
22
- unless opt[:body_only]
23
- if @definition['status'] != response.status
24
- return ["Invalid status: expected #{@definition['status']} but got #{response.status}"]
25
- end
26
-
27
- unless @definition['headers'].normalize_keys.subset_of?(response.headers.normalize_keys)
28
- return ["Invalid headers: expected #{@definition['headers'].inspect} to be a subset of #{response.headers.inspect}"]
29
- end
30
- end
31
-
32
- if @definition['body']
33
- if @definition['body']['type'] && @definition['body']['type'] == 'string'
34
- validate_as_pure_string response.body
35
- else
36
- response.respond_to?(:body) ? validate_as_json(response.body) : validate_as_json(response)
37
- end
38
- else
39
- []
40
- end
41
- end
42
-
43
- private
44
-
45
- def validate_as_pure_string response_body
46
- errors = []
47
- if @definition['body']['required'] && response_body.nil?
48
- errors << 'The response does not contain a body'
49
- end
50
-
51
- pattern = @definition['body']['pattern']
52
- if pattern && !(response_body =~ Regexp.new(pattern))
53
- errors << "The response does not match the pattern #{pattern}"
54
- end
55
-
56
- errors
57
- end
58
-
59
- def validate_as_json response_body
60
- JSON::Validator.fully_validate(@definition['body'], response_body, :version => :draft3)
61
- end
62
- end
63
- end
@@ -1,24 +0,0 @@
1
- module Pacto
2
- class ResponseAdapter
3
- def initialize(response)
4
- @response = response
5
- end
6
-
7
- def status
8
- @response.code
9
- end
10
-
11
- def body
12
- @response.body
13
- end
14
-
15
- def headers
16
- # Normalize headers values according to RFC2616
17
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
18
- normalized_headers = @response.headers.map do |(key, value)|
19
- [key, value.join(',')]
20
- end
21
- Hash[normalized_headers]
22
- end
23
- end
24
- end
@@ -1,57 +0,0 @@
1
- module Pacto
2
- module Stubs
3
- class BuiltIn
4
-
5
- def initialize
6
- register_callbacks
7
- end
8
-
9
- def stub_request! request, response
10
- stub = WebMock.stub_request(request.method, "#{request.host}#{request.path}")
11
- stub = stub.with(request_details(request)) if Pacto.configuration.strict_matchers
12
- stub.to_return({
13
- :status => response.status,
14
- :headers => response.headers,
15
- :body => format_body(response.body)
16
- })
17
- end
18
-
19
- def reset!
20
- WebMock.reset!
21
- WebMock.reset_callbacks
22
- end
23
-
24
- private
25
-
26
- def register_callbacks
27
- WebMock.after_request do |request_signature, response|
28
- contracts = Pacto.contract_for request_signature
29
- Pacto.configuration.callback.process contracts, request_signature, response
30
- end
31
- end
32
-
33
- def format_body(body)
34
- if body.is_a?(Hash) || body.is_a?(Array)
35
- body.to_json
36
- else
37
- body
38
- end
39
- end
40
-
41
- def request_details request
42
- details = {}
43
- unless request.params.empty?
44
- details[webmock_params_key(request)] = request.params
45
- end
46
- unless request.headers.empty?
47
- details[:headers] = request.headers
48
- end
49
- details
50
- end
51
-
52
- def webmock_params_key request
53
- request.method == :get ? :query : :body
54
- end
55
- end
56
- end
57
- end
@@ -1,133 +0,0 @@
1
- describe Pacto do
2
- let(:tag) { 'contract_tag' }
3
- let(:another_tag) { 'another_tag' }
4
- let(:contract) { double('contract') }
5
- let(:another_contract) { double('another_contract') }
6
-
7
- after do
8
- described_class.unregister_all!
9
- end
10
-
11
- describe '.register' do
12
- context 'no tag' do
13
- it 'registers the contract with the default tag' do
14
- described_class.register_contract contract
15
- expect(described_class.registered[:default]).to include(contract)
16
- end
17
- end
18
-
19
- context 'one tag' do
20
- it 'registers a contract under a given tag' do
21
- described_class.register_contract(contract, tag)
22
- expect(described_class.registered[tag]).to include(contract)
23
- end
24
-
25
- it 'does not duplicate a contract when it has already been registered with the same tag' do
26
- described_class.register_contract(contract, tag)
27
- described_class.register_contract(contract, tag)
28
- expect(described_class.registered[tag]).to include(contract)
29
- expect(described_class.registered[tag]).to have(1).items
30
- end
31
- end
32
-
33
- context 'multiple tags' do
34
- it 'registers a contract using different tags' do
35
- described_class.register_contract(contract, tag, another_tag)
36
- expect(described_class.registered[tag]).to include(contract)
37
- expect(described_class.registered[another_tag]).to include(contract)
38
- end
39
-
40
- it 'registers a tag with different contracts ' do
41
- described_class.register_contract(contract, tag)
42
- described_class.register_contract(another_contract, tag)
43
- expect(described_class.registered[tag]).to include(contract, another_contract)
44
- end
45
-
46
- end
47
-
48
- context 'with a block' do
49
- it 'has a compact syntax for registering multiple contracts' do
50
- described_class.configure do |c|
51
- c.register_contract 'new_api/create_item_v2', :item, :new
52
- c.register_contract 'authentication', :default
53
- c.register_contract 'list_items_legacy', :legacy
54
- c.register_contract 'get_item_legacy', :legacy
55
- end
56
- expect(described_class.registered[:new]).to include('new_api/create_item_v2')
57
- expect(described_class.registered[:default]).to include('authentication')
58
- expect(described_class.registered[:legacy]).to include('list_items_legacy', 'get_item_legacy')
59
- end
60
- end
61
- end
62
-
63
- describe '.use' do
64
- before do
65
- described_class.register_contract(contract, tag)
66
- described_class.register_contract(another_contract, :default)
67
- end
68
-
69
- context 'when a contract has been registered' do
70
- let(:response_body) { double('response_body') }
71
-
72
- it 'stubs a contract with default values' do
73
- contract.should_receive(:stub_contract!)
74
- another_contract.should_receive(:stub_contract!)
75
- expect(described_class.use(tag)).to eq 2
76
- end
77
-
78
- it 'stubs default contract if unused tag' do
79
- another_contract.should_receive(:stub_contract!)
80
- expect(described_class.use(another_tag)).to eq 1
81
- end
82
- end
83
-
84
- context 'when contract has not been registered' do
85
- it 'raises an argument error' do
86
- described_class.unregister_all!
87
- expect { described_class.use('unregistered') }.to raise_error ArgumentError
88
- end
89
- end
90
- end
91
-
92
- describe '.unregister_all!' do
93
- it 'unregisters all previously registered contracts' do
94
- described_class.register_contract(contract, tag)
95
- described_class.unregister_all!
96
- expect(described_class.registered).to be_empty
97
- end
98
- end
99
-
100
- describe '.contract_for' do
101
- let(:request_signature) { double('request signature') }
102
-
103
- context 'when no contracts are found for a request' do
104
- it 'returns an empty list' do
105
- expect(described_class.contract_for request_signature).to be_empty
106
- end
107
- end
108
-
109
- context 'when contracts are found for a request' do
110
- let(:contracts_that_match) { create_contracts 2, true }
111
- let(:contracts_that_dont_match) { create_contracts 3, false }
112
- let(:all_contracts) { contracts_that_match + contracts_that_dont_match }
113
-
114
- it 'returns the matching contracts' do
115
- register_and_use all_contracts
116
- expect(described_class.contract_for request_signature).to eq(contracts_that_match)
117
- end
118
- end
119
- end
120
-
121
- def create_contracts(total, matches)
122
- total.times.map do
123
- double('contract',
124
- :stub_contract! => double('request matcher'),
125
- :matches? => matches)
126
- end.to_set
127
- end
128
-
129
- def register_and_use contracts
130
- contracts.each { |contract| described_class.register_contract contract }
131
- Pacto.use :default
132
- end
133
- end
@@ -1,20 +0,0 @@
1
- module Pacto
2
- describe HashMergeProcessor do
3
- describe '#process' do
4
- let(:response_body_string) { 'a simple string' }
5
- let(:response_body_hash) {
6
- {'a' => 'simple hash'}
7
- }
8
-
9
- it 'does not change contract if values is nil' do
10
- expect(subject.process(response_body_string, nil)).to eq response_body_string
11
- end
12
-
13
- it 'merges response body with values' do
14
- merged_body = {'a' => 'simple hash', 'b' => :key}
15
- expect(subject.process(response_body_hash, {:b => :key})).to eq merged_body.to_s
16
- end
17
-
18
- end
19
- end
20
- end