droxi 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,8 @@
1
1
  require 'dropbox_sdk'
2
2
  require 'minitest/autorun'
3
3
 
4
+ require_relative 'testutils'
5
+ require_relative '../lib/droxi/commands'
4
6
  require_relative '../lib/droxi/complete'
5
7
  require_relative '../lib/droxi/settings'
6
8
  require_relative '../lib/droxi/state'
@@ -12,99 +14,162 @@ describe Complete do
12
14
  rand(length).times.map { CHARACTERS.sample }.join
13
15
  end
14
16
 
15
- describe "when resolving a local search path" do
16
- it "must resolve unqualified string to working directory" do
17
+ describe 'when resolving a local search path' do
18
+ it 'must resolve unqualified string to working directory' do
17
19
  Complete.local_search_path('').must_equal Dir.pwd
18
20
  Complete.local_search_path('f').must_equal Dir.pwd
19
21
  end
20
22
 
21
- it "must resolve / to root directory" do
23
+ it 'must resolve / to root directory' do
22
24
  Complete.local_search_path('/').must_equal '/'
23
25
  Complete.local_search_path('/f').must_equal '/'
24
26
  end
25
27
 
26
- it "must resolve directory name to named directory" do
28
+ it 'must resolve directory name to named directory' do
27
29
  Complete.local_search_path('/home/').must_equal '/home'
28
30
  Complete.local_search_path('/home/f').must_equal '/home'
29
31
  end
30
32
 
31
- it "must resolve ~/ to home directory" do
33
+ it 'must resolve ~/ to home directory' do
32
34
  Complete.local_search_path('~/').must_equal Dir.home
33
35
  Complete.local_search_path('~/f').must_equal Dir.home
34
36
  end
35
37
 
36
- it "must resolve ./ to working directory" do
38
+ it 'must resolve ./ to working directory' do
37
39
  Complete.local_search_path('./').must_equal Dir.pwd
38
40
  Complete.local_search_path('./f').must_equal Dir.pwd
39
41
  end
40
42
 
41
- it "must resolve ../ to parent directory" do
43
+ it 'must resolve ../ to parent directory' do
42
44
  Complete.local_search_path('../').must_equal File.dirname(Dir.pwd)
43
45
  Complete.local_search_path('../f').must_equal File.dirname(Dir.pwd)
44
46
  end
45
47
 
46
- it "won't raise an exception on a bogus string" do
47
- Complete.local_search_path('~bogus')
48
+ it 'must resolve a bogus string to working directory' do
49
+ Complete.local_search_path('~bogus/bogus').must_equal Dir.pwd
48
50
  end
49
51
  end
50
52
 
51
- describe "when finding potential local tab completions" do
53
+ describe 'when finding potential local tab completions' do
52
54
  def check(path)
53
- 100.times.all? do |i|
55
+ 100.times.all? do
54
56
  prefix = path + random_string(5)
55
57
  Complete.local(prefix).all? { |match| match.start_with?(prefix) }
56
58
  end.must_equal true
57
- 1000.times.any? do |i|
59
+ 1000.times.any? do
58
60
  prefix = path + random_string(5)
59
61
  !Complete.local(prefix).empty?
60
62
  end.must_equal true
61
63
  end
62
64
 
63
- it "seed must prefix results for unqualified string" do check('') end
64
- it "seed must prefix results for /" do check('/') end
65
- it "seed must prefix results for named directory" do check('/home/') end
66
- it "seed must prefix results for ~/" do check('~/') end
67
- it "seed must prefix results for ./" do check('./') end
68
- it "seed must prefix results for ../" do check('../') end
65
+ it 'seed must prefix results for unqualified string' do
66
+ check('')
67
+ end
68
+
69
+ it 'seed must prefix results for /' do
70
+ check('/')
71
+ end
72
+
73
+ it 'seed must prefix results for named directory' do
74
+ check('/home/')
75
+ end
76
+
77
+ it 'seed must prefix results for ~/' do
78
+ check('~/')
79
+ end
80
+
81
+ it 'seed must prefix results for ./' do
82
+ check('./')
83
+ end
84
+
85
+ it 'seed must prefix results for ../' do
86
+ check('../')
87
+ end
69
88
 
