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
@@ -2,11 +2,11 @@
2
2
  require 'pacto/rspec'
3
3
 
4
4
  describe 'pacto/rspec' do
5
- let(:contract_path) { 'spec/fixtures/contracts/simple_contract.json' }
6
- let(:strict_contract_path) { 'spec/fixtures/contracts/strict_contract.json' }
5
+ let(:contract_path) { contract_file 'simple_contract' }
6
+ let(:strict_contract_path) { contract_file 'strict_contract' }
7
7
 
8
8
  around :each do |example|
9
- run_pacto do
9
+ with_pacto(port: 8000) do
10
10
  example.run
11
11
  end
12
12
  end
@@ -31,7 +31,7 @@ describe 'pacto/rspec' do
31
31
 
32
32
  context 'successful investigations' do
33
33
  let(:contracts) do
34
- Pacto.load_contracts 'spec/fixtures/contracts/', 'http://dummyprovider.com'
34
+ Pacto.load_contracts contracts_folder, 'http://dummyprovider.com'
35
35
  end
36
36
 
37
37
  before(:each) do
@@ -43,7 +43,7 @@ describe 'pacto/rspec' do
43
43
  contracts.stub_providers(device_id: 42)
44
44
  Pacto.validate!
45
45
 
46
- Faraday.get('http://dummyprovider.com/hello') do |req|
46
+ Faraday.get('http://dummyprovider.com/api/hello') do |req|
47
47
  req.headers = { 'Accept' => 'application/json' }
48
48
  end
49
49
  end
@@ -54,9 +54,9 @@ describe 'pacto/rspec' do
54
54
  expect(Pacto).to_not have_failed_investigations
55
55
 
56
56
  # Increasingly strict assertions
57
- expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/hello')
58
- expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/hello').with(headers: { 'Accept' => 'application/json' })
59
- expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/hello').against_contract(/simple_contract.json/)
57
+ expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/api/hello')
58
+ expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/api/hello').with(headers: { 'Accept' => 'application/json' })
59
+ expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/api/hello').against_contract(/simple_contract.json/)
60
60
  end
61
61
 
62
62
  it 'supports negative assertions' do
