holoserve 0.4.1 → 0.4.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/README.rdoc CHANGED
@@ -98,11 +98,11 @@ Returns a list of all requests that has been received, but couldn't be handled.
98
98
 
99
99
  === GET /_control/history
100
100
 
101
- Returns a list of all names of pairs that has been triggered.
101
+ Returns a list of hashes, which include the names/id of pairs that has been triggered, with a request variant and a list of response variants.
102
102
 
103
103
  ==== Response example
104
104
 
105
- [ "create_received", "update_received" ]
105
+ [{"id":"test_request","request_variant":"default","response_variants":["default","alternative"]}]
106
106
 
107
107
  === DELETE /_control/history
108
108
 
@@ -112,9 +112,10 @@ Removes all entries from the history.
112
112
 
113
113
  The request/response pair file should have the following format.
114
114
 
115
- - request:
115
+ requests:
116
+ default:
116
117
  imports:
117
- - path: "test_fixture.users.0"
118
+ - path: "test_fixtures.users.0"
118
119
  as: "parameters"
119
120
  only: [ "username", "password" ]
120
121
  method: "POST"
@@ -123,55 +124,64 @@ The request/response pair file should have the following format.
123
124
  HTTP_USER_AGENT: "Ruby"
124
125
  oauth:
125
126
  oauth_token: "12345"
126
- responses:
127
- default:
128
- status: 200
129
- "user_one == :existing":
130
- imports:
131
- - path: "test_fixture.users.0"
132
- as: "json.user"
133
- transitions:
134
- session_one: "existing"
135
- "user_one == :missing":
136
- json:
137
- message: "user not found"
138
- - request:
127
+ responses:
128
+ default:
129
+ status: 200
130
+ found:
131
+ condition: "username == :existing"
139
132
  imports:
140
- - path: "test_fixture.users.0"
141
- as: "parameters"
142
- only: "username"
143
- method: "POST"
144
- path: "/session"
145
- headers:
146
- HTTP_USER_AGENT: "Ruby"
147
- parameters:
148
- password: "invalid"
149
- oauth:
150
- oauth_token: "12345"
151
- responses:
152
- "user_one == :existing":
153
- status: 401
154
- json:
155
- message: "invalid password"
156
- "user_one == :missing":
157
- status: 200
158
- json:
159
- message: "user not found"
160
-
161
- The fixture data where this example pair definition relies on, could look like to following.
133
+ - path: "test_fixtures.users.0"
134
+ as: "json.user"
135
+ transitions:
136
+ session_one: "existing"
137
+ not_found:
138
+ condition: "username == :missing"
139
+ json:
140
+ message: "user not found"
141
+
142
+ The fixture data where this example pair definition relies on, could look like the following.
162
143
 
163
144
  users:
164
145
  - email: "one@test.com"
165
146
  username: "one"
166
147
  password: "valid"
167
148
 
168
- This example defines two request/response pairs and two situations. The two pairs differs only the parameters that they
169
- match against. The first one handles the case in which the parameters <tt>username=one</tt> and <tt>password=valid</tt>
170
- are posted and the second one reacts on the transmission of <tt>username=one</tt> and <tt>password=invalid</tt>.
149
+ This example defines a request/response pair situation. It uses <tt>username=one</tt> and <tt>password=valid</tt>
150
+ as parameters.
171
151
 
172
- If the state includes <tt>user_one == :existing</tt>, the first response would return the complete user object encoded
173
- as json, while the second one would reply the message "invalid password". In the state includes
174
- <tt>user_one == :missing</tt>, both requests would be replied with the message "user not found".
152
+ If the state includes <tt>condition: "user_one == :existing"</tt>, the response would return the complete user object encoded
153
+ as json. If the state includes <tt>user_one == :missing</tt>, the request would be replied with the message "user not found".
175
154
 
176
155
  The <tt>transitions</tt> part of the response allows you set the successor state. If a response is selected, the
177
156
  corresponding transitions will be evaluated.
157
+
158
+
159
+ requests:
160
+ default:
161
+ imports:
162
+ - path: "test_fixtures.users.0.email"
163
+ as: "parameters"
164
+ method: "POST"
165
+ path: "/valid_email"
166
+ responses:
167
+ default:
168
+ status: 200
169
+ found:
170
+ condition: "email == :existing"
171
+ imports:
172
+ - path: "test_fixtures.users.0.username"
173
+ as: "json.user.username"
174
+ transitions:
175
+ session_one: "existing"
176
+ not_found:
177
+ condition: "email == :missing"
178
+ json:
179
+ message: "email address not found"
180
+
181
+ This example relies on the same fixture file used above.
182
+
183
+ This time it uses <tt>email=one@test.com</tt> as a parameter.
184
+ If the state includes <tt>condition: "email == :existing"</tt>, the response would return a username of the email address
185
+ owner as json.
186
+
187
+ If the state includes <tt>email == :missing</tt>, the response would be a message "email address not found" in json.
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ load File.join(File.dirname(__FILE__), "tasks", "features.rake")
5
5
  load File.join(File.dirname(__FILE__), "tasks", "gem.rake")
6
6
  load File.join(File.dirname(__FILE__), "tasks", "goliath.rake")
