pact-mock_service 0.0.1

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 (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