dpla-couchrest 1.2.1.pre.dpla

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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +176 -0
  6. data/README.md +66 -0
  7. data/Rakefile +23 -0
  8. data/THANKS.md +21 -0
  9. data/VERSION +1 -0
  10. data/couchrest.gemspec +36 -0
  11. data/examples/word_count/markov +38 -0
  12. data/examples/word_count/views/books/chunked-map.js +3 -0
  13. data/examples/word_count/views/books/united-map.js +1 -0
  14. data/examples/word_count/views/markov/chain-map.js +6 -0
  15. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  16. data/examples/word_count/views/word_count/count-map.js +6 -0
  17. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  18. data/examples/word_count/word_count.rb +46 -0
  19. data/examples/word_count/word_count_query.rb +40 -0
  20. data/examples/word_count/word_count_views.rb +26 -0
  21. data/history.txt +214 -0
  22. data/init.rb +1 -0
  23. data/lib/couchrest.rb +146 -0
  24. data/lib/couchrest/attributes.rb +89 -0
  25. data/lib/couchrest/commands/generate.rb +71 -0
  26. data/lib/couchrest/commands/push.rb +103 -0
  27. data/lib/couchrest/database.rb +402 -0
  28. data/lib/couchrest/design.rb +91 -0
  29. data/lib/couchrest/document.rb +105 -0
  30. data/lib/couchrest/helper/attachments.rb +29 -0
  31. data/lib/couchrest/helper/pager.rb +103 -0
  32. data/lib/couchrest/helper/streamer.rb +60 -0
  33. data/lib/couchrest/helper/upgrade.rb +51 -0
  34. data/lib/couchrest/middlewares/logger.rb +263 -0
  35. data/lib/couchrest/monkeypatches.rb +25 -0
  36. data/lib/couchrest/rest_api.rb +166 -0
  37. data/lib/couchrest/server.rb +92 -0
  38. data/lib/couchrest/support/inheritable_attributes.rb +107 -0
  39. data/spec/.gitignore +1 -0
  40. data/spec/couchrest/couchrest_spec.rb +197 -0
  41. data/spec/couchrest/database_spec.rb +914 -0
  42. data/spec/couchrest/design_spec.rb +206 -0
  43. data/spec/couchrest/document_spec.rb +400 -0
  44. data/spec/couchrest/helpers/pager_spec.rb +115 -0
  45. data/spec/couchrest/helpers/streamer_spec.rb +134 -0
  46. data/spec/couchrest/rest_api_spec.rb +241 -0
  47. data/spec/couchrest/server_spec.rb +35 -0
  48. data/spec/fixtures/attachments/README +3 -0
  49. data/spec/fixtures/attachments/couchdb.png +0 -0
  50. data/spec/fixtures/attachments/test.html +11 -0
  51. data/spec/fixtures/views/lib.js +3 -0
  52. data/spec/fixtures/views/test_view/lib.js +3 -0
  53. data/spec/fixtures/views/test_view/only-map.js +4 -0
  54. data/spec/fixtures/views/test_view/test-map.js +3 -0
  55. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  56. data/spec/spec.opts +5 -0
  57. data/spec/spec_helper.rb +46 -0
  58. data/utils/remap.rb +27 -0
  59. data/utils/subset.rb +30 -0
  60. metadata +212 -0