7
7
  load File.join(File.dirname(__FILE__), "tasks", "rdoc.rake")
8
- load File.join(File.dirname(__FILE__), "tasks", "spec.rake")
8
+ load File.join(File.dirname(__FILE__), "tasks", "spec.rake")
@@ -0,0 +1,42 @@
1
+ require 'goliath/api'
2
+ require 'goliath/rack/templates'
3
+ require 'slim'
4
+ require 'sass'
5
+ require 'coffee_script'
6
+
7
+ module Holoserve::Interface::Control::Index
8
+
9
+ class Fetch < Goliath::API
10
+ include Goliath::Rack::Templates
11
+ include Holoserve::Interface::Control::Helper
12
+
13
+ def response(environment)
14
+ case environment["REQUEST_URI"]
15
+ when "/_control"
16
+ render_slim :index
17
+ when "/_control/stylesheets/screen.css"
18
+ render_scss :screen
19
+ when "/_control/javascripts/all.js"
20
+ render_coffee :all
21
+ else
22
+ not_found
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def render_slim(template)
29
+ ok slim(template, :views => File.join(Holoserve::Interface::ROOT, "views")), "text/html"
30
+ end
31
+
32
+ def render_scss(template)
33
+ ok scss(template, :views => File.join(Holoserve::Interface::ROOT, "stylesheets")), "text/css"
34
+ end
35
+
36
+ def render_coffee(template)
37
+ ok coffee(template, :views => File.join(Holoserve::Interface::ROOT, "javascripts")), "text/javascript"
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -4,6 +4,7 @@ module Holoserve::Interface::Control
4
4
 
5
5
  autoload :Bucket, File.join(File.dirname(__FILE__), "control", "bucket")
6
6
  autoload :History, File.join(File.dirname(__FILE__), "control", "history")
7
+ autoload :Index, File.join(File.dirname(__FILE__), "control", "index")
7
8
  autoload :Pair, File.join(File.dirname(__FILE__), "control", "pair")
8
9
  autoload :State, File.join(File.dirname(__FILE__), "control", "state")
9
10
 
@@ -25,6 +26,10 @@ module Holoserve::Interface::Control
25
26
  [ 400, { }, [ "bad request" ] ]
26
27
  end
27
28
 
29
+ def not_found
30
+ [ 404, { }, [ "not found" ] ]
31
+ end
32
+
28
33
  def bucket
29
34
  config[:bucket] ||= [ ]
30
35
  end
@@ -0,0 +1,38 @@
1
+ require 'json'
2
+
3
+ class Holoserve::Interface::Event
4
+
5
+ def on_open(environment)
6
+ self.class.handler = environment["handler"]
7
+ end
8
+
9
+ def on_message(environment, message)
10
+ environment.logger.info "MESSAGE #{message}"
11
+ end
12
+
13
+ def on_close(environment)
14
+ environment.logger.info("WS CLOSED")
15
+ end
16
+
17
+ def on_error(environment, error)
18
+ environment.logger.error error
19
+ end
20
+
21
+ def self.handler=(value)
22
+ @handler = value
23
+ end
24
+
25
+ def self.send_pair_event(id)
26
+ send_message "pair:#{id}"
27
+ end
28
+
29
+ def self.send_bucket_event(request)
30
+ send_message "bucket:#{JSON.dump(request)}"
31
+ end
32
+
33
+ def self.send_message(text)
34
+ return unless @handler
35
+ @handler.send_text_frame text
36
+ end
37
+
38
+ end
@@ -3,43 +3,58 @@ require 'pp'
3
3
 
4
4
  class Holoserve::Interface::Fake < Goliath::API
5
5
 
6
+ class NoResponseError < StandardError
7
+
8
+ attr_reader :id
9
+ attr_reader :request_variant
10
+
11
+ def initialize(id, request_variant)
12
+ @id, @request_variant = id, request_variant
13
+ end
14
+
15
+ end
16
+
6
17
  use Goliath::Rack::Params
7
18
 
8
19
  def response(env)
9
20
  request = Holoserve::Request::Decomposer.new(env, params).hash
10
- pair = Holoserve::Pair::Finder.new(pairs, request).pair
21
+ finder = Holoserve::Pair::Finder.new(pairs, request)
22
+ pair = finder.pair
11
23
  if pair
12
- id, responses = *pair.values_at(:id, :responses)
13
-
14
- history << id
15
- logger.info "received handled request with id '#{id}'"
16
-
17
- selector = Holoserve::Response::Selector.new responses, state, logger
18
- default_response, selected_responses = selector.default_response, selector.selected_responses
24
+ id, request_variant, responses = finder.id, finder.variant, pair[:responses]
25
+
26
+ selector = Holoserve::Response::Selector.new responses, state.merge(:request_variant => request_variant), logger
27
+ response_variant = selector.selection
28
+
29
+ response = if response_variant == :default
30
+ responses[:default] || { }
31
+ elsif response_variant
32
+ Holoserve::Tool::Merger.new(responses[:default] || { }, responses[response_variant]).result
33
+ else
34
+ raise NoResponseError, id, request_variant
35
+ end
36
+ Holoserve::State::Updater.new(state, response[:transitions]).perform
37
+ history << {:id => id, :request_variant => request_variant, :response_variant => response_variant}
19
38
 