70
89
  it "won't raise an exception on a bogus string" do
71
90
  Complete.local('~bogus')
72
91
  end
73
92
  end
74
93
 
75
- describe "when resolving a remote search path" do
76
- client = DropboxClient.new(Settings[:access_token])
77
- begin
78
- client.file_create_folder('/testing')
79
- rescue DropboxError
94
+ describe 'when finding local directory tab completions' do
95
+ it 'must include all directories and only directories' do
96
+ entries = Dir.entries(Dir.pwd).select do |entry|
97
+ File.directory?(entry) && !/^..?$/.match(entry)
98
+ end
99
+ matches = Complete.local_dir('').map { |match| match.chomp('/') }
100
+ matches.sort.must_equal entries.sort
80
101
  end
102
+
103
+ it 'must append a / to the end of options' do
104
+ Complete.local_dir('').all? { |option| option.end_with?('/') }
105
+ end
106
+ end
107
+
108
+ describe 'when resolving a remote search path' do
109
+ client = DropboxClient.new(Settings[:access_token])
110
+ TestUtils.ignore(DropboxError) { client.file_create_folder('/testing') }
81
111
  state = State.new(client)
82
112
  state.pwd = '/testing'
83
113
 
84
- it "must resolve unqualified string to working directory" do
114
+ it 'must resolve unqualified string to working directory' do
85
115
  Complete.remote_search_path('', state).must_equal state.pwd
86
116
  Complete.remote_search_path('f', state).must_equal state.pwd
87
117
  end
88
118
 
89
- it "must resolve / to root directory" do
119
+ it 'must resolve / to root directory' do
90
120
  Complete.remote_search_path('/', state).must_equal '/'
91
121
  Complete.remote_search_path('/f', state).must_equal '/'
92
122
  end
93
123
 
94
- it "must resolve directory name to named directory" do
124
+ it 'must resolve directory name to named directory' do
95
125
  Complete.remote_search_path('/testing/', state).must_equal '/testing'
96
126
  Complete.remote_search_path('/testing/f', state).must_equal '/testing'
97
127
  end
98
128
 
99
- it "must resolve ./ to working directory" do
129
+ it 'must resolve ./ to working directory' do
100
130
  Complete.remote_search_path('./', state).must_equal state.pwd
101
131
  Complete.remote_search_path('./f', state).must_equal state.pwd
102
132
  end
103
133
 
104
- it "must resolve ../ to parent directory" do
134
+ it 'must resolve ../ to parent directory' do
105
135
  parent = File.dirname(state.pwd)
106
136
  Complete.remote_search_path('../', state).must_equal parent
107
137
  Complete.remote_search_path('../f', state).must_equal parent
108
138
  end
109
139
  end
140
+
141
+ describe 'when finding remote tab completions' do
142
+ client = DropboxClient.new(Settings[:access_token])
143
+ state = State.new(client)
144
+ state.pwd = '/testing'
145
+ Commands::RM.exec(client, state, '/testing/*')
146
+ %w(/testing /testing/one /testing/two).each do |dir|
147
+ Commands::MKDIR.exec(client, state, dir) unless state.metadata(dir)
148
+ end
149
+ `echo hello > test.txt`
150
+ Commands::PUT.exec(client, state, 'test.txt')
151
+ `rm test.txt`
152
+
153
+ it 'must return only matches of which the string is a prefix' do
154
+ Complete.remote('t', state).must_equal ['two/', 'test.txt ']
155
+ end
156
+
157
+ it 'must return only directories if requested' do
158
+ Complete.remote_dir('', state).must_equal %w(one/ two/)
159
+ end
160
+ end
161
+
162
+ describe 'when resolving command names' do
163
+ before do
164
+ @words = %w(plank plague plonk lake lag lock)
165
+ end
166
+
167
+ it 'must return matches if and only if the string is a prefix' do
168
+ Complete.command('pla', @words).length.must_equal 2
169
+ end
170
+
171
+ it 'must return matches that end with a space' do
172
+ Complete.command('plank', @words).must_equal ['plank ']
173
+ end
174
+ end
110
175
  end