@@ -0,0 +1,914 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe CouchRest::Database do
4
+ before(:each) do
5
+ @cr = CouchRest.new(COUCHHOST)
6
+ @db = @cr.database(TESTDB)
7
+ @db.delete! rescue RestClient::ResourceNotFound
8
+ @db = @cr.create_db(TESTDB) # rescue nil
9
+ end
10
+
11
+ describe "database name including slash" do
12
+ it "should escape the name in the URI" do
13
+ db = @cr.database("foo/bar")
14
+ db.name.should == "foo/bar"
15
+ db.root.should == "#{COUCHHOST}/foo%2Fbar"
16
+ db.uri.should == "/foo%2Fbar"
17
+ end
18
+ end
19
+
20
+ describe "#info" do
21
+ it "should request basic database data" do
22
+ @db.info['db_name'].should eql(TESTDB)
23
+ end
24
+ end
25
+
26
+ describe "map query with _temp_view in Javascript" do
27
+ before(:each) do
28
+ @db.bulk_save([
29
+ {"wild" => "and random"},
30
+ {"mild" => "yet local"},
31
+ {"another" => ["set","of","keys"]}
32
+ ])
33
+ @temp_view = {:map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"}
34
+ end
35
+ it "should return the result of the temporary function" do
36
+ rs = @db.temp_view(@temp_view)
37
+ rs['rows'].select{|r|r['key'] == 'wild' && r['value'] == 'and random'}.length.should == 1
38
+ end
39
+ it "should work with a range" do
40
+ rs = @db.temp_view(@temp_view, :startkey => "b", :endkey => "z")
41
+ rs['rows'].length.should == 2
42
+ end
43
+ it "should work with a key" do
44
+ rs = @db.temp_view(@temp_view, :key => "wild")
45
+ rs['rows'].length.should == 1
46
+ end
47
+ it "should work with a limit" do
48
+ rs = @db.temp_view(@temp_view, :limit => 1)
49
+ rs['rows'].length.should == 1
50
+ end
51
+ it "should work with multi-keys" do
52
+ rs = @db.temp_view(@temp_view, :keys => ["another", "wild"])
53
+ rs['rows'].length.should == 2
54
+ end
55
+ end
56
+
57
+ describe "map/reduce query with _temp_view in Javascript" do
58
+ before(:each) do
59
+ @db.bulk_save([
60
+ {"beverage" => "beer", :count => 4},
61
+ {"beverage" => "beer", :count => 2},
62
+ {"beverage" => "tea", :count => 3}
63
+ ])
64
+ end
65
+ it "should return the result of the temporary function" do
66
+ rs = @db.temp_view(:map => "function(doc){emit(doc.beverage, doc.count)}", :reduce => "function(beverage,counts){return sum(counts)}")
67
+ # rs.should == 'x'
68
+ rs['rows'][0]['value'].should == 9
69
+ end
70
+ end
71
+
72
+ describe "saving a view" do
73
+ before(:each) do
74
+ @view = {'test' => {'map' => <<-JS
75
+ function(doc) {
76
+ var reg = new RegExp("\\\\W");
77
+ if (doc.word && !reg.test(doc.word)) {
78
+ emit(doc.word,null);
79
+ }
80
+ }
81
+ JS
82
+ }}
83
+ @db.save_doc({
84
+ "_id" => "_design/test",
85
+ :views => @view
86
+ })
87
+ end
88
+ it "should work properly" do
89
+ r = @db.bulk_save([
90
+ {"word" => "once"},
91
+ {"word" => "and again"}
92
+ ])
93
+ r = @db.view('test/test')
94
+ r['total_rows'].should == 1
95
+ end
96
+ it "should round trip" do
97
+ @db.get("_design/test")['views'].should == @view
98
+ end
99
+ end
100
+
101
+ describe "select from an existing view" do
102
+ before(:each) do
103
+ r = @db.save_doc({
104
+ "_id" => "_design/first",
105
+ :views => {
106
+ :test => {
107
+ :map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"
108
+ }
109
+ }
110
+ })
111
+ @db.bulk_save([
112
+ {"wild" => "and random"},
113
+ {"mild" => "yet local"},
114
+ {"another" => ["set","of","keys"]}
115
+ ])
116
+ end
117
+ it "should have the view" do
118
+ @db.get('_design/first')['views']['test']['map'].should include("for(var w in doc)")
119
+ end
120
+ it "should list from the view" do
121
+ rs = @db.view('first/test')
122
+ rs['rows'].select{|r|r['key'] == 'wild' && r['value'] == 'and random'}.length.should == 1
123
+ end
124
+ it "should work with a range" do
125
+ rs = @db.view('first/test', :startkey => "b", :endkey => "z")
126
+ rs['rows'].length.should == 2
127
+ end
128
+ it "should work with a key" do
129
+ rs = @db.view('first/test', :key => "wild")
130
+ rs['rows'].length.should == 1
131
+ end
132
+ it "should work with a limit" do
133
+ rs = @db.view('first/test', :limit => 1)
134
+ rs['rows'].length.should == 1
135
+ end
136
+ it "should work with multi-keys" do
137
+ rs = @db.view('first/test', :keys => ["another", "wild"])
138
+ rs['rows'].length.should == 2
139
+ end
140
+ it "should not modify given params" do
141
+ original_params = {:keys => ["another", "wild"]}
142
+ params = original_params.dup
143
+ rs = @db.view('first/test', params)
144
+ params.should == original_params
145
+ end
146
+ it "should accept a block" do
147
+ rows = []
148
+ rs = @db.view('first/test', :include_docs => true) do |row|
149
+ rows << row
150
+ end
151
+ rows.length.should == 3
152
+ rs["total_rows"].should == 3
153
+ end
154
+ it "should accept a block with several params" do
155
+ rows = []
156
+ rs = @db.view('first/test', :include_docs => true, :limit => 2) do |row|
157
+ rows << row
158
+ end
159
+ rows.length.should == 2
160
+ end
161
+ it "should accept a payload" do
162
+ rs = @db.view('first/test', {}, :keys => ["another", "wild"])
163
+ rs['rows'].length.should == 2
164
+ end
165
+ it "should accept a payload with block" do
166
+ rows = []
167
+ rs = @db.view('first/test', {:include_docs => true}, :keys => ["another", "wild"]) do |row|
168
+ rows << row
169
+ end
170
+ rows.length.should == 2
171
+ rows.first['doc']['another'].should_not be_empty
172
+ end
173
+ end
174
+
175
+ describe "#changes" do
176
+ # uses standard view method, so not much testing required
177
+ before(:each) do
178
+ [
179
+ {"wild" => "and random"},
180
+ {"mild" => "yet local"},
181
+ {"another" => ["set","of","keys"]}
182
+ ].each do |d|
183
+ @db.save_doc(d)
184
+ end
185
+ end
186
+
187
+ it "should produce a basic list of changes" do
188
+ c = @db.changes
189
+ c['results'].length.should eql(3)
190
+ end
191
+
192
+ it "should provide id of last document" do
193
+ c = @db.changes
194
+ doc = @db.get(c['results'].last['id'])
195
+ doc['another'].should_not be_empty
196
+ end
197
+ end
198
+
199
+ describe "GET (document by id) when the doc exists" do
200
+ before(:each) do
201
+ @r = @db.save_doc({'lemons' => 'from texas', 'and' => 'spain'})
202
+ @docid = "http://example.com/stuff.cgi?things=and%20stuff"
203
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
204
+ end
205
+ it "should get the document" do
206
+ doc = @db.get(@r['id'])
207
+ doc['lemons'].should == 'from texas'
208
+ end
209
+ it "should work with a funky id" do
210
+ @db.get(@docid)['will-exist'].should == 'here'
211
+ end
212
+ end
213
+
214
+ describe "POST (adding bulk documents)" do
215
+ it "should add them without ids" do
216
+ rs = @db.bulk_save([
217
+ {"wild" => "and random"},
218
+ {"mild" => "yet local"},
219
+ {"another" => ["set","of","keys"]}
220
+ ])
221
+ rs.each do |r|
222
+ @db.get(r['id']).rev.should == r["rev"]
223
+ end
224
+ end
225
+
226
+ it "should use uuids when ids aren't provided" do
227
+ @db.server.stub!(:next_uuid).and_return('asdf6sgadkfhgsdfusdf')
228
+
229
+ docs = [{'key' => 'value'}, {'_id' => 'totally-uniq'}]
230
+ id_docs = [{'key' => 'value', '_id' => 'asdf6sgadkfhgsdfusdf'}, {'_id' => 'totally-uniq'}]
231
+ CouchRest.should_receive(:post).with("#{COUCHHOST}/couchrest-test/_bulk_docs", {:docs => id_docs})
232
+
233
+ @db.bulk_save(docs)
234
+ end
235
+
236
+ it "should add them with uniq ids" do
237
+ rs = @db.bulk_save([
238
+ {"_id" => "oneB", "wild" => "and random"},
239
+ {"_id" => "twoB", "mild" => "yet local"},
240
+ {"another" => ["set","of","keys"]}
241
+ ])
242
+ rs.each do |r|
243
+ @db.get(r['id']).rev.should == r["rev"]
244
+ end
245
+ end
246
+
247
+ it "should empty the bulk save cache if no documents are given" do
248
+ @db.save_doc({"_id" => "bulk_cache_1", "val" => "test"}, true)
249
+ lambda do
250
+ @db.get('bulk_cache_1')
251
+ end.should raise_error(RestClient::ResourceNotFound)
252
+ @db.bulk_save
253
+ @db.get("bulk_cache_1")["val"].should == "test"
254
+ end
255
+
256
+ it "should make an atomic write when all_or_nothing is set" do
257
+ docs = [{"_id" => "oneB", "wild" => "and random"}, {"_id" => "twoB", "mild" => "yet local"}]
258
+ CouchRest.should_receive(:post).with("#{COUCHHOST}/couchrest-test/_bulk_docs", {:all_or_nothing => true, :docs => docs})
259
+
260
+ @db.bulk_save(docs, false, true)
261
+ end
262
+
263
+ it "should raise an error that is useful for recovery" do
264
+ @r = @db.save_doc({"_id" => "taken", "field" => "stuff"})
265
+ begin
266
+ rs = @db.bulk_save([
267
+ {"_id" => "taken", "wild" => "and random"},
268
+ {"_id" => "free", "mild" => "yet local"},
269
+ {"another" => ["set","of","keys"]}
270
+ ])
271
+ rescue RestClient::RequestFailed => e
272
+ # soon CouchDB will provide _which_ docs conflicted
273
+ MultiJson.decode(e.response.body)['error'].should == 'conflict'
274
+ end
275
+ end
276
+ end
277
+
278
+ describe "new document without an id" do
279
+ it "should start empty" do
280
+ @db.documents["total_rows"].should == 0
281
+ end
282
+ it "should create the document and return the id" do
283
+ r = @db.save_doc({'lemons' => 'from texas', 'and' => 'spain'})
284
+ r2 = @db.get(r['id'])
285
+ r2["lemons"].should == "from texas"
286
+ end
287
+ it "should use PUT with UUIDs" do
288
+ CouchRest.should_receive(:put).and_return({"ok" => true, "id" => "100", "rev" => "55"})
289
+ r = @db.save_doc({'just' => ['another document']})
290
+ end
291
+
292
+ end
293
+
294
+ describe "fetch_attachment" do
295
+ before do
296
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
297
+ @doc = {
298
+ "_id" => "mydocwithattachment",
299
+ "field" => ["some value"],
300
+ "_attachments" => {
301
+ "test.html" => {
302
+ "type" => "text/html",
303
+ "data" => @attach
304
+ }
305
+ }
306
+ }
307
+ @db.save_doc(@doc)
308
+ end
309
+
310
+ # Depreacated
311
+ # it "should get the attachment with the doc's _id" do
312
+ # @db.fetch_attachment("mydocwithattachment", "test.html").should == @attach
313
+ # end
314
+
315
+ it "should get the attachment with the doc itself" do
316
+ @db.fetch_attachment(@db.get('mydocwithattachment'), 'test.html').should == @attach
317
+ end
318
+ end
319
+
320
+ describe "PUT attachment from file" do
321
+ before(:each) do
322
+ filename = FIXTURE_PATH + '/attachments/couchdb.png'
323
+ @file = File.open(filename, "rb")
324
+ end
325
+ after(:each) do
326
+ @file.close
327
+ end
328
+ it "should save the attachment to a new doc" do
329
+ r = @db.put_attachment({'_id' => 'attach-this'}, 'couchdb.png', image = @file.read, {:content_type => 'image/png'})
330
+ r['ok'].should == true
331
+ doc = @db.get("attach-this")
332
+ attachment = @db.fetch_attachment(doc, "couchdb.png")
333
+ (attachment == image).should be_true
334
+ #if attachment.respond_to?(:net_http_res)
335
+ # attachment.net_http_res.body.should == image
336
+ #end
337
+ end
338
+ end
339
+
340
+ describe "PUT document with attachment" do
341
+ before(:each) do
342
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
343
+ doc = {
344
+ "_id" => "mydocwithattachment",
345
+ "field" => ["some value"],
346
+ "_attachments" => {
347
+ "test.html" => {
348
+ "type" => "text/html",
349
+ "data" => @attach
350
+ }
351
+ }
352
+ }
353
+ @db.save_doc(doc)
354
+ @doc = @db.get("mydocwithattachment")
355
+ end
356
+ it "should save and be indicated" do
357
+ @doc['_attachments']['test.html']['length'].should == @attach.length
358
+ end
359
+ it "should be there" do
360
+ attachment = @db.fetch_attachment(@doc,"test.html")
361
+ attachment.should == @attach
362
+ end
363
+ end
364
+
365
+ describe "PUT document with attachment stub" do
366
+ before(:each) do
367
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
368
+ doc = {
369
+ '_id' => 'mydocwithattachment',
370
+ 'field' => ['some_value'],
371
+ '_attachments' => {
372
+ 'test.html' => {
373
+ 'type' => 'text/html', 'data' => @attach
374
+ }
375
+ }
376
+ }
377
+ @db.save_doc(doc)
378
+ doc['_rev'].should_not be_nil
379
+ doc['field'] << 'another value'
380
+ @db.save_doc(doc)["ok"].should be_true
381
+ end
382
+
383
+ it 'should be there' do
384
+ doc = @db.get('mydocwithattachment')
385
+ attachment = @db.fetch_attachment(doc, 'test.html')
386
+ Base64.decode64(attachment).should == @attach
387
+ end
388
+ end
389
+
390
+ describe "PUT document with multiple attachments" do
391
+ before(:each) do
392
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
393
+ @attach2 = "<html><head><title>Other Doc</title></head><body><p>Has more words.</p></body></html>"
394
+ @doc = {
395
+ "_id" => "mydocwithattachment",
396
+ "field" => ["some value"],
397
+ "_attachments" => {
398
+ "test.html" => {
399
+ "type" => "text/html",
400
+ "data" => @attach
401
+ },
402
+ "other.html" => {
403
+ "type" => "text/html",
404
+ "data" => @attach2
405
+ }
406
+ }
407
+ }
408
+ @db.save_doc(@doc)
409
+ @doc = @db.get("mydocwithattachment")
410
+ end
411
+ it "should save and be indicated" do
412
+ @doc['_attachments']['test.html']['length'].should == @attach.length
413
+ @doc['_attachments']['other.html']['length'].should == @attach2.length
414
+ end
415
+ it "should be there" do
416
+ attachment = @db.fetch_attachment(@doc,"test.html")
417
+ attachment.should == @attach
418
+ end
419
+ it "should be there" do
420
+ attachment = @db.fetch_attachment(@doc,"other.html")
421
+ attachment.should == @attach2
422
+ end
423
+ end
424
+
425
+ describe "DELETE an attachment directly from the database" do
426
+ before(:each) do
427
+ doc = {
428
+ '_id' => 'mydocwithattachment',
429
+ '_attachments' => {
430
+ 'test.html' => {
431
+ 'type' => 'text/html',
432
+ 'data' => "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
433
+ }
434
+ }
435
+ }
436
+ @db.save_doc(doc)
437
+ @doc = @db.get('mydocwithattachment')
438
+ end
439
+ it "should delete the attachment" do
440
+ lambda { @db.fetch_attachment(@doc,'test.html') }.should_not raise_error
441
+ @db.delete_attachment(@doc, "test.html")
442
+ @doc = @db.get('mydocwithattachment') # avoid getting a 409
443
+ lambda{ @db.fetch_attachment(@doc,'test.html')}.should raise_error
444
+ end
445
+
446
+ it "should force a delete even if we get a 409" do
447
+ @doc['new_attribute'] = 'something new'
448
+ @db.put_attachment(@doc, 'test', File.open(File.join(FIXTURE_PATH, 'attachments', 'test.html')).read)
449
+ # at this point the revision number changed, if we try to save doc one more time
450
+ # we would get a 409.
451
+ lambda{ @db.save_doc(@doc) }.should raise_error
452
+ lambda{ @db.delete_attachment(@doc, "test", true) }.should_not raise_error
453
+ end
454
+ end
455
+
456
+ describe "POST document with attachment (with funky name)" do
457
+ before(:each) do
458
+ @attach = "<html><head><title>My Funky Doc</title></head><body><p>Has words.</p></body></html>"
459
+ @doc = {
460
+ "field" => ["some other value"],
461
+ "_attachments" => {
462
+ "http://example.com/stuff.cgi?things=and%20stuff" => {
463
+ "type" => "text/html",
464
+ "data" => @attach
465
+ }
466
+ }
467
+ }
468
+ @docid = @db.save_doc(@doc)['id']
469
+ end
470
+ it "should save and be indicated" do
471
+ doc = @db.get(@docid)
472
+ doc['_attachments']['http://example.com/stuff.cgi?things=and%20stuff']['length'].should == @attach.length
473
+ end
474
+ it "should be there" do
475
+ doc = @db.get(@docid)
476
+ attachment = @db.fetch_attachment(doc,"http://example.com/stuff.cgi?things=and%20stuff")
477
+ attachment.should == @attach
478
+ end
479
+ end
480
+
481
+ describe "PUT (new document with url id)" do
482
+ it "should create the document" do
483
+ @docid = "http://example.com/stuff.cgi?things=and%20stuff"
484
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
485
+ lambda{@db.save_doc({'_id' => @docid})}.should raise_error(RestClient::Request::RequestFailed)
486
+ @db.get(@docid)['will-exist'].should == 'here'
487
+ end
488
+ end
489
+
490
+ describe "PUT (new document with id)" do
491
+ it "should start without the document" do
492
+ # r = @db.save_doc({'lemons' => 'from texas', 'and' => 'spain'})
493
+ @db.documents['rows'].each do |doc|
494
+ doc['id'].should_not == 'my-doc'
495
+ end
496
+ # should_not include({'_id' => 'my-doc'})
497
+ # this needs to be a loop over docs on content with the post
498
+ # or instead make it return something with a fancy <=> method
499
+ end
500
+ it "should create the document" do
501
+ @db.save_doc({'_id' => 'my-doc', 'will-exist' => 'here'})
502
+ lambda{@db.save_doc({'_id' => 'my-doc'})}.should raise_error(RestClient::Request::RequestFailed)
503
+ end
504
+ end
505
+
506
+ describe "PUT (existing document with rev)" do
507
+ before(:each) do
508
+ @db.save_doc({'_id' => 'my-doc', 'will-exist' => 'here'})
509
+ @doc = @db.get('my-doc')
510
+ @docid = "http://example.com/stuff.cgi?things=and%20stuff"
511
+ @db.save_doc({'_id' => @docid, 'now' => 'save'})
512
+ end
513
+ it "should start with the document" do
514
+ @doc['will-exist'].should == 'here'
515
+ @db.get(@docid)['now'].should == 'save'
516
+ end
517
+ it "should save with url id" do
518
+ doc = @db.get(@docid)
519
+ doc['yaml'] = ['json', 'word.']
520
+ @db.save_doc doc
521
+ @db.get(@docid)['yaml'].should == ['json', 'word.']
522
+ end
523
+ it "should fail to resave without the rev" do
524
+ @doc['them-keys'] = 'huge'
525
+ @doc['_rev'] = 'wrong'
526
+ # @db.save_doc(@doc)
527
+ lambda {@db.save_doc(@doc)}.should raise_error
528
+ end
529
+ it "should update the document" do
530
+ @doc['them-keys'] = 'huge'
531
+ @db.save_doc(@doc)
532
+ now = @db.get('my-doc')
533
+ now['them-keys'].should == 'huge'
534
+ end
535
+ end
536
+
537
+ describe "cached bulk save" do
538
+ it "stores documents in a database-specific cache" do
539
+ td = {"_id" => "btd1", "val" => "test"}
540
+ @db.save_doc(td, true)
541
+ @db.instance_variable_get("@bulk_save_cache").should == [td]
542
+
543
+ end
544
+
545
+ it "doesn't save to the database until the configured cache size is exceded" do
546
+ @db.bulk_save_cache_limit = 3
547
+ td1 = {"_id" => "td1", "val" => true}
548
+ td2 = {"_id" => "td2", "val" => 4}
549
+ @db.save_doc(td1, true)
550
+ @db.save_doc(td2, true)
551
+ lambda do
552
+ @db.get(td1["_id"])
553
+ end.should raise_error(RestClient::ResourceNotFound)
554
+ lambda do
555
+ @db.get(td2["_id"])
556
+ end.should raise_error(RestClient::ResourceNotFound)
557
+ td3 = {"_id" => "td3", "val" => "foo"}
558
+ @db.save_doc(td3, true)
559
+ @db.get(td1["_id"])["val"].should == td1["val"]
560
+ @db.get(td2["_id"])["val"].should == td2["val"]
561
+ @db.get(td3["_id"])["val"].should == td3["val"]
562
+ end
563
+
564
+ it "clears the bulk save cache the first time a non bulk save is requested" do
565
+ td1 = {"_id" => "blah", "val" => true}
566
+ td2 = {"_id" => "steve", "val" => 3}
567
+ @db.bulk_save_cache_limit = 50
568
+ @db.save_doc(td1, true)
569
+ lambda do
570
+ @db.get(td1["_id"])
571
+ end.should raise_error(RestClient::ResourceNotFound)
572
+ @db.save_doc(td2)
573
+ @db.get(td1["_id"])["val"].should == td1["val"]
574
+ @db.get(td2["_id"])["val"].should == td2["val"]
575
+ end
576
+ end
577
+
578
+ describe "DELETE existing document" do
579
+ before(:each) do
580
+ @r = @db.save_doc({'lemons' => 'from texas', 'and' => 'spain'})
581
+ @docid = "http://example.com/stuff.cgi?things=and%20stuff"
582
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
583
+ end
584
+ it "should work" do
585
+ doc = @db.get(@r['id'])
586
+ doc['and'].should == 'spain'
587
+ @db.delete_doc doc
588
+ lambda{@db.get @r['id']}.should raise_error
589
+ end
590
+ it "should work with uri id" do
591
+ doc = @db.get(@docid)
592
+ @db.delete_doc doc
593
+ lambda{@db.get @docid}.should raise_error
594
+ end
595
+ it "should fail without an _id" do
596
+ lambda{@db.delete_doc({"not"=>"a real doc"})}.should raise_error(ArgumentError)
597
+ end
598
+ it "should defer actual deletion when using bulk save" do
599
+ doc = @db.get(@docid)
600
+ @db.delete_doc doc, true
601
+ lambda{@db.get @docid}.should_not raise_error
602
+ @db.bulk_save
603
+ lambda{@db.get @docid}.should raise_error
604
+ end
605
+
606
+ end
607
+
608
+ describe "UPDATE existing document" do
609
+ before :each do
610
+ @id = @db.save_doc({
611
+ 'article' => 'Pete Doherty Kicked Out For Nazi Anthem',
612
+ 'upvotes' => 10,
613
+ 'link' => 'http://beatcrave.com/2009-11-30/pete-doherty-kicked-out-for-nazi-anthem/'})['id']
614
+ end
615
+ it "should work under normal conditions" do
616
+ @db.update_doc @id do |doc|
617
+ doc['upvotes'] += 1
618
+ end
619
+ @db.get(@id)['upvotes'].should == 11
620
+ end
621
+ it "should fail if update_limit is reached" do
622
+ lambda do
623
+ @db.update_doc @id do |doc|
624
+ # modify and save the doc so that a collision happens
625
+ conflicting_doc = @db.get @id
626
+ conflicting_doc['upvotes'] += 1
627
+ @db.save_doc conflicting_doc
628
+
629
+ # then try saving it through the update
630
+ doc['upvotes'] += 1
631
+ end
632
+ end.should raise_error(RestClient::RequestFailed)
633
+ end
634
+ it "should not fail if update_limit is not reached" do
635
+ limit = 5
636
+ lambda do
637
+ @db.update_doc @id do |doc|
638
+ # same as the last spec except we're only forcing 5 conflicts
639
+ if limit > 0
640
+ conflicting_doc = @db.get @id
641
+ conflicting_doc['upvotes'] += 1
642
+ @db.save_doc conflicting_doc
643
+ limit -= 1
644
+ end
645
+ doc['upvotes'] += 1
646
+ doc
647
+ end
648
+ end.should_not raise_error
649
+ @db.get(@id)['upvotes'].should == 16
650
+ end
651
+ end
652
+
653
+ describe "COPY existing document" do
654
+ before :each do
655
+ @r = @db.save_doc({'artist' => 'Zappa', 'title' => 'Muffin Man'})
656
+ @docid = 'tracks/zappa/muffin-man'
657
+ @doc = @db.get(@r['id'])
658
+ end
659
+ describe "to a new location" do
660
+ it "should work" do
661
+ @db.copy_doc @doc, @docid
662
+ newdoc = @db.get(@docid)
663
+ newdoc['artist'].should == 'Zappa'
664
+ end
665
+ it "should fail without an _id" do
666
+ lambda{@db.copy_doc({"not"=>"a real doc"})}.should raise_error(ArgumentError)
667
+ end
668
+ end
669
+ describe "to an existing location" do
670
+ before :each do
671
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
672
+ end
673
+ it "should fail without a rev" do
674
+ lambda{@db.copy_doc @doc, @docid}.should raise_error(RestClient::RequestFailed)
675
+ end
676
+ it "should succeed with a rev" do
677
+ @to_be_overwritten = @db.get(@docid)
678
+ @db.copy_doc @doc, "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
679
+ newdoc = @db.get(@docid)
680
+ newdoc['artist'].should == 'Zappa'
681
+ end
682
+ it "should succeed given the doc to overwrite" do
683
+ @to_be_overwritten = @db.get(@docid)
684
+ @db.copy_doc @doc, @to_be_overwritten
685
+ newdoc = @db.get(@docid)
686
+ newdoc['artist'].should == 'Zappa'
687
+ end
688
+ end
689
+ end
690
+
691
+
692
+ it "should list documents" do
693
+ 5.times do
694
+ @db.save_doc({'another' => 'doc', 'will-exist' => 'anywhere'})
695
+ end
696
+ ds = @db.documents
697
+ ds['rows'].should be_an_instance_of(Array)
698
+ ds['rows'][0]['id'].should_not be_nil
699
+ ds['total_rows'].should == 5
700
+ end
701
+
702
+ # This is redundant with the latest view code, but left in place for prosterity.
703
+ describe "documents / _all_docs" do
704
+ before(:each) do
705
+ 9.times do |i|
706
+ @db.save_doc({'_id' => "doc#{i}",'another' => 'doc', 'will-exist' => 'here'})
707
+ end
708
+ end
709
+ it "should list documents with keys and such" do
710
+ ds = @db.documents
711
+ ds['rows'].should be_an_instance_of(Array)
712
+ ds['rows'][0]['id'].should == "doc0"
713
+ ds['total_rows'].should == 9
714
+ end
715
+ it "should take query params" do
716
+ ds = @db.documents(:startkey => 'doc0', :endkey => 'doc3')
717
+ ds['rows'].length.should == 4
718
+ ds = @db.documents(:key => 'doc0')
719
+ ds['rows'].length.should == 1
720
+ end
721
+ it "should work with multi-key" do
722
+ rs = @db.documents :keys => ["doc0", "doc7"]
723
+ rs['rows'].length.should == 2
724
+ end
725
+ it "should work with include_docs" do
726
+ ds = @db.documents(:startkey => 'doc0', :endkey => 'doc3', :include_docs => true)
727
+ ds['rows'][0]['doc']['another'].should == "doc"
728
+ end
729
+ it "should have the bulk_load macro" do
730
+ rs = @db.bulk_load ["doc0", "doc7"]
731
+ rs['rows'].length.should == 2
732
+ rs['rows'][0]['doc']['another'].should == "doc"
733
+ end
734
+ end
735
+
736
+
737
+ describe "#compact" do
738
+ # Can cause failures in recent versions of CouchDB, just ensure
739
+ # we actually send the right command.
740
+ it "should compact the database" do
741
+ CouchRest.should_receive(:post).with("#{@cr.uri}/couchrest-test/_compact")
742
+ @cr.database('couchrest-test').compact!
743
+ end
744
+ end
745
+
746
+ describe "deleting a database" do
747
+ it "should start with the test database" do
748
+ @cr.databases.should include('couchrest-test')
749
+ end
750
+ it "should delete the database" do
751
+ db = @cr.database('couchrest-test')
752
+ r = db.delete!
753
+ r['ok'].should == true
754
+ @cr.databases.should_not include('couchrest-test')
755
+ end
756
+ end
757
+
758
+ #
759
+ # Replicating databases is often a time consuming process, so instead of
760
+ # trying to send commands to CouchDB, we just validate that the post
761
+ # command contains the correct parameters.
762
+ #
763
+
764
+ describe "simply replicating a database" do
765
+ before(:each) do
766
+ @other_db = @cr.database(REPLICATIONDB)
767
+ end
768
+
769
+ it "should replicate via pulling" do
770
+ CouchRest.should_receive(:post).with(
771
+ include("/_replicate"),
772
+ include(
773
+ :create_target => false,
774
+ :continuous => false,
775
+ :source => "#{@cr.uri}/#{@db.name}",
776
+ :target => @other_db.name
777
+ )
778
+ )
779
+ @other_db.replicate_from @db
780
+ end
781
+
782
+ it "should replicate via pushing" do
783
+ CouchRest.should_receive(:post).with(
784
+ include("/_replicate"),
785
+ include(
786
+ :create_target => false,
787
+ :continuous => false,
788
+ :source => @db.name,
789
+ :target => "#{@cr.uri}/#{@other_db.name}"
790
+ )
791
+ )
792
+ @db.replicate_to @other_db
793
+ end
794
+
795
+ it "should replacicate with a specific doc" do
796
+ CouchRest.should_receive(:post).with(
797
+ include("/_replicate"),
798
+ include(
799
+ :create_target => false,
800
+ :continuous => false,
801
+ :doc_ids => ['test_doc'],
802
+ :source => @db.name,
803
+ :target => "#{@cr.uri}/#{@other_db.name}"
804
+ )
805
+ )
806
+ @db.replicate_to @other_db, false, false, ['test_doc']
807
+ end
808
+
809
+ describe "implicitly creating target" do
810
+ it "should replicate via pulling" do
811
+ CouchRest.should_receive(:post).with(
812
+ include("/_replicate"),
813
+ include(
814
+ :create_target => true,
815
+ :continuous => false
816
+ )
817
+ )
818
+ @other_db.replicate_from(@db, false, true)
819
+ end
820
+
821
+ it "should replicate via pushing" do
822
+ CouchRest.should_receive(:post).with(
823
+ include("/_replicate"),
824
+ include(
825
+ :create_target => true,
826
+ :continuous => false
827
+ )
828
+ )
829
+ @db.replicate_to(@other_db, false, true)
830
+ end
831
+ end
832
+
833
+ describe "continuous replication" do
834
+ it "should replicate via pulling" do
835
+ CouchRest.should_receive(:post).with(
836
+ include("/_replicate"),
837
+ include(
838
+ :create_target => false,
839
+ :continuous => true
840
+ )
841
+ )
842
+ @other_db.replicate_from(@db, true)
843
+ end
844
+
845
+ it "should replicate via pushing" do
846
+ CouchRest.should_receive(:post).with(
847
+ include("/_replicate"),
848
+ include(
849
+ :create_target => false,
850
+ :continuous => true
851
+ )
852
+ )
853
+ @db.replicate_to(@other_db, true)
854
+ end
855
+ end
856
+ end
857
+
858
+
859
+ describe "#create!" do
860
+ before(:each) do
861
+ @db = @cr.database('couchrest-test-db_to_create')
862
+ @db.delete! if @cr.databases.include?('couchrest-test-db_to_create')
863
+ end
864
+
865
+ it "should just work fine" do
866
+ @cr.databases.should_not include('couchrest-test-db_to_create')
867
+ @db.create!
868
+ @cr.databases.should include('couchrest-test-db_to_create')
869
+ end
870
+ end
871
+
872
+ describe "#recreate!" do
873
+ before(:each) do
874
+ @db = @cr.database('couchrest-test-db_to_create')
875
+ @db2 = @cr.database('couchrest-test-db_to_recreate')
876
+ @cr.databases.include?(@db.name) ? nil : @db.create!
877
+ @cr.databases.include?(@db2.name) ? @db2.delete! : nil
878
+ end
879
+
880
+ it "should drop and recreate a database" do
881
+ @cr.databases.should include(@db.name)
882
+ @db.recreate!
883
+ @cr.databases.should include(@db.name)
884
+ end
885
+
886
+ it "should recreate a db even though it doesn't exist" do
887
+ @cr.databases.should_not include(@db2.name)
888
+ @db2.recreate!
889
+ @cr.databases.should include(@db2.name)
890
+ end
891
+ end
892
+
893
+ describe "searching a database" do
894
+ before(:each) do
895
+ search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
896
+ 'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); ret.add(doc['age'], {'field':'age'}); return ret; }" }
897
+ @db.save_doc({'_id' => '_design/search', 'fulltext' => {'people' => search_function}})
898
+ @db.save_doc({'_id' => 'john', 'name' => 'John', 'age' => '31'})
899
+ @db.save_doc({'_id' => 'jack', 'name' => 'Jack', 'age' => '32'})
900
+ @db.save_doc({'_id' => 'dave', 'name' => 'Dave', 'age' => '33'})
901
+ end
902
+
903
+ it "should be able to search a database using couchdb-lucene" do
904
+ if couchdb_lucene_available?
905
+ result = @db.search('search/people', :q => 'name:J*')
906
+ doc_ids = result['rows'].collect{ |row| row['id'] }
907
+ doc_ids.size.should == 2
908
+ doc_ids.should include('john')
909
+ doc_ids.should include('jack')
910
+ end
911
+ end
912
+ end
913
+
914
+ end