20
- update_state default_response, selected_responses
39
+ Holoserve::Interface::Event.send_pair_event id
40
+ logger.info "handled request [#{id}] with request variant [#{request_variant}] and response variant [#{response_variant}]"
21
41
 
22
- response = Holoserve::Response::Combiner.new(default_response, selected_responses).response
23
42
  Holoserve::Response::Composer.new(response).response_array
24
43
  else
25
44
  bucket << request
45
+ Holoserve::Interface::Event.send_bucket_event request
26
46
  logger.error "received unhandled request\n" + request.pretty_inspect
27
47
 
28
48
  not_found
29
49
  end
50
+ rescue NoResponseError => error
51
+ logger.warn "could not select any response for request [#{error.id}] with request variant [#{error.request_variant}]"
30
52
  end
31
53
 
32
54
  private
33
55
 
34
- def update_state(default_response, selected_responses)
35
- Holoserve::State::Updater.new(state, default_response[:transitions]).perform
36
- (selected_responses || [ ]).each do |response|
37
- Holoserve::State::Updater.new(state, response[:transitions]).perform
38
- end
39
- end
40
-
41
56
  def not_found
42
- [ 404, { :"Content-Type" => "text/plain" }, [ "no response found for this request" ] ]
57
+ [ 404, { :"Content-Type" => "text/plain" }, [ "no response found for this request\n" ] ]
43
58
  end
44
59
 
45
60
  def bucket
@@ -3,8 +3,22 @@ require 'goliath/api'
3
3
  class Holoserve::Interface < Goliath::API
4
4
 
5
5
  autoload :Control, File.join(File.dirname(__FILE__), "interface", "control")
6
+ autoload :Event, File.join(File.dirname(__FILE__), "interface", "event")
6
7
  autoload :Fake, File.join(File.dirname(__FILE__), "interface", "fake")
7
8
 
9
+ ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", "..")).freeze unless defined?(ROOT)
10
+
11
+ use Rack::Static,
12
+ :root => File.join(ROOT, "public"),
13
+ :urls => {
14
+ "/_control/favicon.ico" => "favicon.ico",
15
+ "/_control/javascripts/vendor/jquery-1.7.2.min.js" => "javascripts/vendor/jquery-1.7.2.min.js",
16
+ "/_control/javascripts/vendor/jquery.color.js" => "javascripts/vendor/jquery.color.js",
17
+ "/_control/javascripts/vendor/bootstrap-transition.js" => "javascripts/vendor/bootstrap-transition.js"
18
+ }
19
+
20
+ map "/_control/event", Event
21
+
8
22
  get "/_control/bucket", Control::Bucket::Fetch
9
23
  delete "/_control/bucket", Control::Bucket::Delete
10
24
 
@@ -18,6 +32,8 @@ class Holoserve::Interface < Goliath::API
18
32
  get "/_control/state", Control::State::Fetch
19
33
  delete "/_control/state", Control::State::Delete
20
34
 
35
+ get "/_control*", Control::Index::Fetch
36
+
21
37
  map "/*", Fake
22
38
 
23
39
  end
@@ -6,9 +6,31 @@ class Holoserve::Pair::Finder
6
6
  end
7
7
 
8
8
  def pair
9
+ return @pair unless find_pair.nil?
10
+ nil
11
+ end
12
+
13
+ def id
14
+ return @id unless find_pair.nil?
15
+ nil
16
+ end
17
+
18
+ def variant
19
+ return @variant unless find_pair.nil?
20
+ nil
21
+ end
22
+
23
+ private
24
+
25
+ def find_pair
9
26
  return nil unless @pairs
10
27
  @pairs.each do |id, pair|
11
- return pair.merge(:id => id) if Holoserve::Request::Matcher.new(@request, pair[:request]).match?
28
+ @variant = Holoserve::Request::Selector.new(@request, pair[:requests]).selection
29
+ unless @variant.nil?
30
+ @pair = pair
31
+ @id = id
32
+ return ""
33
+ end
12
34
  end
13
35
  nil
14
36
  end
@@ -7,6 +7,9 @@ class Holoserve::Pair::Loader
7
7
  @fixtures, @pairs = { }, { }
8
8
  @fixture_file_pattern, @pair_file_pattern = fixture_file_pattern, pair_file_pattern
9
9
  @logger = logger
10
+ @validator = Holoserve::Pair::Validator.new
11
+ rescue Holoserve::Pair::Validator::InvalidSchemaError => error
12
+ @logger.error error.inspect
10
13
  end
11
14
 
12
15
  def pairs
@@ -21,22 +24,33 @@ class Holoserve::Pair::Loader
21
24
  Dir[ @fixture_file_pattern ].each do |filename|
22
25
  id = extract_id filename
23
26
  fixture = load_file filename
24
- @fixtures[id] = fixture if fixture
25
- @logger.info "loaded fixture '#{id}'"
27
+ if fixture
28
+ @fixtures[id] = fixture
29
+ @logger.info "loaded fixture '#{id}'"
30
+ end
26
31
  end
27
32
  @fixtures.freeze
28
33
  end
29
34
 
30
35
  def load_pairs
31
36
  Dir[ @pair_file_pattern ].each do |filename|
