couchrest 1.2.1 → 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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