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.
Files changed (34) hide show
  1. data/lib/shokkenki/consumer/configuration/provider_configuration.rb +30 -0
  2. data/lib/shokkenki/consumer/configuration/session.rb +38 -0
  3. data/lib/shokkenki/consumer/consumer.rb +9 -0
  4. data/lib/shokkenki/consumer/dsl/http_methods.rb +16 -0
  5. data/lib/shokkenki/consumer/dsl/order.rb +70 -0
  6. data/lib/shokkenki/consumer/dsl/session.rb +13 -0
  7. data/lib/shokkenki/consumer/dsl/term_dsl.rb +13 -0
  8. data/lib/shokkenki/consumer/model/fixture.rb +21 -0
  9. data/lib/shokkenki/consumer/model/interaction.rb +29 -0
  10. data/lib/shokkenki/consumer/model/patronage.rb +31 -0
  11. data/lib/shokkenki/consumer/model/provider.rb +33 -0
  12. data/lib/shokkenki/consumer/model/role.rb +27 -0
  13. data/lib/shokkenki/consumer/model/simplification.rb +16 -0
  14. data/lib/shokkenki/consumer/model/ticket.rb +38 -0
  15. data/lib/shokkenki/consumer/rspec/example_group_binding.rb +17 -0
  16. data/lib/shokkenki/consumer/rspec/hooks.rb +32 -0
  17. data/lib/shokkenki/consumer/rspec/rspec_configuration.rb +25 -0
  18. data/lib/shokkenki/consumer/rspec.rb +2 -0
  19. data/lib/shokkenki/consumer/session.rb +83 -0
  20. data/lib/shokkenki/consumer/stubber/http_stubber.rb +97 -0
  21. data/lib/shokkenki/consumer/stubber/interaction.rb +51 -0
  22. data/lib/shokkenki/consumer/stubber/interactions.rb +41 -0
  23. data/lib/shokkenki/consumer/stubber/interactions_middleware.rb +34 -0
  24. data/lib/shokkenki/consumer/stubber/rack_response.rb +28 -0
  25. data/lib/shokkenki/consumer/stubber/request.rb +72 -0
  26. data/lib/shokkenki/consumer/stubber/restful_middleware.rb +33 -0
  27. data/lib/shokkenki/consumer/stubber/server.rb +69 -0
  28. data/lib/shokkenki/consumer/stubber/server_application_error.rb +16 -0
  29. data/lib/shokkenki/consumer/stubber/stub_server_middleware.rb +46 -0
  30. data/lib/shokkenki/consumer/stubber/stubbed_response_middleware.rb +34 -0
  31. data/lib/shokkenki/consumer/stubber/unmatched_requests_middleware.rb +22 -0
  32. data/lib/shokkenki/consumer/stubber/unused_interactions_middleware.rb +21 -0
  33. data/lib/shokkenki/consumer/version.rb +7 -0
  34. 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
@@ -0,0 +1,7 @@
1
+ module Shokkenki
2
+ module Consumer
3
+ module Version
4
+ STRING = '0.0.2'
5
+ end
6
+ end
7
+ end