32
- id = extract_id filename
33
- pair = load_file filename
34
- @pairs[id] = pair_with_imports pair if pair
35
- @logger.info "loaded pair '#{id}'"
37
+ load_pair filename
36
38
  end
37
39
  @pairs.freeze
38
40
  end
39
41
 
42
+ def load_pair(filename)
43
+ id = extract_id filename
44
+ pair = load_file filename
45
+ if pair
46
+ @validator.validate(pair)
47
+ @pairs[id] = pair_with_imports pair
48
+ @logger.info "loaded pair '#{id}'"
49
+ end
50
+ rescue Holoserve::Pair::Validator::InvalidError => error
51
+ @logger.error error.inspect
52
+ end
53
+
40
54
  def extract_id(filename)
41
55
  File.basename filename, ".*"
42
56
  end
@@ -52,18 +66,21 @@ class Holoserve::Pair::Loader
52
66
  rescue JSON::ParserError
53
67
  nil
54
68
  end
55
- end
69
+ end
56
70
  Holoserve::Tool::Hash::KeySymbolizer.new(data).hash
57
71
  end
58
72
 
59
73
  def pair_with_imports(pair)
60
- result = {
61
- :request => Holoserve::Fixture::Importer.new(pair[:request], @fixtures).result,
62
- :responses => { }
63
- }
64
- (pair[:responses] || { }).each do |id, response|
65
- result[:responses][id] = Holoserve::Fixture::Importer.new(response, @fixtures).result
74
+ result = { :requests => { }, :responses => { } }
75
+
76
+ pair[:requests].each do |variant, request|
77
+ result[:requests][variant] = Holoserve::Fixture::Importer.new(request, @fixtures).result
66
78
  end
79
+
80
+ pair[:responses].each do |variant, response|
81
+ result[:responses][variant] = Holoserve::Fixture::Importer.new(response, @fixtures).result
82
+ end
83
+
67
84
  result
68
85
  end
69
86
 
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'kwalify'
3
+
4
+ class Holoserve::Pair::Validator
5
+
6
+ class Error < StandardError
7
+
8
+ def initialize(errors)
9
+ @errors = errors
10
+ end
11
+
12
+ def to_s
13
+ "<#{self.class} error count = #{@errors.size}>"
14
+ end
15
+
16
+ def inspect
17
+ "#{self.class}\n " + @errors.join("\n ")
18
+ end
19
+
20
+ end
21
+
22
+ class InvalidError < Error; end
23
+ class InvalidSchemaError < Error; end
24
+
25
+ attr_accessor :schema_path
26
+
27
+ def initialize(schema_path = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "schema", "schema.yaml")))
28
+ @schema_path = schema_path
29
+ @meta_validator = Kwalify::MetaValidator.instance
30
+ load_schema
31
+ @validator = Kwalify::Validator.new(@schema)
32
+ end
33
+
34
+ def validate(hash)
35
+ errors = @validator.validate(hash)
36
+ raise InvalidError, errors if errors && !errors.empty?
37
+ end
38
+
39
+ private
40
+
41
+ def load_schema
42
+ @schema = Kwalify::Yaml::load_file(@schema_path)
43
+ errors = @meta_validator.validate(@schema)
44
+ raise InvalidSchemaError, errors if errors && !errors.empty?
45
+ end
46
+
47
+ end
@@ -3,5 +3,6 @@ module Holoserve::Pair
3
3
 
4
4
  autoload :Finder, File.join(File.dirname(__FILE__), "pair", "finder")
5
5
  autoload :Loader, File.join(File.dirname(__FILE__), "pair", "loader")
6
+ autoload :Validator, File.join(File.dirname(__FILE__), "pair", "validator")
6
7
 
7
8
  end
@@ -0,0 +1,21 @@
1
+
2
+ class Holoserve::Request::Selector
3
+
4
+ attr_accessor :request
5
+
6
+ def initialize(request, request_subsets)
7
+ @request, @request_subsets = request, request_subsets
8
+ end
9
+
10
+ def selection
11
+ if Holoserve::Tool::Hash::Matcher.new(@request, @request_subsets[:default]).match?
12
+ @request_subsets.each do |variant, subset|
13
+ next if variant == :default
14
+ return variant if Holoserve::Tool::Hash::Matcher.new(@request, subset).match?
15
+ end
16
+ return :default
17
+ end
18
+ nil
19
+ end
20
+
21
+ end
@@ -2,6 +2,6 @@
2
2
  module Holoserve::Request
3
3
 
4
4
  autoload :Decomposer, File.join(File.dirname(__FILE__), "request", "decomposer")
5
- autoload :Matcher, File.join(File.dirname(__FILE__), "request", "matcher")
5
+ autoload :Selector, File.join(File.dirname(__FILE__), "request", "selector")
6
6
 
7
7
  end
@@ -7,7 +7,7 @@ class Holoserve::Response::Combiner
7
7
 
8
8
  def response
9
9
  @responses.inject @default do |result, response|
10
- Holoserve::Tool::Merger.new(result, response, :fusion).result
10
+ Holoserve::Tool::Merger.new(result, response).result
11
11
  end
12
12
  end
13
13
 
