holoserve 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -7,12 +7,13 @@ run faster and be independent from other API and network problems.
7
7
  == Concept
8
8
 
9
9
  HoloServe runs a rack application server, that matches any incoming request to a list of request profiles defined in
10
- the server layout. If a match is found, the defined static response is returned. The name of the matched
11
- request/response pair is saved in a request history. If no match is found, a 404 is returned and the
12
- request data is stored in the bucket for unhandeled requests. These informations can be used to extend the server layout
13
- with missing request handlers.
10
+ the server layout. If a match is found, the defined static response is returned. The response is defined by a default
11
+ and the current server situation.
12
+ The name of the matched request/response pair is saved in a request history. If no match is found, a 404 is returned and
13
+ the request data is stored in the bucket for unhandeled requests. These informations can be used to extend the server
14
+ layout with missing request handlers.
14
15
 
15
- Layouts, history and bucket can be accessed via control routes, which are described below.
16
+ The layout, situation, history and bucket can be accessed via control routes, which are described below.
16
17
 
17
18
  == Installation
18
19
 
@@ -26,43 +27,40 @@ To start up an empty Holoserve instance, type...
26
27
 
27
28
  holoserve
28
29
 
29
- To load and select a server layout during start up, you can provide some parameters.
30
+ To load a server layout and define a situation during start up, use these parameters.
30
31
 
31
- holoserve -f layouts.yml -l backend
32
+ holoserve -l layouts.yml -s backend_without_users
32
33
 
33
34
  == Control routes
34
35
 
35
36
  If you're using Ruby, you can control Holoserve via the
