couchrest 1.2.1 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,165 +1,64 @@
1
1
  module CouchRest
2
2
 
3
- # CouchRest RestAPI
4
- #
5
- # The basic low-level interface for all REST requests to the database. Everything must pass
6
- # through here before it is sent to the server.
7
- #
8
- # Six types of REST requests are supported: get, put, post, delete, copy and head.
9
- #
10
- # Requests that do not have a payload, get, delete and copy, accept the URI and options parameters,
11
- # where as put and post both expect a document as the second parameter.
12
- #
13
- # The API will try to intelegently split the options between the JSON parser and RestClient API.
14
- #
15
- # The following options will be recognised as header options and automatically added
16
- # to the header hash:
17
- #
18
- # * :content_type, type of content to be sent, especially useful when sending files as this will set the file type. The default is :json.
19
- # * :accept, the content type to accept in the response. This should pretty much always be :json.
20
- #
21
- # The following request options are supported:
22
3
  #
23
- # * :method override the requested method (should not be used!).
24
- # * :url override the URL used in the request.
25
- # * :payload override the document or data sent in the message body (only PUT or POST).
26
- # * :headers any additional headers (overrides :content_type and :accept)
27
- # * :timeout and :open_timeout the time in miliseconds to wait for the request, see RestClient API for more details.
28
- # * :verify_ssl, :ssl_client_cert, :ssl_client_key, and :ssl_ca_file, SSL handling methods.
29
- #
30
- #
31
- # Any other remaining options will be sent to the MultiJSON backend except for the :raw option.
32
- #
33
- # When :raw is true in PUT and POST requests, no attempt will be made to convert the document payload to JSON. This is
34
- # not normally necessary as IO and Tempfile objects will not be parsed anyway. The result of the request will
35
- # *always* be parsed.
4
+ # CouchRest RestAPI
36
5
  #
37
- # For all other requests, mainly GET, the :raw option will make no attempt to parse the result. This
38
- # is useful for receiving files from the database.
6
+ # Backwards compatible wrapper for instantiating a connection and performing
7
+ # a request on demand. Useful for quick requests, but not recommended for general
8
+ # usage due the extra overhead of establishing a connection.
39
9
  #
40
10
 
41
11
  module RestAPI
42
12
 
43
13
  # Send a GET request.
44
- def get(uri, options = {})
45
- execute(uri, :get, options)
14
+ def get(url, options = {})
15
+ connection(url, options) do |uri, conn|
16
+ conn.get(uri.request_uri, options)
17
+ end
46
18
  end
47
19
 
48
20
  # Send a PUT request.
49
- def put(uri, doc = nil, options = {})
50
- execute(uri, :put, options, doc)
21
+ def put(url, doc = nil, options = {})
22
+ connection(url, options) do |uri, conn|
23
+ conn.put(uri.request_uri, doc, options)
24
+ end
51
25
  end
52
26
 
53
27
  # Send a POST request.
54
- def post(uri, doc = nil, options = {})
55
- execute(uri, :post, options, doc)
28
+ def post(url, doc = nil, options = {})
29
+ connection(url, options) do |uri, conn|
30
+ conn.post(uri.request_uri, doc, options)
31
+ end
56
32
  end
57
33
 
58
34
  # Send a DELETE request.
59
- def delete(uri, options = {})
60
- execute(uri, :delete, options)
61
- end
62
-
63
- # Send a COPY request to the URI provided.
64
- def copy(uri, destination, options = {})
65
- opts = options.nil? ? {} : options.dup
66
- # also copy headers!
67
- opts[:headers] = options[:headers].nil? ? {} : options[:headers].dup
68
- opts[:headers]['Destination'] = destination
69
- execute(uri, :copy, opts)
70
- end
71
-
72
- # Send a HEAD request.
73
- def head(uri, options = {})
74
- execute(uri, :head, options)
75
- end
76
-
77
- # The default RestClient headers used in each request.
78
- def default_headers
79
- {
80
- :content_type => :json,
81
- :accept => :json
82
- }
83
- end
84
-
85
- private
86
-
87
- # Perform the RestClient request by removing the parse specific options, ensuring the
88
- # payload is prepared, and sending the request ready to parse the response.
89
- def execute(url, method, options = {}, payload = nil)
90
- request, parser = prepare_and_split_options(url, method, options)
91
- # Prepare the payload if it is provided
92
- request[:payload] = payload_from_doc(payload, parser) if payload
93
- begin
94
- parse_response(RestClient::Request.execute(request), parser)
95
- rescue Exception => e
96
- if $DEBUG
97
- raise "Error while sending a #{method.to_s.upcase} request #{uri}\noptions: #{opts.inspect}\n#{e}"
98
- else
99
- raise e
100
- end
35
+ def delete(url, options = {})
36
+ connection(url, options) do |uri, conn|
37
+ conn.delete(uri.request_uri, options)
101
38
  end
