couchmodel 0.1.0.beta3 → 0.1.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/README.rdoc +22 -7
  2. data/lib/core_extension/array.rb +18 -2
  3. data/lib/core_extension/string.rb +12 -2
  4. data/lib/couch_model/active_model.rb +31 -8
  5. data/lib/couch_model/base/accessor.rb +8 -5
  6. data/lib/couch_model/base/association.rb +36 -15
  7. data/lib/couch_model/base/finder.rb +1 -0
  8. data/lib/couch_model/base/setup.rb +6 -1
  9. data/lib/couch_model/base.rb +55 -17
  10. data/lib/couch_model/collection.rb +41 -21
  11. data/lib/couch_model/configuration.rb +42 -46
  12. data/lib/couch_model/database.rb +2 -2
  13. data/lib/couch_model/design.rb +18 -14
  14. data/lib/couch_model/row.rb +34 -0
  15. data/lib/couch_model/server.rb +2 -2
  16. data/lib/couch_model/transport.rb +55 -34
  17. data/lib/couch_model/view.rb +9 -5
  18. data/spec/fake_transport.yml +84 -29
  19. data/spec/fake_transport_helper.rb +5 -3
  20. data/spec/integration/basic_spec.rb +26 -12
  21. data/spec/integration/design/membership.design +1 -1
  22. data/spec/integration/design/user.design +12 -0
  23. data/spec/lib/core_extension/array_spec.rb +24 -0
  24. data/spec/lib/couch_model/active_model_spec.rb +51 -0
  25. data/spec/lib/couch_model/base_spec.rb +30 -1
  26. data/spec/lib/couch_model/collection_spec.rb +31 -7
  27. data/spec/lib/couch_model/configuration_spec.rb +2 -2
  28. data/spec/lib/couch_model/core/accessor_spec.rb +11 -3
  29. data/spec/lib/couch_model/core/association_spec.rb +26 -0
  30. data/spec/lib/couch_model/core/setup_spec.rb +8 -0
  31. data/spec/lib/couch_model/design_spec.rb +1 -5
  32. data/spec/lib/couch_model/row_spec.rb +71 -0
  33. data/spec/lib/couch_model/transport_spec.rb +18 -1
  34. data/spec/lib/couch_model/view_spec.rb +2 -2
  35. data/spec/spec_helper.rb +1 -1
  36. metadata +6 -3
@@ -1,70 +1,66 @@
1
1
 
2
2
  module CouchModel
3
3
 
4
- class Configuration
4
+ module Configuration
5
5
 
6
6
  CLASS_KEY = "model_class".freeze unless defined?(CLASS_KEY)
7
7
  CLASS_VIEW_NAME = "all".freeze unless defined?(CLASS_VIEW_NAME)
8
8
 
9
- class << self
9
+ @fake_transport = false
10
+ @databases = [ ]
11
+ @designs = [ ]
10
12
 
11
- @@fake_transport = false
12
- @@databases = [ ]
13
- @@designs = [ ]
14
-
15
- def fake_transport=(value)
16
- @@fake_transport = value
17
- end
13
+ def self.fake_transport=(value)
14
+ @fake_transport = value
15
+ end
18
16
 
19
- def fake_transport
20
- @@fake_transport
21
- end
17
+ def self.fake_transport
18
+ @fake_transport
19
+ end
22
20
 
23
- def design_directory=(value)
24
- @@design_directory = value
25
- end
21
+ def self.design_directory=(value)
22
+ @design_directory = value
23
+ end
26
24
 
27
- def design_directory
28
- class_variable_defined?(:@@design_directory) ? @@design_directory : ""
29
- end
25
+ def self.design_directory
26
+ instance_variable_defined?(:@design_directory) ? @design_directory : ""
27
+ end
30
28
 
31
- def register_database(database)
32
- result = @@databases.select{ |element| element == database }.first
33
- unless result
34
- @@databases << database
35
- result = database
36
- end
37
- result
29
+ def self.register_database(database)
30
+ result = @databases.select{ |element| element == database }.first
31
+ unless result
32
+ @databases << database
33
+ result = database
38
34
  end
35
+ result
36
+ end
39
37
 
40
- def databases
41
- @@databases
42
- end
38
+ def self.databases
39
+ @databases
40
+ end
43
41
 