36
37
  {Holoserve Connector}[https://github.com/skrill/holoserve-connector] gem.
37
38
 
38
- === POST /_control/layouts
39
+ === POST /_control/layout.:format
39
40
 
40
- It should receive a parameter named <tt>file</tt> that contains an uploaded YML file with the server layouts. See
41
- {YML server layouts file format}[rdoc-label:YML-server-layouts-file-format] below.
41
+ It should receive a parameter named <tt>file</tt> that contains a file with the server layout. The format of the file
42
+ should fit the specified format. The format can be <tt>yaml</tt> or <tt>json</tt>. See
43
+ {Layout file format}[rdoc-label:Layout-file-format] below.
42
44
 
43
- === DELETE /_control/layouts
45
+ === GET /_control/layout.:format
44
46
 
45
- Removes all server layouts.
47
+ Returns the server layout in the requested format.
46
48
 
47
- === GET /_control/layouts/ids
49
+ === DELETE /_control/layout
48
50
 
49
- Returns the list of ids of all server layouts.
51
+ Removes the server layout.
50
52
 
51
- ==== Response example
52
-
53
- [ "one", "two" ]
53
+ === PUT /_control/situation/:name
54
54
 
55
- === PUT /_control/layouts/:id/current
55
+ Sets the current situation.
56
56
 
57
- Makes the layout with id <tt>:id</tt> the currently active one.
57
+ === GET /_control/situation
58
58
 
59
- === GET /_control/layouts/current
60
-
61
- Returns the id of the current layout.
59
+ Returns the name of the current situation.
62
60
 
63
61
  ==== Response example
64
62
 
65
- one
63
+ backend_without_users
66
64
 
67
65
  === GET /_control/bucket/requests
68
66
 
@@ -106,40 +104,42 @@ Returns a list of all names of pairs that has been triggered.
106
104
 
107
105
  Removes all entries from the history.
108
106
 
109
- == YML server layouts file format
110
-
111
- The server layouts file should have the following format.
112
-
113
- one:
114
- -
115
- name: "test_received"
116
- request:
117
- method: "POST"
118
- path: "/test"
119
- headers:
120
- HTTP_USER_AGENT: "Ruby"
121
- HTTP_AUTHORIZATION: "OAuth oauth_token=12345"
122
- body:
123
- "test=value"
124
- parameters:
125
- test: "value"
126
- oauth:
127
- oauth_token: "12345"
128
- response:
107
+ == Layout file format
108
+
109
+ The server layout file should have the following format.
110
+
111
+ -
112
+ name: "test_received"
113
+ request:
114
+ method: "POST"
115
+ path: "/test"
116
+ headers:
117
+ HTTP_USER_AGENT: "Ruby"
118
+ HTTP_AUTHORIZATION: "OAuth oauth_token=12345"
119
+ body:
120
+ "test=value"
121
+ parameters:
122
+ test: "value"
123
+ oauth:
124
+ oauth_token: "12345"
125
+ responses:
126
+ default:
129
127
  status: 200
128
+ one:
130
129
  body:
131
130
  "ok"
132
- -
133
- request:
134
- method: "GET"
135
- path: "/test"
136
- response:
131
+ -
132
+ request:
133
+ method: "GET"
134
+ path: "/test"
135
+ responses:
136
+ default:
137
137
  status: 200
138
138
  body:
139
139
  "ok too"
140
140
 
141
- This example would define a server layout named <tt>one</tt> that has two request/response pairs. The first pair would
142
- have the name <tt>test_received</tt> and would match a <tt>POST</tt> request to the path <tt>/test</tt>.
141
+ This example would define a server layout that has two request/response pairs. The first pair would have the name
142
+ <tt>test_received</tt> and would match a <tt>POST</tt> request to the path <tt>/test</tt>.
143
143
 
144
144
  Notice, that the given request attributes are the _minimal_ values that have to match the incomong one. An incoming
145
145
  request may has much more attributes (see bucket example above). If a request is matched, the corresponding pair name is
@@ -148,3 +148,6 @@ placed in the history.
148
148
  As the sections <tt>headers</tt> and <tt>body</tt> are raw values from the request, the sections <tt>parameters</tt> and
149
149
  <tt>oauth</tt> contain high-level values. The <tt>parameters</tt> hash is taken from the request body or the query and
150
150
  the hash containing the OAuth values is parsed from a fitting <tt>HTTP_AUTHORIZATION</tt> header.
151
+
152
+ The response is defined in the <tt>responses</tt> section of each pair. The server will response a merge of default
153
+ response and the response defined by the current situation.
data/Rakefile CHANGED
@@ -27,6 +27,13 @@ begin
27
27
  rescue LoadError
28
28
  end
29
29
 
30
+ begin
31
+ require 'rspec/core/rake_task'
32
+
33
+ RSpec::Core::RakeTask.new
34
+ rescue LoadError
35
+ end
36
+
30
37
  begin
31
38
  require 'rdoc'
32
39
  require 'rdoc/task'
data/bin/holoserve CHANGED
@@ -14,11 +14,11 @@ OptionParser.new do |parser|
14
14
  parser.on("-p", "--port PORT", Integer, "The port holoserve should listen to.") do |value|
15
15
  options[:port] = value
16
16
  end
17
- parser.on("-f", "--layouts-file FILE", "Load the specified layouts file on startup.") do |value|
18
- options[:layouts_filename] = value
17
+ parser.on("-l", "--layout-file FILE", "Load the specified layout file on startup.") do |value|
18
+ options[:layout_filename] = value
19
19
  end
20
- parser.on("-l", "--layout LAYOUT", "Selects the specified layout as the current one.") do |value|
21
- options[:layout] = value
20
+ parser.on("-s", "--situation SITUATION", "Sets the situation.") do |value|
21
+ options[:situation] = value
22
22
  end
23
23
  parser.on_tail("-h", "--help", "Shows the help message.") do
24
24
  puts parser
@@ -2,47 +2,44 @@ require 'yaml'
2
2
 
3
3
  class Holoserve::Configuration
4
4
 
5
+ class InvalidFormatError < StandardError; end
6
+
5
7
  attr_reader :logger
6
8
 
7
- attr_reader :layouts
8
- attr_reader :layout_id
9
+ attr_reader :layout
10
+ attr_reader :situation
9
11
 
10
12
  def initialize(logger)
11
13
  @logger = logger
12
14
  end
13
15
 
14
- def layouts=(hash)
15
- @layouts = Holoserve::Tool::Hash::KeySymbolizer.new(hash).hash
16
- end
17
-
18
- def layout_id=(value)
19
- @layout_id = value.to_sym
20
- logger.info "made '#{value}' the current layout"
16
+ def layout=(hash_or_array)
17
+ @layout = Holoserve::Tool::Hash::KeySymbolizer.new(hash_or_array).hash
21
18
  end
22
19
 
23
- def clear_layouts!
24
- self.layouts = nil
20
+ def situation=(value)
21
+ @situation = value.to_sym
22
+ logger.info "made '#{value}' the current situation"
25
23
  end
26
24
 
27
- def layout_ids
28
- self.layouts ? self.layouts.keys : [ ]
25
+ def clear_layout!
26
+ self.layout = nil
29
27
  end
30
28
 
31
- def layout_id?(id)
32
- self.layout_ids.include? id.to_sym
33
- end
34
-
35
- def layout
36
- self.layouts ? self.layouts[self.layout_id] : nil
29
+ def load_layout_from_yaml_file(file)
30
+ self.layout = YAML::load_file file
31
+ logger.info "loaded layouts from yaml file #{file.path}"
32
+ rescue Psych::SyntaxError => error
33
+ self.clear_layout!
34
+ raise InvalidFormatError, error.to_s
37
35
  end
38
36
 
39
- def load_layouts_from_yml_file(file)
40
- self.layouts = YAML::load_file file
41
- logger.info "loaded layouts from file #{file.path}"
42
- logger.info "available layout(s): #{self.layout_ids.join(", ")}"
43
- rescue Psych::SyntaxError => error
44
- self.clear_layouts!
45
- raise error
37
+ def load_layout_from_json_file(file)
38
+ self.layout = JSON.parse File.read(file)
39
+ logger.info "loaded layouts from json file #{file.path}"
40
+ rescue JSON::ParserError => error
41
+ self.clear_layout!
42
+ raise InvalidFormatError, error.to_s
46
43
  end
47
44
 
48
45
  end
@@ -4,35 +4,47 @@ require 'json'
4
4
 
5
5
  class Holoserve::Interface::Control < Sinatra::Base
6
6
 
7
- post "/_control/layouts" do
7
+ mime_type :yaml, "application/x-yaml"
8
+ mime_type :json, "application/json"
9
+
10
+ post "/_control/layout.:format" do |format|
8
11
  begin
9
- configuration.load_layouts_from_yml_file params["file"][:tempfile]
10
- respond_json_acknowledgement
11
- rescue Psych::SyntaxError => error
12
+ if format == "yaml"
13
+ configuration.load_layout_from_yaml_file params["file"][:tempfile]
14
+ respond_json_acknowledgement
15
+ elsif format == "json"
16
+ configuration.load_layout_from_json_file params["file"][:tempfile]
17
+ respond_json_acknowledgement
18
+ else
19
+ not_acceptable
20
+ end
21
+ rescue Holoserve::Configuration::InvalidFormatError => error
12
22
  error 400, error.inspect
13
23
  end
14
24
  end
15
25
 
16
- delete "/_control/layouts" do
17
- configuration.clear_layouts!
18
- respond_json_acknowledgement
26
+ get "/_control/layout.:format" do |format|
27
+ if format == "yaml"
28
+ respond_yaml configuration.layout
29
+ elsif format == "json"
30
+ respond_json configuration.layout
31
+ else
32
+ not_acceptable
33
+ end
19
34
  end
20
35
 
21
- get "/_control/layouts/ids" do
22
- respond_json configuration.layout_ids
36
+ delete "/_control/layout" do
37
+ configuration.clear_layout!
38
+ respond_json_acknowledgement
23
39
  end
24
40
 
25
- put "/_control/layouts/:id/current" do |id|
26
- if configuration.layout_id?(id)
27
- configuration.layout_id = id
28
- respond_json_acknowledgement
29
- else
30
- not_found
31
- end
41
+ put "/_control/situation/:name" do |situation|
42
+ configuration.situation = situation
43
+ respond_json_acknowledgement
32
44
  end
33
45
 
34
- get "/_control/layouts/current" do
35
- configuration.layout_id.to_s
46
+ get "/_control/situation" do
47
+ configuration.situation.to_s
36
48
  end
37
49
 
38
50
  get "/_control/bucket/requests" do
@@ -55,10 +67,19 @@ class Holoserve::Interface::Control < Sinatra::Base
55
67
  end
56
68
 
57
69
  def respond_json(object)
58
- content_type "application/json"
70
+ content_type :json
59
71
  JSON.dump object
60
72
  end
61
73
 
74
+ def respond_yaml(object)
75
+ content_type :yaml
76
+ object.to_yaml
77
+ end
78
+
79
+ def not_acceptable
80
+ [ 406, { }, [ "format not acceptable" ] ]
81
+ end
82
+
62
83
  def bucket
63
84
  Holoserve.instance.bucket
64
85
  end
@@ -10,7 +10,14 @@ class Holoserve::Interface::Fake
10
10
  history.pair_names << name
11
11
  logger.info "received handled request with name '#{name}'"
12
12
  end
13
- Holoserve::Response::Composer.new(pair[:response]).response_array
13
+ responses = pair[:responses]
14
+ response = Holoserve::Tool::Merger.new(responses[:default] || { }, responses[configuration.situation.to_sym] || { }).result
15
+ if response.empty?
16
+ logger.warn "received request #{pair[:name]} with undefined response"
17
+ not_found
18
+ else
19
+ Holoserve::Response::Composer.new(response).response_array
20
+ end
14
21
  else
15
22
  bucket.requests << request
16
23
  logger.error "received unhandled request\n" + request.pretty_inspect
@@ -45,7 +45,7 @@ class Holoserve::Request::Matcher
45
45
  def match_parameters?
46
46
  match = true
47
47
  (@request_subset[:parameters] || { }).each do |key, value|
48
- match &&= @request[:parameters][key] == value
48
+ match &&= @request[:parameters].is_a?(Hash) && (@request[:parameters][key] == value)
49
49
  end
50
50
  match
51
51
  end
@@ -53,7 +53,7 @@ class Holoserve::Request::Matcher
53
53
  def match_oauth?
54
54
  match = true
55
55
  (@request_subset[:oauth] || { }).each do |key, value|
56
- match &&= @request[:oauth][key] == value
56
+ match &&= @request[:oauth].is_a?(Hash) && (@request[:oauth][key] == value)
57
57
  end
58
58
  match
59
59
  end
@@ -7,8 +7,8 @@ class Holoserve::Runner
7
7
 
8
8
  def initialize(options = { })
9
9
  @port = options[:port] || 4250
10
- @layouts_filename = options[:layouts_filename]
11
- @layout = options[:layout]
10
+ @layout_filename = options[:layout_filename]
11
+ @situation = options[:situation]
12
12
 
13
13
  @rackup_options = Unicorn::Configurator::RACKUP
14
14
  @rackup_options[:port] = @port
@@ -20,8 +20,8 @@ class Holoserve::Runner
20
20
 
21
21
  def start
22
22
  @unicorn.start
23
- upload_layouts if @layouts_filename
24
- set_layout if @layout
23
+ upload_layout if @layout_filename
24
+ set_situation if @situation
25
25
  end
26
26
 
27
27
  def join
@@ -42,18 +42,22 @@ class Holoserve::Runner
42
42
 
43
43
  private
44
44
 
45
- def upload_layouts
45
+ def upload_layout
46
+ format = File.extname(@layout_filename).sub(/^\./, "")
47
+ raise ArgumentError, "file extension indicates wrong format '#{format}' (choose yaml or json)" unless [ "yaml", "json" ].include?(format)
46
48
  Holoserve::Tool::Uploader.new(
47
- @layouts_filename,
49
+ @layout_filename,
48
50
  :post,
49
- "http://localhost:#{port}/_control/layouts",
51
+ "http://localhost:#{port}/_control/layout.#{format}",
50
52
  :expected_status_code => 200
51
53
  ).upload
52
54
  nil
53
55
  end
54
56
 
55
- def set_layout
56
- Transport::JSON.request :put, "http://localhost:#{port}/_control/layouts/#{@layout}/current", :expected_status_code => 200
57
+ def set_situation
58
+ Transport::JSON.request :put,
59
+ "http://localhost:#{port}/_control/situation/#{@situation}",
60
+ :expected_status_code => 200
57
61
  nil
58
62
  end
59
63
 
@@ -1,12 +1,18 @@
1
1
 
2
2
  class Holoserve::Tool::Hash::KeySymbolizer
3
3
 
4
- def initialize(hash)
5
- @hash = hash
4
+ def initialize(hash_or_array)
5
+ @hash_or_array = hash_or_array
6
6
  end
7
7
 
8
8
  def hash
9
- symbolize_keys @hash
9
+ if @hash_or_array.is_a?(Hash)
10
+ symbolize_keys @hash_or_array
11
+ elsif @hash_or_array.is_a?(Array)
12
+ symbolize_keys_of_all @hash_or_array
13
+ else
14
+ @hash_or_array
15
+ end
10
16
  end
11
17
 
12
18
  private
@@ -27,6 +33,7 @@ class Holoserve::Tool::Hash::KeySymbolizer
27
33
  end
28
34
 
29
35
  def symbolize_keys_of_all(array)
36
+ return nil unless array.is_a?(Array)
30
37
  array.map do |item|
31
38
  if item.is_a?(Hash)
32
39
  symbolize_keys item
@@ -0,0 +1,51 @@
1
+
2
+ class Holoserve::Tool::Merger
3
+
4
+ def initialize(hash_or_array_one, hash_or_array_two)
5
+ @hash_or_array_one, @hash_or_array_two = hash_or_array_one, hash_or_array_two
6
+ end
7
+
8
+ def result
9
+ if @hash_or_array_one.is_a?(Hash) && @hash_or_array_two.is_a?(Hash)
10
+ merged_hash
11
+ elsif @hash_or_array_one.is_a?(Array) && @hash_or_array_two.is_a?(Array)
12
+ merged_array
13
+ else
14
+ @hash_or_array_two
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def merged_hash
21
+ result = { }
22
+ (@hash_or_array_one.keys + @hash_or_array_two.keys).uniq.each do |key|
23
+ value_one, value_two = @hash_or_array_one[key],@hash_or_array_two[key]
24
+ result[key] = if values_mergeable?(value_one, value_two)
25
+ self.class.new(value_one, value_two).result
26
+ else
27
+ value_two || value_one
28
+ end
29
+ end
30
+ result
31
+ end
32
+
33
+ def merged_array
34
+ result = Array.new [ @hash_or_array_one.length, @hash_or_array_two.length ].max
35
+ result.each_index do |index|
36
+ value_one, value_two = @hash_or_array_one[index], @hash_or_array_two[index]
37
+ result[index] = if values_mergeable?(value_one, value_two)
38
+ self.class.new(value_one, value_two).result
39
+ else
40
+ value_two || value_one
41
+ end
42
+ end
43
+ result
44
+ end
45
+
46
+ def values_mergeable?(value_one, value_two)
47
+ (value_one.is_a?(Hash) && value_two.is_a?(Hash)) ||
48
+ (value_one.is_a?(Array) && value_two.is_a?(Array))
49
+ end
50
+
51
+ end
@@ -2,6 +2,7 @@
2
2
  module Holoserve::Tool
3
3
 
4
4
  autoload :Hash, File.join(File.dirname(__FILE__), "tool", "hash")
5
+ autoload :Merger, File.join(File.dirname(__FILE__), "tool", "merger")
5
6
  autoload :Uploader, File.join(File.dirname(__FILE__), "tool", "uploader")
6
7
 
7
8
  end
data/spec/helper.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'rspec'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "holoserve"))
@@ -0,0 +1,61 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "helper"))
2
+
3
+ describe Holoserve::Tool::Merger do
4
+
5
+ subject { described_class.new argument_one, argument_two }
6
+
7
+ describe "result" do
8
+
9
+ context "of two hashes" do
10
+
11
+ let(:argument_one) { { :test => "value" } }
12
+ let(:argument_two) { { :test => "another value" } }
13
+
14
+ it "should merge nested values" do
15
+ subject.result.should == { :test => "another value" }
16
+ end
17
+
18
+ end
19
+
20
+ context "of two nested hashes" do
21
+
22
+ let(:argument_one) { { :test => { :regular =>"value", :nested => "value" } } }
23
+ let(:argument_two) { { :test => { :nested => "another value" } } }
24
+
25
+ it "should merge nested values" do
26
+ subject.result.should == { :test => { :regular => "value", :nested => "another value" } }
27
+ end
28
+
29
+ end
30
+
31
+ context "of two arrays" do
32
+
33
+ let(:argument_one) { [ { :test => "value" }, { :regular => "value", :another_test => "value" } ] }
34
+ let(:argument_two) { [ { :test => "another value" }, { :another_test => "another value" } ] }
35
+
36
+ it "should merge all nested hashes" do
37
+ subject.result.should == [
38
+ { :test => "another value" },
39
+ { :regular => "value", :another_test => "another value" }
40
+ ]
41
+ end
42
+
43
+ end
44
+
45
+ context "of two arrays with different lengths" do
46
+
47
+ let(:argument_one) { [ { :test => "value" } ] }
48
+ let(:argument_two) { [ { :test => "another value" }, { :another_test => "another value" } ] }
49
+
50
+ it "should merge all nested hashes" do
51
+ subject.result.should == [
52
+ { :test => "another value" },
53
+ { :another_test => "another value" }
54
+ ]
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: holoserve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-19 00:00:00.000000000 Z
12
+ date: 2012-01-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
16
- requirement: &70292202591440 !ruby/object:Gem::Requirement
16
+ requirement: &70247827043580 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70292202591440
24
+ version_requirements: *70247827043580
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: sinatra
27
- requirement: &70292202590900 !ruby/object:Gem::Requirement
27
+ requirement: &70247827042840 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70292202590900
35
+ version_requirements: *70247827042840
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: unicorn
38
- requirement: &70292202590400 !ruby/object:Gem::Requirement
38
+ requirement: &70247827042360 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70292202590400
46
+ version_requirements: *70247827042360
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: transport
49
- requirement: &70292202589740 !ruby/object:Gem::Requirement
49
+ requirement: &70247827041740 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70292202589740
57
+ version_requirements: *70247827041740
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: cucumber
60
- requirement: &70292202589060 !ruby/object:Gem::Requirement
60
+ requirement: &70247827041200 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70292202589060
68
+ version_requirements: *70247827041200
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
- requirement: &70292202588380 !ruby/object:Gem::Requirement
71
+ requirement: &70247827040660 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70292202588380
79
+ version_requirements: *70247827040660
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: transport
82
- requirement: &70292202587840 !ruby/object:Gem::Requirement
82
+ requirement: &70247827040180 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70292202587840
90
+ version_requirements: *70247827040180
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: oauth
93
- requirement: &70292202587300 !ruby/object:Gem::Requirement
93
+ requirement: &70247827039660 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,7 +98,7 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *70292202587300
101
+ version_requirements: *70247827039660
102
102
  description: This tool can be used to fake webservice APIs for testing proposals.
