pact-mock_service 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +29 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +100 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +314 -0
  10. data/Rakefile +6 -0
  11. data/bin/pact-mock-service +3 -0
  12. data/lib/pact/consumer/app_manager.rb +159 -0
  13. data/lib/pact/consumer/interactions_filter.rb +48 -0
  14. data/lib/pact/consumer/mock_service/app.rb +84 -0
  15. data/lib/pact/consumer/mock_service/interaction_delete.rb +33 -0
  16. data/lib/pact/consumer/mock_service/interaction_list.rb +76 -0
  17. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +73 -0
  18. data/lib/pact/consumer/mock_service/interaction_post.rb +31 -0
  19. data/lib/pact/consumer/mock_service/interaction_replay.rb +139 -0
  20. data/lib/pact/consumer/mock_service/log_get.rb +28 -0
  21. data/lib/pact/consumer/mock_service/missing_interactions_get.rb +30 -0
  22. data/lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb +31 -0
  23. data/lib/pact/consumer/mock_service/pact_post.rb +33 -0
  24. data/lib/pact/consumer/mock_service/rack_request_helper.rb +51 -0
  25. data/lib/pact/consumer/mock_service/verification_get.rb +68 -0
  26. data/lib/pact/consumer/mock_service.rb +2 -0
  27. data/lib/pact/consumer/mock_service_client.rb +65 -0
  28. data/lib/pact/consumer/mock_service_interaction_expectation.rb +37 -0
  29. data/lib/pact/consumer/request.rb +27 -0
  30. data/lib/pact/consumer/server.rb +90 -0
  31. data/lib/pact/consumer_contract/consumer_contract_writer.rb +84 -0
  32. data/lib/pact/mock_service/cli.rb +49 -0
  33. data/lib/pact/mock_service/version.rb +5 -0
  34. data/lib/pact/mock_service.rb +1 -0
  35. data/pact-mock-service.gemspec +41 -0
  36. data/spec/lib/pact/consumer/app_manager_spec.rb +42 -0
  37. data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
  38. data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +78 -0
  39. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +70 -0
  40. data/spec/lib/pact/consumer/mock_service/interaction_replay_spec.rb +12 -0
  41. data/spec/lib/pact/consumer/mock_service/rack_request_helper_spec.rb +88 -0
  42. data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +142 -0
  43. data/spec/lib/pact/consumer/mock_service_client_spec.rb +88 -0
  44. data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +54 -0
  45. data/spec/lib/pact/consumer/service_consumer_spec.rb +11 -0
  46. data/spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb +111 -0
  47. data/spec/spec_helper.rb +16 -0
  48. data/spec/support/a_consumer-a_producer.json +32 -0
  49. data/spec/support/a_consumer-a_provider.json +32 -0
  50. data/spec/support/active_support_if_configured.rb +6 -0
  51. data/spec/support/app_for_config_ru.rb +4 -0
  52. data/spec/support/case-insensitive-response-header-matching.json +21 -0
  53. data/spec/support/case-insensitive-response-header-matching.rb +15 -0
  54. data/spec/support/consumer_contract_template.json +24 -0
  55. data/spec/support/dsl_spec_support.rb +7 -0
  56. data/spec/support/factories.rb +82 -0
  57. data/spec/support/generated_index.md +4 -0
  58. data/spec/support/generated_markdown.md +55 -0
  59. data/spec/support/interaction_view_model.json +63 -0
  60. data/spec/support/interaction_view_model_with_terms.json +50 -0
  61. data/spec/support/markdown_pact.json +48 -0
  62. data/spec/support/missing_provider_states_output.txt +25 -0
  63. data/spec/support/options.json +21 -0
  64. data/spec/support/options_app.rb +15 -0
  65. data/spec/support/pact_helper.rb +57 -0
  66. data/spec/support/shared_examples_for_request.rb +94 -0
  67. data/spec/support/spec_support.rb +20 -0
  68. data/spec/support/stubbing.json +22 -0
  69. data/spec/support/stubbing_using_allow.rb +29 -0
  70. data/spec/support/term.json +48 -0
  71. data/spec/support/test_app_fail.json +61 -0
  72. data/spec/support/test_app_pass.json +38 -0
  73. data/spec/support/test_app_with_right_content_type_differ.json +23 -0
  74. data/tasks/spec.rake +6 -0
  75. metadata +388 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "consumer": {
3
+ "name": "an easy consumer"
4
+ },
5
+ "provider": {
6
+ "name": "a provider which returns headers that don't match the expected case"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "a test request",
11
+ "request": {
12
+ "method": "get",
13
+ "path": "/"
14
+ },
15
+ "response": {
16
+ "status": 200,
17
+ "headers": {"Content-Type": "application/hippo"}
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,15 @@
1
+ module Pact
2
+ module Test
3
+ class CaseInsensitiveResponseHeadersApp
4
+
5
+ def call env
6
+ [200, {'cOnTent-tYpe' => 'application/hippo'},[]]
7
+ end
8
+
9
+ end
10
+ end
11
+ end
12
+
13
+ Pact.service_provider "Provider" do
14
+ app { Pact::Test::CaseInsensitiveResponseHeadersApp.new }
15
+ end
@@ -0,0 +1,24 @@
1
+ {
2
+ "provider": {
3
+ "name": "a provider"
4
+ },
5
+ "consumer": {
6
+ "name": "a consumer"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "request one",
11
+ "request": {
12
+ "method": "get",
13
+ "path": "/path_one"
14
+ },
15
+ "response": {
16
+ "status" : 200
17
+ },
18
+ "provider_state": "state one"
19
+ }
20
+ ],
21
+ "metadata": {
22
+ "pactSpecificationVersion": "1.0"
23
+ }
24
+ }
@@ -0,0 +1,7 @@
1
+ def global_method
2
+ "I'm global"
3
+ end
4
+
5
+ def global_app
6
+ "I'm a global app"
7
+ end
@@ -0,0 +1,82 @@
1
+ require 'hashie'
2
+ require 'hashie/extensions/key_conversion'
3
+
4
+ module Pact
5
+ module HashUtils
6
+
7
+ class Converter < Hash
8
+ include Hashie::Extensions::KeyConversion
9
+ include Hashie::Extensions::DeepMerge
10
+ end
11
+
12
+ def symbolize_keys hash
13
+ Hash[Converter[hash].symbolize_keys]
14
+ end
15
+
16
+ def stringify_keys hash
17
+ Hash[Converter[hash].stringify_keys]
18
+ end
19
+
20
+ def deep_merge hash1, hash2
21
+ Converter[hash1].deep_merge(Converter[hash2])
22
+ end
23
+ end
24
+ end
25
+
26
+ class InteractionFactory
27
+
28
+ extend Pact::HashUtils
29
+
30
+ def self.create hash = {}
31
+ defaults = {
32
+ 'description' => 'a description',
33
+ 'provider_state' => 'a thing exists',
34
+ 'request' => {
35
+ 'path' => '/path',
36
+ 'method' => 'get',
37
+ },
38
+ 'response' => {
39
+ 'status' => 200,
40
+ 'body' => {a: 'response body'}
41
+ }
42
+ }
43
+ Pact::Interaction.from_hash(stringify_keys(deep_merge(defaults, stringify_keys(hash))))
44
+ end
45
+ end
46
+
47
+
48
+ class ConsumerContractFactory
49
+ extend Pact::HashUtils
50
+ DEFAULTS = {:consumer_name => 'consumer',
51
+ :provider_name => 'provider',
52
+ :interactions => [InteractionFactory.create]}
53
+
54
+ def self.create overrides = {}
55
+ options = deep_merge(symbolize_keys(DEFAULTS), symbolize_keys(overrides))
56
+ Pact::ConsumerContract.new({:consumer => Pact::ServiceConsumer.new(name: options[:consumer_name]),
57
+ :provider => Pact::ServiceProvider.new(name: options[:provider_name]),
58
+ :interactions => options[:interactions]})
59
+ end
60
+ end
61
+
62
+
63
+
64
+ class ResponseFactory
65
+ extend Pact::HashUtils
66
+ DEFAULTS = {:status => 200, :body => {a: 'body'}}.freeze
67
+ def self.create_hash overrides = {}
68
+ deep_merge(DEFAULTS, overrides)
69
+ end
70
+ end
71
+
72
+ class RequestFactory
73
+ extend Pact::HashUtils
74
+ DEFAULTS = {:path => '/path', :method => 'get', :query => 'query', :headers => {}}.freeze
75
+ def self.create_hash overrides = {}
76
+ deep_merge(DEFAULTS, overrides)
77
+ end
78
+
79
+ def self.create_actual overrides = {}
80
+ Pact::Consumer::Request::Actual.from_hash(create_hash(overrides))
81
+ end
82
+ end
@@ -0,0 +1,4 @@
1
+ ### Pacts for Some Consumer
2
+
3
+ * [Some Provider](Some Provider.md)
4
+ * [Some other provider](Some other provider.md)
@@ -0,0 +1,55 @@
1
+ ### A pact between Some Consumer and Some Provider
2
+
3
+ #### Requests from Some Consumer to Some Provider
4
+
5
+ * [A request for alligators](#a_request_for_alligators_given_alligators_exist) given alligators exist
6
+
7
+ * [A request for polar bears](#a_request_for_polar_bears)
8
+
9
+ #### Interactions
10
+
11
+ <a name="a_request_for_alligators_given_alligators_exist"></a>
12
+ Given **alligators exist**, upon receiving **a request for alligators** from Some Consumer, with
13
+ ```json
14
+ {
15
+ "method": "get",
16
+ "path": "/alligators"
17
+ }
18
+ ```
19
+ Some Provider will respond with:
20
+ ```json
21
+ {
22
+ "status": 200,
23
+ "headers": {
24
+ "Content-Type": "application/json"
25
+ },
26
+ "body": {
27
+ "alligators": [
28
+ {
29
+ "name": "Bob",
30
+ "phoneNumber": "12345678"
31
+ }
32
+ ]
33
+ }
34
+ }
35
+ ```
36
+ <a name="a_request_for_polar_bears"></a>
37
+ Upon receiving **a request for polar bears** from Some Consumer, with
38
+ ```json
39
+ {
40
+ "method": "get",
41
+ "path": "/polar-bears"
42
+ }
43
+ ```
44
+ Some Provider will respond with:
45
+ ```json
46
+ {
47
+ "status": 404,
48
+ "headers": {
49
+ "Content-Type": "application/json"
50
+ },
51
+ "body": {
52
+ "message": "Sorry, due to climate change, the polar bears are currently unavailable."
53
+ }
54
+ }
55
+ ```
@@ -0,0 +1,63 @@
1
+ {
2
+ "provider": {
3
+ "name": "a provider"
4
+ },
5
+ "consumer": {
6
+ "name": "a consumer"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "a request with a body and headers",
11
+ "request": {
12
+ "method": "get",
13
+ "path": "/path",
14
+ "query": "some=thing",
15
+ "headers": {
16
+ "key": "a header"
17
+ },
18
+ "body": {
19
+ "key": "a body"
20
+ }
21
+ },
22
+ "response": {}
23
+ },
24
+ {
25
+ "description": "a request with an empty body and empty headers",
26
+ "request": {
27
+ "method": "get",
28
+ "path": "/",
29
+ "headers": {},
30
+ "body": {}
31
+ },
32
+ "response": {}
33
+ },
34
+ {
35
+ "description": "a response with a body and headers",
36
+ "request": {
37
+ "method": "get",
38
+ "path": "/"
39
+ },
40
+ "response": {
41
+ "headers": {
42
+ "key": "a header"
43
+ },
44
+ "body": {
45
+ "key": "a body"
46
+ },
47
+ "status": 200
48
+ }
49
+ },
50
+ {
51
+ "description": "a response with an empty body and empty headers",
52
+ "request": {
53
+ "method": "get",
54
+ "path": "/"
55
+ },
56
+ "response": {
57
+ "status": 200,
58
+ "headers": {},
59
+ "body": {}
60
+ }
61
+ }
62
+ ]
63
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "provider": {
3
+ "name": "a provider"
4
+ },
5
+ "consumer": {
6
+ "name": "a consumer"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "an interaction with terms",
11
+ "request": {
12
+ "method": "post",
13
+ "path": "/path",
14
+ "query": "some=thing",
15
+ "headers": {
16
+ "key": "a header"
17
+ },
18
+ "body": {
19
+ "term": {
20
+ "json_class": "Pact::Term",
21
+ "data": {
22
+ "generate": "sunny",
23
+ "matcher": {
24
+ "json_class": "Regexp",
25
+ "o": 0,
26
+ "s": "sun"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ },
32
+ "response": {
33
+ "status": 200,
34
+ "body": {
35
+ "term": {
36
+ "json_class": "Pact::Term",
37
+ "data": {
38
+ "generate": "rainy",
39
+ "matcher": {
40
+ "json_class": "Regexp",
41
+ "o": 0,
42
+ "s": "rain"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "provider": {
3
+ "name": "Some Provider"
4
+ },
5
+ "consumer": {
6
+ "name": "Some Consumer"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "a request for alligators",
11
+ "provider_state": "alligators exist",
12
+ "request": {
13
+ "method": "get",
14
+ "path": "/alligators"
15
+ },
16
+ "response": {
17
+ "headers" : {"Content-Type": "application/json"},
18
+ "status" : 200,
19
+ "body" : {
20
+ "alligators": [{
21
+ "name": "Bob",
22
+ "phoneNumber" : {
23
+ "json_class": "Pact::Term",
24
+ "data": {
25
+ "generate": "12345678",
26
+ "matcher": {"json_class":"Regexp","o":0,"s":"\\d+"}
27
+ }
28
+ }
29
+ }]
30
+ }
31
+ }
32
+ },{
33
+ "description": "a request for polar bears",
34
+ "provider_state": null,
35
+ "request": {
36
+ "method": "get",
37
+ "path": "/polar-bears"
38
+ },
39
+ "response": {
40
+ "headers" : {"Content-Type": "application/json"},
41
+ "status" : 404,
42
+ "body" : {
43
+ "message": "Sorry, due to climate change, the polar bears are currently unavailable."
44
+ }
45
+ }
46
+ }
47
+ ]
48
+ }
@@ -0,0 +1,25 @@
1
+ Pact.provider_states_for "Consumer 1" do
2
+
3
+ provider_state "state1" do
4
+ set_up do
5
+ # Your set up code goes here
6
+ end
7
+ end
8
+
9
+ provider_state "state2" do
10
+ set_up do
11
+ # Your set up code goes here
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ Pact.provider_states_for "Consumer 2" do
18
+
19
+ provider_state "state3" do
20
+ set_up do
21
+ # Your set up code goes here
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,21 @@
1
+ {
2
+ "consumer": {
3
+ "name": "Consumer"
4
+ },
5
+ "provider": {
6
+ "name": "Provider"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "an OPTIONS request",
11
+ "request": {
12
+ "method": "options",
13
+ "path": "/"
14
+ },
15
+ "response": {
16
+ "status": 200
17
+ },
18
+ "provider_state": null
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,15 @@
1
+ require 'pact/provider/rspec'
2
+
3
+ class App
4
+ def self.call env
5
+ if env['REQUEST_METHOD'] == 'OPTIONS'
6
+ [200, {}, []]
7
+ else
8
+ [500, {}, ["Expected an options request"]]
9
+ end
10
+ end
11
+ end
12
+
13
+ Pact.service_provider 'Provider' do
14
+ app { App }
15
+ end
@@ -0,0 +1,57 @@
1
+ # This is the pact_helper for rake pact:tests
2
+ require 'json'
3
+ require 'pact/provider/rspec'
4
+ require './spec/support/active_support_if_configured'
5
+
6
+ module Pact
7
+ module Test
8
+ class TestApp
9
+ def call env
10
+ if env['PATH_INFO'] == '/weather'
11
+ [200, {'Content-Type' => 'application/json'}, [{message: WEATHER[:current_state], :array => [{"foo"=> "blah"}]}.to_json]]
12
+ elsif env['PATH_INFO'] == '/sometext'
13
+ [200, {'Content-Type' => 'text/plain'}, ['some text']]
14
+ elsif env['PATH_INFO'] == '/content_type_is_important'
15
+ [200, {'Content-Type' => 'application/json'}, [{message: "A message", note: "This will cause verify to fail if it using the wrong content type differ."}.to_json]]
16
+ else
17
+ raise "unexpected path #{env['PATH_INFO']}!!!"
18
+ end
19
+ end
20
+ end
21
+
22
+ Pact.configure do | config |
23
+ config.logger.level = Logger::DEBUG
24
+ config.diff_formatter = :unix
25
+ end
26
+
27
+ Pact.service_provider "Some Provider" do
28
+ app { TestApp.new }
29
+
30
+ honours_pact_with 'some-test-consumer' do
31
+ pact_uri './spec/support/test_app_pass.json'
32
+ end
33
+ end
34
+
35
+ Pact.set_up do
36
+ WEATHER ||= {}
37
+ end
38
+
39
+ #one with a top level consumer
40
+ Pact.provider_states_for 'some-test-consumer' do
41
+
42
+ provider_state "the weather is sunny" do
43
+ set_up do
44
+
45
+ WEATHER[:current_state] = 'sunny'
46
+ end
47
+ end
48
+ end
49
+
50
+ #one without a top level consumer
51
+ Pact.provider_state "the weather is cloudy" do
52
+ set_up do
53
+ WEATHER[:current_state] = 'cloudy'
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,94 @@
1
+ shared_examples "a request" do
2
+
3
+ describe 'matching' do
4
+ let(:expected) do
5
+ Pact::Request::Expected.from_hash(
6
+ {'method' => 'get', 'path' => 'path', 'query' => /b/}
7
+ )
8
+ end
9
+
10
+ let(:actual) do
11
+ Pact::Consumer::Request::Actual.from_hash({'method' => 'get', 'path' => 'path', 'query' => 'blah', 'headers' => {}, 'body' => ''})
12
+ end
13
+
14
+ it "should match" do
15
+ expect(expected.difference(actual)).to eq({})
16
+ end
17
+ end
18
+
19
+ describe 'full_path' do
20
+ context "with empty path" do
21
+ subject { described_class.from_hash({:path => '', :method => 'get', :query => '', :headers => {}}) }
22
+ it "returns the full path"do
23
+ expect(subject.full_path).to eq "/"
24
+ end
25
+ end
26
+ context "with a path" do
27
+ subject { described_class.from_hash({:path => '/path', :method => 'get', :query => '', :headers => {}}) }
28
+ it "returns the full path"do
29
+ expect(subject.full_path).to eq "/path"
30
+ end
31
+ end
32
+ context "with a path and query" do
33
+ subject { described_class.from_hash({:path => '/path', :method => 'get', :query => "something", :headers => {}}) }
34
+ it "returns the full path"do
35
+ expect(subject.full_path).to eq "/path?something"
36
+ end
37
+ end
38
+ context "with a path and a query that is a Term" do
39
+ subject { described_class.from_hash({:path => '/path', :method => 'get', :headers => {}, :query => Pact::Term.new(generate: 'a', matcher: /a/)}) }
40
+ it "returns the full path with reified path" do
41
+ expect(subject.full_path).to eq "/path?a"
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "building from a hash" do
47
+
48
+ let(:raw_request) do
49
+ {
50
+ 'method' => 'get',
51
+ 'path' => '/mallory',
52
+ 'query' => 'query',
53
+ 'headers' => {
54
+ 'Content-Type' => 'application/json'
55
+ },
56
+ 'body' => 'hello mallory'
57
+ }
58
+ end
59
+
60
+ subject { described_class.from_hash(raw_request) }
61
+
62
+ it "extracts the method" do
63
+ expect(subject.method).to eq 'get'
64
+ end
65
+
66
+ it "extracts the path" do
67
+ expect(subject.path).to eq '/mallory'
68
+ end
69
+
70
+ it "extracts the body" do
71
+ expect(subject.body).to eq 'hello mallory'
72
+ end
73
+
74
+ it "extracts the query" do
75
+ expect(subject.query).to eq 'query'
76
+ end
77
+
78
+ it "blows up if method is absent" do
79
+ raw_request.delete 'method'
80
+ expect { described_class.from_hash(raw_request) }.to raise_error
81
+ end
82
+
83
+ it "blows up if path is absent" do
84
+ raw_request.delete 'path'
85
+ expect { described_class.from_hash(raw_request) }.to raise_error
86
+ end
87
+
88
+ it "does not blow up if body is missing" do
89
+ raw_request.delete 'body'
90
+ expect { described_class.from_hash(raw_request) }.to_not raise_error
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,20 @@
1
+ require 'pact/rspec'
2
+
3
+ module Pact
4
+ module SpecSupport
5
+
6
+ extend self
7
+
8
+ def remove_ansicolor string
9
+ string.gsub(/\e\[(\d+)m/, '')
10
+ end
11
+
12
+ Pact::RSpec.with_rspec_2 do
13
+
14
+ def instance_double *args
15
+ double(*args)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ {
2
+ "consumer": {
3
+ "name": "Consumer"
4
+ },
5
+ "provider": {
6
+ "name": "Provider"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "a test request",
11
+ "request": {
12
+ "method": "get",
13
+ "path": "/"
14
+ },
15
+ "response": {
16
+ "status": 200,
17
+ "body": "stubbing works"
18
+ },
19
+ "provider_state": "something is stubbed"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,29 @@
1
+ require 'pact/provider/rspec'
2
+ require 'rspec/mocks'
3
+ require './spec/support/active_support_if_configured'
4
+
5
+ class StubbedThing
6
+ def self.stub_me
7
+ end
8
+ end
9
+
10
+ class App
11
+ def self.call env
12
+ [200, {}, [StubbedThing.stub_me]]
13
+ end
14
+ end
15
+
16
+ Pact.provider_states_for 'Consumer' do
17
+ provider_state 'something is stubbed' do
18
+ set_up do
19
+ allow(StubbedThing).to receive(:stub_me).and_return("stubbing works")
20
+ end
21
+ end
22
+ end
23
+
24
+ # Include the ExampleMethods module after the provider states are declared
25
+ # to ensure the ordering doesn't matter
26
+
27
+ Pact.service_provider 'Provider' do
28
+ app { App }
29
+ end