@@ -1,14 +1,23 @@
1
+ require 'fileutils'
1
2
  require 'minitest/autorun'
2
3
 
3
- require_relative '../lib/droxi/settings'
4
+ def suppress_warnings
5
+ prev_verbose, $VERBOSE = $VERBOSE, nil
6
+ yield
7
+ $VERBOSE = prev_verbose
8
+ end
9
+
10
+ suppress_warnings { require_relative '../lib/droxi/settings' }
4
11
 
5
12
  describe Settings do
6
13
  KEY, VALUE = :test_key, :test_value
14
+ RC_PATH = File.expand_path('~/.config/droxi/testrc')
15
+ Settings.config_file_path = RC_PATH
7
16
 
8
17
  describe 'when attempting access with a bogus key' do
9
18
  it 'must return nil' do
10
19
  Settings.delete(KEY)
11
- Settings[KEY].must_equal nil
20
+ Settings[KEY].must_be_nil
12
21
  end
13
22
  end
14
23
 
@@ -16,6 +25,7 @@ describe Settings do
16
25
  it 'must return the associated value' do
17
26
  Settings[KEY] = VALUE
18
27
  Settings[KEY].must_equal VALUE
28
+ Settings.delete(KEY)
19
29
  end
20
30
  end
21
31
 
@@ -30,7 +40,7 @@ describe Settings do
30
40
  describe 'when deleting a bogus key' do
31
41
  it 'must return nil' do
32
42
  Settings.delete(KEY)
33
- Settings.delete(KEY).must_equal nil
43
+ Settings.delete(KEY).must_be_nil
34
44
  end
35
45
  end
36
46
 
@@ -38,7 +48,7 @@ describe Settings do
38
48
  it 'must delete the key and return the associated value' do
39
49
  Settings[KEY] = VALUE
40
50
  Settings.delete(KEY).must_equal VALUE
41
- Settings[KEY].must_equal nil
51
+ Settings[KEY].must_be_nil
42
52
  end
43
53
  end
44
54
 
@@ -46,6 +56,7 @@ describe Settings do
46
56
  it 'must return true for valid keys' do
47
57
  Settings[KEY] = VALUE
48
58
  Settings.include?(KEY).must_equal true
59
+ Settings.delete(KEY)
49
60
  end
50
61
 
51
62
  it 'must return false for bogus keys' do
@@ -53,4 +64,26 @@ describe Settings do
53
64
  Settings.include?(KEY).must_equal false
54
65
  end
55
66
  end
67
+
68
+ describe 'when reading settings from disk' do
69
+ it 'must return an empty hash when rc file is missing' do
70
+ FileUtils.rm(RC_PATH) if File.exist?(RC_PATH)
71
+ Settings.read.must_equal({})
72
+ end
73
+
74
+ it 'must parse options correctly for valid rc file' do
75
+ IO.write(RC_PATH, "access_token=x\noldpwd=y\nbogus=z\nnonsense\n")
76
+ suppress_warnings do
77
+ Settings.read.must_equal(access_token: 'x', oldpwd: 'y')
78
+ end
79
+ end
80
+
81
+ it 'must restore identical settings from previous save' do
82
+ hash = { access_token: 'x', oldpwd: 'y' }
83
+ hash.each { |key, value| Settings[key] = value }
84
+ Settings.save.must_be_nil
85
+ hash.each_key { |key| Settings.delete(key) }
86
+ Settings.read.must_equal hash
87
+ end
88
+ end
56
89
  end