@@ -24,26 +24,19 @@ class Holoserve::Response::Selector
24
24
  @sandbox = Sandbox.new state, logger
25
25
  end
26
26
 
27
- def default_response
28
- @responses[:default] ?
29
- @responses[:default] :
30
- { }
31
- end
32
-
33
- def selected_responses
34
- result = [ ]
35
- (@responses || { }).each do |line, response|
36
- next if line.to_s == "default"
27
+ def selection
28
+ @responses.each do |key, response|
29
+ next if key.to_sym == :default
37
30
  begin
38
31
  match = @sandbox.instance_eval do
39
- eval line.to_s
32
+ eval response[:condition]
40
33
  end
41
- result << response if match
34
+ return key.to_sym if match
42
35
  rescue Object => error
43
36
  @logger.error error.inspect
44
37
  end
45
38
  end
46
- result
39
+ @responses.has_key?(:default) ? :default : nil
47
40
  end
48
41
 
49
42
  end
@@ -0,0 +1,53 @@
1
+
2
+ class Holoserve::Tool::Hash::Matcher
3
+
4
+ attr_accessor :hash
5
+ attr_accessor :subset
6
+
7
+ def initialize(hash, subset)
8
+ @hash, @subset = hash, subset
9
+ end
10
+
11
+ def match?
12
+ return false if @hash.length < @subset.length
13
+ @subset.each do |key, value|
14
+ if @hash.has_key?(key)
15
+ return false unless match_value?(@hash[key], value)
16
+ else
17
+ return false
18
+ end
19
+ end
20
+ true
21
+ end
22
+
23
+ private
24
+
25
+ def match_value?(value_one, value_two)
26
+ if value_one.is_a?(Hash) && value_two.is_a?(Hash)
27
+ return false unless match_hash?(value_one, value_two)
28
+ elsif value_one.is_a?(Array) && value_two.is_a?(Array)
29
+ return false unless match_array?(value_one, value_two)
30
+ else
31
+ return false unless value_one == value_two
32
+ end
33
+ true
34
+ end
35
+
36
+ def match_hash?(hash, subset)
37
+ tmp = Holoserve::Tool::Hash::Matcher.new(hash, subset)
38
+ return false unless tmp.match?
39
+ true
40
+ end
41
+
42
+ def match_array?(array, subset)
43
+ return false if array.length < subset.length
44
+ subset.each_index do |i|
45
+ if subset[i].is_a?(Hash) && array[i].is_a?(Hash)
46
+ return false unless match_hash?(array[i], subset[i])
47
+ elsif subset[i].is_a?(Array) && array[i].is_a?(Array)
48
+ return false unless match_array?(array[i], subset[i])
49
+ end
50
+ end
51
+ true
52
+ end
53
+ end
@@ -2,5 +2,6 @@
2
2
  module Holoserve::Tool::Hash
3
3
 
4
4
  autoload :KeySymbolizer, File.join(File.dirname(__FILE__), "hash", "key_symbolizer")
5
+ autoload :Matcher, File.join(File.dirname(__FILE__), "hash", "matcher")
5
6
 
6
7
  end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "helper"))
