cloud_encrypted_sync 0.1.1 → 0.1.2
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/Gemfile.lock +1 -1
- data/bin/ces +1 -1
- data/lib/cloud_encrypted_sync/adapter_template.rb +2 -5
- data/lib/cloud_encrypted_sync/configuration.rb +1 -1
- data/lib/cloud_encrypted_sync/cryptographer.rb +0 -5
- data/lib/cloud_encrypted_sync/dummy_adapter.rb +3 -3
- data/lib/cloud_encrypted_sync/errors.rb +0 -1
- data/lib/cloud_encrypted_sync/index.rb +5 -5
- data/lib/cloud_encrypted_sync/{master.rb → synchronizer.rb} +16 -24
- data/lib/cloud_encrypted_sync/version.rb +1 -1
- data/lib/cloud_encrypted_sync.rb +7 -7
- data/test/test_helper.rb +27 -12
- data/test/unit/adapter_liaison_test.rb +35 -14
- data/test/unit/adapter_template_test.rb +30 -0
- data/test/unit/configuration_test.rb +11 -3
- data/test/unit/cryptographer_test.rb +6 -21
- data/test/unit/index_test.rb +47 -0
- data/test/unit/synchronizer_test.rb +169 -0
- metadata +9 -5
- data/test/unit/master_test.rb +0 -138
data/Gemfile.lock
CHANGED
data/bin/ces
CHANGED
@@ -38,14 +38,11 @@ module CloudEncryptedSync
|
|
38
38
|
|
39
39
|
def register_subclass_with_parent(subclass)
|
40
40
|
name = formated_name_of(subclass)
|
41
|
-
|
42
|
-
raise Errors::RegistrationError.new("#{name} already registered")
|
43
|
-
else
|
44
|
-
children[name] = subclass
|
45
|
-
end
|
41
|
+
children[name] ||= subclass
|
46
42
|
end
|
47
43
|
|
48
44
|
def formated_name_of(subclass)
|
45
|
+
puts "Subclass: #{subclass}"
|
49
46
|
subclass.name.match(/([^:]+)$/)[0].underscore.to_sym
|
50
47
|
end
|
51
48
|
end
|
@@ -57,7 +57,7 @@ module CloudEncryptedSync
|
|
57
57
|
|
58
58
|
@option_parser = OptionParser.new do |opts|
|
59
59
|
opts.banner = "Usage: #{executable_name} [options] /path/to/folder/to/sync"
|
60
|
-
opts.on('--data-dir PATH',"Data directory where
|
60
|
+
opts.on('--data-dir PATH',"Data directory where indexes and config file are found.") do |path|
|
61
61
|
clo[:data_dir] = path
|
62
62
|
end
|
63
63
|
opts.on('--adapter ADAPTERNAME', 'Name of cloud adapter to use.') do |adapter_name|
|
@@ -4,7 +4,6 @@ module CloudEncryptedSync
|
|
4
4
|
|
5
5
|
class << self
|
6
6
|
|
7
|
-
|
8
7
|
def encrypt_data(data)
|
9
8
|
iv = generate_random_iv
|
10
9
|
encrypted_data = crypt_data(:encrypt, iv, data)
|
@@ -21,10 +20,6 @@ module CloudEncryptedSync
|
|
21
20
|
Digest::SHA2.hexdigest(data,512)
|
22
21
|
end
|
23
22
|
|
24
|
-
def generate_random_key
|
25
|
-
initialized_cipher.random_key.unpack('H*')[0]
|
26
|
-
end
|
27
|
-
|
28
23
|
#######
|
29
24
|
private
|
30
25
|
#######
|
@@ -5,12 +5,12 @@ module CloudEncryptedSync
|
|
5
5
|
class << self
|
6
6
|
|
7
7
|
def write(data,key)
|
8
|
-
stored_data[key] = data
|
8
|
+
stored_data[bucket_name][key] = data
|
9
9
|
end
|
10
10
|
|
11
11
|
def parse_command_line_options(opts,command_line_options)
|
12
12
|
opts.on('--bucket BUCKETNAME', 'Name of cloud adapter to use.') do |bucket_name|
|
13
|
-
command_line_options[:
|
13
|
+
command_line_options[:bucket] = bucket_name
|
14
14
|
end
|
15
15
|
return command_line_options
|
16
16
|
end
|
@@ -37,7 +37,7 @@ module CloudEncryptedSync
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def bucket_name
|
40
|
-
Configuration.settings[:
|
40
|
+
Configuration.settings[:bucket].to_sym
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -18,15 +18,15 @@ module CloudEncryptedSync
|
|
18
18
|
def write
|
19
19
|
local_hash = compile_local_hash #recompile
|
20
20
|
liaison.push(local_hash.to_yaml,index_key) #push to remote
|
21
|
-
File.open(
|
21
|
+
File.open(index_path, 'w') { |file| YAML.dump(local_hash, file) } #save to local
|
22
22
|
end
|
23
23
|
|
24
24
|
def full_file_path(relative_path)
|
25
|
-
normalized_sync_path+
|
25
|
+
normalized_sync_path+relative_path
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
"#{Configuration.data_folder_path}/#{
|
28
|
+
def index_path
|
29
|
+
"#{Configuration.data_folder_path}/#{index_filename}"
|
30
30
|
end
|
31
31
|
|
32
32
|
def file_key(full_path)
|
@@ -60,7 +60,7 @@ module CloudEncryptedSync
|
|
60
60
|
@index_key ||= Cryptographer.hash_data(Configuration.settings[:encryption_key])
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
63
|
+
def index_filename
|
64
64
|
"#{normalized_sync_path.gsub(/[^A-Za-z0-9]/,'_')}.index.yml"
|
65
65
|
end
|
66
66
|
|
@@ -1,27 +1,23 @@
|
|
1
1
|
module CloudEncryptedSync
|
2
|
-
class
|
2
|
+
class Synchronizer
|
3
3
|
|
4
4
|
class << self
|
5
5
|
attr_accessor :finalize_required
|
6
6
|
|
7
|
-
def
|
8
|
-
sync
|
9
|
-
end
|
10
|
-
|
11
|
-
def sync
|
7
|
+
def run
|
12
8
|
begin
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
delete_local_files
|
10
|
+
delete_remote_files
|
11
|
+
pull_files
|
12
|
+
push_files
|
13
|
+
finalize
|
18
14
|
rescue Errors::IncompleteConfigurationError => exception
|
19
15
|
puts exception.message
|
20
16
|
end
|
21
17
|
end
|
22
18
|
|
23
|
-
def push_files
|
24
|
-
progress_meter = ProgressMeter.new(
|
19
|
+
def push_files
|
20
|
+
progress_meter = ProgressMeter.new(files_to_push.keys.size,:label => 'Pushing Files: ')
|
25
21
|
pushed_files_counter = 0
|
26
22
|
files_to_push.each_pair do |key,relative_path|
|
27
23
|
puts #newline for progress meter
|
@@ -38,7 +34,7 @@ module CloudEncryptedSync
|
|
38
34
|
end
|
39
35
|
end
|
40
36
|
|
41
|
-
def pull_files
|
37
|
+
def pull_files
|
42
38
|
progress_meter = ProgressMeter.new(files_to_pull.keys.size,:label => 'Pulling Files: ')
|
43
39
|
pulled_files_counter = 0
|
44
40
|
files_to_pull.each_pair do |key,relative_path|
|
@@ -46,7 +42,7 @@ module CloudEncryptedSync
|
|
46
42
|
puts #newline for progress meter
|
47
43
|
if File.exist?(full_path) and (Index.file_key(full_path) == key)
|
48
44
|
#already exists. probably left over from an earlier aborted pull
|
49
|
-
puts "Not Pulling (already exists): #{
|
45
|
+
puts "Not Pulling (already exists): #{full_path}"
|
50
46
|
else
|
51
47
|
Dir.mkdir(File.dirname(full_path)) unless File.exist?(File.dirname(full_path))
|
52
48
|
puts "Pulling: #{relative_path}"
|
@@ -62,7 +58,7 @@ module CloudEncryptedSync
|
|
62
58
|
end
|
63
59
|
end
|
64
60
|
|
65
|
-
def delete_remote_files
|
61
|
+
def delete_remote_files
|
66
62
|
remote_files_to_delete.each_pair do |key,path|
|
67
63
|
puts "Deleting Remote: #{path}"
|
68
64
|
liaison.delete(key)
|
@@ -70,10 +66,10 @@ module CloudEncryptedSync
|
|
70
66
|
end
|
71
67
|
end
|
72
68
|
|
73
|
-
def delete_local_files
|
69
|
+
def delete_local_files
|
74
70
|
local_files_to_delete.each_pair do |key,relative_path|
|
75
71
|
full_path = Index.full_file_path(relative_path)
|
76
|
-
if !File.exist?(full_path)
|
72
|
+
if !File.exist?(full_path)
|
77
73
|
puts "Not Deleting Local: #{relative_path}"
|
78
74
|
else
|
79
75
|
puts "Deleting Local: #{relative_path}"
|
@@ -83,7 +79,7 @@ module CloudEncryptedSync
|
|
83
79
|
end
|
84
80
|
end
|
85
81
|
|
86
|
-
def finalize
|
82
|
+
def finalize
|
87
83
|
Index.write if finalize_required
|
88
84
|
end
|
89
85
|
|
@@ -95,12 +91,8 @@ module CloudEncryptedSync
|
|
95
91
|
AdapterLiaison.instance
|
96
92
|
end
|
97
93
|
|
98
|
-
def last_sync_date
|
99
|
-
@last_sync_date ||= File.exist?(Index.snapshot_path) ? File.stat(Index.snapshot_path).ctime : nil
|
100
|
-
end
|
101
|
-
|
102
94
|
def last_sync_hash
|
103
|
-
@last_sync_hash ||= File.exist?(Index.
|
95
|
+
@last_sync_hash ||= File.exist?(Index.index_path) ? YAML.load(File.read(Index.index_path)) : {}
|
104
96
|
end
|
105
97
|
|
106
98
|
def files_to_push
|
data/lib/cloud_encrypted_sync.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
require '
|
2
|
-
require 'yaml'
|
3
|
-
require 'openssl'
|
1
|
+
require 'active_support/core_ext/string'
|
4
2
|
require 'digest'
|
3
|
+
require 'fileutils'
|
5
4
|
require 'find'
|
6
|
-
require '
|
7
|
-
|
8
|
-
require
|
5
|
+
require 'openssl'
|
6
|
+
require 'singleton'
|
7
|
+
require 'yaml'
|
9
8
|
|
10
9
|
require File.expand_path('../cloud_encrypted_sync/adapter_liaison', __FILE__)
|
11
10
|
require File.expand_path('../cloud_encrypted_sync/adapter_template', __FILE__)
|
@@ -14,4 +13,5 @@ require File.expand_path('../cloud_encrypted_sync/cryptographer', __FILE__)
|
|
14
13
|
require File.expand_path('../cloud_encrypted_sync/dummy_adapter', __FILE__)
|
15
14
|
require File.expand_path('../cloud_encrypted_sync/errors', __FILE__)
|
16
15
|
require File.expand_path('../cloud_encrypted_sync/index', __FILE__)
|
17
|
-
require File.expand_path('../cloud_encrypted_sync/progress_meter', __FILE__)
|
16
|
+
require File.expand_path('../cloud_encrypted_sync/progress_meter', __FILE__)
|
17
|
+
require File.expand_path('../cloud_encrypted_sync/synchronizer', __FILE__)
|
data/test/test_helper.rb
CHANGED
@@ -13,30 +13,45 @@ require 'cloud_encrypted_sync'
|
|
13
13
|
module CloudEncryptedSync
|
14
14
|
class ActiveSupport::TestCase
|
15
15
|
|
16
|
-
setup :
|
17
|
-
setup :preset_environment
|
16
|
+
setup :initialize_environment
|
18
17
|
setup :capture_stdout
|
19
18
|
teardown :deactivate_fake_fs
|
20
19
|
teardown :release_stdout
|
21
20
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
Index.instance_variable_set(:@local,
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
21
|
+
def initialize_environment
|
22
|
+
initialize_fake_fs
|
23
|
+
stub_configuration
|
24
|
+
Index.instance_variable_set(:@local,nil)
|
25
|
+
Index.instance_variable_set(:@remote,nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
def stub_configuration
|
29
|
+
Configuration.stubs(:settings).returns({
|
30
|
+
:encryption_key => 'asdf',
|
31
|
+
:adapter_name => 'dummy',
|
32
|
+
:bucket => "test-bucket",
|
33
|
+
:sync_path => test_source_folder
|
34
|
+
})
|
35
|
+
Configuration.stubs(:data_folder_path).returns("#{Etc.getpwuid.dir}/.cloud_encrypted_sync")
|
36
|
+
end
|
37
|
+
|
38
|
+
def unstub_configuration
|
39
|
+
Configuration.unstub(:settings)
|
40
|
+
Configuration.unstub(:data_folder_path)
|
31
41
|
end
|
32
42
|
|
33
43
|
def test_source_folder
|
34
44
|
@test_source_folder ||= File.expand_path('../test_folder', __FILE__)
|
35
45
|
end
|
36
46
|
|
37
|
-
def
|
47
|
+
def initialize_fake_fs
|
38
48
|
FakeFS.activate!
|
39
49
|
FakeFS::FileSystem.clear
|
50
|
+
FileUtils.mkdir_p test_source_folder
|
51
|
+
FileUtils.mkdir_p test_source_folder + '/test_sub_folder'
|
52
|
+
File.open(test_source_folder + '/test_sub_folder/test_file_one.txt', 'w') do |test_file|
|
53
|
+
test_file.write('Test File One')
|
54
|
+
end
|
40
55
|
end
|
41
56
|
|
42
57
|
def deactivate_fake_fs
|
@@ -3,29 +3,50 @@ require 'test_helper'
|
|
3
3
|
module CloudEncryptedSync
|
4
4
|
class AdapterLiaisonTest < ActiveSupport::TestCase
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
:adapter_name => 'dummy',
|
10
|
-
:bucket => "test-bucket",
|
11
|
-
:sync_path => test_source_folder
|
12
|
-
})
|
13
|
-
Configuration.stubs(:data_folder_path).returns("#{Etc.getpwuid.dir}/.cloud_encrypted_sync")
|
6
|
+
test 'should require available adapter' do
|
7
|
+
Dir.stubs(:glob).returns(['/path/cloud_encrypted_sync_first_test_adapter-1.2.3'])
|
8
|
+
assert_raises(LoadError) { AdapterLiaison.clone.instance }
|
14
9
|
end
|
15
10
|
|
16
11
|
test 'should encrypt when writing' do
|
17
12
|
precrypted_data = File.read(test_source_folder + '/test_sub_folder/test_file_one.txt')
|
18
|
-
|
19
|
-
Adapters::Dummy.expects(:write).with(anything,
|
20
|
-
AdapterLiaison.instance.push(precrypted_data,
|
13
|
+
Cryptographer.expects(:encrypt_data).with(precrypted_data)
|
14
|
+
Adapters::Dummy.expects(:write).with(anything,'test_key')
|
15
|
+
AdapterLiaison.instance.push(precrypted_data,'test_key')
|
21
16
|
end
|
22
17
|
|
23
18
|
test 'should decrypt_when_reading' do
|
24
19
|
precrypted_data = File.read(test_source_folder + '/test_sub_folder/test_file_one.txt')
|
25
20
|
encrypted_data = Cryptographer.encrypt_data(precrypted_data)
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
Adapters::Dummy.expects(:read).with('test_key').returns(encrypted_data)
|
22
|
+
assert_equal(precrypted_data,AdapterLiaison.instance.pull('test_key'))
|
23
|
+
end
|
24
|
+
|
25
|
+
test 'should forward delete to dummy' do
|
26
|
+
Adapters::Dummy.expects(:delete).with('test_key').returns(true)
|
27
|
+
AdapterLiaison.instance.delete('test_key')
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'should forward key exists to dummy' do
|
31
|
+
Adapters::Dummy.expects(:key_exists?).with('test_key').returns(true)
|
32
|
+
AdapterLiaison.instance.key_exists?('test_key')
|
33
|
+
end
|
34
|
+
|
35
|
+
test 'should find lastest versions of available adapters' do
|
36
|
+
Dir.stubs(:glob).returns([
|
37
|
+
'/path/cloud_encrypted_sync_first_test_adapter-1.2.3',
|
38
|
+
'/path/cloud_encrypted_sync_second_test_adapter-4.5.6',
|
39
|
+
'/path/cloud_encrypted_sync_second_test_adapter-7.8.9'
|
40
|
+
])
|
41
|
+
assert_equal({'first_test' => '1.2.3', 'second_test' => '7.8.9'}, AdapterLiaison.instance.send(:latest_versions_of_installed_adapters))
|
42
|
+
end
|
43
|
+
|
44
|
+
test 'should exercise dummy adapter for test coverage' do
|
45
|
+
dummy = Adapters::Dummy
|
46
|
+
dummy.write('dummy test data','testkey')
|
47
|
+
assert_equal('dummy test data',dummy.read('testkey'))
|
48
|
+
dummy.delete('testkey')
|
49
|
+
assert_equal(false,dummy.key_exists?('testkey'))
|
29
50
|
end
|
30
51
|
|
31
52
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module CloudEncryptedSync
|
4
|
+
class AdapterTemplateTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
test 'should register with parent class on inheritance' do
|
7
|
+
Adapters::Template.expects(:register_subclass_with_parent).returns(true)
|
8
|
+
Class.new(Adapters::Template)
|
9
|
+
end
|
10
|
+
|
11
|
+
test 'should contain registered adapters' do
|
12
|
+
assert_equal([:dummy],Adapters::Template.children.keys)
|
13
|
+
end
|
14
|
+
|
15
|
+
test 'should raise errors on public methods' do
|
16
|
+
|
17
|
+
method_argument_map = {
|
18
|
+
:parse_command_line_options => [:foo,:bar],
|
19
|
+
:write => [:foo,:bar],
|
20
|
+
:read => :foobar,
|
21
|
+
:delete => :foobar,
|
22
|
+
:key_exists? => :foobar
|
23
|
+
}
|
24
|
+
method_argument_map.each_pair do |method,arguments|
|
25
|
+
assert_raise(Errors::TemplateMethodCalled) { Adapters::Template.send(method,*arguments) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -4,19 +4,20 @@ module CloudEncryptedSync
|
|
4
4
|
class ConfigurationTest < ActiveSupport::TestCase
|
5
5
|
|
6
6
|
def setup
|
7
|
+
unstub_configuration
|
7
8
|
Configuration.instance_variable_set(:@command_line_options,nil)
|
8
9
|
Configuration.instance_variable_set(:@settings,nil)
|
9
10
|
Configuration.instance_variable_set(:@option_parser,nil)
|
10
11
|
Object.send(:remove_const,:ARGV) #if defined?(::ARGV)
|
11
12
|
end
|
12
13
|
|
13
|
-
test 'should
|
14
|
+
test 'should load settings' do
|
14
15
|
::ARGV = '--adapter dummy --bucket foobar --data-dir ~/test/folder --encryption-key somestringofcharacters /some/path'.split(/\s/)
|
15
16
|
settings = Configuration.settings
|
16
17
|
assert_equal('dummy',settings[:adapter_name])
|
17
18
|
assert_equal('~/test/folder',settings[:data_dir])
|
18
19
|
assert_equal('somestringofcharacters',settings[:encryption_key])
|
19
|
-
assert_equal('foobar',settings[:
|
20
|
+
assert_equal('foobar',settings[:bucket])
|
20
21
|
end
|
21
22
|
|
22
23
|
test 'should gracefully fail without path in ARGV' do
|
@@ -24,10 +25,17 @@ module CloudEncryptedSync
|
|
24
25
|
assert_raise(Errors::IncompleteConfigurationError) { Configuration.settings }
|
25
26
|
end
|
26
27
|
|
27
|
-
test 'should gracefully fail when not provided encryption_key and
|
28
|
+
test 'should gracefully fail when not provided encryption_key and provided path in ARGV' do
|
28
29
|
::ARGV = '--adapter dummy --bucket foobar /some/path/to/sync'.split(/\s/)
|
29
30
|
assert_raise(Errors::IncompleteConfigurationError) { Configuration.settings }
|
30
31
|
end
|
31
32
|
|
33
|
+
test 'should create data folder if it does not exist' do
|
34
|
+
::ARGV = '--adapter dummy --bucket foobar --data-dir /test --encryption-key somestringofcharacters /some/path'.split(/\s/)
|
35
|
+
assert ! File.exist?('/test')
|
36
|
+
Configuration.settings
|
37
|
+
assert File.exist?('/test')
|
38
|
+
end
|
39
|
+
|
32
40
|
end
|
33
41
|
end
|
@@ -3,31 +3,16 @@ require 'test_helper'
|
|
3
3
|
module CloudEncryptedSync
|
4
4
|
class CryptographerTest < ActiveSupport::TestCase
|
5
5
|
|
6
|
-
def setup
|
7
|
-
Configuration.stubs(:settings).returns({
|
8
|
-
:encryption_key => 'asdf',
|
9
|
-
:adapter_name => 'dummy',
|
10
|
-
:bucket => "test-bucket",
|
11
|
-
:data_dir => "#{Etc.getpwuid.dir}/.cloud_encrypted_sync",
|
12
|
-
:sync_path => test_source_folder
|
13
|
-
})
|
14
|
-
end
|
15
|
-
|
16
|
-
test 'should hash data' do
|
17
|
-
hash = Cryptographer.hash_data('abc123')
|
18
|
-
assert_equal('c70b5dd9ebfb6f51d09d4132b7170c9d20750a7852f00680f65658f0310e810056e6763c34c9a00b0e940076f54495c169fc2302cceb312039271c43469507dc',hash)
|
19
|
-
end
|
20
|
-
|
21
6
|
test 'should encrypt and decrypt data' do
|
22
|
-
|
23
|
-
encrypted_data = Cryptographer.encrypt_data(
|
7
|
+
precrypted_data = "123xyz"
|
8
|
+
encrypted_data = Cryptographer.encrypt_data(precrypted_data)
|
24
9
|
decrypted_data = Cryptographer.decrypt_data(encrypted_data)
|
25
|
-
assert_equal(
|
10
|
+
assert_equal(precrypted_data,decrypted_data)
|
26
11
|
end
|
27
12
|
|
28
|
-
test '
|
29
|
-
|
13
|
+
test 'should hash data' do
|
14
|
+
hashed_data = Cryptographer.hash_data('abc123')
|
15
|
+
assert_equal(128,hashed_data.length)
|
30
16
|
end
|
31
|
-
|
32
17
|
end
|
33
18
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module CloudEncryptedSync
|
4
|
+
class IndexTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
test 'should compile local directory hash' do
|
7
|
+
assert_equal('',$stdout.string)
|
8
|
+
hash = Index.local
|
9
|
+
assert_equal(1,hash.keys.size)
|
10
|
+
assert_equal('test_sub_folder/test_file_one.txt',hash[hash.keys.first])
|
11
|
+
assert_match(/\% Complete/,$stdout.string)
|
12
|
+
end
|
13
|
+
|
14
|
+
test 'should fetch remote directory hash' do
|
15
|
+
AdapterLiaison.instance.expects(:pull).returns({:some => 'hash'}.to_yaml)
|
16
|
+
assert_equal({:some => 'hash'},Index.remote)
|
17
|
+
end
|
18
|
+
|
19
|
+
test 'should return empty hash if no remote index' do
|
20
|
+
Index.instance_variable_set(:@remote,nil)
|
21
|
+
AdapterLiaison.instance.expects(:pull).raises(Errors::NoSuchKey)
|
22
|
+
assert_equal({},Index.remote)
|
23
|
+
end
|
24
|
+
|
25
|
+
test 'should recompile and write local and remote hashes' do
|
26
|
+
Configuration.send(:touch_data_folder)
|
27
|
+
AdapterLiaison.instance.expects(:push)
|
28
|
+
FakeFS::File.any_instance.expects(:read).returns('Testing 123')
|
29
|
+
Index.write
|
30
|
+
end
|
31
|
+
|
32
|
+
test 'should return full normalized file path' do
|
33
|
+
assert_match(/.+\/test\/test_folder\/foobar$/,Index.full_file_path('foobar'))
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'should leave sync path unchanged' do
|
37
|
+
Configuration.stubs(:settings).returns({:sync_path => '/foo/bar/'})
|
38
|
+
assert_equal('/foo/bar/',Index.send(:normalize_sync_path))
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'should add trailing slash to sync path' do
|
42
|
+
Configuration.stubs(:settings).returns({:sync_path => '/foo/bar'})
|
43
|
+
assert_equal('/foo/bar/',Index.send(:normalize_sync_path))
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module CloudEncryptedSync
|
4
|
+
class SynchronizerTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
SYNC_METHODS = [:delete_local_files, :delete_remote_files, :push_files, :pull_files, :finalize]
|
7
|
+
|
8
|
+
test 'should run full sync' do
|
9
|
+
SYNC_METHODS.each { |method_name| Synchronizer.expects(method_name) }
|
10
|
+
|
11
|
+
Synchronizer.run
|
12
|
+
end
|
13
|
+
|
14
|
+
test 'should puts error message to stdout when config is incomplete' do
|
15
|
+
Configuration.stubs(:settings).raises(Errors::IncompleteConfigurationError,'test message')
|
16
|
+
AdapterLiaison.expects(:push).never
|
17
|
+
AdapterLiaison.expects(:pull).never
|
18
|
+
|
19
|
+
assert_equal('',$stdout.string)
|
20
|
+
Synchronizer.run
|
21
|
+
assert_match(/test message/,$stdout.string)
|
22
|
+
end
|
23
|
+
|
24
|
+
test 'should push files' do
|
25
|
+
Adapters::Dummy.expects(:write)
|
26
|
+
|
27
|
+
assert_equal('',$stdout.string)
|
28
|
+
Synchronizer.push_files
|
29
|
+
assert_match(/\% Complete/,$stdout.string)
|
30
|
+
end
|
31
|
+
|
32
|
+
test 'should not push files that already exist' do
|
33
|
+
AdapterLiaison.instance.stubs(:key_exists?).returns(true)
|
34
|
+
Synchronizer.push_files
|
35
|
+
assert_match(/\(already exists\)/,$stdout.string)
|
36
|
+
end
|
37
|
+
|
38
|
+
test 'should pull files' do
|
39
|
+
Index.stubs(:remote).returns({'new_file_key' => 'test_sub_folder/new_file.txt'})
|
40
|
+
Adapters::Dummy.expects(:read).with('new_file_key').returns(Cryptographer.encrypt_data('foobar'))
|
41
|
+
assert_equal('',$stdout.string)
|
42
|
+
assert_difference('Dir["#{test_source_folder}/**/*"].length') do
|
43
|
+
Synchronizer.pull_files
|
44
|
+
end
|
45
|
+
assert_match(/\% Complete/,$stdout.string)
|
46
|
+
end
|
47
|
+
|
48
|
+
test 'should not pull files that already exist' do
|
49
|
+
Synchronizer.stubs(:files_to_pull).returns({:foo => 'bar'})
|
50
|
+
File.stubs(:exist?).returns(true)
|
51
|
+
Index.stubs(:file_key).returns(:foo)
|
52
|
+
Synchronizer.pull_files
|
53
|
+
assert_match(/\(already exists\)/,$stdout.string)
|
54
|
+
end
|
55
|
+
|
56
|
+
test 'should gracefully recover if pull fails' do
|
57
|
+
Synchronizer.stubs(:files_to_pull).returns({:foo => 'bar'})
|
58
|
+
AdapterLiaison.instance.stubs(:pull).raises(Errors::NoSuchKey)
|
59
|
+
Synchronizer.pull_files
|
60
|
+
assert_match(/Failed to pull/,$stdout.string)
|
61
|
+
end
|
62
|
+
|
63
|
+
test 'should delete files from cloud' do
|
64
|
+
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
65
|
+
Index.stubs(:local).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
66
|
+
Synchronizer.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
67
|
+
Adapters::Dummy.expects(:delete).with('deleted_file_key').returns(true)
|
68
|
+
Synchronizer.delete_remote_files
|
69
|
+
assert_match(/Deleting Remote/,$stdout.string)
|
70
|
+
end
|
71
|
+
|
72
|
+
test 'should delete local files' do
|
73
|
+
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
74
|
+
Synchronizer.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'}.merge(Index.local))
|
75
|
+
assert_difference('Dir["#{test_source_folder}/**/*"].length',-1) do
|
76
|
+
Synchronizer.delete_local_files
|
77
|
+
end
|
78
|
+
assert_match(/Deleting Local/,$stdout.string)
|
79
|
+
end
|
80
|
+
|
81
|
+
test 'should gracefully recover if local file disappears before delete' do
|
82
|
+
Synchronizer.stubs(:local_files_to_delete).returns({:foo => 'bar'})
|
83
|
+
File.stubs(:exist?).returns(false)
|
84
|
+
Synchronizer.delete_local_files
|
85
|
+
assert_match(/Not Deleting Local/,$stdout.string)
|
86
|
+
end
|
87
|
+
|
88
|
+
test 'should finalize' do
|
89
|
+
Synchronizer.instance_variable_set(:@finalize_required,true)
|
90
|
+
Index.expects(:write)
|
91
|
+
|
92
|
+
Synchronizer.finalize
|
93
|
+
end
|
94
|
+
|
95
|
+
test 'should want to push everything on first run with local files and empty remote' do
|
96
|
+
Index.stubs(:remote).returns({})
|
97
|
+
Index.stubs(:local).returns({"new_file_key"=>"test_sub_folder/new_file.txt"})
|
98
|
+
Synchronizer.stubs(:last_sync_hash).returns({})
|
99
|
+
assert_equal(Index.local,Synchronizer.send(:files_to_push))
|
100
|
+
end
|
101
|
+
|
102
|
+
test 'should want to push new files with available last sync hash' do
|
103
|
+
new_file_hash = {"new_file_key"=>"test_sub_folder/new_file.txt"}
|
104
|
+
Index.stubs(:remote).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
105
|
+
Index.stubs(:local).returns({"old_file_key"=>"test_sub_folder/old_file.txt"}.merge(new_file_hash))
|
106
|
+
Synchronizer.stubs(:last_sync_hash).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
107
|
+
assert_equal(new_file_hash,Synchronizer.send(:files_to_push))
|
108
|
+
end
|
109
|
+
|
110
|
+
test 'should want to push new files with local and remote files and empty last sync hash' do
|
111
|
+
new_file_hash = {"new_file_key"=>"test_sub_folder/new_file.txt"}
|
112
|
+
Index.stubs(:remote).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
113
|
+
Index.stubs(:local).returns({"old_file_key"=>"test_sub_folder/old_file.txt"}.merge(new_file_hash))
|
114
|
+
Synchronizer.stubs(:last_sync_hash).returns({})
|
115
|
+
assert_equal(new_file_hash,Synchronizer.send(:files_to_push))
|
116
|
+
end
|
117
|
+
|
118
|
+
test 'should want to puull everything on first run with no local files and remote files available' do
|
119
|
+
Index.stubs(:remote).returns({"new_file_key"=>"test_sub_folder/new_file.txt"})
|
120
|
+
Index.stubs(:local).returns({})
|
121
|
+
Synchronizer.stubs(:last_sync_hash).returns({})
|
122
|
+
assert_equal(Index.remote,Synchronizer.send(:files_to_pull))
|
123
|
+
end
|
124
|
+
|
125
|
+
test 'should want to pull new files with available last sync hash' do
|
126
|
+
new_file_hash = {"new_file_key"=>"test_sub_folder/new_file.txt"}
|
127
|
+
Index.stubs(:remote).returns({"old_file_key"=>"test_sub_folder/old_file.txt"}.merge(new_file_hash))
|
128
|
+
Index.stubs(:local).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
129
|
+
Synchronizer.stubs(:last_sync_hash).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
130
|
+
assert_equal(new_file_hash,Synchronizer.send(:files_to_pull))
|
131
|
+
end
|
132
|
+
|
133
|
+
test 'should want to pull new files with local and remote files and empty last sync hash' do
|
134
|
+
new_file_hash = {"new_file_key"=>"test_sub_folder/new_file.txt"}
|
135
|
+
Index.stubs(:remote).returns({"old_file_key"=>"test_sub_folder/old_file.txt"}.merge(new_file_hash))
|
136
|
+
Index.stubs(:local).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
137
|
+
Synchronizer.stubs(:last_sync_hash).returns({})
|
138
|
+
assert_equal(new_file_hash,Synchronizer.send(:files_to_pull))
|
139
|
+
end
|
140
|
+
|
141
|
+
test 'should not want to delete remote files if last sync hash is empty' do
|
142
|
+
Index.stubs(:remote).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
143
|
+
Index.stubs(:local).returns({})
|
144
|
+
Synchronizer.stubs(:last_sync_hash).returns({})
|
145
|
+
assert_equal({},Synchronizer.send(:remote_files_to_delete))
|
146
|
+
end
|
147
|
+
|
148
|
+
test 'should want to delete remote files' do
|
149
|
+
Index.stubs(:remote).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
150
|
+
Index.stubs(:local).returns({})
|
151
|
+
Synchronizer.stubs(:last_sync_hash).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
152
|
+
assert_equal(Index.remote,Synchronizer.send(:remote_files_to_delete))
|
153
|
+
end
|
154
|
+
|
155
|
+
test 'should not want to delete local files if last sync hash is empty' do
|
156
|
+
Index.stubs(:remote).returns({})
|
157
|
+
Index.stubs(:local).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
158
|
+
Synchronizer.stubs(:last_sync_hash).returns({})
|
159
|
+
assert_equal({},Synchronizer.send(:local_files_to_delete))
|
160
|
+
end
|
161
|
+
|
162
|
+
test 'should want to delete local files' do
|
163
|
+
Index.stubs(:remote).returns({})
|
164
|
+
Index.stubs(:local).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
165
|
+
Synchronizer.stubs(:last_sync_hash).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
166
|
+
assert_equal(Index.local,Synchronizer.send(:local_files_to_delete))
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloud_encrypted_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mocha
|
@@ -100,15 +100,17 @@ files:
|
|
100
100
|
- lib/cloud_encrypted_sync/dummy_adapter.rb
|
101
101
|
- lib/cloud_encrypted_sync/errors.rb
|
102
102
|
- lib/cloud_encrypted_sync/index.rb
|
103
|
-
- lib/cloud_encrypted_sync/master.rb
|
104
103
|
- lib/cloud_encrypted_sync/progress_meter.rb
|
104
|
+
- lib/cloud_encrypted_sync/synchronizer.rb
|
105
105
|
- lib/cloud_encrypted_sync/version.rb
|
106
106
|
- test/test_helper.rb
|
107
107
|
- test/unit/adapter_liaison_test.rb
|
108
|
+
- test/unit/adapter_template_test.rb
|
108
109
|
- test/unit/configuration_test.rb
|
109
110
|
- test/unit/cryptographer_test.rb
|
110
|
-
- test/unit/
|
111
|
+
- test/unit/index_test.rb
|
111
112
|
- test/unit/progress_meter_test.rb
|
113
|
+
- test/unit/synchronizer_test.rb
|
112
114
|
homepage: https://github.com/jsgarvin/cloud_encrypted_sync
|
113
115
|
licenses: []
|
114
116
|
post_install_message:
|
@@ -136,7 +138,9 @@ summary: Encrypted sync of folder contents to/from cloud storage.
|
|
136
138
|
test_files:
|
137
139
|
- test/test_helper.rb
|
138
140
|
- test/unit/adapter_liaison_test.rb
|
141
|
+
- test/unit/adapter_template_test.rb
|
139
142
|
- test/unit/configuration_test.rb
|
140
143
|
- test/unit/cryptographer_test.rb
|
141
|
-
- test/unit/
|
144
|
+
- test/unit/index_test.rb
|
142
145
|
- test/unit/progress_meter_test.rb
|
146
|
+
- test/unit/synchronizer_test.rb
|
data/test/unit/master_test.rb
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
module CloudEncryptedSync
|
4
|
-
class MasterTest < ActiveSupport::TestCase
|
5
|
-
|
6
|
-
def setup
|
7
|
-
Configuration.stubs(:settings).returns({
|
8
|
-
:encryption_key => 'asdf',
|
9
|
-
:adapter_name => 'dummy',
|
10
|
-
:bucket => "test-bucket",
|
11
|
-
:sync_path => test_source_folder
|
12
|
-
})
|
13
|
-
Configuration.stubs(:data_folder_path).returns("#{Etc.getpwuid.dir}/.cloud_encrypted_sync")
|
14
|
-
end
|
15
|
-
|
16
|
-
test 'should generate directory hash' do
|
17
|
-
assert_equal('',$stdout.string)
|
18
|
-
hash = Index.local
|
19
|
-
assert_equal(1,hash.keys.size)
|
20
|
-
assert_equal('test_sub_folder/test_file_one.txt',hash[hash.keys.first])
|
21
|
-
assert_match(/\% Complete/,$stdout.string)
|
22
|
-
end
|
23
|
-
|
24
|
-
test 'should_return_nil_if_never_synced_before' do
|
25
|
-
Master.stubs(:snapshot_file_path).returns('/non/existant/file')
|
26
|
-
assert_equal(nil,Master.send(:last_sync_date))
|
27
|
-
end
|
28
|
-
|
29
|
-
test 'should want to push everything on first run with local files and empty remote' do
|
30
|
-
Index.stubs(:remote).returns({})
|
31
|
-
Index.stubs(:local).returns({"old_file_key"=>"test_sub_folder/old_file.txt"})
|
32
|
-
Master.stubs(:last_sync_hash).returns({})
|
33
|
-
assert_equal(Index.local,Master.send(:files_to_push))
|
34
|
-
end
|
35
|
-
|
36
|
-
test 'should push files' do
|
37
|
-
Master.stubs(:remote_directory_hash).returns({})
|
38
|
-
Master.stubs(:last_sync_hash).returns({})
|
39
|
-
Adapters::Dummy.stubs(:key_exists?).returns(false)
|
40
|
-
Adapters::Dummy.expects(:write).with(any_parameters).returns(true)
|
41
|
-
assert_equal('',$stdout.string)
|
42
|
-
Master.push_files!
|
43
|
-
assert_match(/\% Complete/,$stdout.string)
|
44
|
-
end
|
45
|
-
|
46
|
-
test 'should want to pull everything on first run with remote files and empty local' do
|
47
|
-
Index.stubs(:remote).returns({'new_file_key' => 'test_sub_folder/new_file.txt'})
|
48
|
-
Index.stubs(:local).returns({})
|
49
|
-
Master.stubs(:last_sync_hash).returns({})
|
50
|
-
assert_equal({'new_file_key' => 'test_sub_folder/new_file.txt'},Master.send(:files_to_pull))
|
51
|
-
end
|
52
|
-
|
53
|
-
test 'should pull files' do
|
54
|
-
Index.stubs(:remote).returns({'new_file_key' => 'test_sub_folder/new_file.txt'})
|
55
|
-
Index.stubs(:local).returns({})
|
56
|
-
Master.stubs(:last_sync_hash).returns({})
|
57
|
-
Adapters::Dummy.expects(:read).with('new_file_key').returns(Cryptographer.encrypt_data('foobar'))
|
58
|
-
assert_equal('',$stdout.string)
|
59
|
-
assert_difference('Dir["#{test_source_folder}/**/*"].length') do
|
60
|
-
Master.pull_files!
|
61
|
-
end
|
62
|
-
assert_match(/\% Complete/,$stdout.string)
|
63
|
-
end
|
64
|
-
|
65
|
-
test 'should only want to push new files on later run' do
|
66
|
-
Index.stubs(:remote).returns({'old_file_key' => 'test_sub_folder/old_file.txt'})
|
67
|
-
Index.stubs(:local).returns({'new_file_key' => 'test_sub_folder/new_file.txt', 'old_file_key' => 'test_sub_folder/old_file.txt'})
|
68
|
-
Master.stubs(:last_sync_hash).returns({'old_file_key' => 'test_sub_folder/old_file.txt'})
|
69
|
-
assert_equal({'new_file_key' => 'test_sub_folder/new_file.txt'},Master.send(:files_to_push))
|
70
|
-
end
|
71
|
-
|
72
|
-
test 'should want to pull new files from cloud' do
|
73
|
-
Index.stubs(:remote).returns({'new_file_key' => 'test_sub_folder/new_file.txt', 'old_file_key' => 'test_sub_folder/old_file.txt'})
|
74
|
-
Index.stubs(:local).returns({'old_file_key' => 'test_sub_folder/old_file.txt'})
|
75
|
-
Master.stubs(:last_sync_hash).returns({'old_file_key' => 'test_sub_folder/old_file.txt'})
|
76
|
-
assert_equal({'new_file_key' => 'test_sub_folder/new_file.txt'},Master.send(:files_to_pull))
|
77
|
-
end
|
78
|
-
|
79
|
-
test 'should want to delete locally missing files from cloud' do
|
80
|
-
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
81
|
-
Index.stubs(:local).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
82
|
-
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
83
|
-
assert_equal({'deleted_file_key' => 'test_sub_folder/deleted_file.txt'},Master.send(:remote_files_to_delete))
|
84
|
-
end
|
85
|
-
|
86
|
-
test 'should delete files from cloud' do
|
87
|
-
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
88
|
-
Index.stubs(:local).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
89
|
-
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
90
|
-
Adapters::Dummy.expects(:delete).with('deleted_file_key').returns(true)
|
91
|
-
Master.delete_remote_files!
|
92
|
-
end
|
93
|
-
|
94
|
-
test 'should want to delete appropriate files locally' do
|
95
|
-
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
96
|
-
Index.stubs(:local).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
97
|
-
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'})
|
98
|
-
assert_equal({'deleted_file_key' => 'test_sub_folder/deleted_file.txt'},Master.send(:local_files_to_delete))
|
99
|
-
end
|
100
|
-
|
101
|
-
test 'should delete local files' do
|
102
|
-
Index.stubs(:remote).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt'})
|
103
|
-
Master.stubs(:last_sync_hash).returns({'saved_file_key' => 'test_sub_folder/saved_file.txt', 'deleted_file_key' => 'test_sub_folder/deleted_file.txt'}.merge(Index.local))
|
104
|
-
assert_difference('Dir["#{test_source_folder}/**/*"].length',-1) do
|
105
|
-
Master.delete_local_files!
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
test 'should finalize' do
|
110
|
-
FileUtils.mkdir_p(Configuration.data_folder_path)
|
111
|
-
sample_directory_hash = {'sample_file_key' => 'test_sub_folder/sample_file.txt'}
|
112
|
-
Master.instance_variable_set(:@finalize_required,true)
|
113
|
-
Master.stubs(:directory_hash).returns(sample_directory_hash)
|
114
|
-
Adapters::Dummy.expects(:write).with(anything,Index.send(:index_key)).returns(true)
|
115
|
-
Master.finalize!
|
116
|
-
end
|
117
|
-
|
118
|
-
test 'should decrypt remote directory file' do
|
119
|
-
#setup mock data
|
120
|
-
sample_directory_hash = {'sample_file_key' => 'test_sub_folder/sample_file.txt'}
|
121
|
-
encrypted_directory_hash = Cryptographer.encrypt_data(sample_directory_hash.to_yaml)
|
122
|
-
Adapters::Dummy.expects(:read).with(Index.send(:index_key)).returns(encrypted_directory_hash)
|
123
|
-
|
124
|
-
#do actual test
|
125
|
-
decrypted_remote_hash = Index.remote
|
126
|
-
assert_equal(sample_directory_hash,decrypted_remote_hash)
|
127
|
-
end
|
128
|
-
|
129
|
-
test 'should puts error message to stdout' do
|
130
|
-
Configuration.stubs(:settings).raises(Errors::IncompleteConfigurationError,'test message')
|
131
|
-
assert_equal('',$stdout.string)
|
132
|
-
Master.expects(:pull_files).never
|
133
|
-
Master.sync
|
134
|
-
assert_match(/test message/,$stdout.string)
|
135
|
-
end
|
136
|
-
|
137
|
-
end
|
138
|
-
end
|