pacto 0.4.0.rc1 → 0.4.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile +2 -7
  4. data/Rakefile +0 -5
  5. data/appveyor.yml +12 -0
  6. data/features/configuration/strict_matchers.feature +4 -4
  7. data/features/generate/generation.feature +8 -10
  8. data/features/support/env.rb +3 -7
  9. data/features/validate/validation.feature +3 -3
  10. data/lib/pacto.rb +5 -4
  11. data/lib/pacto/actors/json_generator.rb +1 -1
  12. data/lib/pacto/body_parsing.rb +42 -0
  13. data/lib/pacto/consumer/faraday_driver.rb +5 -2
  14. data/lib/pacto/contract.rb +23 -24
  15. data/lib/pacto/contract_factory.rb +4 -4
  16. data/lib/pacto/core/configuration.rb +18 -10
  17. data/lib/pacto/core/http_middleware.rb +1 -1
  18. data/lib/pacto/core/pacto_request.rb +3 -15
  19. data/lib/pacto/core/pacto_response.rb +3 -13
  20. data/lib/pacto/errors.rb +68 -0
  21. data/lib/pacto/formats/legacy/contract.rb +49 -0
  22. data/lib/pacto/formats/legacy/contract_builder.rb +129 -0
  23. data/lib/pacto/formats/legacy/contract_factory.rb +63 -0
  24. data/lib/pacto/formats/legacy/contract_generator.rb +77 -0
  25. data/lib/pacto/formats/legacy/generator/filters.rb +46 -0
  26. data/lib/pacto/formats/legacy/generator_hint.rb +36 -0
  27. data/lib/pacto/formats/legacy/request_clause.rb +39 -0
  28. data/lib/pacto/formats/legacy/response_clause.rb +31 -0
  29. data/lib/pacto/formats/swagger/contract.rb +86 -0
  30. data/lib/pacto/formats/swagger/contract_factory.rb +45 -0
  31. data/lib/pacto/formats/swagger/request_clause.rb +53 -0
  32. data/lib/pacto/formats/swagger/response_clause.rb +31 -0
  33. data/lib/pacto/generator.rb +4 -4
  34. data/lib/pacto/handlers/json_handler.rb +19 -0
  35. data/lib/pacto/handlers/text_handler.rb +17 -0
  36. data/lib/pacto/request_clause.rb +9 -19
  37. data/lib/pacto/response_clause.rb +4 -4
  38. data/lib/pacto/server.rb +41 -2
  39. data/lib/pacto/stubs/uri_pattern.rb +5 -5
  40. data/lib/pacto/stubs/webmock_adapter.rb +4 -1
  41. data/lib/pacto/test_helper.rb +16 -13
  42. data/lib/pacto/version.rb +1 -1
  43. data/pacto-server.gemspec +1 -3
  44. data/pacto.gemspec +1 -1
  45. data/sample_apis/user_api.rb +16 -0
  46. data/samples/contracts/user.json +51 -0
  47. data/samples/cops.rb +3 -0
  48. data/samples/server_cli.sh +3 -3
  49. data/spec/fabricators/contract_fabricator.rb +17 -8
  50. data/spec/fixtures/{deprecated_contracts → contracts/deprecated}/deprecated_contract.json +2 -2
  51. data/spec/fixtures/contracts/{contract.json → legacy/contract.json} +0 -0
  52. data/spec/fixtures/contracts/{contract_with_examples.json → legacy/contract_with_examples.json} +0 -0
  53. data/spec/fixtures/contracts/{simple_contract.json → legacy/simple_contract.json} +1 -1
  54. data/spec/fixtures/contracts/{strict_contract.json → legacy/strict_contract.json} +0 -0
  55. data/spec/fixtures/contracts/{templating_contract.json → legacy/templating_contract.json} +0 -0
  56. data/spec/fixtures/{swagger → contracts/swagger}/petstore.yaml +0 -0
  57. data/spec/integration/e2e_spec.rb +6 -12
  58. data/spec/integration/forensics/integration_matcher_spec.rb +5 -11
  59. data/spec/integration/rspec_spec.rb +12 -12
  60. data/spec/integration/templating_spec.rb +1 -1
  61. data/spec/spec_helper.rb +14 -2
  62. data/spec/unit/pacto/contract_factory_spec.rb +1 -2
  63. data/spec/unit/pacto/contract_spec.rb +44 -70
  64. data/spec/unit/pacto/core/investigation_spec.rb +4 -3
  65. data/spec/unit/pacto/formats/legacy/contract_builder_spec.rb +93 -0
  66. data/spec/unit/pacto/formats/legacy/contract_factory_spec.rb +29 -0
  67. data/spec/unit/pacto/formats/legacy/contract_generator_spec.rb +173 -0
  68. data/spec/unit/pacto/formats/legacy/contract_spec.rb +41 -0
  69. data/spec/unit/pacto/formats/legacy/generator/filters_spec.rb +104 -0
  70. data/spec/unit/pacto/formats/legacy/request_clause_spec.rb +79 -0
  71. data/spec/unit/pacto/formats/legacy/response_clause_spec.rb +45 -0
  72. data/spec/unit/pacto/formats/swagger/contract_factory_spec.rb +58 -0
  73. data/spec/unit/pacto/formats/swagger/contract_spec.rb +47 -0
  74. data/spec/unit/pacto/investigation_registry_spec.rb +1 -2
  75. data/spec/unit/pacto/pacto_spec.rb +6 -4
  76. data/spec/unit/pacto/stubs/uri_pattern_spec.rb +7 -8
  77. data/spec/unit/pacto/stubs/webmock_adapter_spec.rb +2 -4
  78. data/tasks/release.rake +1 -1
  79. metadata +53 -53
  80. data/lib/pacto/contract_builder.rb +0 -125
  81. data/lib/pacto/exceptions/invalid_contract.rb +0 -12
  82. data/lib/pacto/generator/filters.rb +0 -42
  83. data/lib/pacto/generator/hint.rb +0 -26
  84. data/lib/pacto/generator/native_contract_generator.rb +0 -74
  85. data/lib/pacto/native_contract_factory.rb +0 -60
  86. data/lib/pacto/swagger_contract_factory.rb +0 -90
  87. data/spec/pacto/dummy_server.rb +0 -4
  88. data/spec/pacto/dummy_server/dummy.rb +0 -51
  89. data/spec/pacto/dummy_server/jruby_workaround_helper.rb +0 -23
  90. data/spec/pacto/dummy_server/playback_servlet.rb +0 -22
  91. data/spec/unit/pacto/contract_builder_spec.rb +0 -89
  92. data/spec/unit/pacto/generator/filters_spec.rb +0 -100
  93. data/spec/unit/pacto/generator/native_contract_generator_spec.rb +0 -171
  94. data/spec/unit/pacto/native_contract_factory_spec.rb +0 -26
  95. data/spec/unit/pacto/request_clause_spec.rb +0 -75
  96. data/spec/unit/pacto/response_clause_spec.rb +0 -41
  97. data/spec/unit/pacto/server/playback_servlet_spec.rb +0 -27
  98. data/spec/unit/pacto/swagger_contract_factory_spec.rb +0 -56
