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.
- 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
|