102
39
  end
103
40
 
104
- # Prepare two hashes, one for the request to the REST backend and a second
105
- # for the JSON parser.
106
- #
107
- # Returns an array of request and parser options.
108
- #
109
- def prepare_and_split_options(url, method, options)
110
- request = {
111
- :url => url,
112
- :method => method,
113
- :headers => default_headers.merge(options[:headers] || {})
114
- }
115
- parser = {
116
- :raw => false,
117
- :head => (method == :head)
118
- }
119
- # Split the options
120
- (options || {}).each do |k,v|
121
- k = k.to_sym
122
- next if k == :headers # already dealt with
123
- if restclient_option_keys.include?(k)
124
- request[k] = v
125
- elsif header_option_keys.include?(k)
126
- request[:headers][k] = v
127
- else
128
- parser[k] = v
129
- end
41
+ # Send a COPY request to the URI provided.
42
+ def copy(url, destination, options = {})
43
+ connection(url, options) do |uri, conn|
44
+ conn.copy(uri.request_uri, destination, options)
130
45
  end
131
- [request, parser]
132
46
  end
133
47
 
134
- # Check if the provided doc is nil or special IO device or temp file. If not,
135
- # encode it into a string.
136
- #
137
- # The options supported are:
138
- # * :raw TrueClass, if true the payload will not be altered.
139
- #
140
- def payload_from_doc(doc, opts = {})
141
- if (opts.delete(:raw) || doc.nil? || doc.is_a?(IO) || doc.is_a?(Tempfile))
142
- doc
143
- else
144
- MultiJson.encode(doc.respond_to?(:as_couch_json) ? doc.as_couch_json : doc)
48
+ # Send a HEAD request.
49
+ def head(url, options = {})
50
+ connection(url, options) do |uri, conn|
51
+ conn.head(uri.request_uri, options)
145
52
  end
146
53
  end
147
54
 
148
- # Parse the response provided.
149
- def parse_response(result, opts = {})
150
- opts = opts.update(:create_additions => true) if decode_json_objects
151
- (opts.delete(:raw) || opts.delete(:head)) ? result : MultiJson.decode(result, opts.update(:max_nesting => false))
152
- end
153
-
154
- # An array of all the options that should be passed through to restclient.
155
- # Anything not in this list will be passed to the JSON parser.
156
- def restclient_option_keys
157
- [:method, :url, :payload, :headers, :timeout, :open_timeout,
158
- :verify_ssl, :ssl_client_cert, :ssl_client_key, :ssl_ca_file]
159
- end
55
+ protected
160
56
 
161
- def header_option_keys
162
- [ :content_type, :accept ]
57
+ def connection(url, options)
58
+ uri = URI url
59
+ conn = CouchRest::Connection.new(uri, options)
60
+ res = yield uri, conn
61
+ res
163
62
  end
164
63
 
165
64
  end
@@ -1,53 +1,31 @@
1
1
  module CouchRest
2
2
  class Server
3
3
 
4
- attr_accessor :uri, :uuid_batch_count, :available_databases
4
+ # URI object of the link to the server we're using.
5
+ attr_reader :uri
6
+
7
+ # Number of UUIDs to fetch from the server when preparing to save new
8
+ # documents. Set to 1000 by default.
9
+ attr_reader :uuid_batch_count
10
+
11
+ # Accessor for the current internal array of UUIDs ready to be used when
12
+ # saving new documents. See also #next_uuid.
13
+ attr_reader :uuids
5
14
 