@@ -0,0 +1,41 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'unit/pacto/contract_spec'
3
+
4
+ module Pacto
5
+ module Formats
6
+ module Legacy
7
+ describe Contract do
8
+ let(:request_clause) do
9
+ Pacto::Formats::Legacy::RequestClause.new(
10
+ http_method: 'GET',
11
+ host: 'http://example.com',
12
+ path: '/',
13
+ schema: {
14
+ type: 'object',
15
+ required: true # , :properties => double('body definition properties')
16
+ }
17
+ )
18
+ end
19
+
20
+ let(:response_clause) do
21
+ ResponseClause.new(status: 200)
22
+ end
23
+ let(:adapter) { double 'provider' }
24
+ let(:file) { contract_file 'contract', 'legacy' }
25
+ let(:consumer_driver) { double }
26
+ let(:provider_actor) { double }
27
+
28
+ subject(:contract) do
29
+ described_class.new(
30
+ request: request_clause,
31
+ response: response_clause,
32
+ file: file,
33
+ name: 'sample'
34
+ )
35
+ end
36
+
37
+ it_behaves_like 'a contract'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,104 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Formats
4
+ module Legacy
5
+ module Generator
6
+ describe Filters do
7
+ let(:record_host) do
8
+ 'http://example.com'
9
+ end
10
+ let(:request) do
11
+ RequestClause.new(
12
+ host: record_host,
13
+ http_method: 'GET',
14
+ path: '/abcd',
15
+ headers: {
16
+ 'Server' => ['example.com'],
17
+ 'Connection' => ['Close'],
18
+ 'Content-Length' => [1234],
19
+ 'Via' => ['Some Proxy'],
20
+ 'User-Agent' => ['rspec']
21
+ },
22
+ params: {
23
+ 'apikey' => "<%= ENV['MY_API_KEY'] %>"
24
+ }
25
+ )
26
+ end
27
+ let(:varies) { ['User-Agent'] }
28
+ let(:response) do
29
+ Faraday::Response.new(
30
+ status: 200,
31
+ response_headers: {
32
+ 'Date' => Time.now.rfc2822,
33
+ 'Last-Modified' => Time.now.rfc2822,
34
+ 'ETag' => 'abc123',
35
+ 'Server' => ['Fake Server'],
36
+ 'Content-Type' => ['application/json'],
37
+ 'Vary' => varies
38
+ },
39
+ body: double('dummy body')
40
+ )
41
+ end
42
+
43
+ describe '#filter_request_headers' do
44
+ subject(:filtered_request_headers) { described_class.new.filter_request_headers(request, response).keys.map(&:downcase) }
45
+ it 'keeps important request headers' do
46
+ expect(filtered_request_headers).to include 'user-agent'
47
+ end
48
+
49
+ it 'filters informational request headers' do
50
+ expect(filtered_request_headers).not_to include 'via'
51
+ expect(filtered_request_headers).not_to include 'date'
52
+ expect(filtered_request_headers).not_to include 'server'
53
+ expect(filtered_request_headers).not_to include 'content-length'
54
+ expect(filtered_request_headers).not_to include 'connection'
55
+ end
56
+
57
+ context 'multiple Vary elements' do
58
+ context 'as a single string' do
59
+ let(:varies) do
60
+ ['User-Agent,Via']
61
+ end
62
+ it 'keeps each header' do
63
+ expect(filtered_request_headers).to include 'user-agent'
64
+ expect(filtered_request_headers).to include 'via'
65
+ end
66
+ end
67
+ context 'as multiple items' do
68
+ let(:varies) do
69
+ %w(User-Agent Via)
70
+ end
71
+ it 'keeps each header' do
72
+ expect(filtered_request_headers).to include 'user-agent'
73
+ expect(filtered_request_headers).to include 'via'
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ describe '#filter_response_headers' do
80
+ subject(:filtered_response_headers) { described_class.new.filter_response_headers(request, response).keys.map(&:downcase) }
81
+ it 'keeps important response headers' do
82
+ expect(filtered_response_headers).to include 'content-type'
83
+ end
84
+
85
+ it 'filters connection control headers' do
86
+ expect(filtered_response_headers).not_to include 'content-length'
87
+ expect(filtered_response_headers).not_to include 'via'
88
+ end
89
+
90
+ it 'filters freshness headers' do
91
+ expect(filtered_response_headers).not_to include 'date'
92
+ expect(filtered_response_headers).not_to include 'last-modified'
93
+ expect(filtered_response_headers).not_to include 'eTag'
94
+ end
95
+
96
+ it 'filters x-* headers' do
97
+ expect(filtered_response_headers).not_to include 'x-men'
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,79 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Formats
4
+ module Legacy
5
+ describe RequestClause do
6
+ let(:host) { 'http://localhost' }
7
+ let(:method) { 'GET' }
8
+ let(:path) { '/hello_world' }
9
+ let(:headers) { { 'accept' => 'application/json' } }
10
+ let(:params) { { 'foo' => 'bar' } }
11
+ let(:body) { double :body }
12
+ let(:params_as_json) { "{\"foo\":\"bar\"}" }
13
+ let(:absolute_uri) { "#{host}#{path}" }
14
+ subject(:request) do
15
+ req_hash = {
16
+ host: host,
17
+ 'http_method' => method,
18
+ 'path' => path,
19
+ 'headers' => headers,
20
+ 'params' => params
21
+ }
22
+ # The default test is for missing keys, not explicitly nil keys
23
+ req_hash.merge!('schema' => body) if body
24
+ described_class.new(req_hash)
25
+ end
26
+
27
+ it 'has a host' do
28
+ expect(request.host).to eq host
29
+ end
30
+
31
+ describe '#http_method' do
32
+ it 'delegates to definition' do
33
+ expect(request.http_method).to eq :get
34
+ end
35
+
36
+ it 'downcases the method' do
37
+ expect(request.http_method).to eq request.http_method.downcase
38
+ end
39
+
40
+ it 'returns a symbol' do
41
+ expect(request.http_method).to be_kind_of Symbol
42
+ end
43
+ end
44
+
45
+ describe '#schema' do
46
+ it 'delegates to definition\'s body' do
47
+ expect(request.schema).to eq body
48
+ end
49
+
50
+ describe 'when definition does not have a schema' do
51
+ let(:body) { nil }
52
+
53
+ it 'returns an empty empty hash' do
54
+ expect(request.schema).to eq({})
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#path' do
60
+ it 'delegates to definition' do
61
+ expect(request.path).to eq path
62
+ end
63
+ end
64
+
65
+ describe '#headers' do
66
+ it 'delegates to definition' do
67
+ expect(request.headers).to eq headers
68
+ end
69
+ end
70
+
71
+ describe '#params' do
72
+ it 'delegates to definition' do
73
+ expect(request.params).to eq params
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,45 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Formats
4
+ module Legacy
5
+ describe ResponseClause do
6
+ let(:body_definition) do
7
+ Fabricate(:schema)
8
+ end
9
+
10
+ let(:definition) do
11
+ {
12
+ 'status' => 200,
13
+ 'headers' => {
14
+ 'Content-Type' => 'application/json'
15
+ },
16
+ 'schema' => body_definition
17
+ }
18
+ end
19
+
20
+ subject(:response) { described_class.new(definition) }
21
+
22
+ it 'has a status' do
23
+ expect(response.status).to eq(200)
24
+ end
25
+
26
+ it 'has a headers hash' do
27
+ expect(response.headers).to eq(
28
+ 'Content-Type' => 'application/json'
29
+ )
30
+ end
31
+
32
+ it 'has a schema' do
33
+ expect(response.schema).to eq(body_definition)
34
+ end
35
+
36
+ it 'has a default value for the schema' do
37
+ definition.delete 'schema'
38
+ response = described_class.new(definition)
39
+ expect(response.schema).to eq(Hash.new)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Formats
4
+ module Swagger
5
+ describe ContractFactory do
6
+ let(:swagger_file) { contract_file('petstore', 'swagger') }
7
+ let(:expected_schema) do
8
+ {
9
+ 'type' => 'array',
10
+ 'items' => {
11
+ 'required' => %w(id name),
12
+ 'properties' => {
13
+ 'id' => { 'type' => 'integer', 'format' => 'int64' },
14
+ 'name' => { 'type' => 'string' },
15
+ 'tag' => { 'type' => 'string' }
16
+ }
17
+ }
18
+ }
19
+ end
20
+ describe '#load_hints' do
21
+ pending 'loads hints from Swagger' do
22
+ hints = subject.load_hints(swagger_file)
23
+ expect(hints.size).to eq(3) # number of API operations
24
+ hints.each do | hint |
25
+ expect(hint).to be_a_kind_of(Pacto::Generator::Hint)
26
+ expect(hint.host).to eq('petstore.swagger.wordnik.com')
27
+ expect([:get, :post]).to include(hint.http_method)
28
+ expect(hint.path).to match(/\/pets/)
29
+ end
30
+ end
31
+ end
32
+
33
+ describe '#build_from_file' do
34
+ it 'loads Contracts from Swagger' do
35
+ contracts = subject.build_from_file(swagger_file)
36
+ expect(contracts.size).to eq(3) # number of API operations
37
+ contracts.each do | contract |
38
+ expect(contract).to be_a(Pacto::Formats::Swagger::Contract)
39
+
40
+ request_clause = contract.request
41
+ expect(request_clause.host).to eq('petstore.swagger.wordnik.com')
42
+ expect([:get, :post]).to include(request_clause.http_method)
43
+ expect(request_clause.path).to match(/\/pets/)
44
+
45
+ response_clause = contract.response
46
+ if request_clause.http_method == :get
47
+ expect(response_clause.status).to eq(200)
48
+ else
49
+ expect(response_clause.status).to eq(201)
50
+ end
51
+ expect(response_clause.schema).to eq(expected_schema) if response_clause.status == 200
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,47 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'unit/pacto/contract_spec'
3
+
4
+ module Pacto
5
+ module Formats
6
+ module Swagger
7
+ describe Contract do
8
+ let(:swagger_yaml) do
9
+ ''"
10
+ swagger: '2.0'
11
+ info:
12
+ title: Sample API
13
+ version: N/A
14
+ consumes:
15
+ - application/json
16
+ produces:
17
+ - application/json
18
+ paths:
19
+ /:
20
+ get:
21
+ operationId: sample
22
+ responses:
23
+ 200:
24
+ description: |-
25
+ Success.
26
+ "''
27
+ end
28
+ let(:swagger_definition) do
29
+ ::Swagger.build(swagger_yaml, format: :yaml)
30
+ end
31
+ let(:api_operation) do
32
+ swagger_definition.operations.first
33
+ end
34
+ let(:adapter) { double 'provider' }
35
+ let(:file) { Tempfile.new(['swagger', '.yaml']).path }
36
+ let(:consumer_driver) { double }
37
+ let(:provider_actor) { double }
38
+
39
+ subject(:contract) do
40
+ described_class.new(api_operation, file: file)
41
+ end
42
+
43
+ it_behaves_like 'a contract'
44
+ end
45
+ end
46
+ end
47
+ end
@@ -55,11 +55,10 @@ describe Pacto::InvestigationRegistry do
55
55
  end