103
103
  email: philipp.bruell@skrill.com
104
104
  executables:
@@ -127,9 +127,12 @@ files:
127
127
  - lib/holoserve/runner.rb
128
128
  - lib/holoserve/tool/hash/key_symbolizer.rb
129
129
  - lib/holoserve/tool/hash.rb
130
+ - lib/holoserve/tool/merger.rb
130
131
  - lib/holoserve/tool/uploader.rb
131
132
  - lib/holoserve/tool.rb
132
133
  - lib/holoserve.rb
134
+ - spec/helper.rb
135
+ - spec/lib/holoserve/tool/merger_spec.rb
133
136
  homepage: http://github.com/skrill/holoserve
134
137
  licenses: []
135
138
  post_install_message:
@@ -144,7 +147,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
147
  version: '0'
145
148
  segments:
146
149
  - 0
147
- hash: 1129492518361910852
150
+ hash: 460979945885990413
148
151
  required_rubygems_version: !ruby/object:Gem::Requirement
149
152
  none: false
150
153
  requirements:
@@ -157,4 +160,5 @@ rubygems_version: 1.8.10
157
160
  signing_key:
158
161
  specification_version: 3
159
162
  summary: Tool to fake HTTP APIs.
160
- test_files: []
163
+ test_files:
164
+ - spec/lib/holoserve/tool/merger_spec.rb