@@ -77,11 +77,11 @@ describe 'pacto/rspec' do
77
77
  expect_to_raise(/the following requests were not matched.*#{Regexp.quote unmatched_url}/m) { expect(Pacto).to_not have_unmatched_requests }
78
78
 
79
79
  # Expected failures
80
- expect_to_raise(/no matching request was received/) { expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/hello').with(headers: { 'Accept' => 'text/plain' }) }
80
+ expect_to_raise(/no matching request was received/) { expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/api/hello').with(headers: { 'Accept' => 'text/plain' }) }
81
81
  # No support for with accepting a block
82
- # expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/hello').with { |req| req.body == "abc" }
83
- expect_to_raise(/but it was validated against/) { expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/hello').against_contract(/strict_contract.json/) }
84
- expect_to_raise(/but it was validated against/) { expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/hello').against_contract('simple_contract.json') }
82
+ # expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/api/hello').with { |req| req.body == "abc" }
83
+ expect_to_raise(/but it was validated against/) { expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/api/hello').against_contract(/strict_contract.json/) }
84
+ expect_to_raise(/but it was validated against/) { expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/api/hello').against_contract('simple_contract.json') }
85
85
  expect_to_raise(/but no matching request was received/) { expect(Pacto).to have_validated(:get, 'http://dummyprovider.com/strict') }
86
86
  end
87
87
 
@@ -2,7 +2,7 @@
2
2
  require 'securerandom'
3
3
 
4
4
  describe 'Templating' do
5
- let(:contract_path) { 'spec/fixtures/contracts/templating_contract.json' }
5
+ let(:contract_path) { contract_file 'templating_contract' }
6
6
  let(:contracts) { Pacto.load_contracts(contract_path, 'http://dummyprovider.com') }
7
7
 
8
8
  let(:key) { SecureRandom.hex }
@@ -3,7 +3,6 @@ require 'coveralls_helper'
3
3
  require 'webmock/rspec'
4
4
  require 'pacto'
5
5
  require 'pacto/test_helper'
6
- require 'pacto/dummy_server'
7
6
  require 'fabrication'
8
7
  require 'stringio'
9
8
  require 'rspec'
@@ -14,7 +13,6 @@ require_relative 'unit/pacto/actor_spec.rb'
14
13
  RSpec.configure do |config|
15
14
  config.raise_errors_for_deprecations!
16
15
  config.include Pacto::TestHelper
17
- config.include Pacto::DummyServer::JRubyWorkaroundHelper
18
16
  config.expect_with :rspec do |c|
19
17
  c.syntax = :expect
20
18
  end
@@ -23,6 +21,20 @@ RSpec.configure do |config|
23
21
  end
24
22
  end
25
23
 
24
+ def default_pacto_format
25
+ ENV['PACTO_DEFAULT_FORMAT'] || 'legacy'
26
+ end
27
+
28
+ def contracts_folder(format = default_pacto_format)
29
+ "spec/fixtures/contracts/#{format}"
30
+ end
31
+
32
+ def contract_file(name, format = default_pacto_format)
33
+ file = Dir.glob("#{contracts_folder(format)}/#{name}.*").first
34
+ fail "Could not find a #{format} contract for #{name}" if file.nil?
35
+ file
36
+ end
37
+
26
38
  def sample_contract
27
39
  # Memoized for test speed
28
40
  @sample_contract ||= Fabricate(:contract)
@@ -5,8 +5,7 @@ module Pacto
5
5
  describe ContractFactory do
6
6
  let(:host) { 'http://localhost' }
7
7
  let(:contract_name) { 'contract' }
8
- let(:contracts_path) { %w(spec fixtures contracts) }
9
- let(:contract_path) { File.join(contracts_path, "#{contract_name}.json") }
8
+ let(:contract_path) { File.join(contracts_folder, "#{contract_name}.json") }
10
9
  let(:contract_files) { [contract_path, contract_path] }
11
10
  subject(:contract_factory) { described_class }
12
11
 
@@ -1,89 +1,63 @@
1
1
  # -*- encoding : utf-8 -*-
2
- module Pacto
3
- describe Contract do
4
- let(:request_clause) do
5
- Pacto::RequestClause.new(
6
- http_method: 'GET',
7
- host: 'http://example.com',
8
- path: '/',
9
- schema: {
10
- type: 'object',
11
- required: true # , :properties => double('body definition properties')
12
- }
13
- )
14
- end
15
- let(:response_clause) do
16
- Pacto::ResponseClause.new(status: 200)
17
- end
18
- let(:adapter) { double 'provider' }
19
- let(:file) { 'contract.json' }
20
- let(:consumer_driver) { double }
21
- let(:provider_actor) { double }
2
+ RSpec.shared_examples 'a contract' do
3
+ before do
4
+ Pacto.configuration.adapter = adapter
5
+ allow(consumer_driver).to receive(:respond_to?).with(:execute).and_return true
6
+ allow(provider_actor).to receive(:respond_to?).with(:build_response).and_return true
7
+ Pacto.configuration.default_consumer.driver = consumer_driver
8
+ Pacto.configuration.default_provider.actor = provider_actor
9
+ end
22
10
 
23
- subject(:contract) do
24
- described_class.new(
25
- request: request_clause,
26
- response: response_clause,
27
- file: file,
28
- name: 'sample'
29
- )
30
- end
11
+ it 'is a type of Contract' do
12
+ expect(subject).to be_a_kind_of(Pacto::Contract)
13
+ end
31
14
 
32
- before do
33
- Pacto.configuration.adapter = adapter
34
- allow(consumer_driver).to receive(:respond_to?).with(:execute).and_return true
35
- allow(provider_actor).to receive(:respond_to?).with(:build_response).and_return true
36
- Pacto.configuration.default_consumer.driver = consumer_driver
37
- Pacto.configuration.default_provider.actor = provider_actor
15
+ describe '#stub_contract!' do
16
+ it 'register a stub for the contract' do
17
+ expect(adapter).to receive(:stub_request!).with(contract)
18
+ contract.stub_contract!
38
19
  end
20
+ end
39
21
 
40
- describe '#stub_contract!' do
41
- it 'register a stub for the contract' do
42
- expect(adapter).to receive(:stub_request!).with(contract)
43
- contract.stub_contract!
44
- end
45
- end
22
+ context 'investigations' do
23
+ let(:request) { Pacto.configuration.default_consumer.build_request contract }
24
+ let(:fake_response) { Fabricate(:pacto_response) } # double('fake response') }
25
+ let(:cop) { double 'cop' }
26
+ let(:investigation_citations) { [double('investigation result')] }
46
27
 
47
- context 'investigations' do
48
- let(:request) { Pacto.configuration.default_consumer.build_request contract }
49
- let(:fake_response) { Fabricate(:pacto_response) } # double('fake response') }
50
- let(:cop) { double 'cop' }
51
- let(:investigation_citations) { [double('investigation result')] }
28
+ before do
29
+ Pacto::Cops.active_cops.clear
30
+ Pacto::Cops.active_cops << cop
31
+ allow(cop).to receive(:investigate).with(an_instance_of(Pacto::PactoRequest), fake_response, contract).and_return investigation_citations
32
+ end
52
33
 
34
+ describe '#simulate_request' do
53
35
  before do
54
- Pacto::Cops.active_cops.clear
55
- Pacto::Cops.active_cops << cop
56
- allow(cop).to receive(:investigate).with(an_instance_of(Pacto::PactoRequest), fake_response, contract).and_return investigation_citations
36
+ allow(consumer_driver).to receive(:execute).with(an_instance_of(Pacto::PactoRequest)).and_return fake_response
57
37
  end
58
38
 
59
- describe '#simulate_request' do
60
- before do
61
- allow(consumer_driver).to receive(:execute).with(an_instance_of(Pacto::PactoRequest)).and_return fake_response
62
- end
63
-
64
- it 'generates the response' do
65
- expect(consumer_driver).to receive(:execute).with(an_instance_of(Pacto::PactoRequest))
66
- contract.simulate_request
67
- end
39
+ it 'generates the response' do
40
+ expect(consumer_driver).to receive(:execute).with(an_instance_of(Pacto::PactoRequest))
41
+ contract.simulate_request
42
+ end
68
43
 
69
- it 'returns the result of the validating the generated response' do
70
- expect(cop).to receive(:investigate).with(an_instance_of(Pacto::PactoRequest), fake_response, contract)
71
- investigation = contract.simulate_request
72
- expect(investigation.citations).to eq investigation_citations
73
- end
44
+ it 'returns the result of the validating the generated response' do
45
+ expect(cop).to receive(:investigate).with(an_instance_of(Pacto::PactoRequest), fake_response, contract)
46
+ investigation = contract.simulate_request
47
+ expect(investigation.citations).to eq investigation_citations
74
48
  end
75
49
  end
50
+ end
76
51
 
77
- describe '#matches?' do
78
- let(:request_pattern) { double('request_pattern') }
79
- let(:request_signature) { double('request_signature') }
52
+ describe '#matches?' do
53
+ let(:request_pattern) { double('request_pattern') }
54
+ let(:request_signature) { double('request_signature') }
80
55
 
81
- it 'delegates to the request pattern' do
82
- expect(Pacto::RequestPattern).to receive(:for).and_return(request_pattern)
83
- expect(request_pattern).to receive(:matches?).with(request_signature)
56
+ it 'delegates to the request pattern' do
57
+ expect(Pacto::RequestPattern).to receive(:for).and_return(request_pattern)
58
+ expect(request_pattern).to receive(:matches?).with(request_signature)
84
59
 
85
- contract.matches?(request_signature)
86
- end
60
+ contract.matches?(request_signature)
87
61
  end
88
62
  end
89
63
  end
@@ -3,7 +3,8 @@ module Pacto
3
3
  describe Investigation do
4
4
  let(:request) { double('request') }
5
5
  let(:response) { double('response') }
6
- let(:contract) { Fabricate(:contract) }
6
+ let(:file) { 'foobar' }
7
+ let(:contract) { Fabricate(:contract, file: file) }
7
8
  let(:investigation_citations) { [] }
8
9
  let(:investigation_citations_with_errors) { ['an error occurred'] }
9
10
 
@@ -44,9 +45,9 @@ module Pacto
44
45
  end
45
46
 
46
47
  it 'returns the contract with an exact string name match' do
47
- allow(contract).to receive(:file).and_return('foo')
48
48
  investigation = Pacto::Investigation.new request, response, contract, investigation_citations
49
- expect(investigation.against_contract? 'foo').to eq(contract)
49
+ expect(investigation.against_contract? 'foobar').to eq(contract)
50
+ expect(investigation.against_contract? 'foo').to be_nil
50
51
  expect(investigation.against_contract? 'bar').to be_nil
51
52
  end
52
53
 
@@ -0,0 +1,93 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Formats
4
+ module Legacy
5
+ describe ContractBuilder do
6
+ let(:data) { subject.build_hash }
7
+ describe '#name' do
8
+ it 'sets the contract name' do
9
+ subject.name = 'foo'
10
+ expect(data).to include(name: 'foo')
11
+ end
12
+ end
13
+
14
+ describe '#add_example' do
15
+ let(:examples) { subject.build_hash[:examples] }
16
+ it 'adds named examples to the contract' do
17
+ subject.add_example 'foo', Fabricate(:pacto_request), Fabricate(:pacto_response)
18
+ subject.add_example 'bar', Fabricate(:pacto_request), Fabricate(:pacto_response)
19
+ expect(examples).to be_a(Hash)
20
+ expect(examples.keys).to include('foo', 'bar')
21
+ expect(examples['foo'][:response]).to include(status: 200)
22
+ expect(data)
23
+ end
24
+ end
25
+
26
+ context 'without examples' do
27
+ describe '#infer_schemas' do
28
+ it 'does not add schemas' do
29
+ subject.name = 'test'
30
+ subject.infer_schemas
31
+ expect(data[:request][:schema]).to be_nil
32
+ expect(data[:response][:schema]).to be_nil
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'with examples' do
38
+ before(:each) do
39
+ subject.add_example 'success', Fabricate(:pacto_request), Fabricate(:pacto_response)
40
+ subject.add_example 'not found', Fabricate(:pacto_request), Fabricate(:pacto_response)
41
+ end
42
+
43
+ describe '#without_examples' do
44
+ it 'stops the builder from including examples in the final data' do
45
+ expect(subject.build_hash.keys).to include(:examples)
46
+ expect(subject.without_examples.build_hash.keys).to_not include(:examples)
47
+ end
48
+ end
49
+
50
+ describe '#infer_schemas' do
51
+ it 'adds schemas' do
52
+ subject.name = 'test'
53
+ subject.infer_schemas
54
+ contract = subject.build
55
+ expect(contract.request.schema).to_not be_nil
56
+ expect(contract.request.schema).to_not be_nil
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'generating from interactions' do
62
+ let(:request) { Fabricate(:pacto_request) }
63
+ let(:response) { Fabricate(:pacto_response) }
64
+ let(:data) { subject.generate_response(request, response).build_hash }
65
+ let(:contract) { subject.generate_contract(request, response).build }
66
+
67
+ describe '#generate_response' do
68
+ it 'sets the response status' do
69
+ expect(data[:response]).to include(
70
+ status: 200
71
+ )
72
+ end
73
+
74
+ it 'sets response headers' do
75
+ expect(data[:response][:headers]).to be_a(Hash)
76
+ end
77
+ end
78
+
79
+ describe '#infer_schemas' do
80
+ it 'sets the schemas based on the examples' do
81
+ expect(contract.request.schema).to_not be_nil
82
+ expect(contract.request.schema).to_not be_nil
83
+ end
84
+ end
85
+ end
86
+
87
+ skip '#add_request_header'
88
+ skip '#add_response_header'
89
+ skip '#filter'
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Pacto
5
+ module Formats
6
+ module Legacy
7
+ describe ContractFactory do
8
+ let(:host) { 'http://localhost' }
9
+ let(:contract_format) { 'legacy' }
10
+ let(:contract_name) { 'contract' }
11
+ let(:contract_path) { contract_file(contract_name, contract_format) }
12
+ subject(:contract_factory) { described_class.new }
13
+
14
+ it 'builds a Contract given a JSON file path and a host' do
15
+ contract = contract_factory.build_from_file(contract_path, host)
16
+ expect(contract).to be_a(Pacto::Formats::Legacy::Contract)
17
+ end
18
+
19
+ context 'deprecated contracts' do
20
+ let(:contract_format) { 'deprecated' }
21
+ let(:contract_name) { 'deprecated_contract' }
22
+ it 'can no longer be loaded' do
23
+ expect { contract_factory.build_from_file(contract_path, host) }.to raise_error(/old syntax no longer supported/)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,173 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Formats
4
+ module Legacy
5
+ describe ContractGenerator do
6
+ let(:record_host) do
7
+ 'http://example.com'
8
+ end
9
+ let(:request_clause) { Fabricate(:request_clause, params: { 'api_key' => "<%= ENV['MY_API_KEY'] %>" }) }
10
+ let(:response_adapter) do
11
+ Faraday::Response.new(
12
+ status: 200,
13
+ response_headers: {
14
+ 'Date' => [Time.now],
15
+ 'Server' => ['Fake Server'],
16
+ 'Content-Type' => ['application/json'],
17
+ 'Vary' => ['User-Agent']
18
+ },
19
+ body: 'dummy body' # body is just a string
20
+ )
21
+ end
22
+ let(:filtered_request_headers) { double('filtered_response_headers') }
23
+ let(:filtered_response_headers) { double('filtered_response_headers') }
24
+ let(:response_body_schema) { '{"message": "dummy generated schema"}' }
25
+ let(:version) { 'draft3' }
26
+ let(:schema_generator) { double('schema_generator') }
27
+ let(:validator) { double('validator') }
28
+ let(:filters) { double :filters }
29
+ let(:consumer) { double 'consumer' }
30
+ let(:request_file) { 'request.json' }
31
+ let(:generator) { described_class.new version, schema_generator, validator, filters, consumer }
32
+ let(:request_contract) do
33
+ Fabricate(:partial_contract, request: request_clause, file: request_file)
34
+ end
35
+ let(:request) do
36
+ Pacto.configuration.default_consumer.build_request request_contract
37
+ end
38
+
39
+ def pretty(obj)
40
+ MultiJson.encode(obj, pretty: true).gsub(/^$\n/, '')
41
+ end
42
+
43
+ describe '#generate_from_partial_contract' do
44
+ # TODO: Deprecate partial contracts?
45
+ let(:generated_contract) { Fabricate(:contract) }
46
+ before do
47
+ expect(Pacto).to receive(:load_contract).with(request_file, record_host).and_return request_contract
48
+ expect(consumer).to receive(:request).with(request_contract).and_return([request, response_adapter])
49
+ end
50
+
51
+ it 'parses the request' do
52
+ expect(generator).to receive(:save).with(request_file, request, anything)
53
+ generator.generate_from_partial_contract request_file, record_host
54
+ end
55
+
56
+ it 'fetches a response' do
57
+ expect(generator).to receive(:save).with(request_file, anything, response_adapter)
58
+ generator.generate_from_partial_contract request_file, record_host
59
+ end
60
+
61
+ it 'saves the result' do
62
+ expect(generator).to receive(:save).with(request_file, request, response_adapter).and_return generated_contract
63
+ expect(generator.generate_from_partial_contract request_file, record_host).to eq(generated_contract)
64
+ end
65
+ end
66
+
67
+ describe '#save' do
68
+ before do
69
+ allow(filters).to receive(:filter_request_headers).with(request, response_adapter).and_return filtered_request_headers
70
+ allow(filters).to receive(:filter_response_headers).with(request, response_adapter).and_return filtered_response_headers
71
+ end
72
+ context 'invalid schema' do
73
+ it 'raises an error if schema generation fails' do
74
+ expect(schema_generator).to receive(:generate).and_raise ArgumentError.new('Could not generate schema')
75
+ expect { generator.save request_file, request, response_adapter }.to raise_error
76
+ end
77
+
78
+ it 'raises an error if the generated contract is invalid' do
79
+ expect(schema_generator).to receive(:generate).and_return response_body_schema
80
+ expect(validator).to receive(:validate).and_raise InvalidContract.new('dummy error')
81
+ expect { generator.save request_file, request, response_adapter }.to raise_error
82
+ end
83
+ end
84
+
85
+ context 'valid schema' do
86
+ let(:raw_contract) do
87
+ expect(schema_generator).to receive(:generate).with(request_file, response_adapter.body, Pacto.configuration.generator_options).and_return response_body_schema
88
+ expect(validator).to receive(:validate).and_return true
89
+ generator.save request_file, request, response_adapter
90
+ end
91
+ subject(:generated_contract) { JSON.parse raw_contract }
92
+
93
+ it 'sets the schema to the generated json-schema' do
94
+ expect(subject['response']['schema']).to eq(JSON.parse response_body_schema)
95
+ end
96
+
97
+ it 'sets the request attributes' do
98
+ generated_request = subject['request']
99
+ expect(generated_request['params']).to eq(request.uri.query_values)
100
+ expect(generated_request['path']).to eq(request.uri.path)
101
+ end
102
+
103
+ it 'preserves ERB in the request params' do
104
+ generated_request = subject['request']
105
+ expect(generated_request['params']).to eq('api_key' => "<%= ENV['MY_API_KEY'] %>")
106
+ end
107
+
108
+ it 'normalizes the request method' do
109
+ generated_request = subject['request']
110
+ expect(generated_request['http_method']).to eq(request.method.downcase.to_s)
111
+ end
112
+
113
+ it 'sets the response attributes' do
114
+ generated_response = subject['response']
115
+ expect(generated_response['status']).to eq(response_adapter.status)
116
+ end
117
+
118
+ it 'generates pretty JSON' do
119
+ expect(raw_contract).to eq(pretty(subject))
120
+ end
121
+ end
122
+
123
+ context 'with hints' do
124
+ let(:request1) { Fabricate(:pacto_request, host: 'example.com', path: '/album/5/cover') }
125
+ let(:request2) { Fabricate(:pacto_request, host: 'example.com', path: '/album/7/cover') }
126
+ let(:response1) { Fabricate(:pacto_response) }
127
+ let(:response2) { Fabricate(:pacto_response) }
128
+ let(:contracts_path) { Dir.mktmpdir }
129
+
130
+ before(:each) do
131
+ allow(filters).to receive(:filter_request_headers).with(request1, response1).and_return request1.headers
132
+ allow(filters).to receive(:filter_response_headers).with(request1, response1).and_return response1.headers
133
+ allow(filters).to receive(:filter_request_headers).with(request2, response2).and_return request2.headers
134
+ allow(filters).to receive(:filter_response_headers).with(request2, response2).and_return response2.headers
135
+ allow(schema_generator).to receive(:generate).with(request_file, response1.body, Pacto.configuration.generator_options).and_return response_body_schema
136
+ allow(schema_generator).to receive(:generate).with(request_file, response2.body, Pacto.configuration.generator_options).and_return response_body_schema
137
+ allow(validator).to receive(:validate).twice.and_return true
138
+ Pacto.configuration.contracts_path = contracts_path
139
+ Pacto::Generator.configure do |c|
140
+ c.hint 'Get Album Cover', http_method: :get, host: 'http://example.com', path: '/album/{id}/cover', target_file: 'album_services/get_album_cover.json'
141
+ end
142
+ Pacto.generate!
143
+ end
144
+
145
+ it 'names the contract based on the hint' do
146
+ contract1 = generator.generate request1, response1
147
+ expect(contract1.name).to eq('Get Album Cover')
148
+ end
149
+
150
+ it 'sets the path to match the hint' do
151
+ contract1 = generator.generate request1, response1
152
+ expect(contract1.request.path).to eq('/album/{id}/cover')
153
+ end
154
+
155
+ it 'sets the target file based on the hint' do
156
+ contract1 = generator.generate request1, response1
157
+ expected_path = File.expand_path('album_services/get_album_cover.json', contracts_path)
158
+ real_expected_path = Pathname.new(expected_path).realpath.to_s
159
+ expected_file_uri = Addressable::URI.convert_path(real_expected_path).to_s
160
+ expect(contract1.file).to eq(expected_file_uri)
161
+ end
162
+
163
+ xit 'does not create duplicate contracts' do
164
+ contract1 = generator.generate request1, response1
165
+ contract2 = generator.generate request2, response2
166
+ expect(contract1).to eq(contract2)
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end