6
15
  def initialize(server = 'http://127.0.0.1:5984', uuid_batch_count = 1000)
7
- @uri = server
16
+ @uri = prepare_uri(server).freeze
8
17
  @uuid_batch_count = uuid_batch_count
9
18
  end
10
19
 
11
- # Lists all "available" databases.
12
- # An available database, is a database that was specified
13
- # as avaiable by your code.
14
- # It allows to define common databases to use and reuse in your code
15
- def available_databases
16
- @available_databases ||= {}
17
- end
18
-
19
- # Adds a new available database and create it unless it already exists
20
- #
21
- # Example:
22
- #
23
- # @couch = CouchRest::Server.new
24
- # @couch.define_available_database(:default, "tech-blog")
25
- #
26
- def define_available_database(reference, db_name, create_unless_exists = true)
27
- available_databases[reference.to_sym] = create_unless_exists ? database!(db_name) : database(db_name)
28
- end
29
-
30
- # Checks that a database is set as available
31
- #
32
- # Example:
33
- #
34
- # @couch.available_database?(:default)
35
- #
36
- def available_database?(ref_or_name)
37
- ref_or_name.is_a?(Symbol) ? available_databases.keys.include?(ref_or_name) : available_databases.values.map{|db| db.name}.include?(ref_or_name)
38
- end
39
-
40
- def default_database=(name, create_unless_exists = true)
41
- define_available_database(:default, name, create_unless_exists = true)
42
- end
43
-
44
- def default_database
45
- available_databases[:default]
20
+ # Lazy load the connection for the current thread
21
+ def connection
22
+ conns = (Thread.current['couchrest.connections'] ||= {})
23
+ conns[uri.to_s] ||= Connection.new(uri)
46
24
  end
47
25
 
48
26
  # Lists all databases on the server
49
27
  def databases
50
- CouchRest.get "#{@uri}/_all_dbs"
28
+ connection.get "_all_dbs"
51
29
  end
52
30
 
53
31
  # Returns a CouchRest::Database for the given name
@@ -57,35 +35,44 @@ module CouchRest
57
35
 
58
36
  # Creates the database if it doesn't exist
59
37
  def database!(name)
60
- CouchRest.head "#{@uri}/#{name}" # Check if the URL is valid
38
+ connection.head name # Check if the URL is valid
61
39
  database(name)
62
- rescue RestClient::ResourceNotFound # Thrown if the HTTP HEAD fails
40
+ rescue CouchRest::NotFound # Thrown if the HTTP HEAD fails
63
41
  create_db(name)
64
42
  end
65
43
 
66
44
  # GET the welcome message
67
45
  def info
68
- CouchRest.get "#{@uri}/"
46
+ connection.get ""
69
47
  end
70
48
 
71
49
  # Create a database
72
50
  def create_db(name)
73
- CouchRest.put "#{@uri}/#{name}"
51
+ connection.put name
74
52
  database(name)
75
53
  end
76
54
 
77
55
  # Restart the CouchDB instance
78
56
  def restart!
79
- CouchRest.post "#{@uri}/_restart"
57
+ connection.post "_restart"
80
58
  end
81
59
 
82
60
  # Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
83
61
  def next_uuid(count = @uuid_batch_count)
84
- @uuids ||= []
85
- if @uuids.empty?
86
- @uuids = CouchRest.get("#{@uri}/_uuids?count=#{count}")["uuids"]
62
+ if uuids.nil? || uuids.empty?
63
+ @uuids = connection.get("_uuids?count=#{count}")["uuids"]
87
64
  end
88
- @uuids.pop
65
+ uuids.pop
66
+ end
67
+
68
+ protected
69
+
70
+ def prepare_uri(url)
71
+ uri = URI(url)
72
+ uri.path = ""
73
+ uri.query = nil
74
+ uri.fragment = nil
75
+ uri
89
76
  end
90
77
 
91
78
  end