44
- def setup_databases(options = { })
45
- delete_if_exists = options[:delete_if_exists] || false
46
- create_if_missing = options[:create_if_missing] || false
42
+ def self.setup_databases(options = { })
43
+ delete_if_exists = options[:delete_if_exists] || false
44
+ create_if_missing = options[:create_if_missing] || false
47
45
 
48
- @@databases.each do |database|
49
- database.delete_if_exists! if delete_if_exists
50
- database.create_if_missing! if create_if_missing
51
- end
46
+ @databases.each do |database|
47
+ database.delete_if_exists! if delete_if_exists
48
+ database.create_if_missing! if create_if_missing
52
49
  end
50
+ end
53
51
 
54
- def register_design(design)
55
- @@designs << design
56
- end
52
+ def self.register_design(design)
53
+ @designs << design
54
+ end
57
55
 
58
- def designs
59
- @@designs
60
- end
56
+ def self.designs
57
+ @designs
58
+ end
61
59
 
62
- def setup_designs
63
- @@designs.each do |design|
64
- design.push
65
- end
60
+ def self.setup_designs
61
+ @designs.each do |design|
62
+ design.push
66
63
  end
67
-
68
64
  end
69
65
 
70
66
  end
@@ -4,10 +4,10 @@ require File.join(File.dirname(__FILE__), "collection")
4
4
 
5
5
  module CouchModel
6
6
 
7
+ # The Database class provides methods create, delete and retrieve informations
8
+ # of a CouchDB database.
7
9
  class Database
8
10
 
9
- class Error < StandardError; end
10
-
11
11
  attr_reader :server
12
12
  attr_reader :name
13
13
 
@@ -6,6 +6,7 @@ require 'yaml'
6
6
 
7
7
  module CouchModel
8
8
 
9
+ # The Design class acts as a wrapper for CouchDB design documents.
9
10
  class Design
10
11
 
11
12
  attr_reader :database
@@ -22,9 +23,9 @@ module CouchModel
22
23
  @views = [ ]
23
24
 
24
25
  load_file
25
- self.id = attributes[:id] if attributes[:id]
26
- self.language = attributes[:language] if attributes[:language]
27
- self.views = attributes[:views] if attributes[:views]
26
+ self.id = attributes[:id] if attributes.has_key?(:id)
27
+ self.language = attributes[:language] if attributes.has_key?(:language)
28
+ self.views = attributes[:views] if attributes.has_key?(:views)
28
29
  end
29
30
 
30
31
  def filename
@@ -32,34 +33,33 @@ module CouchModel
32
33
  end
33
34
 
34
35
  def load_file
35
- return false unless File.exists?(filename)
36
- attributes = YAML::load_file filename
37
- self.id = attributes[:id]
38
- self.language = attributes[:language]
39
- self.views = attributes[:views]
36
+ self.id, self.language, self.views = YAML::load_file(self.filename).values_at(:id, :language, :views)
40
37
  true
38
+ rescue Errno::ENOENT
39
+ false
41
40
  end
42
41
 
43
42
  def views=(view_hash)
44
43
  @views = [ ]
45
44
  view_hash.each do |view_name, view|
46
- @views << View.new(self, view.merge(:name => view_name)) if view.is_a?(Hash)
45
+ @views << View.new(self, view.merge(:name => view_name.to_s)) if view.is_a?(Hash)
47
46
  end if view_hash.is_a?(Hash)
48
47
  end
49
48
 
50
49
  def generate_view(name, options = { })
51
- view = View.new self, options.merge(:name => name)
50
+ view = View.new self, options.merge(:name => name.to_s)
52
51
  @views.insert 0, view
53
52
  view
54
53
  end
55
54
 
56
55
  def to_hash
56
+ rev = self.rev
57
57
  hash = {
58
58
  "_id" => "_design/#{self.id}",
59
59
  "language" => self.language,
60
60
  "views" => { }
61
61
  }
62
- hash.merge! "_rev" => self.rev if self.rev
62
+ hash.merge! "_rev" => rev if rev
63
63
  @views.each { |view| hash["views"].merge! view.to_hash }
64
64
  hash
65
65
  end
@@ -72,10 +72,10 @@ module CouchModel
72
72
  end
73
73
 
74
74
  def push
75
- response = Transport.request :get, self.url
76
- self.rev = response["_rev"] if response["_rev"]
75
+ url = self.url
76
+ evaluate Transport.request(:get, url)
77
77
 
78
- Transport.request :put, self.url, :json => self.to_hash, :expected_status_code => 201
78
+ Transport.request :put, url, :body => self.to_hash, :expected_status_code => 201
79
79
  true