56
56
 
57
57
  describe '.failed_investigations' do
58
- let(:contract) { Fabricate(:contract) }
58
+ let(:contract) { Fabricate(:contract, name: 'test') }
59
59
  let(:citations2) { ['a sample citation'] }
60
60
 
61
61
  it 'returns investigations with unsuccessful citations' do
62
- allow(contract).to receive(:name).and_return 'test'
63
62
  investigation_with_successful_citations = Pacto::Investigation.new(request_signature, pacto_response, nil, ['error'])
64
63
  investigation_with_unsuccessful_citations = Pacto::Investigation.new(request_signature, pacto_response, nil, %w(error2 error3))
65
64
 
@@ -17,10 +17,12 @@ describe Pacto do
17
17
  end
18
18
 
19
19
  describe '.validate_contract' do
20
+ let(:contract_path) { contract_file 'contract' }
21
+
20
22
  context 'valid' do
21
23
  it 'returns true' do
22
24
  mock_investigation []
23
- success = described_class.validate_contract 'my_contract.json'
25
+ success = described_class.validate_contract contract_path
24
26
  expect(success).to be true
25
27
  end
26
28
  end
@@ -28,20 +30,20 @@ describe Pacto do
28
30
  context 'invalid' do
29
31
  it 'raises an InvalidContract error' do
