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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README +41 -0
- data/Rakefile +8 -0
- data/analysand.gemspec +30 -0
- data/bin/analysand +28 -0
- data/lib/analysand.rb +5 -0
- data/lib/analysand/bulk_response.rb +14 -0
- data/lib/analysand/change_watcher.rb +276 -0
- data/lib/analysand/connection_testing.rb +46 -0
- data/lib/analysand/database.rb +492 -0
- data/lib/analysand/errors.rb +26 -0
- data/lib/analysand/instance.rb +156 -0
- data/lib/analysand/response.rb +45 -0
- data/lib/analysand/version.rb +3 -0
- data/lib/analysand/view_response.rb +24 -0
- data/script/setup_database.rb +45 -0
- data/spec/analysand/a_session_grantor.rb +40 -0
- data/spec/analysand/change_watcher_spec.rb +84 -0
- data/spec/analysand/database_spec.rb +228 -0
- data/spec/analysand/database_writing_spec.rb +478 -0
- data/spec/analysand/instance_spec.rb +86 -0
- data/spec/analysand/response_spec.rb +22 -0
- data/spec/analysand/view_response_spec.rb +33 -0
- data/spec/fixtures/vcr_cassettes/get_session_does_not_refresh_cookie.yml +73 -0
- data/spec/fixtures/vcr_cassettes/get_session_refreshes_cookie.yml +75 -0
- data/spec/fixtures/vcr_cassettes/head_request_with_etag.yml +40 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/database_access.rb +40 -0
- data/spec/support/example_isolation.rb +86 -0
- data/spec/support/test_parameters.rb +39 -0
- metadata +276 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'analysand/errors'
|
4
|
+
require 'analysand/instance'
|
5
|
+
require 'uri'
|
6
|
+
require 'vcr'
|
7
|
+
|
8
|
+
require File.expand_path('../a_session_grantor', __FILE__)
|
9
|
+
|
10
|
+
module Analysand
|
11
|
+
describe Instance do
|
12
|
+
describe '#initialize' do
|
13
|
+
it 'requires an absolute URI' do
|
14
|
+
lambda { Instance.new(URI("/abc")) }.should raise_error(InvalidURIError)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:instance) { Instance.new(instance_uri) }
|
19
|
+
|
20
|
+
describe '#establish_session' do
|
21
|
+
describe 'given admin credentials' do
|
22
|
+
let(:credentials) { admin_credentials }
|
23
|
+
|
24
|
+
it_should_behave_like 'a session grantor'
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'given member credentials' do
|
28
|
+
let(:credentials) { member1_credentials }
|
29
|
+
|
30
|
+
it_should_behave_like 'a session grantor'
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'given incorrect credentials' do
|
34
|
+
it 'returns [nil, response]' do
|
35
|
+
session, resp = instance.establish_session('wrong', 'wrong')
|
36
|
+
|
37
|
+
session.should be_nil
|
38
|
+
resp.code.should == '401'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#renew_session' do
|
44
|
+
let(:credentials) { admin_credentials }
|
45
|
+
|
46
|
+
before do
|
47
|
+
@session, _ = instance.establish_session(credentials[:username], credentials[:password])
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'if CouchDB refreshes the session cookie' do
|
51
|
+
around do |example|
|
52
|
+
VCR.use_cassette('get_session_refreshes_cookie') { example.call }
|
53
|
+
end
|
54
|
+
|
55
|
+
it_should_behave_like 'a session grantor' do
|
56
|
+
let(:result) { instance.renew_session(@session) }
|
57
|
+
let(:role_locator) do
|
58
|
+
lambda { |resp| resp['userCtx']['roles'] }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'if CouchDB does not refresh the session cookie' do
|
64
|
+
around do |example|
|
65
|
+
VCR.use_cassette('get_session_does_not_refresh_cookie') { example.call }
|
66
|
+
end
|
67
|
+
|
68
|
+
it_should_behave_like 'a session grantor' do
|
69
|
+
let(:result) { instance.renew_session(@session) }
|
70
|
+
let(:role_locator) do
|
71
|
+
lambda { |resp| resp['userCtx']['roles'] }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'given an invalid session' do
|
77
|
+
it 'returns [nil, response]' do
|
78
|
+
session, resp = instance.renew_session({ :token => 'AuthSession=wrong' })
|
79
|
+
|
80
|
+
session.should be_nil
|
81
|
+
resp.code.should == '400'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'analysand/database'
|
4
|
+
require 'analysand/response'
|
5
|
+
|
6
|
+
module Analysand
|
7
|
+
describe Response do
|
8
|
+
let(:db) { Database.new(database_uri) }
|
9
|
+
|
10
|
+
describe '#etag' do
|
11
|
+
let(:response) do
|
12
|
+
VCR.use_cassette('head_request_with_etag') do
|
13
|
+
db.head('abc123', admin_credentials)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'removes quotes from ETags' do
|
18
|
+
response.etag.should == '1-967a00dff5e02add41819138abb3284d'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'analysand/view_response'
|
4
|
+
|
5
|
+
module Analysand
|
6
|
+
describe ViewResponse do
|
7
|
+
describe '#docs' do
|
8
|
+
let(:resp_with_docs) do
|
9
|
+
'{"rows": [{"id":"foo","key":"foo","value":{},"doc":{"foo":"bar"}}]}'
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:resp_without_docs) do
|
13
|
+
'{"rows": [{"id":"foo","key":"foo","value":{}}]}'
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'if the view includes docs' do
|
17
|
+
subject { ViewResponse.new(stub(:body => resp_with_docs)) }
|
18
|
+
|
19
|
+
it 'returns the value of the "doc" key in each row' do
|
20
|
+
subject.docs.should == [{'foo' => 'bar'}]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'if the view does not include docs' do
|
25
|
+
subject { ViewResponse.new(stub(:body => resp_without_docs)) }
|
26
|
+
|
27
|
+
it 'returns []' do
|
28
|
+
subject.docs.should == []
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: http://localhost:5984/_session
|
6
|
+
body:
|
7
|
+
string: name=admin&password=admin
|
8
|
+
headers:
|
9
|
+
Connection:
|
10
|
+
- keep-alive
|
11
|
+
Accept:
|
12
|
+
- "*/*"
|
13
|
+
Keep-Alive:
|
14
|
+
- 30
|
15
|
+
Content-Type:
|
16
|
+
- application/x-www-form-urlencoded
|
17
|
+
response:
|
18
|
+
status:
|
19
|
+
code: 200
|
20
|
+
message: OK
|
21
|
+
headers:
|
22
|
+
Date:
|
23
|
+
- Sat, 31 Mar 2012 22:35:18 GMT
|
24
|
+
Set-Cookie:
|
25
|
+
- AuthSession=YWRtaW46NEY3Nzg2QTY6WVqDNxuHpu4OPp07rib-n9WQLt8; Version=1; Path=/; HttpOnly
|
26
|
+
Server:
|
27
|
+
- CouchDB/1.3.0a-d0a5bf0-git (Erlang OTP/R14B02)
|
28
|
+
Content-Length:
|
29
|
+
- "56"
|
30
|
+
Content-Type:
|
31
|
+
- text/plain; charset=utf-8
|
32
|
+
Cache-Control:
|
33
|
+
- must-revalidate
|
34
|
+
body:
|
35
|
+
string: |
|
36
|
+
{"ok":true,"name":null,"roles":["_admin","site_admin"]}
|
37
|
+
recorded_at: Sat, 31 Mar 2012 22:35:18 GMT
|
38
|
+
- request:
|
39
|
+
method: get
|
40
|
+
uri: http://localhost:5984/_session
|
41
|
+
headers:
|
42
|
+
Cookie:
|
43
|
+
- AuthSession=YWRtaW46NEY3Nzg2QTY6WVqDNxuHpu4OPp07rib-n9WQLt8; Version=1; Path=/; HttpOnly
|
44
|
+
Connection:
|
45
|
+
- keep-alive
|
46
|
+
Accept:
|
47
|
+
- "*/*"
|
48
|
+
Keep-Alive:
|
49
|
+
- 30
|
50
|
+
Content-Type:
|
51
|
+
- application/x-www-form-urlencoded
|
52
|
+
response:
|
53
|
+
status:
|
54
|
+
code: 200
|
55
|
+
message: OK
|
56
|
+
headers:
|
57
|
+
Date:
|
58
|
+
- Sat, 31 Mar 2012 22:35:18 GMT
|
59
|
+
Server:
|
60
|
+
- CouchDB/1.3.0a-d0a5bf0-git (Erlang OTP/R14B02)
|
61
|
+
Content-Length:
|
62
|
+
- "56"
|
63
|
+
Content-Type:
|
64
|
+
- text/plain; charset=utf-8
|
65
|
+
Cache-Control:
|
66
|
+
- must-revalidate
|
67
|
+
body:
|
68
|
+
string: |
|
69
|
+
{"ok":true,"userCtx":{"name":null,"roles":["_admin","site_admin"]},"info":{"authentication_db":"_users","authentication_handlers":["oauth","cookie","default"]}}
|
70
|
+
|
71
|
+
http_version:
|
72
|
+
recorded_at: Sat, 31 Mar 2012 22:35:18 GMT
|
73
|
+
recorded_with: VCR 2.0.1
|
@@ -0,0 +1,75 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: http://localhost:5984/_session
|
6
|
+
body:
|
7
|
+
string: name=admin&password=admin
|
8
|
+
headers:
|
9
|
+
Connection:
|
10
|
+
- keep-alive
|
11
|
+
Accept:
|
12
|
+
- "*/*"
|
13
|
+
Keep-Alive:
|
14
|
+
- 30
|
15
|
+
Content-Type:
|
16
|
+
- application/x-www-form-urlencoded
|
17
|
+
response:
|
18
|
+
status:
|
19
|
+
code: 200
|
20
|
+
message: OK
|
21
|
+
headers:
|
22
|
+
Date:
|
23
|
+
- Sat, 31 Mar 2012 22:35:18 GMT
|
24
|
+
Set-Cookie:
|
25
|
+
- AuthSession=YWRtaW46NEY3Nzg2QTY6WVqDNxuHpu4OPp07rib-n9WQLt8; Version=1; Path=/; HttpOnly
|
26
|
+
Server:
|
27
|
+
- CouchDB/1.3.0a-d0a5bf0-git (Erlang OTP/R14B02)
|
28
|
+
Content-Length:
|
29
|
+
- "56"
|
30
|
+
Content-Type:
|
31
|
+
- text/plain; charset=utf-8
|
32
|
+
Cache-Control:
|
33
|
+
- must-revalidate
|
34
|
+
body:
|
35
|
+
string: |
|
36
|
+
{"ok":true,"name":null,"roles":["_admin","site_admin"]}
|
37
|
+
recorded_at: Sat, 31 Mar 2012 22:35:18 GMT
|
38
|
+
- request:
|
39
|
+
method: get
|
40
|
+
uri: http://localhost:5984/_session
|
41
|
+
headers:
|
42
|
+
Cookie:
|
43
|
+
- AuthSession=YWRtaW46NEY3Nzg2QTY6WVqDNxuHpu4OPp07rib-n9WQLt8; Version=1; Path=/; HttpOnly
|
44
|
+
Connection:
|
45
|
+
- keep-alive
|
46
|
+
Accept:
|
47
|
+
- "*/*"
|
48
|
+
Keep-Alive:
|
49
|
+
- 30
|
50
|
+
Content-Type:
|
51
|
+
- application/x-www-form-urlencoded
|
52
|
+
response:
|
53
|
+
status:
|
54
|
+
code: 200
|
55
|
+
message: OK
|
56
|
+
headers:
|
57
|
+
Date:
|
58
|
+
- Sat, 31 Mar 2012 22:35:18 GMT
|
59
|
+
Set-Cookie:
|
60
|
+
- AuthSession=YWRtaW46NEY3Nzg3RTk6Lj1LSc4dWHBMZoFKm9ZMxq0L3Ec; Version=1; Path=/; HttpOnly
|
61
|
+
Server:
|
62
|
+
- CouchDB/1.3.0a-d0a5bf0-git (Erlang OTP/R14B02)
|
63
|
+
Content-Length:
|
64
|
+
- "56"
|
65
|
+
Content-Type:
|
66
|
+
- text/plain; charset=utf-8
|
67
|
+
Cache-Control:
|
68
|
+
- must-revalidate
|
69
|
+
body:
|
70
|
+
string: |
|
71
|
+
{"ok":true,"userCtx":{"name":null,"roles":["_admin","site_admin"]},"info":{"authentication_db":"_users","authentication_handlers":["oauth","cookie","default"]}}
|
72
|
+
|
73
|
+
http_version:
|
74
|
+
recorded_at: Sat, 31 Mar 2012 22:35:18 GMT
|
75
|
+
recorded_with: VCR 2.0.1
|
@@ -0,0 +1,40 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: head
|
5
|
+
uri: http://admin:admin@localhost:5984/analysand_test/abc123
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- ! '*/*'
|
12
|
+
User-Agent:
|
13
|
+
- Ruby
|
14
|
+
Connection:
|
15
|
+
- keep-alive
|
16
|
+
Keep-Alive:
|
17
|
+
- 30
|
18
|
+
response:
|
19
|
+
status:
|
20
|
+
code: 200
|
21
|
+
message: OK
|
22
|
+
headers:
|
23
|
+
Server:
|
24
|
+
- CouchDB/1.3.0a-d6ab08d-git (Erlang OTP/R15B)
|
25
|
+
Etag:
|
26
|
+
- ! '"1-967a00dff5e02add41819138abb3284d"'
|
27
|
+
Date:
|
28
|
+
- Tue, 22 May 2012 14:21:26 GMT
|
29
|
+
Content-Type:
|
30
|
+
- text/plain; charset=utf-8
|
31
|
+
Content-Length:
|
32
|
+
- '61'
|
33
|
+
Cache-Control:
|
34
|
+
- must-revalidate
|
35
|
+
body:
|
36
|
+
encoding: US-ASCII
|
37
|
+
string: ''
|
38
|
+
http_version:
|
39
|
+
recorded_at: Tue, 22 May 2012 14:21:26 GMT
|
40
|
+
recorded_with: VCR 2.0.1
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
|
3
|
+
require File.expand_path('../support/database_access', __FILE__)
|
4
|
+
require File.expand_path('../support/example_isolation', __FILE__)
|
5
|
+
require File.expand_path('../support/test_parameters', __FILE__)
|
6
|
+
|
7
|
+
require 'vcr'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.include DatabaseAccess
|
11
|
+
config.include ExampleIsolation
|
12
|
+
config.include TestParameters
|
13
|
+
end
|
14
|
+
|
15
|
+
VCR.configure do |c|
|
16
|
+
c.allow_http_connections_when_no_cassette = true
|
17
|
+
c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
|
18
|
+
c.hook_into :webmock
|
19
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module DatabaseAccess
|
4
|
+
##
|
5
|
+
# Sets members and admins for the database.
|
6
|
+
#
|
7
|
+
# Assumes #admin_credentials returns a Hash containing the keys :username and
|
8
|
+
# :password, where those keys' values are the username and password of a
|
9
|
+
# CouchDB admin user.
|
10
|
+
def set_security(members, admins = {})
|
11
|
+
doc = {
|
12
|
+
'members' => members,
|
13
|
+
'admins' => admins
|
14
|
+
}
|
15
|
+
|
16
|
+
credentials = admin_credentials
|
17
|
+
uri = instance_uri
|
18
|
+
|
19
|
+
Net::HTTP.start(uri.host, uri.port) do |h|
|
20
|
+
req = Net::HTTP::Put.new("/#{database_name}/_security")
|
21
|
+
req.basic_auth(credentials[:username], credentials[:password])
|
22
|
+
req.body = doc.to_json
|
23
|
+
|
24
|
+
resp = h.request(req)
|
25
|
+
|
26
|
+
unless Net::HTTPSuccess === resp
|
27
|
+
raise "Unable to set security parameters on #{database_name}: response code = #{resp.code}, body = #{resp.body}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Resets member and admin lists for the test database to [].
|
34
|
+
def clear_security
|
35
|
+
set_security({ 'users' => [], 'roles' => [] },
|
36
|
+
{ 'users' => [], 'roles' => [] })
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# vim:ts=2:sw=2:et:tw=78
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require File.expand_path('../test_parameters', __FILE__)
|
2
|
+
|
3
|
+
##
|
4
|
+
# CouchDB doesn't implement a commit/rollback scheme like other databases, but
|
5
|
+
# it would nevertheless still sometimes be nice to be able to isolate
|
6
|
+
# datastore changes made between tests.
|
7
|
+
#
|
8
|
+
# This module provides methods to drop and create databases, which provides a
|
9
|
+
# slow-yet-effective way of achieving said isolation. Examples:
|
10
|
+
#
|
11
|
+
# describe Something do
|
12
|
+
# let(:database_uri) { 'http://localhost:5984' }
|
13
|
+
# let(:database_name) { 'something' }
|
14
|
+
#
|
15
|
+
# before do
|
16
|
+
# clean_databases!
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# describe AnotherThing do
|
21
|
+
# let(:database_uri) { 'http://localhost:5984' }
|
22
|
+
# let(:database_names) { ['another', 'database'] }
|
23
|
+
#
|
24
|
+
# before do
|
25
|
+
# clean_databases!
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# If both database_name and database_names are specified in the same example
|
30
|
+
# group, the latter will have precedence.
|
31
|
+
#
|
32
|
+
#
|
33
|
+
# Clean databases come with a performance cost
|
34
|
+
# ============================================
|
35
|
+
#
|
36
|
+
# Dropping and creating databases has a real performance cost, so it is
|
37
|
+
# advised that you only use this when absolutely necessary. Databases are
|
38
|
+
# only cleaned when
|
39
|
+
#
|
40
|
+
# clean_databases!
|
41
|
+
#
|
42
|
+
# is included in an example group's before block.
|
43
|
+
module ExampleIsolation
|
44
|
+
def clean_databases!
|
45
|
+
drop_databases!
|
46
|
+
create_databases!
|
47
|
+
end
|
48
|
+
|
49
|
+
def drop_databases!
|
50
|
+
affected_databases.each do |db|
|
51
|
+
uri = instance_uri + "/#{db}"
|
52
|
+
credentials = admin_credentials
|
53
|
+
|
54
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
55
|
+
req = Net::HTTP::Delete.new(uri.path)
|
56
|
+
req.basic_auth(admin_username, admin_password)
|
57
|
+
|
58
|
+
http.request(req)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_databases!
|
64
|
+
affected_databases.each do |db|
|
65
|
+
uri = instance_uri + "/#{db}"
|
66
|
+
credentials = admin_credentials
|
67
|
+
|
68
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
69
|
+
req = Net::HTTP::Put.new(uri.path)
|
70
|
+
req.basic_auth(admin_username, admin_password)
|
71
|
+
|
72
|
+
http.request(req)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def affected_databases
|
78
|
+
if respond_to?(:database_name)
|
79
|
+
[database_name]
|
80
|
+
elsif respond_to?(:database_names)
|
81
|
+
database_names
|
82
|
+
end.flatten
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# vim:ts=2:sw=2:et:tw=78
|