80
80
  end
81
81
 
@@ -87,6 +87,10 @@ module CouchModel
87
87
 
88
88
  attr_writer :rev
89
89
 
90
+ def evaluate(response)
91
+ self.rev = response["_rev"] if response.has_key?("_rev")
92
+ end
93
+
90
94
  end
91
95
 
92
96
  end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), "configuration")
2
+
3
+ module CouchModel
4
+
5
+ # The Row class acts as a wrapper for a CouchDB view result row.
6
+ class Row
7
+
8
+ attr_reader :id
9
+ attr_reader :key
10
+ attr_reader :value
11
+ attr_reader :document
12
+
13
+ def initialize(attributes = { })
14
+ @id, @key, @value, @document = attributes.values_at "id", "key", "value", "doc"
15
+ end
16
+
17
+ def model
18
+ return nil unless @document && @document.has_key?(Configuration::CLASS_KEY)
19
+
20
+ model_class_name = document[Configuration::CLASS_KEY]
21
+ raise StandardError, "no class defined with name [#{model_class_name}]" unless Object.const_defined?(model_class_name)
22
+ instanciate_model model_class_name
23
+ end
24
+
25
+ def instanciate_model(model_class_name)
26
+ model_class = Object.const_get model_class_name
27
+ model = model_class.new
28
+ model.instance_variable_set :@attributes, @document
29
+ model
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -2,10 +2,10 @@ require File.join(File.dirname(__FILE__), "transport")
2
2
 
3
3
  module CouchModel
4
4
 
5
+ # The Server class provides methods to retrieve informations and statistics
6
+ # of a CouchDB server.
5
7
  class Server
6
8
 
7
- class Error < StandardError; end
8
-
9
9
  attr_reader :host
10
10
  attr_reader :port
11
11
 
@@ -6,8 +6,8 @@ module CouchModel
6
6
 
7
7
  module Transport
8
8
 
9
- class Error < StandardError; end
10
-
9
+ # The UnexpectedStatusCodeError is raised if the :expected_status_code option is given to
10
+ # the :request method and the responded status code is different from the expected one.
11
11
  class UnexpectedStatusCodeError < StandardError
12
12
 
13
13
  attr_reader :status_code
@@ -22,47 +22,68 @@ module CouchModel
22
22
 
23
23
  end
24
24
 
25
- class << self
25
+ def self.request(http_method, url, options = { })
26
+ expected_status_code = options[:expected_status_code]
26
27
 
27
- def request(http_method, url, options = { })
28
- expected_status_code = options[:expected_status_code]
29
-
30
- uri = URI.parse @base_url ? @base_url + url : url
28
+ uri = URI.parse url
29
+ response = perform request_object(http_method, uri, options), uri
31
30
 
32
- request_class = request_class http_method
33
- request = request_object request_class, uri, options
31
+ check_status_code response, expected_status_code if expected_status_code
32
+ parse response
33
+ end
34
34
 
35
- response = Net::HTTP.start(uri.host, uri.port) { |connection| connection.request request }
35
+ def self.request_object(http_method, uri, options)
36
+ raise NotImplementedError, "the request method #{http_method} is not implemented" unless
37
+ self.respond_to?(:"#{http_method}_request_object")
36
38
 
37
- raise UnexpectedStatusCodeError, response.code.to_i if expected_status_code && expected_status_code.to_s != response.code
38
- JSON.parse response.body
39
- end
39
+ request_object = send :"#{http_method}_request_object", uri.path, (options[:parameters] || { }), (options[:headers] || { })
40
+ request_object.body = options[:body].to_json if options.has_key?(:body)
41
+ request_object
42
+ end
40
43
 
41
- private
44
+ def self.get_request_object(path, parameters, headers)
45
+ Net::HTTP::Get.new path + serialize_parameters(parameters),
46
+ headers
47
+ end
42
48
 
43
- def request_class(http_method)
44
- Net::HTTP.const_get http_method.capitalize
45
- end
49
+ def self.post_request_object(path, parameters, headers)
50
+ Net::HTTP::Post.new path + serialize_parameters(parameters),
51
+ { "Content-Type" => "application/json" }.merge(headers)
52
+ end
53
+
54
+ def self.put_request_object(path, parameters, headers)
55
+ Net::HTTP::Put.new path + serialize_parameters(parameters),
56
+ { "Content-Type" => "application/json" }.merge(headers)
57
+ end
46
58
 