data/spec/state_spec.rb CHANGED
@@ -1,9 +1,15 @@
1
+ require 'dropbox_sdk'
1
2
  require 'minitest/autorun'
2
3
 
3
- require_relative '../lib/droxi/state'
4
+ require_relative 'testutils'
5
+ require_relative '../lib/droxi/commands'
4
6
  require_relative '../lib/droxi/settings'
7
+ require_relative '../lib/droxi/state'
5
8
 
6
9
  describe State do
10
+ client = DropboxClient.new(Settings[:access_token])
11
+ state = State.new(client)
12
+
7
13
  describe 'when initializing' do
8
14
  it 'must set pwd to root' do
9
15
  State.new(nil).pwd.must_equal '/'
@@ -18,7 +24,6 @@ describe State do
18
24
 
19
25
  describe 'when setting pwd' do
20
26
  it 'must change pwd and set oldpwd to previous pwd' do
21
- state = State.new(nil)
22
27
  state.pwd = '/testing'
23
28
  state.pwd.must_equal '/testing'
24
29
  state.pwd = '/'
@@ -27,8 +32,6 @@ describe State do
27
32
  end
28
33
 
29
34
  describe 'when resolving path' do
30
- state = State.new(nil)
31
-
32
35
  it 'must resolve root to itself' do
33
36
  state.resolve_path('/').must_equal '/'
34
37
  end
@@ -56,44 +59,69 @@ describe State do
56
59
 
57
60
  describe 'when forgetting directory contents' do
58
61
  before do
59
- @state = State.new(nil)
60
- ['/', '/dir'].each { |dir| @state.cache[dir] = { 'contents' => nil } }
61
- 2.times { |i| @state.cache["/dir/file#{i}"] = {} }
62
+ state.cache.add('path' => '/', 'contents' => [], 'is_dir' => true)
63
+ state.cache.add('path' => '/dir', 'contents' => [], 'is_dir' => true)
64
+ 2.times { |i| state.cache.add('path' => "/dir/file#{i}") }
62
65
  end
63
66
 
64
67
  it 'must yield an error for a bogus path' do
65
- lines = []
66
- @state.forget_contents('bogus') { |line| lines << line }
67
- lines.length.must_equal 1
68
+ TestUtils.output_of(state, :forget_contents, 'bogus').length.must_equal 1
68
69
  end
69
70
 
70
71
  it 'must yield an error for a non-directory path' do
71
- lines = []
72
- @state.forget_contents('/dir/file0') { |line| lines << line }
73
- lines.length.must_equal 1
72
+ TestUtils.output_of(state, :forget_contents, '/dir/file0')
73
+ .length.must_equal 1
74
74
  end
75
75
 
76
76
  it 'must yield an error for an already forgotten path' do
77
- lines = []
78
- @state.forget_contents('dir')
79
- @state.forget_contents('dir') { |line| lines << line }
80
- lines.length.must_equal 1
77
+ state.forget_contents('/dir')
78
+ TestUtils.output_of(state, :forget_contents, '/dir')
79
+ .length.must_equal 1
81
80
  end
82
81
 
83
82
  it 'must forget contents of given directory' do
84
- @state.forget_contents('dir')
85
- @state.cache['/dir'].include?('contents').must_equal false
86
- @state.cache.keys.any? do |key|
83
+ state.forget_contents('/dir')
84
+ state.cache['/dir'].include?('contents').must_equal false
85
+ state.cache.keys.any? do |key|
87
86
  key.start_with?('/dir/')
88
87
  end.must_equal false
89
88
  end
90
89
 
91
90
  it 'must forget contents of subdirectories' do
92
- @state.forget_contents('/')
93
- @state.cache['/'].include?('contents').must_equal false
94
- @state.cache.keys.any? do |key|
91
+ state.forget_contents('/')
92
+ state.cache['/'].include?('contents').must_equal false
93
+ state.cache.keys.any? do |key|
95
94
  key.length > 1
