shokkenki-consumer 0.0.2
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.
- data/lib/shokkenki/consumer/configuration/provider_configuration.rb +30 -0
- data/lib/shokkenki/consumer/configuration/session.rb +38 -0
- data/lib/shokkenki/consumer/consumer.rb +9 -0
- data/lib/shokkenki/consumer/dsl/http_methods.rb +16 -0
- data/lib/shokkenki/consumer/dsl/order.rb +70 -0
- data/lib/shokkenki/consumer/dsl/session.rb +13 -0
- data/lib/shokkenki/consumer/dsl/term_dsl.rb +13 -0
- data/lib/shokkenki/consumer/model/fixture.rb +21 -0
- data/lib/shokkenki/consumer/model/interaction.rb +29 -0
- data/lib/shokkenki/consumer/model/patronage.rb +31 -0
- data/lib/shokkenki/consumer/model/provider.rb +33 -0
- data/lib/shokkenki/consumer/model/role.rb +27 -0
- data/lib/shokkenki/consumer/model/simplification.rb +16 -0
- data/lib/shokkenki/consumer/model/ticket.rb +38 -0
- data/lib/shokkenki/consumer/rspec/example_group_binding.rb +17 -0
- data/lib/shokkenki/consumer/rspec/hooks.rb +32 -0
- data/lib/shokkenki/consumer/rspec/rspec_configuration.rb +25 -0
- data/lib/shokkenki/consumer/rspec.rb +2 -0
- data/lib/shokkenki/consumer/session.rb +83 -0
- data/lib/shokkenki/consumer/stubber/http_stubber.rb +97 -0
- data/lib/shokkenki/consumer/stubber/interaction.rb +51 -0
- data/lib/shokkenki/consumer/stubber/interactions.rb +41 -0
- data/lib/shokkenki/consumer/stubber/interactions_middleware.rb +34 -0
- data/lib/shokkenki/consumer/stubber/rack_response.rb +28 -0
- data/lib/shokkenki/consumer/stubber/request.rb +72 -0
- data/lib/shokkenki/consumer/stubber/restful_middleware.rb +33 -0
- data/lib/shokkenki/consumer/stubber/server.rb +69 -0
- data/lib/shokkenki/consumer/stubber/server_application_error.rb +16 -0
- data/lib/shokkenki/consumer/stubber/stub_server_middleware.rb +46 -0
- data/lib/shokkenki/consumer/stubber/stubbed_response_middleware.rb +34 -0
- data/lib/shokkenki/consumer/stubber/unmatched_requests_middleware.rb +22 -0
- data/lib/shokkenki/consumer/stubber/unused_interactions_middleware.rb +21 -0
- data/lib/shokkenki/consumer/version.rb +7 -0
- metadata +258 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'shokkenki/term/term_factory'
|
3
|
+
|
4
|
+
module Shokkenki
|
5
|
+
module Consumer
|
6
|
+
module Stubber
|
7
|
+
class Interaction
|
8
|
+
|
9
|
+
attr_reader :label, :request, :response, :time, :matched_requests
|
10
|
+
|
11
|
+
def self.from_json json
|
12
|
+
attributes = json.with_indifferent_access
|
13
|
+
|
14
|
+
new(
|
15
|
+
:label => attributes[:label],
|
16
|
+
:request => Shokkenki::Term::TermFactory.from_json(attributes[:request]),
|
17
|
+
:response => Shokkenki::Term::TermFactory.from_json(attributes[:response]),
|
18
|
+
:time => attributes[:time]
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize attributes
|
23
|
+
@label = attributes[:label]
|
24
|
+
@request = attributes[:request]
|
25
|
+
@response = attributes[:response]
|
26
|
+
@time = attributes[:time]
|
27
|
+
@matched_requests = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
{
|
32
|
+
:label => @label
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate_response
|
37
|
+
@response.example
|
38
|
+
end
|
39
|
+
|
40
|
+
def match_request? request
|
41
|
+
@request.match? request
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_match request
|
45
|
+
@matched_requests << request
|
46
|
+
request.interaction = self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Shokkenki
|
2
|
+
module Consumer
|
3
|
+
module Stubber
|
4
|
+
class Interactions
|
5
|
+
|
6
|
+
attr_reader :interactions, :requests
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@interactions = []
|
10
|
+
@requests = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def find request
|
14
|
+
matching_interaction = @interactions.find do |interaction|
|
15
|
+
interaction.match_request? request
|
16
|
+
end
|
17
|
+
matching_interaction.add_match(request) if matching_interaction
|
18
|
+
@requests << request
|
19
|
+
matching_interaction
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete_all
|
23
|
+
@interactions.clear
|
24
|
+
@requests.clear
|
25
|
+
end
|
26
|
+
|
27
|
+
def add interaction
|
28
|
+
@interactions << interaction
|
29
|
+
end
|
30
|
+
|
31
|
+
def unmatched_requests
|
32
|
+
@requests.select{ |r| r.interaction.nil? }
|
33
|
+
end
|
34
|
+
|
35
|
+
def unused_interactions
|
36
|
+
@interactions.select { |i| i.matched_requests.empty? }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'interaction'
|
2
|
+
require_relative 'restful_middleware'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Shokkenki
|
6
|
+
module Consumer
|
7
|
+
module Stubber
|
8
|
+
class InteractionsMiddleware < RestfulMiddleware
|
9
|
+
|
10
|
+
def initialize interactions
|
11
|
+
@interactions = interactions
|
12
|
+
end
|
13
|
+
|
14
|
+
get { |env| [200, {}, ["[]"]] }
|
15
|
+
|
16
|
+
post do |env|
|
17
|
+
@interactions.add Interaction.from_json(body_json(env['rack.input']))
|
18
|
+
[204, {}, []]
|
19
|
+
end
|
20
|
+
|
21
|
+
delete do |env|
|
22
|
+
@interactions.delete_all
|
23
|
+
[204, {}, []]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def body_json body_io
|
29
|
+
JSON.parse body_io.read
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
|
3
|
+
module Shokkenki
|
4
|
+
module Consumer
|
5
|
+
module Stubber
|
6
|
+
class RackResponse
|
7
|
+
|
8
|
+
def self.from_interaction interaction
|
9
|
+
defaults = { :status => 200 }
|
10
|
+
response = defaults.merge interaction.generate_response
|
11
|
+
[response[:status], as_rack_headers(response[:headers]), [response[:body]]]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.as_rack_headers headers
|
15
|
+
(headers || []).inject({}) do |h, key_value|
|
16
|
+
k, v = key_value
|
17
|
+
h[as_header_name(k)] = v.to_s
|
18
|
+
h
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.as_header_name name
|
23
|
+
name.to_s.split('-').map{ |word| word.titleize }.join('-')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Shokkenki
|
4
|
+
module Consumer
|
5
|
+
module Stubber
|
6
|
+
class Request
|
7
|
+
|
8
|
+
attr_reader :path, :method, :body, :query, :headers
|
9
|
+
attr_accessor :interaction
|
10
|
+
|
11
|
+
def self.from_rack rack_env
|
12
|
+
env = rack_env.dup
|
13
|
+
new(
|
14
|
+
:path => env['PATH_INFO'],
|
15
|
+
:method => env['REQUEST_METHOD'].downcase,
|
16
|
+
:body => env['rack.input'].read,
|
17
|
+
:query => query_from(env['QUERY_STRING']),
|
18
|
+
:headers => headers_from(env)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize attributes
|
23
|
+
@path = attributes[:path]
|
24
|
+
@method = attributes[:method]
|
25
|
+
@body = attributes[:body]
|
26
|
+
@query = attributes[:query]
|
27
|
+
@headers = attributes[:headers]
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
hash = {
|
32
|
+
:path => @path,
|
33
|
+
:method => @method,
|
34
|
+
:headers => @headers,
|
35
|
+
:query => @query,
|
36
|
+
:body => @body
|
37
|
+
}
|
38
|
+
hash.merge!(:interaction => @interaction.to_hash) if @interaction
|
39
|
+
hash
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.headers_from env
|
43
|
+
env.reject{ |k, v|
|
44
|
+
[
|
45
|
+
'PATH_INFO',
|
46
|
+
'REQUEST_METHOD',
|
47
|
+
'QUERY_STRING'
|
48
|
+
].include?(k)
|
49
|
+
}.
|
50
|
+
reject { |k, v| k.start_with?('rack.') }.
|
51
|
+
reject { |k, v| k.start_with?('async.') }.
|
52
|
+
inject({}) do |headers, key_value|
|
53
|
+
headers[as_header_name(key_value[0])] = key_value[1]
|
54
|
+
headers
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.query_from query_string
|
59
|
+
query_string.split('&').inject({}) do |query, param|
|
60
|
+
k, v = param.split '='
|
61
|
+
query[k.to_sym] = URI.decode v
|
62
|
+
query
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.as_header_name name
|
67
|
+
name.to_s.gsub(/^HTTP_/, '').gsub('_', '-').downcase.to_sym
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Shokkenki
|
2
|
+
module Consumer
|
3
|
+
module Stubber
|
4
|
+
class RestfulMiddleware
|
5
|
+
|
6
|
+
def self.post &block
|
7
|
+
handlers[:post] = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.delete &block
|
11
|
+
handlers[:delete] = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.get &block
|
15
|
+
handlers[:get] = block
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.handlers
|
19
|
+
@handlers ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def call env
|
23
|
+
handler = self.class.handlers[env['REQUEST_METHOD'].downcase.to_sym]
|
24
|
+
handler ? instance_exec(env, &handler) : [405, {'Allow' => allowed_methods.join(', ')}, []]
|
25
|
+
end
|
26
|
+
|
27
|
+
def allowed_methods
|
28
|
+
self.class.handlers.keys.map {|k| k.to_s.upcase }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'rack'
|
4
|
+
require 'rack/handler/webrick'
|
5
|
+
require_relative 'server_application_error'
|
6
|
+
|
7
|
+
# pinched from https://github.com/jnicklas/capybara/blob/master/lib/capybara/server.rb
|
8
|
+
module Shokkenki
|
9
|
+
module Consumer
|
10
|
+
module Stubber
|
11
|
+
class Server
|
12
|
+
|
13
|
+
attr_reader :app, :port, :host, :server_thread, :error
|
14
|
+
|
15
|
+
def initialize attributes
|
16
|
+
@app = attributes[:app]
|
17
|
+
@server_thread = nil # suppress warnings
|
18
|
+
@host = attributes[:host]
|
19
|
+
@port = attributes[:port]
|
20
|
+
end
|
21
|
+
|
22
|
+
def error
|
23
|
+
@app.error
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert_ok!
|
27
|
+
raise ServerApplicationError.new(error) if error
|
28
|
+
end
|
29
|
+
|
30
|
+
def responsive?
|
31
|
+
return false if server_thread && server_thread.join(0)
|
32
|
+
res = Net::HTTP.start(host, @port) do |http|
|
33
|
+
http.get @app.identify_path
|
34
|
+
end
|
35
|
+
assert_ok!
|
36
|
+
ok?(res) && is_app?(res)
|
37
|
+
rescue SystemCallError
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
def is_app? response
|
42
|
+
response.body.to_s == @app.object_id.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def ok? response
|
46
|
+
response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPRedirection)
|
47
|
+
end
|
48
|
+
|
49
|
+
def run app, host, port
|
50
|
+
Rack::Handler::WEBrick.run(app, :Host => host, :Port => port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0))
|
51
|
+
end
|
52
|
+
|
53
|
+
def start
|
54
|
+
@server_thread = Thread.new do
|
55
|
+
run @app, @host, @port
|
56
|
+
end
|
57
|
+
|
58
|
+
Timeout.timeout(10) { @server_thread.join(0.1) until responsive? }
|
59
|
+
rescue Timeout::Error
|
60
|
+
raise "Rack application #{@app} timed out during boot"
|
61
|
+
end
|
62
|
+
|
63
|
+
def shutdown
|
64
|
+
server_thread.kill
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Shokkenki
|
2
|
+
module Consumer
|
3
|
+
module Stubber
|
4
|
+
class ServerApplicationError < StandardError
|
5
|
+
def initialize error
|
6
|
+
super "An error occurred in the stub server: #{error.message}"
|
7
|
+
@error = error
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_backtrace backtrace
|
11
|
+
super (@error.backtrace || []) + backtrace
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative 'interactions'
|
2
|
+
require_relative 'interactions_middleware'
|
3
|
+
require_relative 'stubbed_response_middleware'
|
4
|
+
require_relative 'unmatched_requests_middleware'
|
5
|
+
require_relative 'unused_interactions_middleware'
|
6
|
+
|
7
|
+
# adapted from middleware in https://github.com/jnicklas/capybara/blob/master/lib/capybara/server.rb
|
8
|
+
module Shokkenki
|
9
|
+
module Consumer
|
10
|
+
module Stubber
|
11
|
+
class StubServerMiddleware
|
12
|
+
|
13
|
+
attr_accessor :error
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@interactions = Interactions.new
|
17
|
+
@middlewares = {
|
18
|
+
%r{^#{identify_path}} => lambda(&method(:identify)),
|
19
|
+
%r{^/shokkenki/interactions/unused} => UnusedInteractionsMiddleware.new(@interactions),
|
20
|
+
%r{^/shokkenki/interactions} => InteractionsMiddleware.new(@interactions),
|
21
|
+
%r{^/shokkenki/requests/unmatched} => UnmatchedRequestsMiddleware.new(@interactions),
|
22
|
+
/.*/ => StubbedResponseMiddleware.new(@interactions)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def call env
|
27
|
+
begin
|
28
|
+
handler = @middlewares.find {|path, m| path.match(env['PATH_INFO']) }[1]
|
29
|
+
handler.call env
|
30
|
+
rescue StandardError => e
|
31
|
+
@error = e unless @error
|
32
|
+
raise e
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def identify env
|
37
|
+
[200, {}, [object_id.to_s]]
|
38
|
+
end
|
39
|
+
|
40
|
+
def identify_path
|
41
|
+
'/shokkenki/__identify__'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'request'
|
3
|
+
require_relative 'rack_response'
|
4
|
+
|
5
|
+
module Shokkenki
|
6
|
+
module Consumer
|
7
|
+
module Stubber
|
8
|
+
class StubbedResponseMiddleware
|
9
|
+
|
10
|
+
def initialize interactions
|
11
|
+
@interactions = interactions
|
12
|
+
end
|
13
|
+
|
14
|
+
def call env
|
15
|
+
request = Request.from_rack env
|
16
|
+
interaction = @interactions.find request
|
17
|
+
interaction ? RackResponse.from_interaction(interaction) : no_interaction(request)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def no_interaction request
|
23
|
+
body = {
|
24
|
+
:shokkenki => {
|
25
|
+
:message => 'No matching responses were found for the request.',
|
26
|
+
:request => request.to_hash
|
27
|
+
}
|
28
|
+
}
|
29
|
+
[404, {'Content-Type' => 'application/json'}, [body.to_json]]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'interaction'
|
2
|
+
require_relative 'restful_middleware'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Shokkenki
|
6
|
+
module Consumer
|
7
|
+
module Stubber
|
8
|
+
class UnmatchedRequestsMiddleware < RestfulMiddleware
|
9
|
+
|
10
|
+
def initialize interactions
|
11
|
+
@interactions = interactions
|
12
|
+
end
|
13
|
+
|
14
|
+
get do |env|
|
15
|
+
requests = @interactions.unmatched_requests
|
16
|
+
json = JSON.pretty_generate(requests.collect{|r| r.to_hash})
|
17
|
+
[200, {'Content-Type' => 'application/json'}, [json]]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'restful_middleware'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Shokkenki
|
5
|
+
module Consumer
|
6
|
+
module Stubber
|
7
|
+
class UnusedInteractionsMiddleware < RestfulMiddleware
|
8
|
+
|
9
|
+
def initialize interactions
|
10
|
+
@interactions = interactions
|
11
|
+
end
|
12
|
+
|
13
|
+
get do |env|
|
14
|
+
interactions = @interactions.unused_interactions
|
15
|
+
json = JSON.pretty_generate(interactions.collect{|r| r.to_hash})
|
16
|
+
[200, {'Content-Type' => 'application/json'}, [json]]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|