2
+ require 'yaml'
3
+
4
+ describe Holoserve::Pair::Validator do
5
+
6
+ let(:valid_schema_path) do
7
+ File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "schema", "schema.yaml"))
8
+ end
9
+
10
+ let(:invalid_schema_path) do
11
+ File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "features", "pairs", "invalid", "test_invalid_schema.yaml"))
12
+ end
13
+
14
+ let(:valid_hash) do
15
+ file = YAML::load_file(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "features", "pairs", "test_evaluation.yaml")))
16
+ Holoserve::Tool::Hash::KeySymbolizer.new(file).hash
17
+ end
18
+
19
+ let(:invalid_hash) do
20
+ file = YAML::load_file(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "features", "pairs", "invalid", "test_invalid_headers.yaml")))
21
+ Holoserve::Tool::Hash::KeySymbolizer.new(file).hash
22
+ end
23
+
24
+ subject { described_class.new valid_schema_path }
25
+
26
+ describe "#initialize" do
27
+
28
+ it "should raise an InvalidSchemaError if the schema file is invalid" do
29
+ lambda do
30
+ described_class.new invalid_schema_path
31
+ end.should raise_error(described_class::InvalidSchemaError)
32
+ end
33
+
34
+ end
35
+
36
+ describe "#validate" do
37
+
38
+ it "should return true if a valid pair file is provided" do
39
+ lambda do
40
+ subject.validate(valid_hash)
41
+ end.should_not raise_error
42
+ end
43
+
44
+ it "should return false if an invalid pair file is provided" do
45
+ lambda do
46
+ subject.validate(invalid_hash)
47
+ end.should raise_error(described_class::InvalidError)
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,69 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "helper"))
2
+
3
+ describe Holoserve::Request::Selector do
4
+
5
+ let(:subsets) do
6
+ {
7
+ :default => {
8
+ :method => "GET",
9
+ :path => "/test"
10
+ },
11
+ :test => {
12
+ :headers => {
13
+ :HTTP_TEST => "value"
14
+ }
15
+ }
16
+ }
17
+ end
18
+
19
+ let(:variant_matching_request) do
20
+ {
21
+ :method => "GET",
22
+ :path => "/test",
23
+ :headers => {
24
+ :HTTP_TEST => "value",
25
+ :HTTP_HOST => "localhost"
26
+ }
27
+ }
28
+ end
29
+
30
+ let(:invalid_request) do
31
+ {
32
+ :method => "POST",
33
+ :other_test => {
34
+ :key => "value"
35
+ }
36
+ }
37
+ end
38
+
39
+ let(:default_matching_request) do
40
+ {
41
+ :method => "GET",
42
+ :path => "/test",
43
+ :parameters => {
44
+ :test => "value"
45
+ }
46
+ }
47
+ end
48
+
49
+ subject { described_class.new invalid_request, subsets }
50
+
51
+ describe "#selection" do
52
+
53
+ it "should return nil if request is not matching default nor any other subset" do
54
+ subject.selection.should be_nil
55
+ end
56
+
57
+ it "should return :default if the default subset is matched" do
58
+ subject.request = default_matching_request
59
+ subject.selection.should == :default
60
+ end
61
+
62
+ it "should return :test if the corresponding subset is matched" do
63
+ subject.request = variant_matching_request
64
+ subject.selection.should == :test
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,136 @@
1
+ require File.expand_path("../../../../helper", File.dirname(__FILE__))
2
+
3
+ describe Holoserve::Tool::Hash::Matcher do
4
+
5
+ let(:hash) do
6
+ {
7
+ :test => "value",
8
+ :nested => {
9
+ :unspecified => "value",
10
+ :another => "test value"
11
+ },
12
+ :unspecified => "value",
13
+ :nested_array => [{:hash => "value", :unspecified => "value", :morehash => {:test => "value", :unspecified => "value", :array => [{:test => "value"}]}}]
14
+ }
15
+ end
16
+
17
+ let(:matching_subset) do
18
+ {
19
+ :test => "value"
20
+ }
21
+ end
22
+
23
+ let(:matching_subset_with_nested_hash) do
24
+ {
25
+ :nested => {
26
+ :another => "test value"
27
+ }
28
+ }
29
+ end
30
+
31
+ let(:matching_subset_with_nested_array) do
32
+ {
33
+ :nested_array => [{:hash => "value"}]
34
+ }
35
+ end
36
+
37
+ let(:matching_subset_with_nested_array_and_nested_hash) do
38
+ {
39
+ :nested_array => [{:hash => "value", :morehash => {:test => "value"}}]
40
+ }
41
+ end
42
+
43
+ let(:matching_subset_with_nested_array_and_nested_hash_and_nested_array) do
44
+ {
45
+ :nested_array => [{:hash => "value", :morehash => {:test => "value", :array => [{:test => "value"}]}}]
46
+ }
47
+ end
48
+
49
+ let(:mismatching_subset) do
50
+ {
51
+ :test => "another value"
52
+ }
53
+ end
54
+
55
+ let(:mismatching_subset_with_nested_hash) do
56
+ {
57
+ :nested => {
58
+ :another => "another value"
59
+ }
60
+ }
61
+ end
62
+
63
+ let(:mismatching_subset_with_nested_array) do
64
+ {
65
+ :nested_array => [{:hash => "other value"}]
66
+ }
67
+ end
68
+
69
+ let(:mismatching_subset_with_nested_array_and_nested_hash) do
70
+ {
71
+ :nested_array => [{:hash => "value", :morehash => {:test => "other value"}}]
72
+ }
73
+ end
74
+
75
+ let(:mismatching_subset_with_nested_array_and_nested_hash_and_nested_array) do
76
+ {
77
+ :nested_array => [{:hash => "value", :morehash => {:test => "value", :array => [{:test => "other value"}]}}]
78
+ }
79
+ end
80
+
81
+ subject { described_class.new hash, matching_subset }
82
+
83
+ describe "#match?" do
84
+
85
+ it "should return true if a matching subset is provided" do
86
+ subject.match?.should be_true
87
+ end
88
+
89
+ it "should return false if a mismatching subset is provided" do
90
+ subject.subset = mismatching_subset
91
+ subject.match?.should be_false
92
+ end
93
+
94
+ it "should return true if a matching subset with nested hash is provided" do
95
+ subject.subset = matching_subset_with_nested_hash
96
+ subject.match?.should be_true
97
+ end
98
+
99
+ it "should return false if a mismatching subset with nested hash is provided" do
100
+ subject.subset = mismatching_subset_with_nested_hash
101
+ subject.match?.should be_false
102
+ end
103
+
104
+ it "should return true if a matching subset with nested array is provided" do
105
+ subject.subset = matching_subset_with_nested_array
106
+ subject.match?.should be_true
107
+ end
108
+
109
+ it "should return false if a mismatching subset with nested array is provided" do
110
+ subject.subset = mismatching_subset_with_nested_array
111
+ subject.match?.should be_false
112
+ end
113
+
114
+ it "should return true if a matching subset with nested array and nested hash is provided" do
115
+ subject.subset = matching_subset_with_nested_array_and_nested_hash
116
+ subject.match?.should be_true
117
+ end
118
+
119
+ it "should return false if a mismatching subset with nested array and nested hash is provided" do
120
+ subject.subset = mismatching_subset_with_nested_array_and_nested_hash
121
+ subject.match?.should be_false
122
+ end
123
+
124
+ it "should return true if a matching subset with nested array and nested hash and nested array is provided" do
125
+ subject.subset = matching_subset_with_nested_array_and_nested_hash_and_nested_array
126
+ subject.match?.should be_true
127
+ end
128
+
129
+ it "should return false if a mismatching subset with nested array and nested hash and nested array is provided" do
130
+ subject.subset = mismatching_subset_with_nested_array_and_nested_hash_and_nested_array
131
+ subject.match?.should be_false
132
+ end
133
+
134
+ end
135
+
136
+ end
metadata CHANGED
@@ -1,19 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: holoserve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Philipp Brüll
9
+ - Maximilian Hoffmann
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2012-04-05 00:00:00.000000000 Z
13
+ date: 2012-06-22 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: goliath
16
- requirement: &70271201827600 !ruby/object:Gem::Requirement
17
+ requirement: &70242257528700 !ruby/object:Gem::Requirement
17
18
  none: false
