pact 0.1.28

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.
Files changed (72) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +83 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +238 -0
  7. data/Rakefile +33 -0
  8. data/bin/pact +4 -0
  9. data/lib/pact/app.rb +32 -0
  10. data/lib/pact/configuration.rb +54 -0
  11. data/lib/pact/consumer/app_manager.rb +177 -0
  12. data/lib/pact/consumer/configuration_dsl.rb +71 -0
  13. data/lib/pact/consumer/consumer_contract_builder.rb +79 -0
  14. data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
  15. data/lib/pact/consumer/dsl.rb +98 -0
  16. data/lib/pact/consumer/interaction.rb +70 -0
  17. data/lib/pact/consumer/mock_service.rb +340 -0
  18. data/lib/pact/consumer/rspec.rb +43 -0
  19. data/lib/pact/consumer/run_condor.rb +4 -0
  20. data/lib/pact/consumer/run_mock_contract_service.rb +13 -0
  21. data/lib/pact/consumer/service_consumer.rb +22 -0
  22. data/lib/pact/consumer/service_producer.rb +23 -0
  23. data/lib/pact/consumer.rb +7 -0
  24. data/lib/pact/consumer_contract.rb +110 -0
  25. data/lib/pact/json_warning.rb +23 -0
  26. data/lib/pact/logging.rb +14 -0
  27. data/lib/pact/matchers/matchers.rb +85 -0
  28. data/lib/pact/matchers.rb +1 -0
  29. data/lib/pact/producer/configuration_dsl.rb +62 -0
  30. data/lib/pact/producer/matchers.rb +22 -0
  31. data/lib/pact/producer/pact_spec_runner.rb +57 -0
  32. data/lib/pact/producer/producer_state.rb +81 -0
  33. data/lib/pact/producer/rspec.rb +127 -0
  34. data/lib/pact/producer/test_methods.rb +89 -0
  35. data/lib/pact/producer.rb +1 -0
  36. data/lib/pact/rake_task.rb +64 -0
  37. data/lib/pact/reification.rb +26 -0
  38. data/lib/pact/request.rb +109 -0
  39. data/lib/pact/term.rb +40 -0
  40. data/lib/pact/verification_task.rb +57 -0
  41. data/lib/pact/version.rb +3 -0
  42. data/lib/pact.rb +5 -0
  43. data/lib/tasks/pact.rake +6 -0
  44. data/pact.gemspec +36 -0
  45. data/scratchpad.txt +36 -0
  46. data/spec/features/consumption_spec.rb +146 -0
  47. data/spec/features/producer_states/zebras.rb +28 -0
  48. data/spec/features/production_spec.rb +160 -0
  49. data/spec/integration/pact/configuration_spec.rb +65 -0
  50. data/spec/lib/pact/configuration_spec.rb +35 -0
  51. data/spec/lib/pact/consumer/app_manager_spec.rb +41 -0
  52. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +87 -0
  53. data/spec/lib/pact/consumer/dsl_spec.rb +52 -0
  54. data/spec/lib/pact/consumer/interaction_spec.rb +108 -0
  55. data/spec/lib/pact/consumer/mock_service_spec.rb +147 -0
  56. data/spec/lib/pact/consumer/service_consumer_spec.rb +11 -0
  57. data/spec/lib/pact/consumer_contract_spec.rb +125 -0
  58. data/spec/lib/pact/matchers/matchers_spec.rb +354 -0
  59. data/spec/lib/pact/producer/configuration_dsl_spec.rb +101 -0
  60. data/spec/lib/pact/producer/producer_state_spec.rb +103 -0
  61. data/spec/lib/pact/producer/rspec_spec.rb +48 -0
  62. data/spec/lib/pact/reification_spec.rb +43 -0
  63. data/spec/lib/pact/request_spec.rb +316 -0
  64. data/spec/lib/pact/term_spec.rb +36 -0
  65. data/spec/lib/pact/verification_task_spec.rb +64 -0
  66. data/spec/spec_helper.rb +5 -0
  67. data/spec/support/a_consumer-a_producer.json +34 -0
  68. data/spec/support/pact_rake_support.rb +41 -0
  69. data/spec/support/test_app_fail.json +22 -0
  70. data/spec/support/test_app_pass.json +21 -0
  71. data/tasks/pact-test.rake +19 -0
  72. metadata +381 -0
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ module Pact
4
+ module Consumer
5
+ describe InteractionBuilder do
6
+
7
+ subject {
8
+ interaction_builder = InteractionBuilder.new('Test request', nil).with(request)
9
+ interaction_builder.on_interaction_fully_defined do | interaction |
10
+ producer.callback interaction
11
+ end
12
+ interaction_builder
13
+ }
14
+
15
+ let(:pact_path) { File.expand_path('../../../../pacts/mock', __FILE__) }
16
+
17
+ let(:request) do
18
+ {
19
+ method: 'post',
20
+ path: '/foo',
21
+ body: Term.new(generate: 'waffle', matcher: /ffl/),
22
+ headers: { 'Content-Type' => 'application/json' },
23
+ query: '',
24
+ }
25
+ end
26
+
27
+ let(:response) do
28
+ { baz: /qux/, wiffle: Term.new(generate: 'wiffle', matcher: /iff/) }
29
+ end
30
+
31
+ let(:producer) do
32
+ double(callback: nil)
33
+ end
34
+
35
+ describe "setting up responses" do
36
+
37
+ it "invokes the callback" do
38
+ producer.should_receive(:callback).with(subject.interaction)
39
+ subject.will_respond_with response
40
+ end
41
+
42
+ end
43
+
44
+ describe "to JSON" do
45
+
46
+ let(:parsed_result) do
47
+ JSON.load(JSON.dump(subject.interaction))
48
+ end
49
+
50
+ before do
51
+ subject.will_respond_with response
52
+ end
53
+
54
+ it "contains the request" do
55
+ expect(parsed_result['request']).to eq({
56
+ 'method' => 'post',
57
+ 'path' => '/foo',
58
+ 'headers' => {
59
+ 'Content-Type' => 'application/json'
60
+ },
61
+ 'body' => Term.new(generate: 'waffle', matcher: /ffl/),
62
+ 'query' => ''
63
+ })
64
+ end
65
+
66
+ describe "response" do
67
+
68
+ it "serialises regexes" do
69
+ expect(parsed_result['response']['baz']).to eql /qux/
70
+ end
71
+
72
+ it "serialises terms" do
73
+ term = Term.new(generate:'wiffle', matcher: /iff/)
74
+ parsed_term = parsed_result['response']['wiffle']
75
+ expect(term.matcher).to eql parsed_term.matcher
76
+ expect(term.generate).to eql parsed_term.generate
77
+ end
78
+
79
+ end
80
+
81
+ context "with a producer_state" do
82
+ context "described with a string" do
83
+ subject {
84
+ interaction_builder = InteractionBuilder.new('Test request', "there are no alligators").with(request)
85
+ interaction_builder.on_interaction_fully_defined {}
86
+ interaction_builder
87
+ }
88
+
89
+ it "includes the state name as a string" do
90
+ expect(parsed_result['producer_state']).to eql("there are no alligators")
91
+ end
92
+ end
93
+ context "described with a symbol" do
94
+ subject {
95
+ interaction_builder = InteractionBuilder.new('Test request', :there_are_no_alligators).with(request)
96
+ interaction_builder.on_interaction_fully_defined {}
97
+ interaction_builder
98
+ }
99
+
100
+ it "includes the state name as a symbol" do
101
+ expect(parsed_result['producer_state']).to eql("there_are_no_alligators")
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+ require 'pact/consumer/mock_service'
3
+
4
+ module Pact::Consumer
5
+
6
+ describe InteractionList do
7
+ before do
8
+ InteractionList.instance.clear
9
+ end
10
+
11
+ shared_context "unexpected requests and missed interactions" do
12
+ let(:expected_call) { {request: 'blah'} }
13
+ let(:unexpected_call) { {request: 'meh'} }
14
+ subject {
15
+ interactionList = InteractionList.instance
16
+ interactionList.add expected_call
17
+ interactionList.register_unexpected unexpected_call
18
+ interactionList
19
+ }
20
+ end
21
+
22
+ shared_context "no unexpected requests or missed interactions exist" do
23
+ let(:expected_call) { {request: 'blah'} }
24
+ let(:unexpected_call) { {request: 'meh'} }
25
+ subject {
26
+ interactionList = InteractionList.instance
27
+ interactionList.add expected_call
28
+ interactionList.register_matched expected_call
29
+ interactionList
30
+ }
31
+ end
32
+
33
+ describe "interaction_diffs" do
34
+ context "when unexpected requests and missed interactions exist" do
35
+ include_context "unexpected requests and missed interactions"
36
+ let(:expected) {
37
+ {:missing_interactions=>[{:request=>"blah"}], :unexpected_requests=>[{:request=>"meh"}]}
38
+ }
39
+ it "returns the unexpected requests and missed interactions" do
40
+ expect(subject.interaction_diffs).to eq expected
41
+ end
42
+ end
43
+
44
+ context "when no unexpected requests or missed interactions exist" do
45
+ include_context "no unexpected requests or missed interactions exist"
46
+ let(:expected) {
47
+ {}
48
+ }
49
+ it "returns an empty hash" do
50
+ expect(subject.interaction_diffs).to eq expected
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "all_matched?" do
56
+ context "when unexpected requests or missed interactions exist" do
57
+ include_context "unexpected requests and missed interactions"
58
+ it "returns false" do
59
+ expect(subject.all_matched?).to be_false
60
+ end
61
+ end
62
+ context "when unexpected requests or missed interactions do not exist" do
63
+ include_context "no unexpected requests or missed interactions exist"
64
+ it "returns false" do
65
+ expect(subject.all_matched?).to be_true
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ describe RequestExtractor do
72
+ class TestSubject
73
+ include RequestExtractor
74
+ end
75
+
76
+ let(:rack_env) {
77
+ {
78
+ "CONTENT_LENGTH" => "16",
79
+ "CONTENT_TYPE" => content_type,
80
+ "GATEWAY_INTERFACE" => "CGI/1.1",
81
+ "PATH_INFO" => "/donuts",
82
+ "QUERY_STRING" => "",
83
+ "REMOTE_ADDR" => "127.0.0.1",
84
+ "REMOTE_HOST" => "localhost",
85
+ "REQUEST_METHOD" => "POST",
86
+ "REQUEST_URI" => "http://localhost:4321/donuts",
87
+ "SCRIPT_NAME" => "",
88
+ "SERVER_NAME" => "localhost",
89
+ "SERVER_PORT" => "4321",
90
+ "SERVER_PROTOCOL" => "HTTP/1.1",
91
+ "SERVER_SOFTWARE" => "WEBrick/1.3.1 (Ruby/1.9.3/2013-02-22)",
92
+ "HTTP_ACCEPT" => "text/plain",
93
+ "HTTP_USER_AGENT" => "Ruby",
94
+ "HTTP_HOST" => "localhost:4321",
95
+ "rack.version" => [1, 2 ],
96
+ "rack.input" => StringIO.new(body),
97
+ "rack.errors" => nil,
98
+ "rack.multithread" => true,
99
+ "rack.multiprocess" => false,
100
+ "rack.run_once" => false,
101
+ "rack.url_scheme" => "http",
102
+ "HTTP_VERSION" => "HTTP/1.1",
103
+ "REQUEST_PATH" => "/donuts"
104
+ }
105
+ }
106
+
107
+ subject { TestSubject.new }
108
+
109
+ let(:expected_request) {
110
+ {
111
+ "query" => "",
112
+ "method" => "post",
113
+ "body" => expected_body,
114
+ "path" => "/donuts",
115
+ "headers" => {
116
+ "Content-Type" => content_type,
117
+ "Accept" => "text/plain",
118
+ "User-Agent" => "Ruby",
119
+ "Host" => "localhost:4321",
120
+ "Version" => "HTTP/1.1"
121
+ }
122
+ }
123
+ }
124
+
125
+ context "with a text body" do
126
+ let(:content_type) { "application/x-www-form-urlencoded" }
127
+ let(:body) { 'this is the body' }
128
+ let(:expected_body) { body }
129
+
130
+ it "extracts the body" do
131
+ expect(subject.request_from(rack_env)).to eq expected_request
132
+ end
133
+ end
134
+
135
+ context "with a json body" do
136
+ let(:content_type) { "application/json" }
137
+ let(:body) { '{"a" : "body" }' }
138
+ let(:expected_body) { {"a" => "body"} }
139
+
140
+ it "extracts the body" do
141
+ expect(subject.request_from(rack_env)).to eq expected_request
142
+ end
143
+ end
144
+
145
+
146
+ end
147
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ module Pact::Consumer
4
+ describe ServiceConsumer do
5
+ describe "as_json" do
6
+ it "returns a hash representation of the object" do
7
+ expect(ServiceConsumer.new(:name => "Bob").as_json).to eq :name => "Bob"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+ require 'pact/consumer_contract'
3
+
4
+ module Pact
5
+ describe ConsumerContract do
6
+ describe "as_json" do
7
+
8
+ class MockInteraction
9
+ def as_json(options ={})
10
+ {:mock => "interaction"}
11
+ end
12
+ end
13
+
14
+ before do
15
+ @backup_version = Pact::VERSION
16
+ Pact::VERSION = "1.0"
17
+ DateTime.stub(:now).and_return(DateTime.strptime("2013-08-15T13:27:13+10:00"))
18
+ end
19
+
20
+ let(:service_consumer) { double('ServiceConsumer', :as_json => {:a => 'consumer'}) }
21
+ let(:service_producer) { double('ServiceProducer', :as_json => {:a => 'producer'}) }
22
+ let(:pact) { ConsumerContract.new({:interactions => [MockInteraction.new], :consumer => service_consumer, :producer => service_producer }) }
23
+ let(:expected_as_json) { {:producer=>{:a=>"producer"}, :consumer=>{:a=>"consumer"}, :interactions=>[{:mock=>"interaction"}], :metadata=>{:pact_gem=>{:version=>"1.0"}}} }
24
+
25
+ it "should return a hash representation of the Pact" do
26
+ pact.as_json.should eq expected_as_json
27
+ end
28
+
29
+ after do
30
+ Pact::VERSION = @backup_version
31
+ end
32
+ end
33
+
34
+ describe ".from_json" do
35
+ let(:loaded_pact) { ConsumerContract.from_json(string) }
36
+ context "when the top level object is a ConsumerContract" do
37
+ let(:string) { '{"interactions":[{"request": {"path":"/path", "method" : "get"}}], "consumer": {"name" : "Bob"} }' }
38
+
39
+ it "should create a Pact" do
40
+ loaded_pact.should be_instance_of ConsumerContract
41
+ end
42
+
43
+ it "should have interactions" do
44
+ loaded_pact.interactions.should be_instance_of Array
45
+ end
46
+
47
+ it "should have a consumer" do
48
+ loaded_pact.consumer.should be_instance_of Pact::Consumer::ServiceConsumer
49
+ end
50
+
51
+ it "should have a producer" do
52
+ loaded_pact.producer.should be_instance_of Pact::Consumer::ServiceProducer
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "find_interactions" do
58
+ let(:consumer) { double('ServiceConsumer', :name => 'Consumer')}
59
+ let(:producer) { double('ServiceProducer', :name => 'Producer')}
60
+ let(:interaction1) { {'description' => 'a request for food'} }
61
+ let(:interaction2) { {'description' => 'a request for drink'} }
62
+ subject { ConsumerContract.new(:interactions => [interaction1, interaction2], :consumer => consumer, :producer => producer) }
63
+ context "by description" do
64
+ context "when no interactions are found" do
65
+ it "returns an empty array" do
66
+ expect(subject.find_interactions(:description => /blah/)).to eql []
67
+ end
68
+ end
69
+ context "when interactions are found" do
70
+ it "returns an array of the matching interactions" do
71
+ expect(subject.find_interactions(:description => /request/)).to eql [interaction1, interaction2]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ describe "find_interaction" do
77
+ let(:consumer) { double('ServiceConsumer', :name => 'Consumer')}
78
+ let(:producer) { double('ServiceProducer', :name => 'Producer')}
79
+ let(:interaction1) { {'description' => 'a request for food'} }
80
+ let(:interaction2) { {'description' => 'a request for drink'} }
81
+ subject { ConsumerContract.new(:interactions => [interaction1, interaction2], :consumer => consumer, :producer => producer) }
82
+ context "by description" do
83
+ context "when a match is found" do
84
+ it "returns the interaction" do
85
+ expect(subject.find_interaction :description => /request.*food/).to eql interaction1
86
+ end
87
+ end
88
+ context "when more than one match is found" do
89
+ it "raises an error" do
90
+ expect{ subject.find_interaction(:description => /request/) }.to raise_error "Found more than 1 interaction matching {:description=>/request/} in pact file between Consumer and Producer."
91
+ end
92
+ end
93
+ context "when a match is not found" do
94
+ it "raises an error" do
95
+ expect{ subject.find_interaction(:description => /blah/) }.to raise_error "Could not find interaction matching {:description=>/blah/} in pact file between Consumer and Producer."
96
+ end
97
+ end
98
+ end
99
+ end
100
+ describe "update_pactfile" do
101
+ let(:pacts_dir) { Pathname.new("./tmp/pactfiles") }
102
+ let(:expected_pact_path) { pacts_dir + "test_consumer-test_service.json" }
103
+ let(:expected_pact_string) { 'the_json' }
104
+ let(:consumer) { Pact::Consumer::ServiceConsumer.new(:name => 'test_consumer')}
105
+ let(:producer) { Pact::Consumer::ServiceProducer.new(:name => 'test_service')}
106
+ let(:interactions) { [double("interaction", as_json: "something")]}
107
+ subject { ConsumerContract.new(:consumer => consumer, :producer => producer, :interactions => interactions) }
108
+ before do
109
+ Pact.configuration.stub(:pact_dir).and_return(Pathname.new("./tmp/pactfiles"))
110
+ FileUtils.rm_rf pacts_dir
111
+ FileUtils.mkdir_p pacts_dir
112
+ JSON.should_receive(:pretty_generate).with(instance_of(Pact::ConsumerContract)).and_return(expected_pact_string)
113
+ subject.update_pactfile
114
+ end
115
+
116
+ it "should write to a file specified by the consumer and producer name" do
117
+ File.exist?(expected_pact_path).should be_true
118
+ end
119
+
120
+ it "should write the interactions to the file" do
121
+ File.read(expected_pact_path).should eql expected_pact_string
122
+ end
123
+ end
124
+ end
125
+ end