holoserve 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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