pwl 0.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/.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
|