dpla-couchrest 1.2.1.pre.dpla

Sign up to get free protection for your applications and to get access to all the features.
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