18
19
  requirements:
19
20
  - - ! '>='
@@ -21,10 +22,54 @@ dependencies:
21
22
  version: '0'
22
23
  type: :runtime
23
24
  prerelease: false
24
- version_requirements: *70271201827600
25
+ version_requirements: *70242257528700
26
+ - !ruby/object:Gem::Dependency
27
+ name: slim
28
+ requirement: &70242257527940 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70242257527940
37
+ - !ruby/object:Gem::Dependency
38
+ name: sass
39
+ requirement: &70242257526740 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70242257526740
48
+ - !ruby/object:Gem::Dependency
49
+ name: coffee-script
50
+ requirement: &70242257526220 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: *70242257526220
59
+ - !ruby/object:Gem::Dependency
60
+ name: kwalify
61
+ requirement: &70242257525580 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: *70242257525580
25
70
  - !ruby/object:Gem::Dependency
26
71
  name: rake
27
- requirement: &70271205878240 !ruby/object:Gem::Requirement
72
+ requirement: &70242257523600 !ruby/object:Gem::Requirement
28
73
  none: false
29
74
  requirements:
30
75
  - - ! '>='
@@ -32,10 +77,10 @@ dependencies:
32
77
  version: '0'
33
78
  type: :development
34
79
  prerelease: false
35
- version_requirements: *70271205878240
80
+ version_requirements: *70242257523600
36
81
  - !ruby/object:Gem::Dependency
37
82
  name: rdoc
38
- requirement: &70271201838000 !ruby/object:Gem::Requirement
83
+ requirement: &70242257522300 !ruby/object:Gem::Requirement
39
84
  none: false
40
85
  requirements:
41
86
  - - ! '>='
@@ -43,10 +88,10 @@ dependencies:
43
88
  version: '0'
44
89
  type: :development
45
90
  prerelease: false
46
- version_requirements: *70271201838000
91
+ version_requirements: *70242257522300
47
92
  - !ruby/object:Gem::Dependency
48
93
  name: cucumber
49
- requirement: &70271201834120 !ruby/object:Gem::Requirement
94
+ requirement: &70242257537280 !ruby/object:Gem::Requirement
50
95
  none: false
51
96
  requirements:
52
97
  - - ! '>='
@@ -54,10 +99,10 @@ dependencies:
54
99
  version: '0'
55
100
  type: :development
56
101
  prerelease: false
57
- version_requirements: *70271201834120
102
+ version_requirements: *70242257537280
58
103
  - !ruby/object:Gem::Dependency
59
104
  name: rspec
60
- requirement: &70271201831820 !ruby/object:Gem::Requirement
105
+ requirement: &70242257536160 !ruby/object:Gem::Requirement
61
106
  none: false
62
107
  requirements:
63
108
  - - ! '>='
@@ -65,10 +110,10 @@ dependencies:
65
110
  version: '0'
66
111
  type: :development
67
112
  prerelease: false
68
- version_requirements: *70271201831820
113
+ version_requirements: *70242257536160
69
114
  - !ruby/object:Gem::Dependency
70
115
  name: transport
71
- requirement: &70271205892940 !ruby/object:Gem::Requirement
116
+ requirement: &70242257535520 !ruby/object:Gem::Requirement
72
117
  none: false
73
118
  requirements:
74
119
  - - ! '>='
@@ -76,10 +121,10 @@ dependencies:
76
121
  version: '0'
77
122
  type: :development
78
123
  prerelease: false
79
- version_requirements: *70271205892940
124
+ version_requirements: *70242257535520
80
125
  - !ruby/object:Gem::Dependency
81
126
  name: oauth
82
- requirement: &70271205890640 !ruby/object:Gem::Requirement
127
+ requirement: &70242257534980 !ruby/object:Gem::Requirement
83
128
  none: false
84
129
  requirements:
85
130
  - - ! '>='
@@ -87,7 +132,7 @@ dependencies:
87
132
  version: '0'
88
133
  type: :development
89
134
  prerelease: false
90
- version_requirements: *70271205890640
135
+ version_requirements: *70242257534980
91
136
  description: This tool can be used to fake webservice APIs for testing proposals.
92
137
  email: philipp.bruell@skrill.com
93
138
  executables:
