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
data/samples/consumer.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'pacto'
|
3
|
+
Pacto.load_contracts 'contracts', 'http://localhost:5000'
|
4
|
+
WebMock.allow_net_connect!
|
5
|
+
|
6
|
+
interactions = Pacto.simulate_consumer :my_client do
|
7
|
+
request 'Ping'
|
8
|
+
request 'Echo', body: ->(body) { body.reverse },
|
9
|
+
headers: (proc do |headers|
|
10
|
+
headers['Content-Type'] = 'text/json'
|
11
|
+
headers['Accept'] = 'none'
|
12
|
+
headers
|
13
|
+
end)
|
14
|
+
end
|
15
|
+
puts interactions
|
@@ -0,0 +1 @@
|
|
1
|
+
This folder contains sample contracts.
|
@@ -0,0 +1,93 @@
|
|
1
|
+
// Pacto Contracts describe the constraints we want to put on interactions between a consumer and a provider. It sets some expectations about the headers expected for both the request and response, the expected response status code. It also uses [json-schema](http://json-schema.org/) to define the allowable request body (if one should exist) and response body.
|
2
|
+
{
|
3
|
+
// The Request section comes first. In this case, we're just describing a simple get request that does not require any parameters or a request body.
|
4
|
+
"request": {
|
5
|
+
"headers": {
|
6
|
+
// A request must exactly match these headers for Pacto to believe the request matches the contract, unless `Pacto.configuration.strict_matchers` is false.
|
7
|
+
"Accept": "application/vnd.github.beta+json",
|
8
|
+
"Accept-Encoding": "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
|
9
|
+
},
|
10
|
+
// The `method` and `path` are required. The `path` may be an [rfc6570 URI template](http://tools.ietf.org/html/rfc6570) for more flexible matching.
|
11
|
+
"method": "get",
|
12
|
+
"path": "/repos/thoughtworks/pacto/readme"
|
13
|
+
},
|
14
|
+
"response": {
|
15
|
+
"headers": {
|
16
|
+
"Content-Type": "application/json; charset=utf-8",
|
17
|
+
"Status": "200 OK",
|
18
|
+
"Cache-Control": "public, max-age=60, s-maxage=60",
|
19
|
+
"Etag": "\"fc8e78b0a9694de66d47317768b20820\"",
|
20
|
+
"Vary": "Accept, Accept-Encoding",
|
21
|
+
"Access-Control-Allow-Credentials": "true",
|
22
|
+
"Access-Control-Expose-Headers": "ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval",
|
23
|
+
"Access-Control-Allow-Origin": "*"
|
24
|
+
},
|
25
|
+
"status": 200,
|
26
|
+
"body": {
|
27
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
28
|
+
"description": "Generated from https://api.github.com/repos/thoughtworks/pacto/readme with shasum 3ae59164c6d9f84c0a81f21fb63e17b3b8ce6894",
|
29
|
+
"type": "object",
|
30
|
+
"required": true,
|
31
|
+
"properties": {
|
32
|
+
"name": {
|
33
|
+
"type": "string",
|
34
|
+
"required": true
|
35
|
+
},
|
36
|
+
"path": {
|
37
|
+
"type": "string",
|
38
|
+
"required": true
|
39
|
+
},
|
40
|
+
"sha": {
|
41
|
+
"type": "string",
|
42
|
+
"required": true
|
43
|
+
},
|
44
|
+
"size": {
|
45
|
+
"type": "integer",
|
46
|
+
"required": true
|
47
|
+
},
|
48
|
+
"url": {
|
49
|
+
"type": "string",
|
50
|
+
"required": true
|
51
|
+
},
|
52
|
+
"html_url": {
|
53
|
+
"type": "string",
|
54
|
+
"required": true
|
55
|
+
},
|
56
|
+
"git_url": {
|
57
|
+
"type": "string",
|
58
|
+
"required": true
|
59
|
+
},
|
60
|
+
"type": {
|
61
|
+
"type": "string",
|
62
|
+
"required": true
|
63
|
+
},
|
64
|
+
"content": {
|
65
|
+
"type": "string",
|
66
|
+
"required": true
|
67
|
+
},
|
68
|
+
"encoding": {
|
69
|
+
"type": "string",
|
70
|
+
"required": true
|
71
|
+
},
|
72
|
+
"_links": {
|
73
|
+
"type": "object",
|
74
|
+
"required": true,
|
75
|
+
"properties": {
|
76
|
+
"self": {
|
77
|
+
"type": "string",
|
78
|
+
"required": true
|
79
|
+
},
|
80
|
+
"git": {
|
81
|
+
"type": "string",
|
82
|
+
"required": true
|
83
|
+
},
|
84
|
+
"html": {
|
85
|
+
"type": "string",
|
86
|
+
"required": true
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
{
|
2
|
+
"request": {
|
3
|
+
"headers": {
|
4
|
+
},
|
5
|
+
"http_method": "get",
|
6
|
+
"path": "/api/album/{id}/cover"
|
7
|
+
},
|
8
|
+
"response": {
|
9
|
+
"headers": {
|
10
|
+
"Content-Type": "application/json"
|
11
|
+
},
|
12
|
+
"status": 200,
|
13
|
+
"schema": {
|
14
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
15
|
+
"description": "Generated from http://localhost:5000/api/album/1/cover with shasum db640385d2b346db760dbfd78058101663197bcf",
|
16
|
+
"type": "object",
|
17
|
+
"required": true,
|
18
|
+
"properties": {
|
19
|
+
"cover": {
|
20
|
+
"type": "string",
|
21
|
+
"required": true
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
25
|
+
},
|
26
|
+
"examples": {
|
27
|
+
"default": {
|
28
|
+
"request": {
|
29
|
+
"method": "get",
|
30
|
+
"uri": "http://localhost:5000/api/album/1/cover",
|
31
|
+
"headers": {
|
32
|
+
"User-Agent": "Faraday v0.9.0",
|
33
|
+
"Accept-Encoding": "gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
|
34
|
+
"Accept": "*/*"
|
35
|
+
}
|
36
|
+
},
|
37
|
+
"response": {
|
38
|
+
"status": 200,
|
39
|
+
"headers": {
|
40
|
+
"Content-Type": "application/json",
|
41
|
+
"Content-Length": "17"
|
42
|
+
},
|
43
|
+
"body": "{\"cover\":\"image\"}"
|
44
|
+
}
|
45
|
+
}
|
46
|
+
},
|
47
|
+
"name": "Get Album Cover"
|
48
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"name": "Echo",
|
3
|
+
"request": {
|
4
|
+
"headers": {
|
5
|
+
"Content-Type": "text/plain"
|
6
|
+
},
|
7
|
+
"http_method": "post",
|
8
|
+
"path": "/api/echo",
|
9
|
+
"schema": {
|
10
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
11
|
+
"oneOf": [
|
12
|
+
{ "type": "string", "required": true },
|
13
|
+
{ "type": "object", "required": true }
|
14
|
+
]
|
15
|
+
}
|
16
|
+
},
|
17
|
+
"response": {
|
18
|
+
"status": 201,
|
19
|
+
"schema": {
|
20
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
21
|
+
"oneOf": [
|
22
|
+
{ "type": "string", "required": true },
|
23
|
+
{ "type": "object", "required": true }
|
24
|
+
]
|
25
|
+
}
|
26
|
+
},
|
27
|
+
"examples": {
|
28
|
+
"foo": {
|
29
|
+
"request": {
|
30
|
+
"body": "foo"
|
31
|
+
},
|
32
|
+
"response": {
|
33
|
+
"body": "foo"
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
{
|
2
|
+
"name": "Ping",
|
3
|
+
"request": {
|
4
|
+
"headers": {
|
5
|
+
},
|
6
|
+
"http_method": "get",
|
7
|
+
"path": "/api/ping"
|
8
|
+
},
|
9
|
+
"response": {
|
10
|
+
"headers": {
|
11
|
+
"Content-Type": "application/json"
|
12
|
+
},
|
13
|
+
"status": 200,
|
14
|
+
"schema": {
|
15
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
16
|
+
"description": "Generated from http://localhost:9292/api/ping with shasum 2cf3478c18e3ce877fb823ed435cb75b4a801aaa",
|
17
|
+
"type": "object",
|
18
|
+
"required": true,
|
19
|
+
"properties": {
|
20
|
+
"ping": {
|
21
|
+
"type": "string",
|
22
|
+
"required": true
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
},
|
27
|
+
"examples": {
|
28
|
+
"default": {
|
29
|
+
"request": {
|
30
|
+
},
|
31
|
+
"response": {
|
32
|
+
"body": {
|
33
|
+
"ping": "pong - from the example!"
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
data/samples/cops.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'pacto'
|
3
|
+
Pacto.configure do |c|
|
4
|
+
c.contracts_path = 'contracts'
|
5
|
+
end
|
6
|
+
Pacto.validate!
|
7
|
+
|
8
|
+
# You can create a custom cop that investigates the request/response and sees if it complies with a
|
9
|
+
# contract. The cop should return a list of citations if it finds any problems.
|
10
|
+
class MyCustomCop
|
11
|
+
def investigate(_request, _response, contract)
|
12
|
+
citations = []
|
13
|
+
citations << 'Contract must have a request schema' if contract.request.schema.empty?
|
14
|
+
citations << 'Contract must have a response schema' if contract.response.schema.empty?
|
15
|
+
citations
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Pacto::Cops.active_cops << MyCustomCop.new
|
20
|
+
|
21
|
+
contracts = Pacto.load_contracts('contracts', 'http://localhost:5000')
|
22
|
+
contracts.stub_providers
|
23
|
+
puts contracts.simulate_consumers
|
24
|
+
|
25
|
+
# Or you can completely replace the default set of validators
|
26
|
+
Pacto::Cops.registered_cops.clear
|
27
|
+
Pacto::Cops.register_cop Pacto::Cops::ResponseBodyCop
|
28
|
+
|
29
|
+
contracts = Pacto.load_contracts('contracts', 'http://localhost:5000')
|
30
|
+
puts contracts.simulate_consumers
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# Pacto has a few RSpec matchers to help you ensure a **consumer** and **producer** are
|
3
|
+
# interacting properly. First, let's setup the rspec suite.
|
4
|
+
require 'rspec/autorun' # Not generally needed
|
5
|
+
require 'pacto/rspec'
|
6
|
+
WebMock.allow_net_connect!
|
7
|
+
Pacto.validate!
|
8
|
+
Pacto.load_contracts('contracts', 'http://localhost:5000').stub_providers
|
9
|
+
|
10
|
+
# It's usually a good idea to reset Pacto between each scenario. `Pacto.reset` just clears the
|
11
|
+
# data and metrics about which services were called. `Pacto.clear!` also resets all configuration
|
12
|
+
# and plugins.
|
13
|
+
RSpec.configure do |c|
|
14
|
+
c.after(:each) { Pacto.reset }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Pacto provides some RSpec matchers related to contract testing, like making sure
|
18
|
+
# Pacto didn't received any unrecognized requests (`have_unmatched_requests`) and that
|
19
|
+
# the HTTP requests matched up with the terms of the contract (`have_failed_investigations`).
|
20
|
+
describe Faraday do
|
21
|
+
let(:connection) { described_class.new(url: 'http://localhost:5000') }
|
22
|
+
|
23
|
+
it 'passes contract tests' do
|
24
|
+
connection.get '/api/ping'
|
25
|
+
expect(Pacto).to_not have_failed_investigations
|
26
|
+
expect(Pacto).to_not have_unmatched_requests
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# There are also some matchers for collaboration testing, so you can make sure each scenario is
|
31
|
+
# calling the expected services and sending the right type of data.
|
32
|
+
describe Faraday do
|
33
|
+
let(:connection) { described_class.new(url: 'http://localhost:5000') }
|
34
|
+
before(:each) do
|
35
|
+
connection.get '/api/ping'
|
36
|
+
|
37
|
+
connection.post do |req|
|
38
|
+
req.url '/api/echo'
|
39
|
+
req.headers['Content-Type'] = 'application/json'
|
40
|
+
req.body = '{"foo": "bar"}'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'calls the ping service' do
|
45
|
+
expect(Pacto).to have_validated(:get, 'http://localhost:5000/api/ping').against_contract('Ping')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'sends data to the echo service' do
|
49
|
+
expect(Pacto).to have_investigated('Ping').with_response(body: hash_including('ping' => 'pong - from the example!'))
|
50
|
+
expect(Pacto).to have_investigated('Echo').with_request(body: hash_including('foo' => 'bar'))
|
51
|
+
echoed_body = { 'foo' => 'bar' }
|
52
|
+
expect(Pacto).to have_investigated('Echo').with_request(body: echoed_body).with_response(body: echoed_body)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# Some generation related [configuration](configuration.rb).
|
3
|
+
require 'pacto'
|
4
|
+
WebMock.allow_net_connect!
|
5
|
+
Pacto.configure do |c|
|
6
|
+
c.contracts_path = 'contracts'
|
7
|
+
end
|
8
|
+
WebMock.allow_net_connect!
|
9
|
+
|
10
|
+
# Once we call `Pacto.generate!`, Pacto will record contracts for all requests it detects.
|
11
|
+
Pacto.generate!
|
12
|
+
|
13
|
+
# Now, if we run any code that makes an HTTP call (using an
|
14
|
+
# [HTTP library supported by WebMock](https://github.com/bblimke/webmock#supported-http-libraries))
|
15
|
+
# then Pacto will generate a Contract based on the HTTP request/response.
|
16
|
+
#
|
17
|
+
# This code snippet will generate a Contract and save it to `contracts/samples/contracts/localhost/api/ping.json`.
|
18
|
+
require 'faraday'
|
19
|
+
conn = Faraday.new(url: 'http://localhost:5000')
|
20
|
+
response = conn.get '/api/ping'
|
21
|
+
# We're getting back real data from GitHub, so this should be the actual file encoding.
|
22
|
+
puts response.body
|
23
|
+
|
24
|
+
# The generated contract will contain expectations based on the request/response we observed,
|
25
|
+
# including a best-guess at an appropriate json-schema. Our heuristics certainly aren't foolproof,
|
26
|
+
# so you might want to customize schema!
|
27
|
+
|
28
|
+
# Here's another sample that sends a post request.
|
29
|
+
conn.post do |req|
|
30
|
+
req.url '/api/echo'
|
31
|
+
req.headers['Content-Type'] = 'application/json'
|
32
|
+
req.body = '{"red fish": "blue fish"}'
|
33
|
+
end
|
34
|
+
|
35
|
+
# You can provide hints to Pacto to help it generate contracts. For example, Pacto doesn't have
|
36
|
+
# a good way to know a good name and correct URI template for the service. That means that Pacto
|
37
|
+
# will not know if two similar requests are for the same service or two different services, and
|
38
|
+
# will be forced to give names based on the URI that are not good display names.
|
39
|
+
|
40
|
+
# The hint below tells Pacto that requests to http://localhost:5000/album/1/cover and http://localhost:5000/album/2/cover
|
41
|
+
# are both going to the same service, which is known as "Get Album Cover". This hint will cause Pacto to
|
42
|
+
# generate a Contract for "Get Album Cover" and save it to `contracts/get_album_cover.json`, rather than two
|
43
|
+
# contracts that are stored at `contracts/localhost/album/1/cover.json` and `contracts/localhost/album/2/cover.json`.
|
44
|
+
Pacto::Generator.configure do |c|
|
45
|
+
c.hint 'Get Album Cover', http_method: :get, host: 'http://localhost:5000', path: '/api/album/{id}/cover'
|
46
|
+
end
|
47
|
+
conn.get '/api/album/1/cover'
|
48
|
+
conn.get '/api/album/2/cover'
|
data/samples/rspec.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
data/samples/samples.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# # Overview
|
3
|
+
# Welcome to the Pacto usage samples!
|
4
|
+
# This document gives a quick overview of the main features.
|
5
|
+
#
|
6
|
+
# You can browse the Table of Contents (upper right corner) to view additional samples.
|
7
|
+
#
|
8
|
+
# In addition to this document, here are some highlighted samples:
|
9
|
+
# <ul>
|
10
|
+
# <li><a href="configuration">Configuration</a>: Shows all available configuration options</li>
|
11
|
+
# <li><a href="generation">Generation</a>: More details on generation</li>
|
12
|
+
# <li><a href="rspec">RSpec</a>: More samples for RSpec expectations</li>
|
13
|
+
# </ul>
|
14
|
+
|
15
|
+
# You can also find other samples using the Table of Content (upper right corner), including sample contracts.
|
16
|
+
|
17
|
+
# # Getting started
|
18
|
+
# Once you've installed the Pacto gem, you just require it. If you want, you can also require the Pacto rspec expectations.
|
19
|
+
require 'pacto'
|
20
|
+
require 'pacto/rspec'
|
21
|
+
# Pacto will disable live connections, so you will get an error if
|
22
|
+
# your code unexpectedly calls an service that was not stubbed. If you
|
23
|
+
# want to re-enable connections, run `WebMock.allow_net_connect!`
|
24
|
+
WebMock.allow_net_connect!
|
25
|
+
|
26
|
+
# Pacto can be configured via a block. The `contracts_path` option tells Pacto where it should load or save contracts. See the [Configuration](configuration.html) for all the available options.
|
27
|
+
Pacto.configure do |c|
|
28
|
+
c.contracts_path = 'contracts'
|
29
|
+
end
|
30
|
+
|
31
|
+
# # Generating a Contract
|
32
|
+
|
33
|
+
# Calling `Pacto.generate!` enables contract generation.
|
34
|
+
# Pacto.generate!
|
35
|
+
|
36
|
+
# Now, if we run any code that makes an HTTP call (using an
|
37
|
+
# [HTTP library supported by WebMock](https://github.com/bblimke/webmock#supported-http-libraries))
|
38
|
+
# then Pacto will generate a Contract based on the HTTP request/response.
|
39
|
+
#
|
40
|
+
# We're using the sample APIs in the sample_apis directory.
|
41
|
+
require 'faraday'
|
42
|
+
conn = Faraday.new(url: 'http://localhost:5000')
|
43
|
+
response = conn.get '/api/ping'
|
44
|
+
# This is the real request, so you should see {"ping":"pong"}
|
45
|
+
puts response.body
|
46
|
+
|
47
|
+
# # Testing providers by simulating consumers
|
48
|
+
|
49
|
+
# The generated contract will contain expectations based on the request/response we observed,
|
50
|
+
# including a best-guess at an appropriate json-schema. Our heuristics certainly aren't foolproof,
|
51
|
+
# so you might want to modify the output!
|
52
|
+
|
53
|
+
# We can load the contract and validate it, by sending a new request and making sure
|
54
|
+
# the response matches the JSON schema. Obviously it will pass since we just recorded it,
|
55
|
+
# but if the service has made a change, or if you alter the contract with new expectations,
|
56
|
+
# then you will see a contract investigation message.
|
57
|
+
contracts = Pacto.load_contracts('contracts', 'http://localhost:5000')
|
58
|
+
contracts.simulate_consumers
|
59
|
+
|
60
|
+
# # Stubbing providers for consumer testing
|
61
|
+
# We can also use Pacto to stub the service based on the contract.
|
62
|
+
contracts.stub_providers
|
63
|
+
# The stubbed data won't be very realistic, the default behavior is to return the simplest data
|
64
|
+
# that complies with the schema. That basically means that you'll have "bar" for every string.
|
65
|
+
response = conn.get '/api/ping'
|
66
|
+
# You're now getting stubbed data. You should see {"ping":"bar"} unless you recorded with
|
67
|
+
# the `defaults` option enabled, in which case you will still seee {"ping":"pong"}.
|
68
|
+
puts response.body
|
69
|
+
|
70
|
+
# # Collaboration tests with RSpec
|
71
|
+
|
72
|
+
# Pacto comes with rspec matchers
|
73
|
+
require 'pacto/rspec'
|
74
|
+
|
75
|
+
# It's probably a good idea to reset Pacto between each rspec scenario
|
76
|
+
RSpec.configure do |c|
|
77
|
+
c.after(:each) { Pacto.clear! }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Load your contracts, and stub them if you'd like.
|
81
|
+
Pacto.load_contracts('contracts', 'http://localhost:5000').stub_providers
|
82
|
+
# You can turn on investigation mode so Pacto will detect and validate HTTP requests.
|
83
|
+
Pacto.validate!
|
84
|
+
|
85
|
+
describe 'my_code' do
|
86
|
+
it 'calls a service' do
|
87
|
+
conn = Faraday.new(url: 'http://localhost:5000')
|
88
|
+
response = conn.get '/api/ping'
|
89
|
+
# The have_validated matcher makes sure that Pacto received and successfully validated a request
|
90
|
+
expect(Pacto).to have_validated(:get, 'http://localhost:5000/api/ping')
|
91
|
+
end
|
92
|
+
end
|