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.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.travis.yml +1 -0
- data/README.md +11 -7
- data/VERSION +1 -1
- data/couchrest.gemspec +7 -6
- data/history.txt +8 -0
- data/lib/couchrest.rb +26 -31
- data/lib/couchrest/connection.rb +251 -0
- data/lib/couchrest/database.rb +75 -79
- data/lib/couchrest/design.rb +1 -1
- data/lib/couchrest/exceptions.rb +108 -0
- data/lib/couchrest/helper/pager.rb +3 -1
- data/lib/couchrest/helper/stream_row_parser.rb +93 -0
- data/lib/couchrest/rest_api.rb +33 -134
- data/lib/couchrest/server.rb +34 -47
- data/spec/couchrest/connection_spec.rb +415 -0
- data/spec/couchrest/couchrest_spec.rb +61 -67
- data/spec/couchrest/database_spec.rb +151 -147
- data/spec/couchrest/design_spec.rb +28 -28
- data/spec/couchrest/document_spec.rb +72 -70
- data/spec/couchrest/exceptions_spec.rb +74 -0
- data/spec/couchrest/helpers/pager_spec.rb +22 -22
- data/spec/couchrest/helpers/stream_row_parser_spec.rb +154 -0
- data/spec/couchrest/rest_api_spec.rb +44 -208
- data/spec/couchrest/server_spec.rb +0 -29
- data/spec/spec_helper.rb +11 -6
- metadata +31 -17
- data/lib/couchrest/helper/streamer.rb +0 -63
- data/lib/couchrest/monkeypatches.rb +0 -25
- data/spec/couchrest/helpers/streamer_spec.rb +0 -134
data/lib/couchrest/rest_api.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
38
|
-
#
|
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(
|
45
|
-
|
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(
|
50
|
-
|
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(
|
55
|
-
|
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(
|
60
|
-
|
61
|
-
|
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
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
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
|
162
|
-
|
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
|
data/lib/couchrest/server.rb
CHANGED
@@ -1,53 +1,31 @@
|
|
1
1
|
module CouchRest
|
2
2
|
class Server
|
3
3
|
|
4
|
-
|
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
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
38
|
+
connection.head name # Check if the URL is valid
|
61
39
|
database(name)
|
62
|
-
rescue
|
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
|
-
|
46
|
+
connection.get ""
|
69
47
|
end
|
70
48
|
|
71
49
|
# Create a database
|
72
50
|
def create_db(name)
|
73
|
-
|
51
|
+
connection.put name
|
74
52
|
database(name)
|
75
53
|
end
|
76
54
|
|
77
55
|
# Restart the CouchDB instance
|
78
56
|
def restart!
|
79
|
-
|
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
|
-
|
85
|
-
|
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
|
-
|
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
|