droxi 0.0.5 → 0.1.0

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.
@@ -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