rocking_chair 0.0.2

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.
@@ -0,0 +1,284 @@
1
+ module RockingChair
2
+ class View
3
+
4
+ attr_accessor :database, :keys, :options, :ruby_store, :design_document, :design_document_name, :view_name, :view_document
5
+
6
+ def self.run(database, design_document_name, view_name, options = {})
7
+ raise ArgumentError, "Need a databae, a design_doc_name and a view_name" unless database.present? && design_document_name.present? && view_name.present?
8
+ new(database, design_document_name, view_name, options).find.render
9
+ end
10
+
11
+ def self.run_all(database, options = {})
12
+ new(database, :all, :all, options).find.render_for_all
13
+ end
14
+
15
+ def initialize(database, design_document_name, view_name, options = {})
16
+ unless design_document_name == :all && view_name == :all
17
+ RockingChair::Error.raise_404 unless database.exists?("_design/#{design_document_name}")
18
+ @design_document = JSON.parse(database.storage["_design/#{design_document_name}"])
19
+ @view_document = design_document['views'][view_name] || RockingChair::Error.raise_404
20
+ end
21
+
22
+ @database = database
23
+ @keys = database.storage.keys
24
+ @design_document_name = design_document_name
25
+ @view_name = view_name
26
+ initialize_ruby_store
27
+
28
+ @options = {
29
+ 'reduce' => false,
30
+ 'limit' => nil,
31
+ 'key' => nil,
32
+ 'descending' => false,
33
+ 'include_docs' => false,
34
+ 'without_deleted' => false,
35
+ 'endkey' => nil,
36
+ 'startkey' => nil,
37
+ 'endkey_docid' => nil,
38
+ 'startkey_docid' => nil
39
+ }.update(options)
40
+ @options.assert_valid_keys('reduce', 'limit', 'key', 'descending', 'include_docs', 'without_deleted', 'endkey', 'startkey', 'endkey_docid', 'startkey_docid')
41
+ RockingChair::Helper.jsonfy_options(@options, 'key', 'startkey', 'endkey', 'startkey_docid', 'endkey_docid')
42
+
43
+ normalize_view_name
44
+ end
45
+
46
+ def find
47
+ if view_name == :all
48
+ find_all
49
+ elsif match = view_name.match(/\Aall_documents\Z/)
50
+ find_all_by_class
51
+ elsif match = view_name.match(/\Aby_(\w+)\Z/)
52
+ find_by_attribute(match[1])
53
+ elsif match = view_name.match(/\Aassociation_#{design_document_name}_belongs_to_(\w+)\Z/)
54
+ find_belongs_to(match[1])
55
+ else
56
+ raise "Unknown View implementation for view #{view_name.inspect} in design document _design/#{design_document_name}"
57
+ end
58
+ self
59
+ end
60
+
61
+ def render
62
+ offset = 0
63
+ total_size = keys.size
64
+ filter_by_startkey_docid_and_endkey_docid
65
+ filter_by_limit
66
+
67
+ if options['reduce'].to_s == 'true'
68
+ { "rows" => [{'key' => options['key'], 'value' => keys.size }]}.to_json
69
+ else
70
+ rows = keys.map do |key|
71
+ document = ruby_store[key]
72
+ if options['include_docs'].to_s == 'true'
73
+ {'id' => RockingChair::Helper.access('_id', document), 'value' => nil, 'doc' => (document.respond_to?(:_document) ? document._document : document) }.merge(key_description)
74
+ else
75
+ {'id' => RockingChair::Helper.access('_id', document), 'key' => options['key'], 'value' => nil}.merge(key_description)
76
+ end
77
+ end
78
+ { "total_rows" => total_size, "offset" => offset, "rows" => rows}.to_json
79
+ end
80
+ end
81
+
82
+ def render_for_all
83
+ offset = filter_by_startkey
84
+ filter_by_endkey
85
+ filter_by_limit
86
+
87
+ rows = keys.map do |key|
88
+ document = ruby_store[key]
89
+ if options['include_docs'].to_s == 'true'
90
+ {'id' => document['_id'], 'key' => document['_id'], 'value' => document.update('rev' => document['_rev'])}
91
+ else
92
+ {'id' => document['_id'], 'key' => document['_id'], 'value' => {'rev' => document['_rev']}}
93
+ end
94
+ end
95
+
96
+ { "total_rows" => database.document_count, "offset" => offset, "rows" => rows}.to_json
97
+ end
98
+
99
+ protected
100
+
101
+ def find_all
102
+ sort_by_attribute('_id')
103
+ end
104
+
105
+ def find_all_by_class
106
+ filter_items_without_correct_ruby_class
107
+ filter_deleted_items if options['without_deleted'].to_s == 'true'
108
+ end
109
+
110
+ def find_belongs_to(belongs_to)
111
+ filter_items_by_key([foreign_key_id(belongs_to)])
112
+ filter_items_without_correct_ruby_class
113
+ filter_deleted_items if options['without_deleted'].to_s == 'true'
114
+ end
115
+
116
+ def find_by_attribute(attribute_string)
117
+ attributes = attribute_string.split("_and_")
118
+
119
+ filter_items_by_key(attributes)
120
+ filter_items_without_correct_ruby_class
121
+ filter_deleted_items if options['without_deleted'].to_s == 'true'
122
+ sort_by_attribute(attributes.first)
123
+ end
124
+
125
+ def normalize_view_name
126
+ return if view_name.is_a?(Symbol)
127
+
128
+ if view_name.match(/_withoutdeleted\Z/) || view_name.match(/_without_deleted\Z/)
129
+ options['without_deleted'] = true
130
+ elsif view_name.match(/_withdeleted\Z/) || view_name.match(/_with_deleted\Z/)
131
+ options['without_deleted'] = false
132
+ else
133
+ options['without_deleted'] = view_document['map'].match(/\"soft\" deleted/) ? true : nil
134
+ end
135
+ @view_name = view_name.gsub(/_withoutdeleted\Z/, '').gsub(/_without_deleted\Z/, '').gsub(/_withdeleted\Z/, '').gsub(/_with_deleted\Z/, '')
136
+ end
137
+
138
+ def initialize_ruby_store
139
+ @ruby_store = database.storage.dup
140
+ @ruby_store.each{|k,v| ruby_store[k] = JSON.parse(v) }
141
+ end
142
+
143
+ def filter_items_by_key(attributes)
144
+ if options['startkey']
145
+ filter_items_by_range(attributes)
146
+ else
147
+ filter_items_by_exact_key(attributes)
148
+ end
149
+ end
150
+
151
+ def filter_items_by_exact_key(attributes)
152
+ filter_keys = options['key'].is_a?(Array) ? options['key'] : [options['key']]
153
+ attributes.each_with_index do |attribute, index|
154
+ filter_items_without_attribute_value(attribute, filter_keys[index])
155
+ end
156
+ end
157
+
158
+ def filter_items_by_range(attributes)
159
+ start_keys = options['startkey'].is_a?(Array) ? options['startkey'] : [options['startkey']]
160
+ end_keys = options['endkey'].is_a?(Array) ? options['endkey'] : [options['endkey']]
161
+
162
+ attributes.each_with_index do |attribute, index|
163
+ filter_items_not_in_range(attribute, start_keys[index], end_keys[index])
164
+ end
165
+ end
166
+
167
+ def filter_items_not_in_range(attribute, start_key, end_key)
168
+ @keys = keys.select do |key|
169
+ document = ruby_store[key]
170
+ if end_key
171
+ RockingChair::Helper.access(attribute, document) && (RockingChair::Helper.access(attribute, document) >= start_key) && (RockingChair::Helper.access(attribute, document) <= end_key)
172
+ else
173
+ RockingChair::Helper.access(attribute, document) && (RockingChair::Helper.access(attribute, document) >= start_key)
174
+ end
175
+ end
176
+ end
177
+
178
+ def filter_deleted_items
179
+ @keys = keys.delete_if do |key|
180
+ document = ruby_store[key]
181
+ RockingChair::Helper.access('deleted_at', document).present?
182
+ end
183
+ end
184
+
185
+ def sort_by_attribute(attribute)
186
+ attribute ||= '_id'
187
+ @keys = (options['descending'].to_s == 'true') ?
188
+ keys.sort{|x,y| RockingChair::Helper.access(attribute, ruby_store[y]) <=> RockingChair::Helper.access(attribute, ruby_store[x]) } :
189
+ keys.sort{|x,y| RockingChair::Helper.access(attribute, ruby_store[x]) <=> RockingChair::Helper.access(attribute, ruby_store[y]) }
190
+ end
191
+
192
+ def filter_items_without_attribute_value(attribute, attr_value)
193
+ if attr_value
194
+ @keys = keys.select do |key|
195
+ document = ruby_store[key]
196
+ if RockingChair::Helper.access(attribute, document).is_a?(Array)
197
+ RockingChair::Helper.access(attribute, document).include?(attr_value)
198
+ else
199
+ RockingChair::Helper.access(attribute, document) == attr_value
200
+ end
201
+ end
202
+ else
203
+ @keys = keys.select do |key|
204
+ document = ruby_store[key]
205
+ RockingChair::Helper.access(attribute, document).present?
206
+ end
207
+ end
208
+ end
209
+
210
+ def filter_items_without_correct_ruby_class
211
+ klass_name = design_document_name.classify
212
+ @keys = keys.select do |key|
213
+ document = ruby_store[key]
214
+ RockingChair::Helper.access('ruby_class', document).to_s.classify == klass_name
215
+ end
216
+ end
217
+
218
+ def filter_by_limit
219
+ if options['limit']
220
+ @keys = keys[0, options['limit'].to_i]
221
+ end
222
+ end
223
+
224
+ def filter_by_startkey_docid_and_endkey_docid
225
+ if options['startkey_docid'] || options['endkey_docid']
226
+ @keys = keys.select do |key|
227
+ if options['startkey_docid'] && options['endkey_docid']
228
+ ( key >= options['startkey_docid']) && (key <= options['endkey_docid'])
229
+ elsif options['startkey_docid']
230
+ key >= options['startkey_docid']
231
+ else
232
+ key <= options['endkey_docid']
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ def filter_by_startkey
239
+ offset = 0
240
+ if options['startkey']
241
+ startkey_found = false
242
+ @keys = keys.map do |key|
243
+ if startkey_found || key == options['startkey']
244
+ startkey_found = true
245
+ key
246
+ else
247
+ offset += 1
248
+ nil
249
+ end
250
+ end.compact
251
+ end
252
+ return offset
253
+ end
254
+
255
+ def filter_by_endkey
256
+ if options['endkey']
257
+ endkey_found = false
258
+ @keys = keys.map do |key|
259
+ if key == options['endkey']
260
+ endkey_found = true
261
+ key
262
+ elsif endkey_found
263
+ nil
264
+ else
265
+ key
266
+ end
267
+ end.compact
268
+ end
269
+ end
270
+
271
+ def foreign_key_id(name)
272
+ name.underscore.gsub('/','__').gsub('::','__') + "_id"
273
+ end
274
+
275
+ def key_description
276
+ description = {'key' => options['key']}
277
+ description = {'startkey' => options['startkey'], 'endkey' => options['endkey']} if options['startkey']
278
+ description.update('startkey_docid' => options['startkey_docid']) if options['startkey_docid']
279
+ description.update('endkey_docid' => options['endkey_docid']) if options['endkey_docid']
280
+ description
281
+ end
282
+
283
+ end
284
+ end
@@ -0,0 +1,350 @@
1
+ require File.dirname(__FILE__) + "/test_helper"
2
+
3
+ class CouchRestTest < Test::Unit::TestCase
4
+ context "The HTTP Apdapter for CouchRest" do
5
+ setup do
6
+ RockingChair::Server.reset
7
+ @couch = CouchRest.new("http://127.0.0.1:5984")
8
+ end
9
+
10
+ context "Database API" do
11
+
12
+ should "return the info JSON" do
13
+ assert_equal({"couchdb" => "Welcome","version" => "0.10.1"}, @couch.info)
14
+ end
15
+
16
+ should "return a list of all databases" do
17
+ assert_equal [], @couch.databases
18
+ @couch.create_db('database-name')
19
+ assert_equal ['database-name'], @couch.databases
20
+ end
21
+
22
+ should "create a database" do
23
+ db = @couch.create_db('database-name')
24
+ end
25
+
26
+ should "return information on a database" do
27
+ @couch.create_db('database-name')
28
+ assert_equal({
29
+ "db_name" => "database-name",
30
+ "doc_count" => 0,
31
+ "doc_del_count" => 0,
32
+ "update_seq" => 10,
33
+ "purge_seq" => 0,
34
+ "compact_running" => false,
35
+ "disk_size" => 16473,
36
+ "instance_start_time" => "1265409273572320",
37
+ "disk_format_version" => 4}, @couch.database('database-name').info)
38
+ end
39
+
40
+ should "delete a database" do
41
+ db = @couch.database('database-name')
42
+ db.delete!
43
+ end
44
+
45
+ end
46
+
47
+ context "when asking for UUIDs" do
48
+ should "return a UUID" do
49
+ RockingChair::Database.expects(:uuids).with('2').returns(['1', '2'])
50
+ assert_equal '2', @couch.next_uuid(2)
51
+ end
52
+ end
53
+
54
+ context "Bulk Document API" do
55
+ setup do
56
+ @db = @couch.create_db('RockingChair')
57
+ RockingChair::Database.any_instance.stubs(:rev).returns('the-revision')
58
+ end
59
+
60
+ context "loading all documents" do
61
+ should "load the total documents" do
62
+ 5.times do |i|
63
+ @db.save_doc({'_id' => "item-#{i}", 'a' => 'b'})
64
+ end
65
+ assert_equal({
66
+ "total_rows" => 5, "offset" => 0, "rows" => [
67
+ {"id" => "item-0", "key" => "item-0", "value" => {"rev" => "the-revision"}},
68
+ {"id" => "item-1", "key" => "item-1", "value" => {"rev" => "the-revision"}},
69
+ {"id" => "item-2", "key" => "item-2", "value" => {"rev" => "the-revision"}},
70
+ {"id" => "item-3", "key" => "item-3", "value" => {"rev" => "the-revision"}},
71
+ {"id" => "item-4", "key" => "item-4", "value" => {"rev" => "the-revision"}}
72
+ ]
73
+ }, @db.documents)
74
+ end
75
+
76
+ should "sort the result by ID ascending" do
77
+ @db.save_doc({'_id' => "C", 'a' => 'b'})
78
+ @db.save_doc({'_id' => "B", 'a' => 'b'})
79
+ @db.save_doc({'_id' => "A", 'a' => 'b'})
80
+ assert_equal({
81
+ "total_rows" => 3, "offset" => 0, "rows" => [
82
+ {"id" => "A", "key" => "A", "value" => {"rev" => "the-revision"}},
83
+ {"id" => "B", "key" => "B", "value" => {"rev" => "the-revision"}},
84
+ {"id" => "C", "key" => "C", "value" => {"rev" => "the-revision"}}
85
+ ]
86
+ }, @db.documents)
87
+ end
88
+
89
+ should "sort the result by ID descending" do
90
+ @db.save_doc({'_id' => "C", 'a' => 'b'})
91
+ @db.save_doc({'_id' => "B", 'a' => 'b'})
92
+ @db.save_doc({'_id' => "A", 'a' => 'b'})
93
+ assert_equal({
94
+ "total_rows" => 3, "offset" => 0, "rows" => [
95
+ {"id" => "C", "key" => "C", "value" => {"rev" => "the-revision"}},
96
+ {"id" => "B", "key" => "B", "value" => {"rev" => "the-revision"}},
97
+ {"id" => "A", "key" => "A", "value" => {"rev" => "the-revision"}}
98
+ ]
99
+ }, @db.documents('descending' => true))
100
+ end
101
+
102
+ should "start by the given key" do
103
+ @db.save_doc({'_id' => "C", 'a' => 'b'})
104
+ @db.save_doc({'_id' => "B", 'a' => 'b'})
105
+ @db.save_doc({'_id' => "A", 'a' => 'b'})
106
+
107
+ assert_equal({
108
+ "total_rows" => 3, "offset" => 1, "rows" => [
109
+ {"id" => "B", "key" => "B", "value" => {"rev" => "the-revision"}},
110
+ {"id" => "C", "key" => "C", "value" => {"rev" => "the-revision"}}
111
+ ]
112
+ }, @db.documents(:startkey => 'B'))
113
+ end
114
+
115
+ should "start by the given key, include docs, descending and limit" do
116
+ @db.save_doc({'_id' => "C", 'a' => 'b'})
117
+ @db.save_doc({'_id' => "B", 'a' => 'b'})
118
+ @db.save_doc({'_id' => "A", 'a' => 'b'})
119
+ @db.save_doc({'_id' => "D", 'a' => 'b'})
120
+
121
+ assert_equal({
122
+ "total_rows" => 4, "offset" => 1, "rows" => [
123
+ {"id" => "C", "key" => "C", "value" => {"rev" => "the-revision", '_rev' => 'the-revision', '_id' => 'C', 'a' => 'b'}},
124
+ {"id" => "B", "key" => "B", "value" => {"rev" => "the-revision", '_rev' => 'the-revision', '_id' => 'B', 'a' => 'b'}}
125
+ ]
126
+ }, @db.documents(:startkey => 'C', :limit => 2, :include_docs => true, :descending => true))
127
+ end
128
+ end
129
+
130
+ context "bulk modifications" do
131
+
132
+ should "accept bulk inserts/updates" do
133
+ RockingChair::Database.stubs(:uuid).returns('foo-id')
134
+
135
+ docs = [{"_id" => 'a', "value" => 1}, {"_id" => 'b', 'value' => 2}, {'value' => 3}]
136
+ assert_equal([
137
+ {'id' => 'a', "rev" => 'the-revision'},
138
+ {'id' => 'b', "rev" => 'the-revision'},
139
+ {'id' => 'foo-id', "rev" => 'the-revision'}
140
+ ], @db.bulk_save(docs))
141
+ end
142
+
143
+ should "handle automatic inserts/updates" do
144
+ @db.bulk_save_cache_limit = 5
145
+ assert_nothing_raised do
146
+ 10.times do |i|
147
+ @db.save_doc({'_id' => "new-item-#{i}", 'content' => 'here'}, true)
148
+ end
149
+ end
150
+ assert_equal 10, @db.info['doc_count']
151
+ end
152
+
153
+ should "do updates" do
154
+ @db.save_doc({'_id' => "A", 'a' => 'b'})
155
+ @db.save_doc({'_id' => "B", 'a' => 'b'})
156
+
157
+ docs = [{"_id" => 'A', "a" => 1, '_rev' => 'the-revision'}, {'value' => 3}]
158
+ @db.bulk_save(docs)
159
+
160
+ assert_equal({'_id' => 'A', 'a' => 1, '_rev' => 'the-revision'}, @db.get('A'))
161
+ end
162
+
163
+ should "do deletes" do
164
+ @db.save_doc({'_id' => "A", 'a' => 'b'})
165
+ @db.save_doc({'_id' => "B", 'a' => 'b'})
166
+
167
+ docs = [{"_id" => 'A', "a" => 1, '_rev' => 'the-revision', '_deleted' => true}]
168
+ @db.bulk_save(docs)
169
+
170
+ assert_raise(RestClient::ResourceNotFound) do
171
+ @db.get('A')
172
+ end
173
+ end
174
+
175
+ end
176
+ end
177
+
178
+ context "Document API" do
179
+ setup do
180
+ @db = @couch.create_db('RockingChair')
181
+ RockingChair::Database.any_instance.stubs(:rev).returns('the-revision')
182
+ end
183
+
184
+ context "when retrieving a document (GET)" do
185
+ setup do
186
+ @db.save_doc({'_id' => 'the-doc-id', 'a' => 'b'})
187
+ end
188
+
189
+ should "load the document" do
190
+ assert_equal({"_id" => "the-doc-id","a" => "b", '_rev' => 'the-revision'}, @db.get('the-doc-id'))
191
+ end
192
+
193
+ should "raise a 404 if the document does not exist" do
194
+ assert_raise(RestClient::ResourceNotFound) do
195
+ @db.get('no-such-id')
196
+ end
197
+ end
198
+
199
+ should "load the document by a specific revision" do
200
+ assert_equal({"_id" => "the-doc-id","a" => "b", '_rev' => 'the-revision'}, @db.get('the-doc-id', :rev => 'the-revision'))
201
+ end
202
+
203
+ should "raise a 404 if the revision is not there" do
204
+ assert_raise(RestClient::ResourceNotFound) do
205
+ @db.get('the-doc-id', :rev => 'non-existant-revision')
206
+ end
207
+ end
208
+
209
+ should "load the document witht the revisions history" do
210
+ assert_equal({"_id" => "the-doc-id","a" => "b", '_rev' => 'the-revision', "_revisions" => {"start" => 1,"ids" => ["the-revision"]}}, @db.get('the-doc-id', :revs => 'true'))
211
+ end
212
+
213
+ should "load the document witht the detailed revisions history" do
214
+ assert_equal({"_id" => "the-doc-id","a" => "b", '_rev' => 'the-revision', "_revs_info" => [{"rev" => "the-revision", "status" => "disk"}]}, @db.get('the-doc-id', :revs_info => 'true'))
215
+ end
216
+ end
217
+
218
+ context "when storing a document (PUT)" do
219
+ should "store a new the document" do
220
+ assert_equal 0, @db.info['doc_count']
221
+ @db.save_doc({'_id' => 'new-item', 'content' => 'here'})
222
+ assert_equal 1, @db.info['doc_count']
223
+ assert_equal({"_id" => "new-item","content" => "here", '_rev' => 'the-revision'}, @db.get('new-item'))
224
+ end
225
+
226
+ should "update a document" do
227
+ seq = sequence('revision')
228
+ RockingChair::Database.any_instance.expects(:rev).returns('first-rev').in_sequence(seq)
229
+ RockingChair::Database.any_instance.expects(:rev).returns('second-rev').in_sequence(seq)
230
+
231
+ @db.save_doc({'_id' => 'new-item', 'content' => 'here'})
232
+ @db.save_doc({'_id' => 'new-item', 'content' => 'better', '_rev' => 'first-rev'})
233
+
234
+ assert_equal({"_id" => "new-item","content" => "better", '_rev' => 'second-rev'}, @db.get('new-item'))
235
+ end
236
+
237
+ should "raise 409 on a revision conflict" do
238
+ @db.save_doc({'_id' => 'new-item', 'content' => 'here'})
239
+ assert_raise(HttpAbstraction::Conflict) do
240
+ @db.save_doc({'_id' => 'new-item', 'content' => 'better', '_rev' => 'wrong-revision'})
241
+ end
242
+
243
+ assert_equal({"_id" => "new-item","content" => "here", '_rev' => 'the-revision'}, @db.get('new-item'))
244
+ end
245
+
246
+ should "ignore the batch parameter" do
247
+ assert_nothing_raised do
248
+ @db.save_doc({'_id' => 'new-item', 'content' => 'here'}, false, true)
249
+ end
250
+ assert_equal({"_id" => "new-item","content" => "here", '_rev' => 'the-revision'}, @db.get('new-item'))
251
+ end
252
+
253
+ end
254
+
255
+ context "when storing a document (POST)" do
256
+ should "store a new the document" do
257
+ RockingChair::Database.expects(:uuid).returns('5')
258
+ assert_equal 0, @db.info['doc_count']
259
+ assert_equal({"rev"=>"the-revision", "id"=>"5", "ok"=>true},
260
+ CouchRest.post('127.0.0.1:5984/RockingChair/', {'content' => 'here'}))
261
+ assert_equal 1, @db.info['doc_count']
262
+ end
263
+ end
264
+
265
+ context "when deleting a document (DELETE)" do
266
+ should "delete if the rev matches" do
267
+ RockingChair::Database.any_instance.stubs(:rev).returns('123')
268
+
269
+ @db.save_doc({'a' => 'b', '_id' => 'delete_me'})
270
+ @db.delete_doc({'a' => 'b', '_id' => 'delete_me', '_rev' => '123'})
271
+ assert_raise(RestClient::ResourceNotFound) do
272
+ @db.get('delete_me')
273
+ end
274
+ end
275
+
276
+ should "fail with conflich if the rev does not matche" do
277
+ @db.save_doc({'a' => 'b', '_id' => 'delete_me'})
278
+ assert_raise(HttpAbstraction::Conflict) do
279
+ @db.delete_doc({'a' => 'b', '_id' => 'delete_me', '_rev' => 'wrong-revision'})
280
+ end
281
+ assert_nothing_raised do
282
+ @db.get('delete_me')
283
+ end
284
+ end
285
+ end
286
+
287
+ context "when copying a document (COPY)" do
288
+ setup do
289
+ RockingChair::Database.any_instance.stubs(:rev).returns('123')
290
+ @db.save_doc({'a' => 'b', '_id' => 'original'})
291
+ end
292
+
293
+ context "when using an destination ID" do
294
+
295
+ should "copy" do
296
+ @db.copy_doc({'a' => 'b', '_id' => 'original', '_rev' => '123'}, 'the_new_id')
297
+ assert_equal({'a' => 'b', '_id' => 'the_new_id', '_rev' => '123'}, @db.get('the_new_id'))
298
+ end
299
+
300
+ should "copy with overwrite with revision" do
301
+ seq = sequence('revision')
302
+ RockingChair::Database.any_instance.expects(:rev).returns('first-rev').in_sequence(seq)
303
+ RockingChair::Database.any_instance.expects(:rev).returns('second-rev').in_sequence(seq)
304
+
305
+ @db.save_doc({'1' => '2', '_id' => 'destination'})
306
+
307
+ @db.copy_doc({'a' => 'b', '_id' => 'original', '_rev' => '123'}, "destination?rev=first-rev")
308
+ assert_equal({'a' => 'b', '_id' => 'destination', '_rev' => 'second-rev'}, @db.get('destination'))
309
+ end
310
+
311
+ should "not copy with overwrite if the revision does not match" do
312
+ @db.save_doc({'1' => '2', '_id' => 'destination'})
313
+ assert_raise(HttpAbstraction::Conflict) do
314
+ @db.copy_doc({'a' => 'b', '_id' => 'original', '_rev' => '123'}, 'destination?rev=not-here')
315
+ end
316
+ end
317
+
318
+ end
319
+
320
+ context "when using a destination object" do
321
+ should "copy" do
322
+ destination = {"_id" => 'destination', 'c' => 'a', '_rev' => '123'}
323
+ @db.copy_doc({'a' => 'b', '_id' => 'original', '_rev' => '123'}, destination)
324
+ assert_equal({'a' => 'b', '_id' => 'destination', '_rev' => '123'}, @db.get('destination'))
325
+ end
326
+
327
+ should "copy with overwrite with revision" do
328
+ seq = sequence('revision')
329
+ RockingChair::Database.any_instance.expects(:rev).returns('first-rev').in_sequence(seq)
330
+ RockingChair::Database.any_instance.expects(:rev).returns('second-rev').in_sequence(seq)
331
+
332
+ @db.save_doc({'1' => '2', '_id' => 'destination'})
333
+
334
+ @db.copy_doc({'a' => 'b', '_id' => 'original', '_rev' => '123'}, {'1' => '2', '_id' => 'destination', '_rev' => 'first-rev'})
335
+ assert_equal({'a' => 'b', '_id' => 'destination', '_rev' => 'second-rev'}, @db.get('destination'))
336
+ end
337
+
338
+ should "not copy with overwrite if the revision does not match" do
339
+ @db.save_doc({'1' => '2', '_id' => 'destination'})
340
+ assert_raise(HttpAbstraction::Conflict) do
341
+ @db.copy_doc({'a' => 'b', '_id' => 'original', '_rev' => '123'}, {'1' => '2', '_id' => 'destination', '_rev' => 'missing'})
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ end
348
+
349
+ end
350
+ end