pwl 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.travis.yml +3 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +20 -0
- data/README.md +102 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/bin/pwl +268 -0
- data/lib/pwl/dialog/base.rb +37 -0
- data/lib/pwl/dialog/cocoa.rb +67 -0
- data/lib/pwl/dialog/console.rb +39 -0
- data/lib/pwl/dialog/gnome.rb +27 -0
- data/lib/pwl/dialog.rb +50 -0
- data/lib/pwl/message.rb +46 -0
- data/lib/pwl/password_policy.rb +29 -0
- data/lib/pwl/store.rb +284 -0
- data/lib/pwl.rb +14 -0
- data/pwl.gemspec +94 -0
- data/templates/export.html.erb +61 -0
- data/test/acceptance/test_basics.rb +15 -0
- data/test/acceptance/test_delete.rb +14 -0
- data/test/acceptance/test_dialogs.rb +45 -0
- data/test/acceptance/test_export.rb +42 -0
- data/test/acceptance/test_get.rb +17 -0
- data/test/acceptance/test_init.rb +81 -0
- data/test/acceptance/test_list.rb +30 -0
- data/test/acceptance/test_passwd.rb +23 -0
- data/test/acceptance/test_put.rb +19 -0
- data/test/fixtures/test_all.html +71 -0
- data/test/fixtures/test_empty.html +56 -0
- data/test/helper.rb +79 -0
- data/test/unit/test_error.rb +29 -0
- data/test/unit/test_message.rb +34 -0
- data/test/unit/test_store_construction.rb +62 -0
- data/test/unit/test_store_crud.rb +90 -0
- data/test/unit/test_store_metadata.rb +35 -0
- data/test/unit/test_store_password_policy.rb +61 -0
- data/test/unit/test_store_security.rb +34 -0
- metadata +156 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'nokogiri/diff'
|
3
|
+
|
4
|
+
# Tests `pwl export`
|
5
|
+
class TestExport < Test::Pwl::AppTestCase
|
6
|
+
def test_empty
|
7
|
+
fixture = fixture("test_empty.html").gsub('CREATED_STAMP', DateTime.now.strftime('%F %R')).gsub('MODIFIED_STAMP', 'never').gsub('DATABASE_FILE', store_file)
|
8
|
+
assert_successful_html(fixture, 'export')
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_all
|
12
|
+
test_vector = Hash['foo', 'one', 'bar', 'two', 'Chuck Norris', 'Roundhouse Kick']
|
13
|
+
test_vector.each{|k,v|
|
14
|
+
assert_successful('', "put '#{k}' '#{v}'")
|
15
|
+
}
|
16
|
+
|
17
|
+
now = DateTime.now.strftime('%F %R')
|
18
|
+
fixture = fixture("test_all.html").gsub('CREATED_STAMP', now).gsub('MODIFIED_STAMP', now).gsub('DATABASE_FILE', store_file)
|
19
|
+
|
20
|
+
assert_successful_html(fixture, 'export')
|
21
|
+
end
|
22
|
+
|
23
|
+
def assert_successful_html(expected_out, cmd, password = store_password)
|
24
|
+
out, err, rc = execute(cmd, password)
|
25
|
+
assert_equal(0, rc.exitstatus, "Expected exit status 0, but it was #{rc.exitstatus}. STDERR was: #{err}")
|
26
|
+
assert(err.empty?, "Expected empty STDERR, but it yielded #{err}")
|
27
|
+
|
28
|
+
actual = Nokogiri::HTML(out)
|
29
|
+
expected = Nokogiri::HTML(expected_out)
|
30
|
+
|
31
|
+
differences = actual.diff(expected, :added => true, :removed => true)
|
32
|
+
|
33
|
+
assert_equal(0, differences.count, "Unexpected differences in output. Diff:\n" << differences.collect{|change, node|
|
34
|
+
case change
|
35
|
+
when '+'
|
36
|
+
"Actual: #{node.to_html}"
|
37
|
+
when '-'
|
38
|
+
"Expected: #{node.to_html}"
|
39
|
+
end
|
40
|
+
}.join("\n"))
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
# Tests `pwl get`
|
4
|
+
class TestGet < Test::Pwl::AppTestCase
|
5
|
+
def test_get_unknown_key
|
6
|
+
assert_error('No entry was found for foo', 'get foo')
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_get_blank_key
|
10
|
+
assert_error('may not be blank', 'get')
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_get_known_key
|
14
|
+
assert_successful('', 'put foo bar') # TODO Use a fixture instead of put
|
15
|
+
assert_successful('^bar$', 'get foo')
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'pty'
|
3
|
+
require 'expect'
|
4
|
+
|
5
|
+
class TestInit < Test::Pwl::AppTestCase
|
6
|
+
#
|
7
|
+
# Tests that initing with two matching passwords succeeds
|
8
|
+
#
|
9
|
+
# A session is expected to look like this:
|
10
|
+
#
|
11
|
+
# $ bin/pwl init --force --verbose
|
12
|
+
# Enter new master password:
|
13
|
+
# **************
|
14
|
+
# Enter master password again:
|
15
|
+
# **************
|
16
|
+
# Successfully initialized new store at ...
|
17
|
+
# $
|
18
|
+
#
|
19
|
+
def test_matching_passwords
|
20
|
+
cmd = "bin/pwl init --force --verbose --file \"#{@store_file}\""
|
21
|
+
|
22
|
+
PTY.spawn(cmd){|pwl_out, pwl_in, pid|
|
23
|
+
assert_response('Enter new master password:', pwl_out)
|
24
|
+
pwl_in.puts("secr3tPassw0rd")
|
25
|
+
|
26
|
+
assert_response('Enter master password again:', pwl_out)
|
27
|
+
pwl_in.puts("secr3tPassw0rd")
|
28
|
+
|
29
|
+
assert_response('Successfully initialized new store', pwl_out)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Tests that initing with two passwords that don't match fails
|
35
|
+
#
|
36
|
+
# A session is expected to look like this (using s3cretPassw0rd at the first and secr3tPassw0rd at the second password prompt):
|
37
|
+
#
|
38
|
+
# $ bin/pwl init --force --verbose
|
39
|
+
# Enter new master password:
|
40
|
+
# **************
|
41
|
+
# Enter master password again:
|
42
|
+
# **************
|
43
|
+
# Passwords do not match.
|
44
|
+
# $
|
45
|
+
#
|
46
|
+
def test_unmatching_passwords
|
47
|
+
cmd = "bin/pwl init --force --verbose --file \"#{store_file}\""
|
48
|
+
|
49
|
+
PTY.spawn(cmd){|pwl_out, pwl_in, pid|
|
50
|
+
assert_response('Enter new master password:', pwl_out)
|
51
|
+
pwl_in.puts("s3cretPassw0rd")
|
52
|
+
|
53
|
+
assert_response('Enter master password again:', pwl_out)
|
54
|
+
pwl_in.puts("secr3tPassw0rd")
|
55
|
+
|
56
|
+
assert_response('Passwords do not match\.', pwl_out)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Tests that initing an existing store without --force does not touch the existing store
|
62
|
+
#
|
63
|
+
def test_exists
|
64
|
+
assert_error('already exists', "init")
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Tests that cancelling a forced re-init does not change the store file
|
69
|
+
#
|
70
|
+
def test_cancel
|
71
|
+
# TODO
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def assert_response(expected, actual_io)
|
77
|
+
if !actual_io.expect(/#{expected}/, 1)
|
78
|
+
raise StandardError.new("Expected response '#{expected}' was not matched")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
# Tests `pwl list`
|
4
|
+
class TestList < Test::Pwl::AppTestCase
|
5
|
+
def test_list_empty
|
6
|
+
assert_empty(store.list)
|
7
|
+
assert_error('^$', 'list')
|
8
|
+
assert_error('^List is empty\.$', 'list --verbose')
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_list_all
|
12
|
+
test_vector = Hash['foo', 'one', 'bar', 'two', 'Chuck Norris', 'Roundhouse Kick']
|
13
|
+
test_vector.each{|k,v|
|
14
|
+
assert_successful('', "put '#{k}' '#{v}'")
|
15
|
+
}
|
16
|
+
|
17
|
+
assert_successful(test_vector.keys.join('-'), 'list -s "-"')
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_list_filter
|
21
|
+
test_vector = Hash['foo', 'one', 'foot', 'two', 'Homer Simpson', 'Apu Nahasapeemapetilon']
|
22
|
+
test_vector.each{|k,v|
|
23
|
+
assert_successful('', "put '#{k}' '#{v}'")
|
24
|
+
}
|
25
|
+
|
26
|
+
filter = 'foo'
|
27
|
+
expected = test_vector.keys.select{|k,v| k =~ /#{filter}/}.join(',')
|
28
|
+
assert_successful("^#{expected}$", "list #{filter} -s ,")
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
# Tests `pwl passwd`
|
4
|
+
class TestPasswd < Test::Pwl::AppTestCase
|
5
|
+
def test_blank_password
|
6
|
+
assert_error('May not be blank', 'passwd')
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_standard
|
10
|
+
assert_successful('', 'put foo bar')
|
11
|
+
|
12
|
+
new_pwd = store_password.reverse
|
13
|
+
|
14
|
+
# If we are in a pipe (and we are in these tests), the new password is expected as first arg
|
15
|
+
assert_successful('^$', "passwd \"#{new_pwd}\"")
|
16
|
+
|
17
|
+
# the old password must not work anymore
|
18
|
+
assert_error('The master password is wrong', 'list')
|
19
|
+
|
20
|
+
# re-open with the changed password
|
21
|
+
assert_successful('^bar$', 'get foo', new_pwd)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
# Tests `pwl put`
|
4
|
+
class TestPut < Test::Pwl::AppTestCase
|
5
|
+
def test_put_blank_key
|
6
|
+
assert_error('may not be blank', 'put')
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_put_simple
|
10
|
+
assert_successful('', 'put foo bar')
|
11
|
+
assert_successful('^bar$', 'get foo')
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_put_update
|
15
|
+
assert_successful('', 'put foo bar')
|
16
|
+
assert_successful('', 'put foo baz') # just do it twice
|
17
|
+
assert_successful('^baz$', 'get foo')
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<!DOCTYPE HTML>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
5
|
+
<title>Password Manager Export</title>
|
6
|
+
<style type="text/css">
|
7
|
+
table { page-break-inside:auto}
|
8
|
+
tr { page-break-inside:avoid; page-break-after:auto}
|
9
|
+
thead { display:table-header-group}
|
10
|
+
tfoot { display:table-footer-group; font-size: 0.85em;}
|
11
|
+
|
12
|
+
/* adapted from http://coding.smashingmagazine.com/2008/08/13/top-10-css-table-designs/ */
|
13
|
+
body{
|
14
|
+
font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
|
15
|
+
font-size: 12px;
|
16
|
+
margin: 45px;
|
17
|
+
width: 480px;
|
18
|
+
border-collapse: collapse;
|
19
|
+
text-align: left;
|
20
|
+
}
|
21
|
+
th{
|
22
|
+
font-size: 14px;
|
23
|
+
font-weight: bold;
|
24
|
+
padding: 10px 8px;
|
25
|
+
border-bottom: 2px solid;
|
26
|
+
}
|
27
|
+
td{
|
28
|
+
border-bottom: 1px solid;
|
29
|
+
padding: 6px 8px;
|
30
|
+
}
|
31
|
+
</style>
|
32
|
+
</head>
|
33
|
+
<body>
|
34
|
+
<h1>Password Manager Export</h1>
|
35
|
+
<table>
|
36
|
+
<thead>
|
37
|
+
<tr>
|
38
|
+
<th>Name</th>
|
39
|
+
<th>Value</th>
|
40
|
+
</tr>
|
41
|
+
</thead>
|
42
|
+
<tfoot>
|
43
|
+
<tr>
|
44
|
+
<td colspan="2">
|
45
|
+
This <a href="http://rdoc.info/github/nerab/pwl/master/frames">pwl</a> export was created on CREATED_STAMP.
|
46
|
+
<br/>
|
47
|
+
The database at DATABASE_FILE was last modified MODIFIED_STAMP.
|
48
|
+
</td>
|
49
|
+
</tr>
|
50
|
+
</tfoot>
|
51
|
+
<tbody>
|
52
|
+
|
53
|
+
<tr>
|
54
|
+
<td>foo</td>
|
55
|
+
<td>one</td>
|
56
|
+
</tr>
|
57
|
+
|
58
|
+
<tr>
|
59
|
+
<td>bar</td>
|
60
|
+
<td>two</td>
|
61
|
+
</tr>
|
62
|
+
|
63
|
+
<tr>
|
64
|
+
<td>Chuck Norris</td>
|
65
|
+
<td>Roundhouse Kick</td>
|
66
|
+
</tr>
|
67
|
+
|
68
|
+
</tbody>
|
69
|
+
</table>
|
70
|
+
</body>
|
71
|
+
</html>
|
@@ -0,0 +1,56 @@
|
|
1
|
+
<!DOCTYPE HTML>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
5
|
+
<title>Password Manager Export</title>
|
6
|
+
<style type="text/css">
|
7
|
+
table { page-break-inside:auto}
|
8
|
+
tr { page-break-inside:avoid; page-break-after:auto}
|
9
|
+
thead { display:table-header-group}
|
10
|
+
tfoot { display:table-footer-group; font-size: 0.85em;}
|
11
|
+
|
12
|
+
/* adapted from http://coding.smashingmagazine.com/2008/08/13/top-10-css-table-designs/ */
|
13
|
+
body{
|
14
|
+
font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
|
15
|
+
font-size: 12px;
|
16
|
+
margin: 45px;
|
17
|
+
width: 480px;
|
18
|
+
border-collapse: collapse;
|
19
|
+
text-align: left;
|
20
|
+
}
|
21
|
+
th{
|
22
|
+
font-size: 14px;
|
23
|
+
font-weight: bold;
|
24
|
+
padding: 10px 8px;
|
25
|
+
border-bottom: 2px solid;
|
26
|
+
}
|
27
|
+
td{
|
28
|
+
border-bottom: 1px solid;
|
29
|
+
padding: 6px 8px;
|
30
|
+
}
|
31
|
+
</style>
|
32
|
+
</head>
|
33
|
+
<body>
|
34
|
+
<h1>Password Manager Export</h1>
|
35
|
+
<table>
|
36
|
+
<thead>
|
37
|
+
<tr>
|
38
|
+
<th>Name</th>
|
39
|
+
<th>Value</th>
|
40
|
+
</tr>
|
41
|
+
</thead>
|
42
|
+
<tfoot>
|
43
|
+
<tr>
|
44
|
+
<td colspan="2">
|
45
|
+
This <a href="http://rdoc.info/github/nerab/pwl/master/frames">pwl</a> export was created on CREATED_STAMP.
|
46
|
+
<br/>
|
47
|
+
The database at DATABASE_FILE was never modified.
|
48
|
+
</td>
|
49
|
+
</tr>
|
50
|
+
</tfoot>
|
51
|
+
<tbody>
|
52
|
+
|
53
|
+
</tbody>
|
54
|
+
</table>
|
55
|
+
</body>
|
56
|
+
</html>
|
data/test/helper.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler'
|
6
|
+
begin
|
7
|
+
Bundler.setup(:default, :development)
|
8
|
+
rescue Bundler::BundlerError => e
|
9
|
+
$stderr.puts e.message
|
10
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
+
exit e.status_code
|
12
|
+
end
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
16
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
17
|
+
require 'pwl'
|
18
|
+
require 'tmpdir'
|
19
|
+
|
20
|
+
class Test::Unit::TestCase
|
21
|
+
FIXTURES_DIR = File.join(File.dirname(__FILE__), 'fixtures')
|
22
|
+
end
|
23
|
+
|
24
|
+
module Test
|
25
|
+
module Pwl
|
26
|
+
class TestCase < Test::Unit::TestCase
|
27
|
+
attr_reader :store, :store_file
|
28
|
+
|
29
|
+
def setup
|
30
|
+
@store_file = temp_file_name
|
31
|
+
@store = ::Pwl::Store.new(@store_file, store_password)
|
32
|
+
end
|
33
|
+
|
34
|
+
def teardown
|
35
|
+
File.unlink(@store_file)
|
36
|
+
end
|
37
|
+
|
38
|
+
def store_password
|
39
|
+
's3cret passw0rd'
|
40
|
+
end
|
41
|
+
|
42
|
+
# Make up a name of a file that does not exist in ENV['TMPDIR'] yet
|
43
|
+
def temp_file_name
|
44
|
+
begin
|
45
|
+
result = File.join(Dir.tmpdir, "#{self.class.name}-#{Random.rand}.pstore")
|
46
|
+
end while File.exists?(result)
|
47
|
+
result
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class AppTestCase < TestCase
|
52
|
+
APP = 'bin/pwl'
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def assert_successful(expected_out, cmd, password = store_password)
|
57
|
+
out, err, rc = execute(cmd, password)
|
58
|
+
assert_equal(0, rc.exitstatus, "Expected exit status 0, but it was #{rc.exitstatus}. STDERR was: #{err}")
|
59
|
+
assert(err.empty?, "Expected empty STDERR, but it yielded #{err}")
|
60
|
+
assert(out =~ /#{expected_out}/, "'#{out}' did not match expected response '#{expected_out}'")
|
61
|
+
end
|
62
|
+
|
63
|
+
def assert_error(expected_err, cmd, password = store_password)
|
64
|
+
out, err, rc = execute(cmd, password)
|
65
|
+
assert_not_equal(0, rc.exitstatus, "Expected non-zero exit status, but it was #{rc.exitstatus}. STDOUT was: #{out}")
|
66
|
+
assert(out.empty?, "Expected empty STDOUT, but it yielded #{out}")
|
67
|
+
assert(err =~ /#{expected_err}/, "'#{err}' did not match expected response '#{expected_err}'")
|
68
|
+
end
|
69
|
+
|
70
|
+
def execute(cmd, password)
|
71
|
+
Open3.capture3("echo \"#{password}\" | #{APP} #{cmd} --file \"#{store_file}\"")
|
72
|
+
end
|
73
|
+
|
74
|
+
def fixture(name)
|
75
|
+
File.read(File.join(FIXTURES_DIR, name))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestError < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@message = Pwl::ErrorMessage.new("<%= first %> <%= last %>", 1, :first => 'FIRSTNAME', :last => 'LASTNAME')
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_code_0
|
9
|
+
assert_raise Pwl::ReservedMessageCodeError do
|
10
|
+
Pwl::ErrorMessage.new('', 0)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_to_s
|
15
|
+
assert_equal('Mislav Marohnic', @message.to_s(:first => "Mislav", "last" => "Marohnic"))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_to_s_empty
|
19
|
+
assert_equal('FIRSTNAME LASTNAME', @message.to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_code
|
23
|
+
assert_equal(1, @message.exit_code)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_to_s_extra
|
27
|
+
assert_equal('Apu Nahasapeemapetilon', @message.to_s({:first => 'Apu', 'last' => 'Nahasapeemapetilon', :worklocation => 'Kwik-E-Mart'}))
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestMessageZero < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@msg = Pwl::Message.new("Name: <%= first %> <%= last %>", 0, :first => 'FIRSTNAME', :last => 'LASTNAME')
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_to_s
|
9
|
+
assert_equal('Name: Mislav Marohnic', @msg.to_s(:first => "Mislav", "last" => "Marohnic"))
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_to_s_default
|
13
|
+
assert_equal('Name: FIRSTNAME LASTNAME', @msg.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_code
|
17
|
+
assert_equal(0, @msg.exit_code)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_to_s_extra
|
21
|
+
assert_equal('Name: Apu Nahasapeemapetilon', @msg.to_s({:first => 'Apu', 'last' => 'Nahasapeemapetilon', :worklocation => 'Kwik-E-Mart'}))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TestMessageNonZero < Test::Unit::TestCase
|
26
|
+
def setup
|
27
|
+
@code = Random.rand(255)
|
28
|
+
@msg = Pwl::Message.new("Name: <%= first %> <%= last %>", @code)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_code
|
32
|
+
assert_equal(@code, @msg.exit_code)
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
class TestStoreConstruction < Test::Pwl::TestCase
|
5
|
+
def test_existing_store
|
6
|
+
assert_raise Pwl::Store::FileAlreadyExistsError do
|
7
|
+
Pwl::Store.new(store_file, store_password)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_nonexisting_store
|
12
|
+
assert_raise Pwl::Store::FileNotFoundError do
|
13
|
+
Pwl::Store.open(temp_file_name, store_password)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# fake level (increasingly close to structure)
|
18
|
+
USER = 1 # user root exists
|
19
|
+
SYSTEM = 2 # user and system root exists
|
20
|
+
CREATED = 3 # user and system root exists, system root contains created stamp
|
21
|
+
SALT = 4 # like above, plus salt is set to random value
|
22
|
+
|
23
|
+
def test_existing_uninitialized_store
|
24
|
+
{USER => Pwl::Store::NotInitializedError,
|
25
|
+
SYSTEM => Pwl::Store::NotInitializedError,
|
26
|
+
CREATED => Pwl::Store::NotInitializedError,
|
27
|
+
SALT => Pwl::Store::WrongMasterPasswordError,
|
28
|
+
}.each{|fake_level, error| assert assert_uninitialized(fake_level, error)}
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def assert_uninitialized(fake_level, error)
|
34
|
+
existing_file = Tempfile.new(self.class.name)
|
35
|
+
|
36
|
+
begin
|
37
|
+
fake_store(existing_file, fake_level)
|
38
|
+
|
39
|
+
assert_raise(error) do
|
40
|
+
Pwl::Store.open(existing_file, store_password)
|
41
|
+
end
|
42
|
+
ensure
|
43
|
+
existing_file.close
|
44
|
+
existing_file.unlink
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Pretend that we have a store with correct layout
|
49
|
+
def fake_store(existing_file, fake_level)
|
50
|
+
store = PStore.new(existing_file)
|
51
|
+
store.transaction{
|
52
|
+
store.commit if fake_level < USER
|
53
|
+
store[:user] = {}
|
54
|
+
store.commit if fake_level < SYSTEM
|
55
|
+
store[:system] = {}
|
56
|
+
store.commit if fake_level < CREATED
|
57
|
+
store[:system][:created] = DateTime.now
|
58
|
+
store.commit if fake_level < SALT
|
59
|
+
store[:system][:salt] = Random.rand.to_s
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestStoreCRUD < Test::Pwl::TestCase
|
4
|
+
def test_put_get
|
5
|
+
store.put('foo', 'bar')
|
6
|
+
assert_equal('bar', store.get('foo'))
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_get_blank
|
10
|
+
assert_raise Pwl::Store::BlankKeyError do
|
11
|
+
store.get('')
|
12
|
+
end
|
13
|
+
|
14
|
+
assert_raise Pwl::Store::BlankKeyError do
|
15
|
+
store.get(nil)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_put_get_blank
|
20
|
+
assert_raise Pwl::Store::BlankValueError do
|
21
|
+
store.put('empty', '')
|
22
|
+
end
|
23
|
+
|
24
|
+
assert_raise Pwl::Store::BlankValueError do
|
25
|
+
store.put('nil', nil)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_unknown_key
|
30
|
+
assert_raise Pwl::Store::KeyNotFoundError do
|
31
|
+
store.get('foo')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_list_empty
|
36
|
+
assert_empty(store.list)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_list
|
40
|
+
test_vector = Hash['foo', 'one', 'bar', 'two', 'Chuck Norris', 'Roundhouse Kick']
|
41
|
+
test_vector.each{|k,v| store.put(k, v)}
|
42
|
+
assert_equal(test_vector.keys, store.list)
|
43
|
+
store.list.each{|key|
|
44
|
+
assert_equal(test_vector[key], store.get(key))
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_all
|
49
|
+
test_vector = Hash['foo', 'one', 'bar', 'two', 'Chuck Norris', 'Roundhouse Kick']
|
50
|
+
test_vector.each{|k,v| store.put(k, v)}
|
51
|
+
assert_equal(test_vector, store.all)
|
52
|
+
store.all.each{|k,v|
|
53
|
+
assert_equal(test_vector[k], v)
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_list_filter
|
58
|
+
test_vector = Hash['foo', 'one', 'bar', 'two', 'Chuck Norris', 'Roundhouse Kick']
|
59
|
+
test_vector.each{|k,v| store.put(k, v)}
|
60
|
+
|
61
|
+
filter = 'foo bar'
|
62
|
+
expected = test_vector.keys.select{|k,v| k =~ /#{filter}/}
|
63
|
+
assert_equal(expected, store.list(filter))
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_delete
|
67
|
+
store.put('foo', 'bar')
|
68
|
+
assert_equal('bar', store.delete('foo'))
|
69
|
+
|
70
|
+
assert_raise Pwl::Store::KeyNotFoundError do
|
71
|
+
store.get('foo')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_delete_blank
|
76
|
+
assert_raise Pwl::Store::BlankKeyError do
|
77
|
+
store.delete('')
|
78
|
+
end
|
79
|
+
|
80
|
+
assert_raise Pwl::Store::BlankKeyError do
|
81
|
+
store.delete(nil)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_delete_unknown_key
|
86
|
+
assert_raise Pwl::Store::KeyNotFoundError do
|
87
|
+
store.delete('foo')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestStoreConstruction < Test::Pwl::TestCase
|
4
|
+
# when comparing timestamps, allow not more than this difference in seconds
|
5
|
+
TIMESTAMP_PRECISION = 1
|
6
|
+
|
7
|
+
def test_created
|
8
|
+
assert_equal(nil, store.last_accessed)
|
9
|
+
assert_in_delta(DateTime.now.to_time.to_i, store.created.to_time.to_i, TIMESTAMP_PRECISION)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_last_accessed
|
13
|
+
assert_equal(nil, store.last_accessed)
|
14
|
+
store.put('foobar', 'barfoot')
|
15
|
+
assert_equal(nil, store.last_accessed)
|
16
|
+
store.get('foobar')
|
17
|
+
assert_in_delta(DateTime.now.to_time.to_i, store.last_accessed.to_time.to_i, TIMESTAMP_PRECISION)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_last_accessed_nonexisting
|
21
|
+
assert_equal(nil, store.last_accessed)
|
22
|
+
assert_raise Pwl::Store::KeyNotFoundError do
|
23
|
+
store.get('foobar')
|
24
|
+
end
|
25
|
+
|
26
|
+
# Make sure a failed read is not counted as last_accessed
|
27
|
+
assert_equal(nil, store.last_accessed)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_last_modified
|
31
|
+
assert_equal(nil, store.last_modified)
|
32
|
+
store.put('foobar', 'barfoot')
|
33
|
+
assert_in_delta(DateTime.now.to_time.to_i, store.last_modified.to_time.to_i, TIMESTAMP_PRECISION)
|
34
|
+
end
|
35
|
+
end
|