47
- def request_object(request_class, uri, options)
48
- parameters = options[:parameters] || { }
49
- json = options[:json]
50
-
51
- case request_class.to_s
52
- when "Net::HTTP::Get", "Net::HTTP::Delete"
53
- request_class.new uri.path +
54
- (parameters.empty? ? "" : "?" + parameters.collect{ |key, value| "#{key}=#{URI.escape(value.to_s)}" }.reverse.join("&"))
55
- when "Net::HTTP::Post", "Net::HTTP::Put"
56
- request = request_class.new uri.path, { "Content-Type" => "application/json" }
57
- request.body = JSON.dump(json) if json
58
- request
59
- else
60
- request_class.new uri.path
61
- end
59
+ def self.delete_request_object(path, parameters, headers)
60
+ Net::HTTP::Delete.new path + serialize_parameters(parameters),
61
+ headers
62
+ end
63
+
64
+ def self.serialize_parameters(parameters)
65
+ return "" if parameters.empty?
66
+ "?" + parameters.collect do |key, value|
67
+ value = value.respond_to?(:to_json) ? value.to_json : value.to_s
68
+ "#{key}=#{URI.escape(value)}"
69
+ end.reverse.join("&")
70
+ end
71
+
72
+ def self.perform(request, uri)
73
+ Net::HTTP.start(uri.host, uri.port) do |connection|
74
+ connection.request request
62
75
  end
76
+ end
77
+
78
+ def self.check_status_code(response, expected_status_code)
79
+ response_code = response.code
80
+ raise UnexpectedStatusCodeError, response_code.to_i if expected_status_code.to_s != response_code
81
+ end
63
82
 
83
+ def self.parse(response)
84
+ JSON.parse response.body
64
85
  end
65
-
86
+
66
87
  end
67
88
 
68
- end
89
+ end
@@ -3,6 +3,8 @@ require File.join(File.dirname(__FILE__), "collection")
3
3
 
4
4
  module CouchModel
5
5
 
6
+ # The View class acts as a wrapper for the views that are in the CouchDB design document. It also
7
+ # provides methods to generate simple view javascript functions.
6
8
  class View
7
9
 
8
10
  attr_reader :design
@@ -15,8 +17,8 @@ module CouchModel
15
17
  @name = attributes[:name]
16
18
 
17
19
  generate_functions attributes
18
- @map = attributes[:map] if attributes[:map]
19
- @reduce = attributes[:reduce] if attributes[:reduce]
20
+ @map = attributes[:map] if attributes.has_key?(:map)
21
+ @reduce = attributes[:reduce] if attributes.has_key?(:reduce)
20
22
  end
21
23
 
22
24
  def collection(options = { })
@@ -33,18 +35,20 @@ module CouchModel
33
35
 
34
36
  def generate_functions(options = { })
35
37
  keys = [ (options[:keys] || "_id") ].flatten
38
+ @map = self.class.generate_map_function @design.model_class, keys
39
+ @reduce = nil
40
+ end
36
41
 
42
+ def self.generate_map_function(model_class, keys)
37
43
  emit_values = keys.map{ |key| "document['#{key}']" }
38
44
  check_values = emit_values.select{ |value| value != "document['_id']" }
39
45
 
40
- @map =
41
46
  """function(document) {
42
- if (document['#{Configuration::CLASS_KEY}'] == '#{@design.model_class.to_s}'#{check_values.empty? ? "" : " && " + check_values.join(" && ")}) {
47
+ if (document['#{Configuration::CLASS_KEY}'] == '#{model_class.to_s}'#{check_values.empty? ? "" : " && " + check_values.join(" && ")}) {
43
48
  emit(#{emit_values.size == 1 ? emit_values.first : "[ " + emit_values.join(", ") + " ]"}, null);
44
49
  }
45
50
  }
46
51
  """
47
- @reduce = nil
48
52
  end
49
53
 
50
54
  end
@@ -3,7 +3,7 @@
3
3
  :url: "http://localhost:5984/"
4
4
  :response:
5
5
  :code: "200"
6
- :json:
6
+ :body:
7
7
  "couchdb": "Welcome"
8
8
  "version": "0.10.0"
9
9
  -
@@ -11,7 +11,7 @@
11
11
  :url: "http://localhost:5984/_stats"
12
12
  :response:
13
13
  :code: "200"
