analysand 1.0.1

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,478 @@
1
+ require 'spec_helper'
2
+
3
+ require 'analysand/database'
4
+ require 'analysand/errors'
5
+
6
+ module Analysand
7
+ describe Database do
8
+ let(:db) { Database.new(database_uri) }
9
+
10
+ let(:doc_id) { 'abc123' }
11
+ let(:doc) do
12
+ { 'foo' => 'bar' }
13
+ end
14
+
15
+ before do
16
+ clean_databases!
17
+ clear_security
18
+ end
19
+
20
+ shared_examples_for '#put success examples' do
21
+ def put(*args)
22
+ db.send(method, *args)
23
+ end
24
+
25
+ it 'creates documents' do
26
+ put(doc_id, doc)
27
+
28
+ db.get(doc_id).body['foo'].should == 'bar'
29
+ end
30
+
31
+ it 'returns success on document creation' do
32
+ resp = put(doc_id, doc)
33
+
34
+ resp.should be_success
35
+ end
36
+
37
+ it 'ignores the _id attribute in documents' do
38
+ doc.update('_id' => 'wrong')
39
+ put(doc_id, doc)
40
+
41
+ db.get(doc_id).body['foo'].should == 'bar'
42
+ db.get('wrong').should_not be_success
43
+ end
44
+
45
+ it 'passes credentials' do
46
+ set_security({ 'users' => [member1_username] })
47
+
48
+ put(doc_id, doc, member1_credentials)
49
+ db.get(doc_id).should be_success
50
+ end
51
+
52
+ it 'passes the batch option' do
53
+ resp = put(doc_id, doc, nil, :batch => 'ok')
54
+
55
+ resp.code.should == '202'
56
+ end
57
+
58
+ it 'updates documents' do
59
+ resp = put(doc_id, doc)
60
+
61
+ doc['bar'] = 'baz'
62
+ doc['_rev'] = resp['rev']
63
+
64
+ put(doc_id, doc)
65
+ db.get(doc_id)['bar'].should == 'baz'
66
+ end
67
+
68
+ it 'returns success on document update' do
69
+ resp = put(doc_id, doc)
70
+
71
+ doc['bar'] = 'baz'
72
+ doc['_rev'] = resp['rev']
73
+
74
+ resp = put(doc_id, doc)
75
+ resp.should be_success
76
+ end
77
+
78
+ it 'escapes document IDs' do
79
+ db.put('an ID', doc)
80
+
81
+ db.get('an ID').should be_success
82
+ end
83
+
84
+ it 'handles URN-like IDs' do
85
+ db.put('org.couchdb.doc:one', doc)
86
+
87
+ db.get('org.couchdb.doc:one').should be_success
88
+ end
89
+ end
90
+
91
+ describe '#create' do
92
+ before do
93
+ drop_databases!
94
+ end
95
+
96
+ it 'creates a database' do
97
+ db.create(admin_credentials)
98
+
99
+ db.ping.should be_success
100
+ end
101
+
102
+ it 'returns success' do
103
+ db.create(admin_credentials).should be_success
104
+ end
105
+ end
106
+
107
+ describe '#drop' do
108
+ it 'drops the database' do
109
+ db.drop(admin_credentials)
110
+
111
+ db.ping.should_not be_success
112
+ end
113
+
114
+ it 'returns success' do
115
+ db.drop(admin_credentials).should be_success
116
+ end
117
+ end
118
+
119
+ describe '#drop!' do
120
+ it 'drops the database' do
121
+ db.drop(admin_credentials)
122
+
123
+ db.ping.should_not be_success
124
+ end
125
+
126
+ it 'raises Analysand::CannotDropDatabase on failure' do
127
+ lambda { db.drop!(member1_credentials) }.should raise_error(Analysand::CannotDropDatabase)
128
+ end
129
+ end
130
+
131
+ describe '#put' do
132
+ it_should_behave_like '#put success examples' do
133
+ let(:method) { :put }
134
+ end
135
+
136
+ describe 'on update conflict' do
137
+ before do
138
+ db.put(doc_id, doc)
139
+ end
140
+
141
+ it 'returns the error code' do
142
+ resp = db.put(doc_id, doc)
143
+
144
+ resp.code.should == '409'
145
+ end
146
+
147
+ it 'returns the error body' do
148
+ resp = db.put(doc_id, doc)
149
+
150
+ resp.body.should have_key('error')
151
+ end
152
+ end
153
+ end
154
+
155
+ describe '#put!' do
156
+ it_should_behave_like '#put success examples' do
157
+ let(:method) { :put! }
158
+ end
159
+
160
+ describe 'on update conflict' do
161
+ before do
162
+ db.put(doc_id, doc)
163
+ end
164
+
165
+ it 'raises Analysand::DocumentNotSaved' do
166
+ lambda { db.put!(doc_id, doc) }.should raise_error(Analysand::DocumentNotSaved) { |e|
167
+ e.response.code.should == '409'
168
+ }
169
+ end
170
+ end
171
+ end
172
+
173
+ describe '#ensure_full_commit' do
174
+ before do
175
+ db.put('abc', {}, :batch => :ok)
176
+ end
177
+
178
+ it 'returns success' do
179
+ db.ensure_full_commit.should be_success
180
+ end
181
+
182
+ it 'flushes batched PUTs' do
183
+ db.ensure_full_commit
184
+
185
+ db.get('abc').should be_success
186
+ end
187
+
188
+ it 'accepts credentials' do
189
+ lambda { db.ensure_full_commit(member1_credentials) }.should_not raise_error(ArgumentError)
190
+ end
191
+
192
+ it 'accepts a seq parameter' do
193
+ lambda { db.ensure_full_commit(member1_credentials, :seq => 10) }.should_not raise_error(ArgumentError)
194
+ end
195
+ end
196
+
197
+ describe '#put_attachment' do
198
+ let(:string) { 'an attachment' }
199
+ let(:io) { StringIO.new(string) }
200
+
201
+ it 'creates attachments' do
202
+ db.put_attachment("#{doc_id}/attachment", io)
203
+
204
+ db.get_attachment("#{doc_id}/attachment").body.should == string
205
+ end
206
+
207
+ it 'returns success when the attachment is uploaded' do
208
+ resp = db.put_attachment("#{doc_id}/attachment", io)
209
+
210
+ resp.should be_success
211
+ end
212
+
213
+ it 'passes credentials' do
214
+ set_security({ 'users' => [member1_username] })
215
+
216
+ resp = db.put_attachment("#{doc_id}/a", io, member1_credentials)
217
+ resp.should be_success
218
+ end
219
+
220
+ it 'sends the rev of the target document' do
221
+ resp = db.put!(doc_id, doc)
222
+ rev = resp['rev']
223
+
224
+ resp = db.put_attachment("#{doc_id}/a", io, nil, :rev => rev)
225
+ resp.should be_success
226
+ end
227
+
228
+ it 'sends the content type of the attachment' do
229
+ db.put_attachment("#{doc_id}/a", io, nil, :content_type => 'text/plain')
230
+
231
+ type = db.get_attachment("#{doc_id}/a").get_fields('Content-Type')
232
+ type.should == ['text/plain']
233
+ end
234
+ end
235
+
236
+ describe '#bulk_docs' do
237
+ let(:doc1) { { '_id' => 'doc1', 'foo' => 'bar' } }
238
+ let(:doc2) { { '_id' => 'doc2', 'bar' => 'baz' } }
239
+
240
+ it 'creates many documents' do
241
+ db.bulk_docs([doc1, doc2])
242
+
243
+ db.get('doc1')['foo'].should == 'bar'
244
+ db.get('doc2')['bar'].should == 'baz'
245
+ end
246
+
247
+ it 'updates many documents' do
248
+ r1 = db.put!('doc1', doc1)
249
+ r2 = db.put!('doc2', doc2)
250
+
251
+ doc1['foo'] = 'qux'
252
+ doc2['bar'] = 'quux'
253
+ doc1['_rev'] = r1['rev']
254
+ doc2['_rev'] = r2['rev']
255
+
256
+ db.bulk_docs([doc1, doc2])
257
+
258
+ db.get('doc1')['foo'].should == 'qux'
259
+ db.get('doc2')['bar'].should == 'quux'
260
+ end
261
+
262
+ it 'deletes many documents' do
263
+ r1 = db.put!('doc1', doc1)
264
+ r2 = db.put!('doc2', doc2)
265
+
266
+ d1 = { '_id' => 'doc1', '_rev' => r1['rev'], '_deleted' => true }
267
+ d2 = { '_id' => 'doc2', '_rev' => r2['rev'], '_deleted' => true }
268
+
269
+ db.bulk_docs([d1, d2])
270
+
271
+ db.get('doc1').code.should == '404'
272
+ db.get('doc2').code.should == '404'
273
+ end
274
+
275
+ it 'updates and deletes documents' do
276
+ r1 = db.put!('doc1', doc1)
277
+ r2 = db.put!('doc2', doc2)
278
+
279
+ d1 = { '_id' => 'doc1', '_rev' => r1['rev'], '_deleted' => true }
280
+ d2 = { '_id' => 'doc2', '_rev' => r2['rev'], 'bar' => 'quux' }
281
+
282
+ db.bulk_docs([d1, d2])
283
+
284
+ db.get('doc1').code.should == '404'
285
+ db.get('doc2')['bar'].should == 'quux'
286
+ end
287
+
288
+ it 'returns success if all operations succeeded' do
289
+ resp = db.bulk_docs([doc1, doc2])
290
+
291
+ resp.should be_success
292
+ end
293
+
294
+ it 'returns non-success if one operation had an error' do
295
+ db.put!('doc1', doc1)
296
+
297
+ resp = db.bulk_docs([doc1, doc2])
298
+
299
+ resp.should_not be_success
300
+ end
301
+
302
+ it 'passes credentials' do
303
+ set_security({ 'users' => [member1_username] })
304
+
305
+ resp = db.bulk_docs([doc1, doc2], member1_credentials)
306
+
307
+ resp.should be_success
308
+ end
309
+
310
+ it 'operates in non-atomic mode by default' do
311
+ db.put!('doc1', doc1)
312
+ db.bulk_docs([doc1, doc2])
313
+
314
+ db.get('doc2').should be_success
315
+ end
316
+
317
+ it 'supports all-or-nothing mode' do
318
+ # Force a validation failure to check that all-or-nothing mode is
319
+ # properly enabled.
320
+ db.put!('doc1', doc1)
321
+ db.put!('_design/validation', {
322
+ 'validate_doc_update' => 'function(){throw({forbidden: ""});}'
323
+ }, admin_credentials)
324
+
325
+ db.bulk_docs([doc1, doc2], nil, :all_or_nothing => true)
326
+
327
+ db.get('doc2').code.should == '404'
328
+ end
329
+ end
330
+
331
+ describe '#bulk_docs!' do
332
+ let(:doc1) { { '_id' => 'doc1', 'foo' => 'bar' } }
333
+ let(:doc2) { { '_id' => 'doc2', 'bar' => 'baz' } }
334
+
335
+ it 'returns success if all operations succeeded' do
336
+ resp = db.bulk_docs!([doc1, doc2])
337
+
338
+ resp.should be_success
339
+ end
340
+
341
+ describe 'if an operation fails' do
342
+ before do
343
+ doc2['_id'] = 'doc1'
344
+ end
345
+
346
+ it 'raises Analysand::BulkOperationFailed' do
347
+ lambda { db.bulk_docs!([doc1, doc2]) }.should raise_error(Analysand::BulkOperationFailed)
348
+ end
349
+ end
350
+ end
351
+
352
+ describe '#copy' do
353
+ before do
354
+ db.put!(doc_id, doc)
355
+ end
356
+
357
+ it 'copies one doc to another ID' do
358
+ db.copy(doc_id, 'bar')
359
+
360
+ db.get('bar')['foo'].should == 'bar'
361
+ end
362
+
363
+ it 'returns success if copy succeeds' do
364
+ resp = db.copy(doc_id, 'bar')
365
+
366
+ resp.should be_success
367
+ end
368
+
369
+ it 'returns failure if copy fails' do
370
+ db.put!('bar', {})
371
+ resp = db.copy(doc_id, 'bar')
372
+
373
+ resp.code.should == '409'
374
+ end
375
+
376
+ it 'overwrites documents' do
377
+ resp = db.put!('bar', {})
378
+ db.copy(doc_id, "bar?rev=#{resp['rev']}")
379
+
380
+ db.get('bar')['foo'].should == 'bar'
381
+ end
382
+
383
+ it 'passes credentials' do
384
+ set_security({ 'users' => [member1_username] })
385
+
386
+ db.copy(doc_id, 'bar', member1_credentials)
387
+ db.get('bar')['foo'].should == 'bar'
388
+ end
389
+
390
+ it 'escapes document IDs in URIs' do
391
+ db.copy(doc_id, 'an ID')
392
+
393
+ db.get('an ID')['foo'].should == 'bar'
394
+ end
395
+ end
396
+
397
+ shared_examples_for '#delete success examples' do
398
+ let(:rev) { @put_resp['rev'] }
399
+
400
+ before do
401
+ @put_resp = db.put!(doc_id, doc)
402
+ end
403
+
404
+ def delete(*args)
405
+ db.send(method, *args)
406
+ end
407
+
408
+ it 'deletes documents' do
409
+ db.delete(doc_id, rev)
410
+
411
+ db.get(doc_id).code.should == '404'
412
+ end
413
+
414
+ it 'returns success on deletion' do
415
+ resp = db.delete(doc_id, rev)
416
+
417
+ resp.should be_success
418
+ end
419
+
420
+ it 'passes credentials' do
421
+ set_security({ 'users' => [member1_username] })
422
+
423
+ resp = db.delete(doc_id, rev, member1_credentials)
424
+
425
+ resp.should be_success
426
+ end
427
+
428
+ it 'escapes document IDs in URIs' do
429
+ @put_resp = db.put!('an ID', doc)
430
+
431
+ resp = db.delete('an ID', rev)
432
+ resp.should be_success
433
+ end
434
+ end
435
+
436
+ describe '#delete' do
437
+ it_should_behave_like '#delete success examples' do
438
+ let(:method) { :delete }
439
+ end
440
+
441
+ describe 'on update conflict' do
442
+ before do
443
+ db.put!(doc_id, doc)
444
+ end
445
+
446
+ it 'returns the error code' do
447
+ resp = db.delete(doc_id, nil)
448
+
449
+ resp.code.should == '400'
450
+ end
451
+
452
+ it 'returns the error body' do
453
+ resp = db.delete(doc_id, nil)
454
+
455
+ resp.body.should have_key('error')
456
+ end
457
+ end
458
+ end
459
+
460
+ describe '#delete!' do
461
+ it_should_behave_like '#delete success examples' do
462
+ let(:method) { :delete! }
463
+ end
464
+
465
+ describe 'on update conflict' do
466
+ before do
467
+ db.put!(doc_id, doc)
468
+ end
469
+
470
+ it 'raises Analysand::DocumentNotDeleted' do
471
+ lambda { db.delete!(doc_id, nil) }.should raise_error(Analysand::DocumentNotDeleted) { |e|
472
+ e.response.code.should == '400'
473
+ }
474
+ end
475
+ end
476
+ end
477
+ end
478
+ end