96
95
  end.must_equal false
97
96
  end
98
97
  end
98
+
99
+ describe 'when querying metadata' do
100
+ it 'must return metadata for a valid path' do
101
+ state.metadata('/testing').must_be_instance_of Hash
102
+ end
103
+
104
+ it 'must return nil for an invalid path' do
105
+ state.metadata('/bogus').must_be_nil
106
+ end
107
+ end
108
+
109
+ describe 'when expanding patterns' do
110
+ before do
111
+ TestUtils.exact_structure(client, state, 'sub1', 'sub2')
112
+ state.pwd = '/testing'
113
+ end
114
+
115
+ it 'must not preserve relative paths by default' do
116
+ state.expand_patterns(['*1']).must_equal ['/testing/sub1']
117
+ end
118
+
119
+ it 'must preserve relative paths if requested' do
120
+ state.expand_patterns(['*2'], true).must_equal ['sub2']
121
+ end
122
+
123
+ it 'must return GlobErrors for non-matches' do
124
+ state.expand_patterns(['*3']).must_equal [GlobError.new('*3')]
125
+ end
126
+ end
99
127
  end
data/spec/testutils.rb ADDED
@@ -0,0 +1,65 @@
1
+ require_relative '../lib/droxi/commands'
2
+
3
+ # Module of helper methods for testing.
4
+ module TestUtils
5
+ # The remote directory under which all test-related file manipulation should
6
+ # take place.
7
+ TEST_ROOT = '/testing'
8
+
9
+ # Run the attached block, rescuing the given +Exception+ class.
10
+ def self.ignore(error_class)
11
+ yield
12
+ rescue error_class
13
+ nil
14
+ end
15
+
16
+ # Call the method on the reciever with the given args and return an +Array+
17
+ # of lines of output from the method.
18
+ def self.output_of(receiver, method, *args)
19
+ lines = []
20
+ receiver.send(method, *args) { |line| lines << line }
21
+ lines
22
+ end
23
+
24
+ # Ensure that the remote directory structure under +TEST_ROOT+ contains the
25
+ # given +Array+ of paths.
26
+ def self.structure(client, state, *paths)
27
+ paths.map { |path| "#{TEST_ROOT}/#{path}" }.each do |path|
28
+ next if state.metadata(path)
29
+ if File.extname(path).empty?
30
+ Commands::MKDIR.exec(client, state, path)
31
+ else
32
+ put_temp_file(client, state, path)
33
+ end
34
+ end
35
+ end
36
+
37
+ # Ensure that the given remote paths under +TEST_ROOT+ do NOT exist.
38
+ def self.not_structure(client, state, *paths)
39
+ paths.map! { |path| "#{TEST_ROOT}/#{path}" }
40
+ dead_paths = state.contents(TEST_ROOT).select { |p| paths.include?(p) }
41
+ return if dead_paths.empty?
42
+ Commands::RM.exec(client, state, *dead_paths)
43
+ end
44
+
45
+ # Ensure that the remote directory structure under +TEST_ROOT+ exactly
46
+ # matches the given +Array+ of paths.
47
+ def self.exact_structure(client, state, *paths)
48
+ structure(client, state, *paths)
49
+ paths.map! { |path| "#{TEST_ROOT}/#{path}" }
50
+ dead_paths = state.contents(TEST_ROOT).reject { |p| paths.include?(p) }
51
+ return if dead_paths.empty?
52
+ Commands::RM.exec(client, state, *dead_paths)
53
+ end
54
+
55
+ private
56
+
57
+ # Creates a remote file at the given path.
58
+ def self.put_temp_file(client, state, path)
59
+ `mkdir testing`
60
+ basename = File.basename(path)
61
+ `touch testing/#{basename}`
62
+ Commands::PUT.exec(client, state, "testing/#{basename}", path)
63
+ `rm -rf testing`
64
+ end
65
+ end