@@ -0,0 +1,415 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe CouchRest::Connection do
4
+
5
+ let(:simple_response) { "{\"ok\":true}" }
6
+ let(:parser) { MultiJson }
7
+ let(:parser_opts) { {:max_nesting => false} }
8
+
9
+ it "should exist" do
10
+ conn = CouchRest::Connection.new(URI "http://localhost:5984")
11
+ expect(conn).to respond_to :get
12
+ expect(conn).to respond_to :put
13
+ expect(conn).to respond_to :post
14
+ expect(conn).to respond_to :copy
15
+ expect(conn).to respond_to :delete
16
+ expect(conn).to respond_to :head
17
+ end
18
+
19
+ describe "initialization" do
20
+
21
+ it "should not modify the provided URI" do
22
+ uri = URI("http://localhost:5984/path/random?query=none#fragment")
23
+ s = uri.to_s
24
+ CouchRest::Connection.new(uri)
25
+ expect(uri.to_s).to eql(s)
26
+ end
27
+
28
+ it "should raise an error if not instantiated with a URI" do
29
+ expect { CouchRest::Connection.new("http://localhost:5984") }.to raise_error(/URI::HTTP/)
30
+ end
31
+
32
+ it "should clean the provided URI" do
33
+ conn = CouchRest::Connection.new(URI "http://localhost:5984/path/random?query=none#fragment")
34
+ expect(conn.uri.to_s).to eql("http://localhost:5984")
35
+ end
36
+
37
+ it "should have instantiated an HTTP connection" do
38
+ conn = CouchRest::Connection.new(URI "http://localhost:5984")
39
+ expect(conn.http).to be_a(HTTPClient)
40
+ end
41
+
42
+ it "should use the proxy if defined in parameters" do
43
+ conn = CouchRest::Connection.new(URI("http://localhost:5984"), :proxy => 'http://proxy')
44
+ expect(conn.http.proxy.to_s).to eql('http://proxy')
45
+ end
46
+
47
+ it "should use the proxy if defined in class" do
48
+ CouchRest::Connection.proxy = 'http://proxy'
49
+ conn = CouchRest::Connection.new(URI "http://localhost:5984")
50
+ expect(conn.http.proxy.to_s).to eql('http://proxy')
51
+ CouchRest::Connection.proxy = nil
52
+
53
+ end
54
+
55
+ it "should allow default proxy to be overwritten" do
56
+ CouchRest::Connection.proxy = 'http://proxy'
57
+ conn = CouchRest::Connection.new(URI("http://localhost:5984"), :proxy => 'http://proxy2')
58
+ expect(conn.http.proxy.to_s).to eql('http://proxy2')
59
+ CouchRest::Connection.proxy = nil
60
+ end
61
+
62
+ describe "with SSL options" do
63
+ it "should leave the default if nothing set" do
64
+ default = HTTPClient.new.ssl_config.verify_mode
65
+ conn = CouchRest::Connection.new(URI "https://localhost:5984")
66
+ expect(conn.http.ssl_config.verify_mode).to eql(default)
67
+ end
68
+ it "should support disabling SSL verify mode" do
69
+ conn = CouchRest::Connection.new(URI("https://localhost:5984"), :verify_ssl => false)
70
+ expect(conn.http.ssl_config.verify_mode).to eql(OpenSSL::SSL::VERIFY_NONE)
71
+ end
72
+ it "should support enabling SSL verify mode" do
73
+ conn = CouchRest::Connection.new(URI("https://localhost:5984"), :verify_ssl => true)
74
+ expect(conn.http.ssl_config.verify_mode).to eql(OpenSSL::SSL::VERIFY_PEER)
75
+ end
76
+ it "should support setting specific cert, key, and ca" do
77
+ conn = CouchRest::Connection.new(URI("https://localhost:5984"),
78
+ :ssl_client_cert => 'cert',
79
+ :ssl_client_key => 'key',
80
+ )
81
+ expect(conn.http.ssl_config.client_cert).to eql('cert')
82
+ expect(conn.http.ssl_config.client_key).to eql('key')
83
+ end
84
+
85
+ end
86
+
87
+ describe "with timeout options" do
88
+ it "should be set on the http object" do
89
+ conn = CouchRest::Connection.new(URI("https://localhost:5984"),
90
+ :timeout => 23,
91
+ :open_timeout => 26,
92
+ :read_timeout => 27
93
+ )
94
+
95
+ expect(conn.http.receive_timeout).to eql(23)
96
+ expect(conn.http.connect_timeout).to eql(26)
97
+ expect(conn.http.send_timeout).to eql(27)
98
+ end
99
+
100
+ end
101
+ end
102
+
103
+ describe "basic requests" do
104
+
105
+ let :doc do
106
+ { '_id' => 'test-doc', 'name' => 'test document' }
107
+ end
108
+ let :uri do
109
+ URI(DB.to_s + "/test-doc")
110
+ end
111
+ let :conn do
112
+ CouchRest::Connection.new(uri)
113
+ end
114
+ let :mock_conn do
115
+ CouchRest::Connection.new(URI "http://mock")
116
+ end
117
+
118
+ describe :get do
119
+
120
+ it "should send basic request" do
121
+ DB.save_doc(doc)
122
+ res = conn.get(uri.path)
123
+ expect(res['name']).to eql(doc['name'])
124
+ end
125
+
126
+ it "should raise exception if document missing" do
127
+ uri = URI(DB.to_s + "/missingdoc")
128
+ conn = CouchRest::Connection.new(uri)
129
+ res = nil
130
+ expect { res = conn.get(uri.path) }.to raise_error do |e|
131
+ expect(e).to be_a(CouchRest::RequestFailed)
132
+ expect(e).to be_a(CouchRest::NotFound)
133
+ expect(e.response).to eql(res)
134
+ end
135
+ end
136
+
137
+ it "should handle 'content_type' header" do
138
+ stub_request(:get, "http://mock/db/test")
139
+ .with(:headers => {'content-type' => 'fooo'})
140
+ .to_return(:body => doc.to_json)
141
+ mock_conn.get("db/test", :content_type => 'fooo')
142
+ end
143
+
144
+ it "should handle 'accept' header" do
145
+ stub_request(:get, "http://mock/db/test")
146
+ .with(:headers => {'accept' => 'fooo'})
147
+ .to_return(:body => doc.to_json)
148
+ mock_conn.get("db/test", :accept => 'fooo')
149
+ end
150
+
151
+ it "should not overwrite 'Content-Type' header if provided" do
152
+ stub_request(:get, "http://mock/db/test")
153
+ .with(:headers => {'Content-Type' => 'fooo'})
154
+ .to_return(:body => doc.to_json)
155
+ mock_conn.get("db/test", :headers => { 'Content-Type' => 'fooo' })
156
+ end
157
+
158
+ it "should not overwrite 'Accept' header if provided in headers" do
159
+ stub_request(:get, "http://mock/db/test")
160
+ .with(:headers => {'Accept' => 'fooo'})
161
+ .to_return(:body => doc.to_json)
162
+ mock_conn.get("db/test", :headers => { 'Accept' => 'fooo' })
163
+ end
164
+
165
+ it "should convert 'Content-Type' header options" do
166
+ stub_request(:get, "http://mock/db/test")
167
+ .with(:headers => {'Content-Type' => 'application/json'})
168
+ .to_return(:body => doc.to_json)
169
+ mock_conn.get("db/test", :content_type => :json)
170
+ end
171
+
172
+ it "should maintain query parameters" do
173
+ stub_request(:get, "http://mock/db/test?q=a")
174
+ .to_return(:body => doc.to_json)
175
+ expect(mock_conn.get("db/test?q=a")).to eql(doc)
176
+ end
177
+
178
+ it "should not try to parse result with :raw parameter" do
179
+ json = doc.to_json
180
+ stub_request(:get, "http://mock/db/test")
181
+ .to_return(:body => json)
182
+ expect(mock_conn.get("db/test", :raw => true)).to eql(json)
183
+ end
184
+
185
+ it "should forward parser options" do
186
+ expect(MultiJson).to receive(:load).with(doc.to_json, hash_including(:max_nesting => true))
187
+ stub_request(:get, "http://mock/db/test")
188
+ .to_return(:body => doc.to_json)
189
+ mock_conn.get("db/test", :max_nesting => true)
190
+ end
191
+
192
+ it "should forward parser options (2)" do
193
+ expect(MultiJson).to receive(:load).with(doc.to_json, hash_including(:quirks_mode => true))
194
+ stub_request(:get, "http://mock/db/test")
195
+ .to_return(:body => doc.to_json)
196
+ mock_conn.get("db/test", :quirks_mode => true)
197
+ end
198
+
199
+ context 'when decode_json_objects is true' do
200
+ class TestObject
201
+ def self.json_create(args)
202
+ new
203
+ end
204
+ end
205
+
206
+ before(:all) do
207
+ CouchRest.decode_json_objects = true
208
+ CouchRest.put "#{COUCHHOST}/#{TESTDB}/test", JSON.create_id => TestObject.to_s
209
+ end
210
+
211
+ after(:all) do
212
+ CouchRest.decode_json_objects = false
213
+ end
214
+
215
+ it 'should return the response as a Ruby object' do
216
+ conn = CouchRest::Connection.new(URI(COUCHHOST))
217
+ expect(conn.get("#{TESTDB}/test").class).to eql(TestObject)
218
+ end
219
+ end
220
+
221
+ context 'when decode_json_objects is false (the default)' do
222
+ class TestObject2
223
+ def self.json_create(args)
224
+ new
225
+ end
226
+ end
227
+
228
+ before(:all) do
229
+ CouchRest.decode_json_objects = false
230
+ CouchRest.put "#{COUCHHOST}/#{TESTDB}/test2", JSON.create_id => TestObject.to_s
231
+ end
232
+
233
+ it 'should not return the response as a Ruby object' do
234
+ conn = CouchRest::Connection.new(URI(COUCHHOST))
235
+ expect(conn.get("#{TESTDB}/test2").class).to eql(Hash)
236
+ end
237
+ end
238
+
239
+ describe "with block" do
240
+
241
+ let :sample_data do
242
+ <<-EOF
243
+ {
244
+ "total_rows": 3, "offset": 0, "rows": [
245
+ {"id": "doc1", "key": "doc1", "value": {"rev":"4324BB"}},
246
+ {"id": "doc2", "key": "doc2", "value": {"rev":"2441HF"}},
247
+ {"id": "doc3", "key": "doc3", "value": {"rev":"74EC24"}}
248
+ ]
249
+ }
250
+ EOF
251
+ end
252
+
253
+ it "should handle basic streaming request" do
254
+ stub_request(:get, "http://mock/db/test")
255
+ .to_return(:body => sample_data)
256
+ rows = []
257
+ head = mock_conn.get("db/test") do |row|
258
+ rows << row
259
+ end
260
+ expect(rows.length).to eql(3)
261
+ expect(head['total_rows']).to eql(3)
262
+ expect(rows.first['id']).to eql('doc1')
263
+ end
264
+
265
+ end
266
+
267
+ end
268
+
269
+ describe :put do
270
+
271
+ let :put_doc do
272
+ { '_id' => 'test-put-doc', 'name' => 'test put document' }
273
+ end
274
+
275
+ it "should put a document to the database" do
276
+ conn.put("#{TESTDB}/test-put-doc", put_doc)
277
+ res = conn.get("#{TESTDB}/test-put-doc")
278
+ expect(res['name']).to eql put_doc['name']
279
+ expect(res['_rev']).to_not be_nil
280
+ end
281
+
282
+ it "should convert hash into json data" do
283
+ stub_request(:put, "http://mock/db/test-put")
284
+ .with(:body => put_doc.to_json)
285
+ .to_return(:body => simple_response)
286
+ mock_conn.put("db/test-put", put_doc)
287
+ end
288
+
289
+ it "should send raw data" do
290
+ stub_request(:put, "http://mock/db/test-put")
291
+ .with(:body => 'raw')
292
+ .to_return(:body => simple_response)
293
+ mock_conn.put("db/test-put", 'raw', :raw => true)
294
+ end
295
+
296
+ it "should handle nil doc" do
297
+ stub_request(:put, "http://mock/db/test-put-nil")
298
+ .with(:body => '')
299
+ .to_return(:body => simple_response)
300
+ mock_conn.put("db/test-put-nil", nil)
301
+ end
302
+
303
+ it "should send data file and detect file type" do
304
+ f = File.open(FIXTURE_PATH + '/attachments/test.html')
305
+ stub_request(:put, "http://mock/db/test-put.html")
306
+ .with(:body => f.read, :headers => { 'Content-Type' => 'text/html' })
307
+ .to_return(:body => simple_response)
308
+ f.rewind
309
+ mock_conn.put("db/test-put.html", f)
310
+ end
311
+
312
+ it "should send tempfile and detect file type" do
313
+ f = Tempfile.new('test.png')
314
+ stub_request(:put, "http://mock/db/test-put-image.png")
315
+ .with(:body => f.read, :headers => { 'Content-Type' => 'image/png' })
316
+ .to_return(:body => simple_response)
317
+ f.rewind
318
+ mock_conn.put("db/test-put-image.png", f)
319
+ end
320
+
321
+ it "should send StringIO and detect file type" do
322
+ f = StringIO.new('this is a test file')
323
+ stub_request(:put, "http://mock/db/test-put-text.txt")
324
+ .with(:body => f.read, :headers => { 'Content-Type' => 'text/plain' })
325
+ .to_return(:body => simple_response)
326
+ f.rewind
327
+ mock_conn.put("db/test-put-text.txt", f)
328
+ end
329
+
330
+ it "should use as_couch_json method if available" do
331
+ doc = CouchRest::Document.new(put_doc)
332
+ expect(doc).to receive(:as_couch_json).and_return(put_doc)
333
+ stub_request(:put, "http://mock/db/test-put")
334
+ .to_return(:body => simple_response)
335
+ mock_conn.put('db/test-put', doc)
336
+ end
337
+
338
+ end
339
+
340
+ describe :post do
341
+
342
+ let :post_doc do
343
+ { '_id' => 'test-post-doc', 'name' => 'test post document' }
344
+ end
345
+
346
+ it "should put a document to the database" do
347
+ conn.put("#{TESTDB}/test-post-doc", post_doc)
348
+ res = conn.get("#{TESTDB}/test-post-doc")
349
+ expect(res['name']).to eql post_doc['name']
350
+ expect(res['_rev']).to_not be_nil
351
+ end
352
+
353
+ describe "with block" do
354
+
355
+ let :sample_data do
356
+ <<-EOF
357
+ {
358
+ "total_rows": 3, "offset": 0, "rows": [
359
+ {"id": "doc1", "key": "doc1", "value": {"rev":"4324BB"}},
360
+ {"id": "doc2", "key": "doc2", "value": {"rev":"2441HF"}},
361
+ {"id": "doc3", "key": "doc3", "value": {"rev":"74EC24"}}
362
+ ]
363
+ }
364
+ EOF
365
+ end
366
+
367
+ it "should handle basic streaming request" do
368
+ stub_request(:post, "http://mock/db/test")
369
+ .to_return(:body => sample_data)
370
+ rows = []
371
+ head = mock_conn.post("db/test") do |row|
372
+ rows << row
373
+ end
374
+ expect(rows.length).to eql(3)
375
+ expect(head['total_rows']).to eql(3)
376
+ expect(rows.first['id']).to eql('doc1')
377
+ end
378
+
379
+ end
380
+
381
+ end
382
+
383
+ describe :delete do
384
+ it "should delete a doc" do
385
+ stub_request(:delete, "http://mock/db/test-delete")
386
+ .to_return(:body => simple_response)
387
+ expect(mock_conn.delete('db/test-delete')).to eql('ok' => true)
388
+ end
389
+ end
390
+
391
+ describe :copy do
392
+ it "should copy a doc" do
393
+ stub_request(:copy, "http://mock/db/test-copy")
394
+ .with(:headers => { 'Destination' => "test-copy-dest" })
395
+ .to_return(:body => simple_response)
396
+ expect(mock_conn.copy('db/test-copy', 'test-copy-dest')).to eql('ok' => true)
397
+ end
398
+ end
399
+
400
+ describe :head do
401
+ it "should send head request" do
402
+ stub_request(:head, "http://mock/db/test-head")
403
+ .to_return(:body => "")
404
+ expect { mock_conn.head('db/test-head') }.to_not raise_error
405
+ end
406
+ it "should handle head request when document missing" do
407
+ stub_request(:head, "http://mock/db/test-missing-head")
408
+ .to_return(:status => 404)
409
+ expect { mock_conn.head('db/test-missing-head') }.to raise_error(CouchRest::NotFound)
410
+ end
411
+ end
412
+
413
+ end
414
+
415
+ end