pacto 0.2.5 → 0.3.0.pre
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.
- data/.gitignore +3 -0
- data/.rspec +0 -2
- data/.rubocop-todo.yml +51 -0
- data/.rubocop.yml +1 -0
- data/.travis.yml +4 -2
- data/Guardfile +28 -14
- data/README.md +81 -51
- data/Rakefile +24 -11
- data/features/generation/generation.feature +25 -0
- data/features/journeys/validation.feature +74 -0
- data/features/support/env.rb +16 -0
- data/lib/pacto.rb +63 -34
- data/lib/pacto/contract.rb +25 -11
- data/lib/pacto/contract_factory.rb +13 -44
- data/lib/pacto/core/callback.rb +11 -0
- data/lib/pacto/core/configuration.rb +34 -0
- data/lib/pacto/core/contract_repository.rb +44 -0
- data/lib/pacto/erb_processor.rb +18 -0
- data/lib/pacto/exceptions/invalid_contract.rb +10 -1
- data/lib/pacto/extensions.rb +2 -2
- data/lib/pacto/generator.rb +75 -0
- data/lib/pacto/hash_merge_processor.rb +14 -0
- data/lib/pacto/hooks/erb_hook.rb +17 -0
- data/lib/pacto/logger.rb +42 -0
- data/lib/pacto/meta_schema.rb +17 -0
- data/lib/pacto/rake_task.rb +75 -12
- data/lib/pacto/request.rb +3 -4
- data/lib/pacto/response.rb +27 -19
- data/lib/pacto/server.rb +2 -0
- data/lib/pacto/server/dummy.rb +45 -0
- data/lib/pacto/server/playback_servlet.rb +21 -0
- data/lib/pacto/stubs/built_in.rb +57 -0
- data/lib/pacto/version.rb +1 -1
- data/pacto.gemspec +8 -2
- data/resources/contract_schema.json +216 -0
- data/spec/coveralls_helper.rb +10 -0
- data/spec/integration/data/strict_contract.json +33 -0
- data/spec/integration/data/templating_contract.json +25 -0
- data/spec/integration/e2e_spec.rb +40 -7
- data/spec/integration/templating_spec.rb +55 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/unit/data/simple_contract.json +22 -0
- data/spec/unit/hooks/erb_hook_spec.rb +51 -0
- data/spec/unit/pacto/configuration_spec.rb +51 -0
- data/spec/unit/pacto/contract_factory_spec.rb +4 -35
- data/spec/unit/pacto/contract_spec.rb +59 -31
- data/spec/unit/pacto/core/configuration_spec.rb +28 -0
- data/spec/unit/pacto/core/contract_repository_spec.rb +133 -0
- data/spec/unit/pacto/erb_processor_spec.rb +23 -0
- data/spec/unit/pacto/extensions_spec.rb +11 -11
- data/spec/unit/pacto/generator_spec.rb +142 -0
- data/spec/unit/pacto/hash_merge_processor_spec.rb +20 -0
- data/spec/unit/pacto/logger_spec.rb +44 -0
- data/spec/unit/pacto/meta_schema_spec.rb +70 -0
- data/spec/unit/pacto/pacto_spec.rb +32 -58
- data/spec/unit/pacto/request_spec.rb +83 -34
- data/spec/unit/pacto/response_adapter_spec.rb +9 -11
- data/spec/unit/pacto/response_spec.rb +68 -68
- data/spec/unit/pacto/server/playback_servlet_spec.rb +24 -0
- data/spec/unit/pacto/stubs/built_in_spec.rb +168 -0
- metadata +291 -147
- data/.rspec_integration +0 -4
- data/.rspec_unit +0 -4
- data/lib/pacto/file_pre_processor.rb +0 -12
- data/lib/pacto/instantiated_contract.rb +0 -62
- data/spec/integration/spec_helper.rb +0 -1
- data/spec/integration/utils/dummy_server.rb +0 -34
- data/spec/unit/pacto/file_pre_processor_spec.rb +0 -13
- data/spec/unit/pacto/instantiated_contract_spec.rb +0 -224
- data/spec/unit/spec_helper.rb +0 -5
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rubocop-todo.yml
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`.
|
2
|
+
# The point is for the user to remove these configuration records
|
3
|
+
# one by one as the offences are removed from the code base.
|
4
|
+
|
5
|
+
Blocks:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
CollectionMethods:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
ColonMethodCall:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
DefWithoutParentheses:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Documentation:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
DotPosition:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Encoding:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
HashSyntax:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
IfUnlessModifier:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
LineLength:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
MethodAndVariableSnakeCase:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
MethodLength:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
SpaceInsideHashLiteralBraces:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
SymbolName:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
Void:
|
48
|
+
Enabled: false
|
49
|
+
|
50
|
+
WordArray:
|
51
|
+
Enabled: false
|
data/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
inherit_from: .rubocop-todo.yml
|
data/.travis.yml
CHANGED
data/Guardfile
CHANGED
@@ -1,16 +1,30 @@
|
|
1
|
-
guard :
|
2
|
-
|
3
|
-
watch(
|
4
|
-
watch(
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
guard :rubocop, all_on_start: false do
|
2
|
+
watch(%r{.+\.rb$})
|
3
|
+
watch('.rubocop.yml') { '.' }
|
4
|
+
watch('.rubocop-todo.yml') { '.' }
|
5
|
+
end
|
6
|
+
|
7
|
+
group :tests, halt_on_fail: true do
|
8
|
+
guard :rspec do
|
9
|
+
# Unit tests
|
10
|
+
watch(%r{^spec/unit/.+_spec\.rb$})
|
11
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
12
|
+
watch('spec/spec_helper.rb') { "spec/unit" }
|
13
|
+
watch('spec/unit/spec_helper.rb') { "spec/unit" }
|
14
|
+
watch(%r{^spec/unit/data/.+\.json$}) { "spec/unit" }
|
15
|
+
|
16
|
+
# Integration tests
|
17
|
+
watch(%r{^spec/integration/.+_spec\.rb$})
|
18
|
+
watch(%r{^spec/integration/utils/.+\.rb$}) { "spec/integration" }
|
19
|
+
watch(%r{^lib/.+.rb$}) { "spec/integration" }
|
20
|
+
watch('spec/spec_helper.rb') { "spec/integration" }
|
21
|
+
watch('spec/integration/spec_helper.rb') { "spec/integration" }
|
22
|
+
watch(%r{^spec/integration/data/.+\.json$}) { "spec/integration" }
|
23
|
+
end
|
8
24
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
watch('spec/integration/spec_helper.rb') { "spec/integration" }
|
15
|
-
watch(%r{^spec/integration/data/.+\.json$}) { "spec/integration" }
|
25
|
+
guard :cucumber, all_on_start: false do
|
26
|
+
watch(%r{^features/.+\.feature$})
|
27
|
+
watch(%r{^features/support/.+$}) { 'features' }
|
28
|
+
watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
|
29
|
+
end
|
16
30
|
end
|
data/README.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
[](https://travis-ci.org/thoughtworks/pacto)
|
2
|
+
[](https://codeclimate.com/github/thoughtworks/pacto)
|
3
|
+
[](https://gemnasium.com/thoughtworks/pacto)
|
4
|
+
[](https://coveralls.io/r/thoughtworks/pacto)
|
5
|
+
|
1
6
|
# Pacto
|
2
7
|
|
3
8
|
Pacto is a Ruby implementation of the [Consumer-Driven Contracts](http://martinfowler.com/articles/consumerDrivenContracts.html)
|
@@ -32,32 +37,34 @@ A response has the following attributes:
|
|
32
37
|
Pacto relies on a simple, JSON based language for defining contracts. Below is an example contract for a GET request
|
33
38
|
to the /hello_world endpoint of a provider:
|
34
39
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
}
|
40
|
+
```json
|
41
|
+
{
|
42
|
+
"request": {
|
43
|
+
"method": "GET",
|
44
|
+
"path": "/hello_world",
|
45
|
+
"headers": {
|
46
|
+
"Accept": "application/json"
|
47
|
+
},
|
48
|
+
"params": {}
|
49
|
+
},
|
50
|
+
|
51
|
+
"response": {
|
52
|
+
"status": 200,
|
53
|
+
"headers": {
|
54
|
+
"Content-Type": "application/json"
|
55
|
+
},
|
56
|
+
"body": {
|
57
|
+
"description": "A simple response",
|
58
|
+
"type": "object",
|
59
|
+
"properties": {
|
60
|
+
"message": {
|
61
|
+
"type": "string"
|
58
62
|
}
|
59
63
|
}
|
60
64
|
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
```
|
61
68
|
|
62
69
|
The host address is intentionally left out of the request specification so that we can validate a contract against any provider.
|
63
70
|
It also reinforces the fact that a contract defines the expectation of a consumer, and not the implementation of any specific provider.
|
@@ -68,13 +75,23 @@ There are two ways to validate a contract against a provider: through a Rake tas
|
|
68
75
|
|
69
76
|
### Rake Task
|
70
77
|
|
71
|
-
Pacto includes
|
78
|
+
Pacto includes two Rake tasks. In order to use them, include this in your Rakefile:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
require 'pacto/rake_task'
|
82
|
+
```
|
72
83
|
|
73
|
-
|
84
|
+
Pacto can validate the contract files:
|
74
85
|
|
75
|
-
|
86
|
+
```sh
|
87
|
+
$ rake pacto:meta_validate[dir] # Validates a directory of contract definitions
|
88
|
+
```
|
76
89
|
|
77
|
-
|
90
|
+
Or it can validate contracts against a provider:
|
91
|
+
|
92
|
+
```sh
|
93
|
+
$ rake pacto:validate[host,dir] # Validates all contracts in a given directory against a given host
|
94
|
+
```
|
78
95
|
|
79
96
|
It is recommended that you also include [colorize](https://github.com/fazibear/colorize) to get prettier, colorful output.
|
80
97
|
|
@@ -82,11 +99,27 @@ It is recommended that you also include [colorize](https://github.com/fazibear/c
|
|
82
99
|
|
83
100
|
The easiest way to load a contract from a file and validate it against a host is by using the builder interface:
|
84
101
|
|
85
|
-
|
102
|
+
```ruby
|
103
|
+
require 'pacto'
|
104
|
+
|
105
|
+
WebMock.allow_net_connect!
|
106
|
+
contract = Pacto.build_from_file('/path/to/contract.json', 'http://dummyprovider.com')
|
107
|
+
contract.validate
|
108
|
+
```
|
109
|
+
|
110
|
+
If you don't want to depend on Pacto to do the request you can also validate a response from a real request:
|
86
111
|
|
87
|
-
|
88
|
-
|
89
|
-
|
112
|
+
```ruby
|
113
|
+
require 'pacto'
|
114
|
+
|
115
|
+
WebMock.allow_net_connect!
|
116
|
+
contract = Pacto.build_from_file('/path/to/contract.json', 'http://dummyprovider.com')
|
117
|
+
# Doing the request with ruby stdlib, you can use your favourite lib to do the request
|
118
|
+
response = Net::HTTP.get_response(URI.parse('http://dummyprovider.com')).body
|
119
|
+
contract.validate response, body_only: true
|
120
|
+
```
|
121
|
+
|
122
|
+
Pacto also has the ability to match a request signature to a contract that is currently in used, via ```Pacto.contract_for request_signature```
|
90
123
|
|
91
124
|
## Auto-Generated Stubs
|
92
125
|
|
@@ -95,31 +128,28 @@ to generate a valid JSON document as the response body, and relies on [WebMock](
|
|
95
128
|
to stub any HTTP requests made by your application. Important: the JSON generator is in very early stages and does not work
|
96
129
|
with the entire JSON Schema specification.
|
97
130
|
|
98
|
-
First, register the contracts that are going to be used in the acceptance tests suite:
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
131
|
+
First, register the contracts that are going to be used in the acceptance tests suite. The register_contract method accepts zero or more tags:
|
132
|
+
```ruby
|
133
|
+
require 'pacto'
|
134
|
+
|
135
|
+
contract1 = Pacto.build_from_file('/path/to/contract1.json', 'http://dummyprovider.com')
|
136
|
+
contract2 = Pacto.build_from_file('/path/to/contract2.json', 'http://dummyprovider.com')
|
137
|
+
contract3 = Pacto.build_from_file('/path/to/contract3.json', 'http://dummyprovider.com')
|
138
|
+
Pacto.register_contract(contract1)
|
139
|
+
Pacto.register_contract(contract2, :public_api)
|
140
|
+
Pacto.register_contract(contract3, :public_api, :wip)
|
141
|
+
```
|
105
142
|
Then, in the setup phase of the test, specify which contracts will be used for that test:
|
106
|
-
|
107
|
-
|
108
|
-
|
143
|
+
```ruby
|
144
|
+
Pacto.use('my_tag')
|
145
|
+
```
|
109
146
|
If default values are not specified in the contract's response body, a default value will be automatically generated. It is possible
|
110
147
|
to overwrite those values, however, by passing a second argument:
|
111
|
-
|
112
|
-
|
113
|
-
|
148
|
+
```ruby
|
149
|
+
Pacto.use('my_tag', :value => 'new value')
|
150
|
+
```
|
114
151
|
The values are merged using [hash-deep-merge](https://github.com/Offirmo/hash-deep-merge).
|
115
152
|
|
116
|
-
## Code status
|
117
|
-
|
118
|
-
[](https://travis-ci.org/thoughtworks/pacto)
|
119
|
-
[](https://codeclimate.com/github/thoughtworks/pacto)
|
120
|
-
[](https://gemnasium.com/thoughtworks/pacto)
|
121
|
-
[](https://coveralls.io/r/thoughtworks/pacto)
|
122
|
-
|
123
153
|
## Contributing
|
124
154
|
|
125
155
|
1. Fork it
|
data/Rakefile
CHANGED
@@ -1,16 +1,29 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
|
+
require 'pacto/rake_task'
|
4
|
+
require 'cucumber'
|
5
|
+
require 'cucumber/rake/task'
|
6
|
+
require 'coveralls/rake/task'
|
7
|
+
require 'rubocop/rake_task'
|
3
8
|
|
4
|
-
|
5
|
-
desc "Run unit tests"
|
6
|
-
task :unit do
|
7
|
-
abort unless system('rspec --option .rspec_unit')
|
8
|
-
end
|
9
|
+
Coveralls::RakeTask.new
|
9
10
|
|
10
|
-
|
11
|
-
task
|
12
|
-
|
13
|
-
|
11
|
+
Rubocop::RakeTask.new(:rubocop) do |task|
|
12
|
+
task.patterns = ['**/*.rb', 'Rakefile']
|
13
|
+
# abort rake on failure
|
14
|
+
task.fail_on_error = false
|
15
|
+
end
|
16
|
+
|
17
|
+
Cucumber::Rake::Task.new(:journeys) do |t|
|
18
|
+
t.cucumber_opts = 'features --format pretty'
|
19
|
+
end
|
14
20
|
|
15
|
-
|
21
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
22
|
+
t.pattern = 'spec/unit/**/*_spec.rb'
|
16
23
|
end
|
24
|
+
|
25
|
+
RSpec::Core::RakeTask.new(:integration) do |t|
|
26
|
+
t.pattern = 'spec/integration/**/*_spec.rb'
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => [:unit, :integration, :journeys, :rubocop, 'coveralls:push']
|
@@ -0,0 +1,25 @@
|
|
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"
|
@@ -0,0 +1,74 @@
|
|
1
|
+
Feature: Validation journey
|
2
|
+
Scenario: Meta-validation of a valid contract
|
3
|
+
Given a file named "contracts/my_contract.json" with:
|
4
|
+
"""
|
5
|
+
{
|
6
|
+
"request": {
|
7
|
+
"method": "GET",
|
8
|
+
"path": "/hello_world",
|
9
|
+
"headers": {
|
10
|
+
"Accept": "application/json"
|
11
|
+
},
|
12
|
+
"params": {}
|
13
|
+
},
|
14
|
+
|
15
|
+
"response": {
|
16
|
+
"status": 200,
|
17
|
+
"headers": {
|
18
|
+
"Content-Type": "application/json"
|
19
|
+
},
|
20
|
+
"body": {
|
21
|
+
"description": "A simple response",
|
22
|
+
"type": "object",
|
23
|
+
"properties": {
|
24
|
+
"message": {
|
25
|
+
"type": "string"
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
"""
|
32
|
+
When I successfully run `bundle exec rake pacto:meta_validate['tmp/aruba/contracts/my_contract.json']`
|
33
|
+
Then the output should contain "All contracts successfully meta-validated"
|
34
|
+
|
35
|
+
|
36
|
+
Scenario: Meta-validation of an invalid contract
|
37
|
+
Given a file named "contracts/my_contract.json" with:
|
38
|
+
"""
|
39
|
+
{"request": "yes"}
|
40
|
+
"""
|
41
|
+
When I run `bundle exec rake pacto:meta_validate['tmp/aruba/contracts/my_contract.json']`
|
42
|
+
Then the exit status should be 1
|
43
|
+
And the output should contain "did not match the following type"
|
44
|
+
|
45
|
+
|
46
|
+
Scenario: Meta-validation of a contract with empty request and response
|
47
|
+
Given a file named "contracts/my_contract.json" with:
|
48
|
+
"""
|
49
|
+
{"request": {}, "response": {}}
|
50
|
+
"""
|
51
|
+
When I run `bundle exec rake pacto:meta_validate['tmp/aruba/contracts/my_contract.json']`
|
52
|
+
Then the exit status should be 1
|
53
|
+
And the output should contain "did not contain a required property"
|
54
|
+
|
55
|
+
Scenario: Meta-validation of a contracts response body
|
56
|
+
Given a file named "contracts/my_contract.json" with:
|
57
|
+
"""
|
58
|
+
{
|
59
|
+
"request": {
|
60
|
+
"method": "GET",
|
61
|
+
"path": "/hello_world"
|
62
|
+
},
|
63
|
+
|
64
|
+
"response": {
|
65
|
+
"status": 200,
|
66
|
+
"body": {
|
67
|
+
"required": "anystring"
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
"""
|
72
|
+
When I run `bundle exec rake pacto:meta_validate['tmp/aruba/contracts/my_contract.json']`
|
73
|
+
Then the exit status should be 1
|
74
|
+
And the output should contain "did not match the following type"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../../spec/coveralls_helper'
|
2
|
+
require 'rake'
|
3
|
+
require 'aruba'
|
4
|
+
require 'aruba/cucumber'
|
5
|
+
require 'aruba/in_process'
|
6
|
+
require 'aruba/jruby' if RUBY_PLATFORM == 'java'
|
7
|
+
require 'pacto/server'
|
8
|
+
|
9
|
+
Before do
|
10
|
+
@aruba_timeout_seconds = RUBY_PLATFORM == 'java' ? 60 : 10
|
11
|
+
end
|
12
|
+
|
13
|
+
Before('@needs_server') do
|
14
|
+
@server = Pacto::Server::Dummy.new 8000, '/hello', '{"message": "Hello World!"}'
|
15
|
+
@server.start
|
16
|
+
end
|