30
32
  mock_investigation ['Error 1']
31
- expect { described_class.validate_contract 'my_contract.json' }.to raise_error(InvalidContract)
33
+ expect { described_class.validate_contract contract_path }.to raise_error(Pacto::InvalidContract)
32
34
  end
33
35
  end
34
36
  end
35
37
 
36
38
  describe 'loading contracts' do
37
- let(:contracts_path) { 'path/to/dir' }
39
+ let(:contracts_path) { contracts_folder }
38
40
  let(:host) { 'localhost' }
39
41
 
40
42
  it 'instantiates a contract list' do
41
43
  expect(Pacto::ContractSet).to receive(:new) do |contracts|
42
44
  contracts.each { |contract| expect(contract).to be_a_kind_of(Pacto::Contract) }
43
45
  end
44
- described_class.load_contracts('spec/fixtures/contracts/', host)
46
+ described_class.load_contracts(contracts_path, host)
45
47
  end
46
48
  end
47
49
  end
@@ -21,22 +21,21 @@ module Pacto
21
21
  expect(uri_pattern.pattern).to eql('{scheme}://myhost.com/stuff{?anyvars*}')
22
22
  end
23
23
 
24
- it 'convers segments preceded by : into variables', :deprecated do
25
- request = Fabricate(:request_clause, host: 'myhost.com', path: '/:id')
26
- uri_pattern = UriPattern.for(request)
27
- expect(uri_pattern.keys).to include('id')
28
- expect(uri_pattern.pattern).to_not include(':id')
24
+ it 'fails if segment uses : syntax' do
25
+ expect do
26
+ Fabricate(:request_clause, host: 'myhost.com', path: '/:id')
27
+ end.to raise_error(/old syntax no longer supported/)
29
28
  end
30
29
 
31
30
  it 'creates a regex that does not allow additional path elements' do
32
- request = Fabricate(:request_clause, host: 'myhost.com', path: '/:id')
31
+ request = Fabricate(:request_clause, host: 'myhost.com', path: '/{id}')
33
32
  pattern = UriPattern.for(request)
34
33
  expect(pattern).to match('http://myhost.com/foo')
35
34
  expect(pattern).to_not match('http://myhost.com/foo/bar')
36
35
  end
37
36
 
38
- it 'creates a regex that does allow query parameters', :deprecated do
39
- request = Fabricate(:request_clause, host: 'myhost.com', path: '/:id')
37
+ it 'creates a regex that does allow query parameters' do
38
+ request = Fabricate(:request_clause, host: 'myhost.com', path: '/{id}')
40
39
  pattern = UriPattern.for(request)
41
40
  expect(pattern.match('http://myhost.com/foo?a=b')). to be_truthy
42
41
  expect(pattern.match('http://myhost.com/foo?a=b&c=d')).to be_truthy