@@ -105,16 +150,19 @@ files:
105
150
  - lib/holoserve/fixture.rb
106
151
  - lib/holoserve/interface/control/bucket.rb
107
152
  - lib/holoserve/interface/control/history.rb
153
+ - lib/holoserve/interface/control/index.rb
108
154
  - lib/holoserve/interface/control/pair.rb
109
155
  - lib/holoserve/interface/control/state.rb
110
156
  - lib/holoserve/interface/control.rb
157
+ - lib/holoserve/interface/event.rb
111
158
  - lib/holoserve/interface/fake.rb
112
159
  - lib/holoserve/interface.rb
113
160
  - lib/holoserve/pair/finder.rb
114
161
  - lib/holoserve/pair/loader.rb
162
+ - lib/holoserve/pair/validator.rb
115
163
  - lib/holoserve/pair.rb
116
164
  - lib/holoserve/request/decomposer.rb
117
- - lib/holoserve/request/matcher.rb
165
+ - lib/holoserve/request/selector.rb
118
166
  - lib/holoserve/request.rb
119
167
  - lib/holoserve/response/combiner.rb
120
168
  - lib/holoserve/response/composer.rb
@@ -124,6 +172,7 @@ files:
124
172
  - lib/holoserve/state.rb
125
173
  - lib/holoserve/tool/data_path.rb
126
174
  - lib/holoserve/tool/hash/key_symbolizer.rb
175
+ - lib/holoserve/tool/hash/matcher.rb
127
176
  - lib/holoserve/tool/hash.rb
128
177
  - lib/holoserve/tool/merger.rb
129
178
  - lib/holoserve/tool/uploader.rb
@@ -131,7 +180,10 @@ files:
131
180
  - lib/holoserve.rb
132
181
  - spec/helper.rb
133
182
  - spec/lib/holoserve/fixture/importer_spec.rb
183
+ - spec/lib/holoserve/pair/validator_spec.rb
184
+ - spec/lib/holoserve/request/selector_spec.rb
134
185
  - spec/lib/holoserve/tool/data_path_spec.rb
186
+ - spec/lib/holoserve/tool/hash/matcher_spec.rb
135
187
  - spec/lib/holoserve/tool/merger_spec.rb
136
188
  - spec/lib/holoserve_spec.rb
137
189
  homepage: http://github.com/skrill/holoserve
@@ -148,7 +200,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
148
200
  version: '0'
149
201
  segments:
150
202
  - 0
151
- hash: -4367529671340100891
203
+ hash: 3487198030302980531
152
204
  required_rubygems_version: !ruby/object:Gem::Requirement
153
205
  none: false
154
206
  requirements:
@@ -163,6 +215,9 @@ specification_version: 3
163
215
  summary: Tool to fake HTTP APIs.
164
216
  test_files:
165
217
  - spec/lib/holoserve/fixture/importer_spec.rb
218
+ - spec/lib/holoserve/pair/validator_spec.rb
219
+ - spec/lib/holoserve/request/selector_spec.rb
166
220
  - spec/lib/holoserve/tool/data_path_spec.rb
221
+ - spec/lib/holoserve/tool/hash/matcher_spec.rb
167
222
  - spec/lib/holoserve/tool/merger_spec.rb
168
223
  - spec/lib/holoserve_spec.rb
@@ -1,74 +0,0 @@
1
-
2
- class Holoserve::Request::Matcher
3
-
4
- def initialize(request, request_subset)
5
- @request, @request_subset = request, request_subset
6
- end
7
-
8
- def match?
9
- match_method? &&
10
- match_path? &&
11
- match_headers? &&
12
- match_body? &&
13
- match_parameters? &&
14
- match_oauth? &&
15
- match_json?
16
- end
17
-
18
- private
19
-
20
- def match_method?
21
- @request_subset[:method] ?
22
- @request[:method] == @request_subset[:method] :
23
- true
24
- end
25
-
26
- def match_path?
27
- @request_subset[:path] ?
28
- @request[:path] == @request_subset[:path] :
29
- true
30
- end
31
-
32
- def match_headers?
33
- match = true
34
- (@request_subset[:headers] || { }).each do |key, value|
35
- match &&= @request[:headers][key] == value
36
- end
37
- match
38
- end
39
-
40
- def match_body?
41
- @request_subset[:body] ?
42
- @request[:body] == @request_subset[:body] :
43
- true
44
- end
45
-
46
- def match_parameters?
47
- match = true
48
- (@request_subset[:parameters] || { }).each do |key, value|
49
- match &&= @request[:parameters].is_a?(Hash) && (@request[:parameters][key] == value)
50
- end
51
- match
52
- end
53
-
54
- def match_oauth?
55
- match = true
56
- (@request_subset[:oauth] || { }).each do |key, value|
57
- match &&= @request[:oauth].is_a?(Hash) && (@request[:oauth][key] == value)
58
- end
59
- match
60
- end
61
-
62
- def match_json?
63
- match_hash? :json
64
- end
65
-
66
- def match_hash?(hash_key)
67
- match = true
68
- (@request_subset[hash_key] || { }).each do |key, value|
69
- match &&= @request[hash_key].is_a?(Hash) && (@request[hash_key][key] == value)
70
- end
71
- match
72
- end
73
-
74
- end