14
- :json:
14
+ :body:
15
15
  "httpd_status_codes": "..."
16
16
  "httpd_request_methods": "..."
17
17
  -
@@ -19,7 +19,7 @@
19
19
  :url: "http://localhost:5984/_all_dbs"
20
20
  :response:
21
21
  :code: "200"
22
- :json: [ "development", "test" ]
22
+ :body: [ "development", "test" ]
23
23
  -
24
24
  :http_method: "get"
25
25
  :url: "http://localhost:5984/_uuids"
@@ -27,14 +27,14 @@
27
27
  :count: 3
28
28
  :response:
29
29
  :code: "200"
30
- :json:
30
+ :body:
31
31
  "uuids": [ "uuid_1", "uuid_2", "uuid_3" ]
32
32
  -
33
33
  :http_method: "get"
34
34
  :url: "http://localhost:5984/test"
35
35
  :response:
36
36
  :code: "200"
37
- :json:
37
+ :body:
38
38
  "db_name": "test"
39
39
  "doc_count": "0"
40
40
  -
@@ -42,13 +42,13 @@
42
42
  :url: "http://localhost:5984/new_database"
43
43
  :response:
44
44
  :code: "404"
45
- :json:
45
+ :body:
46
46
  -
47
47
  :http_method: "get"
48
48
  :url: "http://localhost:5984/test/test_model_1"
49
49
  :response:
50
50
  :code: "200"
51
- :json:
51
+ :body:
52
52
  "_id": "test_model_1"
53
53
  "_rev": "0"
54
54
  "model_class": "BaseTestModel"
@@ -59,7 +59,7 @@
59
59
  :url: "http://localhost:5984/test/test_model_2"
60
60
  :response:
61
61
  :code: "200"
62
- :json:
62
+ :body:
63
63
  "_id": "test_model_2"
64
64
  "_rev": "0"
65
65
  "model_class": "BaseTestModel"
@@ -74,7 +74,7 @@
74
74
  :url: "http://localhost:5984/test"
75
75
  :response:
76
76
  :code: "201"
77
- :json:
77
+ :body:
78
78
  "ok": true
79
79
  "id": "test_model_2"
80
80
  "rev": "0"
@@ -83,18 +83,18 @@
83
83
  :url: "http://localhost:5984/test/test_model_1"
84
84
  :response:
85
85
  :code: "201"
86
- :json:
86
+ :body:
87
87
  "ok": true
88
88
  "id": "test_model_1"
89
89
  "rev": "1"
90
90
  -
91
91
  :http_method: "delete"
92
92
  :url: "http://localhost:5984/test/test_model_1"
93
- :parameters:
94
- "rev": "0"
93
+ :headers:
94
+ "If-Match": "0"
95
95
  :response:
96
96
  :code: "200"
97
- :json:
97
+ :body:
98
98
  "ok": true
99
99
  "id": "test_model_1"
100
100
  "rev": "1"
@@ -103,7 +103,7 @@
103
103
  :url: "http://localhost:5984/test/_design/test_design"
104
104
  :response:
105
105
  :code: "200"
106
- :json:
106
+ :body:
107
107
  "_id": "_design/test_design"
108
108
  "_rev": "0"
109
109
  "language": "javascript"
@@ -116,7 +116,7 @@
116
116
  :url: "http://localhost:5984/test/_design/test_design"
117
117
  :response:
118
118
  :code: "201"
119
- :json:
119
+ :body:
120
120
  "ok": true
121
121
  "id": "_design/test_design"
122
122
  "rev": "1"
@@ -124,11 +124,11 @@
124
124
  :http_method: "get"
125
125
  :url: "http://localhost:5984/test/_all_docs"
126
126
  :parameters:
127
- "include_docs": "true"
128
- "limit": "1"
127
+ :include_docs: true
128
+ :limit: 1
129
129
  :response:
130
130
  :code: "200"
131
- :json:
131
+ :body:
132
132
  "total_rows": 1
133
133
  "offset": 0
134
134
  "rows":
@@ -146,24 +146,80 @@
146
146
  :http_method: "get"
147
147
  :url: "http://localhost:5984/test/_all_docs"
148
148
  :parameters:
149
- "include_docs": "true"
150
- "limit": "0"
149
+ :limit: 1
151
150
  :response:
152
151
  :code: "200"
153
- :json:
152
+ :body:
154
153
  "total_rows": 1
155
154
  "offset": 0
