pwl 0.0.1

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