155
+ "rows":
156
+ -
157
+ "id": "test_model_1"
158
+ "key": "test_model_1"
159
+ "value":
160
+ "rev": "0"
161
+ "doc":
162
+ "_id": "test_model_1"
163
+ "_rev": "0"
164
+ "model_class": "CollectionTestModel"
165
+ "name": "phil"
166
+ -
167
+ :http_method: "get"
168
+ :url: "http://localhost:5984/test/_all_docs"
169
+ :parameters:
170
+ :include_docs: true
171
+ :limit: 0
172
+ :response:
173
+ :code: "200"
174
+ :body:
175
+ "total_rows": 1
176
+ "offset": 0
177
+ "rows": [ ]
178
+ -
179
+ :http_method: "get"
180
+ :url: "http://localhost:5984/test/_design/setup_test_model/_view/all"
181
+ :parameters:
182
+ :include_docs: true
183
+ :limit: 0
184
+ :response:
185
+ :code: "200"
186
+ :body:
187
+ "total_rows": 14
188
+ "offset": 0
156
189
  "rows": [ ]
157
190
  -
158
191
  :http_method: "get"
159
192
  :url: "http://localhost:5984/test/_design/association_test_model_one/_view/by_related_id_and_name"
160
193
  :parameters:
161
- "include_docs": "true"
162
- "startkey": "[\"test_model_2\",null]"
163
- "endkey": "[\"test_model_2\",{}]"
194
+ :include_docs: true
195
+ :startkey: [ "test_model_2", null ]
196
+ :endkey: [ "test_model_2", { } ]
197
+ :response:
198
+ :code: "200"
199
+ :body:
200
+ "total_rows": 1
201
+ "offset": 0
202
+ "rows":
203
+ -
204
+ "id": "test_model_1"
205
+ "key": "test_model_2"
206
+ "value":
207
+ "rev": "0"
208
+ "doc":
209
+ "_id": "test_model_1"
210
+ "_rev": "0"
211
+ "model_class": "AssociationTestModelOne"
212
+ "name": "phil"
213
+ -
214
+ :http_method: "get"
215
+ :url: "http://localhost:5984/test/_design/association_test_model_one/_view/by_related_id_and_name"
216
+ :parameters:
217
+ :include_docs: true
218
+ :startkey: [ "test_model_2", "phil" ]
219
+ :endkey: [ "test_model_2", "phil" ]
164
220
  :response:
165
221
  :code: "200"
166
- :json:
222
+ :body:
167
223
  "total_rows": 1
168
224
  "offset": 0
169
225
  "rows":
@@ -181,12 +237,11 @@
181
237
  :http_method: "get"
182
238
  :url: "http://localhost:5984/test/_design/association_test_model_one/_view/by_related_id_and_name"
183
239
  :parameters:
184
- "include_docs": "true"
185
- "startkey": "[\"test_model_2\",\"phil\"]"
186
- "endkey": "[\"test_model_2\",\"phil\"]"
240
+ :startkey: [ "test_model_2", "phil" ]
241
+ :endkey: [ "test_model_2", "phil" ]
187
242
  :response:
188
243
  :code: "200"
189
- :json:
244
+ :body:
190
245
  "total_rows": 1
191
246
  "offset": 0
192
247
  "rows":
@@ -9,16 +9,18 @@ module CouchModel
9
9
  self.stub!(:request).and_return do |http_method, url, options|
10
10
  options ||= { }
11
11
  parameters = options[:parameters]
12
+ headers = options[:headers]
12
13
  expected_status_code = options[:expected_status_code]
13
14
 
14
15
  request = @@fake.detect do |hash|
15
16
  hash[:http_method].to_s == http_method.to_s &&
16
17
  hash[:url].to_s == url.to_s &&
17
- hash[:parameters] == parameters
18
+ hash[:parameters] == parameters &&
19
+ hash[:headers] == headers
18
20
  end
19
- raise StandardError, "no fake request found for [#{http_method} #{url} #{parameters.inspect}]" unless request
21
+ raise StandardError, "no fake request found for [#{http_method} #{url} #{parameters.inspect} #{headers.inspect}]" unless request
20
22
  raise UnexpectedStatusCodeError, request[:response][:code].to_i if expected_status_code && expected_status_code.to_s != request[:response][:code]
21
- request[:response][:json].dup
23
+ request[:response][:body].dup
22
24
  end
23
25
  end
24
26