nightcrawler_swift 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +4 -1
  4. data/Changelog.md +12 -2
  5. data/Gemfile.lock +11 -1
  6. data/README.md +112 -7
  7. data/bin/nswift +1 -1
  8. data/lib/nightcrawler_swift/cli/commands/url_for.rb +9 -0
  9. data/lib/nightcrawler_swift/cli/formatters/basic.rb +40 -0
  10. data/lib/nightcrawler_swift/cli/opt_parser.rb +81 -0
  11. data/lib/nightcrawler_swift/cli/runner.rb +127 -0
  12. data/lib/nightcrawler_swift/cli.rb +16 -173
  13. data/lib/nightcrawler_swift/command.rb +5 -14
  14. data/lib/nightcrawler_swift/commands/delete.rb +4 -6
  15. data/lib/nightcrawler_swift/commands/download.rb +4 -6
  16. data/lib/nightcrawler_swift/commands/list.rb +0 -6
  17. data/lib/nightcrawler_swift/commands/upload.rb +1 -4
  18. data/lib/nightcrawler_swift/connection.rb +15 -15
  19. data/lib/nightcrawler_swift/exceptions.rb +2 -1
  20. data/lib/nightcrawler_swift/gateway.rb +68 -0
  21. data/lib/nightcrawler_swift/version.rb +1 -1
  22. data/lib/nightcrawler_swift.rb +7 -2
  23. data/nightcrawler_swift.gemspec +1 -0
  24. data/spec/lib/nightcrawler_swift/cli/commands/url_for_spec.rb +34 -0
  25. data/spec/lib/nightcrawler_swift/cli/formatters/basic_spec.rb +117 -0
  26. data/spec/lib/nightcrawler_swift/cli/opt_parser_spec.rb +135 -0
  27. data/spec/lib/nightcrawler_swift/{cli_spec.rb → cli/runner_spec.rb} +133 -136
  28. data/spec/lib/nightcrawler_swift/command_spec.rb +17 -32
  29. data/spec/lib/nightcrawler_swift/commands/delete_spec.rb +8 -29
  30. data/spec/lib/nightcrawler_swift/commands/download_spec.rb +8 -29
  31. data/spec/lib/nightcrawler_swift/commands/list_spec.rb +14 -44
  32. data/spec/lib/nightcrawler_swift/commands/upload_spec.rb +1 -8
  33. data/spec/lib/nightcrawler_swift/connection_spec.rb +26 -9
  34. data/spec/lib/nightcrawler_swift/gateway_spec.rb +139 -0
  35. data/spec/lib/nightcrawler_swift_spec.rb +15 -2
  36. data/spec/spec_helper.rb +3 -0
  37. metadata +31 -4
@@ -1,10 +1,16 @@
1
1
  require "optparse"
2
2
  require "json"
3
3
 
4
+ require "nightcrawler_swift/cli/opt_parser"
5
+ require "nightcrawler_swift/cli/runner"
6
+ require "nightcrawler_swift/cli/formatters/basic"
7
+ require "nightcrawler_swift/cli/commands/url_for"
8
+
4
9
  module NightcrawlerSwift
5
- class CLI
10
+ module CLI
6
11
  CONFIG_FILE = ".nswiftrc"
7
12
  CACHE_FILE = ".nswift_cache"
13
+
8
14
  COMMANDS = {
9
15
  "list" => {
10
16
  description: "Lists all files of the bucket/container. Ex: nswift list",
@@ -12,189 +18,27 @@ module NightcrawlerSwift
12
18
  },
13
19
 
14
20
  "download" => {
15
- description: "Downloads a file by path. Format: nswift download <swift path> Ex: nswift download assets/robots.txt > my-robots.txt",
21
+ description: "Downloads a file by path. Format: nswift download <swift_path> Ex: nswift download assets/robots.txt > my-robots.txt",
16
22
  command: NightcrawlerSwift::Download
17
23
  },
18
24
 
19
25
  "upload" => {
20
- description: "Uploads some file. Format: nswift upload <real path> <swift path> Ex: nswift upload assets/robots.txt robots.txt",
26
+ description: "Uploads some file. Format: nswift upload <real_path> <swift_path> Ex: nswift upload assets/robots.txt robots.txt",
21
27
  command: NightcrawlerSwift::Upload
22
28
  },
23
29
 
24
30
  "delete" => {
25
- description: "Deletes a file by path. Format: nswift delete <swift path> Ex: nswift delete robots.txt",
31
+ description: "Deletes a file by path. Format: nswift delete <swift_path> Ex: nswift delete robots.txt",
26
32
  command: NightcrawlerSwift::Delete
33
+ },
34
+
35
+ "url-for" => {
36
+ description: "Returns the public url of an object. Format: nswift url-for <swift_path> Ex: nswift url-for robots.txt",
37
+ command: NightcrawlerSwift::CLI::UrlFor
27
38
  }
28
39
  }
29
40
 
30
- attr_reader :opt_parser, :options
31
- attr_accessor :argv
32
-
33
- def initialize argv
34
- @argv = argv
35
- STDOUT.sync = true
36
- NightcrawlerSwift.logger.formatter = lambda {|severity, datetime, progname, msg| "#{msg}\n"}
37
- end
38
-
39
- def run
40
- configure_default_options
41
- parse_parameters
42
- @command_name = argv.shift
43
- validate_command_and_options
44
- execute_command if @command_name
45
- end
46
-
47
- protected
48
- def command_list command
49
- array = command.new.execute
50
- array.each {|hash| log hash["name"]}
51
- end
52
-
53
- def command_download command
54
- filepath = argv.first
55
- log command.new.execute(filepath)
56
- end
57
-
58
- def command_upload command
59
- realpath = argv.shift
60
- swiftpath = argv.shift
61
-
62
- uploaded = command.new.execute swiftpath, File.open(File.expand_path(realpath), "r")
63
- log(uploaded ? "success" : "failure")
64
- end
65
-
66
- def command_delete command
67
- filepath = argv.first
68
- deleted = command.new.execute(filepath).to_json
69
- log(deleted ? "success" : "failure")
70
- end
71
-
72
- def user_home_dir
73
- Dir.home
74
- end
75
-
76
- def log string
77
- NightcrawlerSwift.logger.info string
78
- end
79
-
80
- private
81
- def configure_default_options
82
- @options = OpenStruct.new
83
- @options.configured = true
84
- @options.default_config_file = true
85
- @options.config_file = File.expand_path(File.join(user_home_dir, CONFIG_FILE))
86
- @options.cache_file = File.expand_path(File.join(user_home_dir, CACHE_FILE))
87
- @options.command = nil
88
- end
89
-
90
- def validate_command_and_options
91
- if @command_name.nil? or argv.nil?
92
- log opt_parser.help
93
- exit
94
- end
95
-
96
- unless options.configured
97
- log "You must configure your swift credentials, take a look at:\n #{options.config_file}"
98
- exit
99
- end
100
- end
101
-
102
- def config_hash
103
- @config_hash ||= JSON.parse(File.read(options.config_file))
104
-
105
- rescue Errno::ENOENT => e
106
- log "No such file or directory - #{options.config_file}"
107
- exit 1
108
- end
109
-
110
- def execute_command
111
- NightcrawlerSwift.configure config_hash
112
- connect_and_execute do
113
- if command = COMMANDS[@command_name]
114
- command_module = command[:command]
115
- command_method = "command_#{@command_name}"
116
- self.send(command_method, command_module)
117
- end
118
- end
119
-
120
- rescue Exceptions::BaseError => e
121
- log e.message
122
- exit 1
123
- end
124
-
125
- def connect_and_execute &block
126
- path = options.cache_file
127
- if File.exist?(path)
128
- hash = JSON.parse File.read(path)
129
- NightcrawlerSwift.connection.auth_response = OpenStruct.new(hash)
130
- NightcrawlerSwift.connection.configure
131
- end
132
-
133
- begin
134
- block.call
135
- ensure
136
- File.open(path, "w") do |f|
137
- f.write(NightcrawlerSwift.connection.auth_response.to_h.to_json)
138
- end
139
- end
140
- end
141
-
142
- def parse_parameters
143
- configure_opt_parser
144
- opt_parser.parse!(argv)
145
- check_rcfile if options.default_config_file
146
-
147
- rescue OptionParser::InvalidOption => e
148
- log e.message
149
- exit 1
150
- end
151
-
152
- def configure_opt_parser
153
- @opt_parser = OptionParser.new do |opts|
154
- opts.banner = "nswift #{NightcrawlerSwift::VERSION}"
155
- opts.separator "Usage: nswift command [options]"
156
-
157
- opts.separator ""
158
- opts.separator "commands:"
159
- COMMANDS.keys.each do |key|
160
- opts.separator " #{key}\t\t\t #{COMMANDS[key][:description]}"
161
- end
162
-
163
- opts.separator ""
164
- opts.separator "options:"
165
-
166
- opts.on("-c", "--config=PATH", String, "Alternative '#{CONFIG_FILE}' file") do |path|
167
- path = File.expand_path(path.strip)
168
- log "Using custom config file at: #{path}"
169
- options.config_file = path
170
- options.default_config_file = false
171
- end
172
-
173
- opts.on_tail("-h", "--help", "Show this message") do
174
- log opts.help
175
- exit
176
- end
177
-
178
- opts.on_tail("-v", "--version", "Show version") do
179
- log NightcrawlerSwift::VERSION
180
- exit
181
- end
182
- end
183
- end
184
-
185
- def check_rcfile
186
- unless File.exist?(options.config_file)
187
- File.open(options.config_file, "w") { |f|
188
- f.write(sample_rcfile)
189
- }
190
- end
191
-
192
- if sample_rcfile == File.read(options.config_file)
193
- options.configured = false
194
- end
195
- end
196
-
197
- def sample_rcfile
41
+ def self.sample_rcfile
198
42
  JSON.pretty_generate({
199
43
  bucket: "<bucket/container name>",
200
44
  tenant_name: "<tenant name>",
@@ -203,6 +47,5 @@ module NightcrawlerSwift
203
47
  auth_url: "<auth url, ex: https://auth.url.com:123/v2.0/tokens>"
204
48
  })
205
49
  end
206
-
207
50
  end
208
51
  end
@@ -11,40 +11,31 @@ module NightcrawlerSwift
11
11
  NightcrawlerSwift.options
12
12
  end
13
13
 
14
+ # :nocov:
14
15
  def execute
15
16
  raise NotImplemented.new
16
17
  end
18
+ # :nocov:
17
19
 
18
20
  protected
19
21
 
20
22
  def get url, params = {}
21
23
  prepare_params params
22
- resource = resource_for url
23
- resource.get(params[:headers])
24
+ Gateway.new(url).request {|r| r.get params[:headers]}
24
25
  end
25
26
 
26
27
  def put url, params = {}
27
28
  prepare_params params
28
- resource = resource_for url
29
- resource.put(params[:body], params[:headers])
29
+ Gateway.new(url).request {|r| r.put params[:body], params[:headers]}
30
30
  end
31
31
 
32
32
  def delete url, params
33
33
  prepare_params params
34
- resource = resource_for url
35
- resource.delete(params[:headers])
34
+ Gateway.new(url).request {|r| r.delete params[:headers]}
36
35
  end
37
36
 
38
37
  private
39
38
 
40
- def resource_for url
41
- RestClient::Resource.new(
42
- url,
43
- verify_ssl: options.verify_ssl,
44
- timeout: options.timeout
45
- )
46
- end
47
-
48
39
  def prepare_params params
49
40
  params[:headers] ||= {}
50
41
  params[:headers]["X-Storage-Token"] = connection.token_id
@@ -2,14 +2,12 @@ module NightcrawlerSwift
2
2
  class Delete < Command
3
3
 
4
4
  def execute path
5
+ if path.nil? or path.empty?
6
+ raise Exceptions::ValidationError.new "Delete command requires a path parameter"
7
+ end
8
+
5
9
  response = delete "#{connection.upload_url}/#{path}", headers: {accept: :json }
6
10
  [200, 201].include?(response.code)
7
-
8
- rescue RestClient::ResourceNotFound => e
9
- raise Exceptions::NotFoundError.new(e)
10
-
11
- rescue StandardError => e
12
- raise Exceptions::ConnectionError.new(e)
13
11
  end
14
12
 
15
13
  end
@@ -2,14 +2,12 @@ module NightcrawlerSwift
2
2
  class Download < Command
3
3
 
4
4
  def execute path
5
+ if path.nil? or path.empty?
6
+ raise Exceptions::ValidationError.new "Download command requires a path parameter"
7
+ end
8
+
5
9
  response = get "#{connection.public_url}/#{options.bucket}/#{path}"
6
10
  response.body
7
-
8
- rescue RestClient::ResourceNotFound => e
9
- raise Exceptions::NotFoundError.new(e)
10
-
11
- rescue StandardError => e
12
- raise Exceptions::ConnectionError.new(e)
13
11
  end
14
12
 
15
13
  end
@@ -4,12 +4,6 @@ module NightcrawlerSwift
4
4
  def execute
5
5
  response = get connection.upload_url, headers: {accept: :json}
6
6
  JSON.parse(response.body)
7
-
8
- rescue RestClient::ResourceNotFound => e
9
- raise Exceptions::NotFoundError.new(e)
10
-
11
- rescue StandardError => e
12
- raise Exceptions::ConnectionError.new(e)
13
7
  end
14
8
 
15
9
  end
@@ -4,12 +4,9 @@ module NightcrawlerSwift
4
4
  def execute path, file
5
5
  content = file.read
6
6
  headers = {etag: etag(content), content_type: content_type(file)}
7
- headers.merge!(cache_control: "max-age=#{options.max_age}") if options.max_age
7
+ headers.merge!(cache_control: "public, max-age=#{options.max_age}") if options.max_age
8
8
  response = put "#{connection.upload_url}/#{path}", body: content, headers: headers
9
9
  [200, 201].include?(response.code)
10
-
11
- rescue RestClient::UnprocessableEntity => e
12
- raise Exceptions::ValidationError.new(e)
13
10
  end
14
11
 
15
12
  private
@@ -7,7 +7,7 @@ module NightcrawlerSwift
7
7
  authenticate!
8
8
  configure
9
9
 
10
- NightcrawlerSwift.logger.debug "[NightcrawlerSwift] Connected, token_id: #{token_id}"
10
+ NightcrawlerSwift.logger.debug "[NightcrawlerSwift] Connected, token_id: #{token_id}"
11
11
  self
12
12
  end
13
13
 
@@ -28,24 +28,24 @@ module NightcrawlerSwift
28
28
  end
29
29
 
30
30
  def authenticate!
31
- auth_options = {
32
- tenantName: opts.tenant_name,
33
- passwordCredentials: {username: opts.username, password: opts.password}
34
- }
35
-
36
- resource = RestClient::Resource.new(
37
- opts.auth_url,
38
- verify_ssl: NightcrawlerSwift.options.verify_ssl,
39
- timeout: NightcrawlerSwift.options.timeout
40
- )
41
-
42
- response = resource.post({ auth: auth_options }.to_json, content_type: :json, accept: :json)
31
+ url = opts.auth_url
32
+ headers = {content_type: :json, accept: :json}
33
+ response = Gateway.new(url).request {|r| r.post(auth_options.to_json, headers)}
43
34
 
44
35
  @auth_response = OpenStruct.new(JSON.parse(response.body))
45
36
  rescue StandardError => e
46
37
  raise Exceptions::ConnectionError.new(e)
47
38
  end
48
39
 
40
+ def auth_options
41
+ {
42
+ auth: {
43
+ tenantName: opts.tenant_name,
44
+ passwordCredentials: {username: opts.username, password: opts.password}
45
+ }
46
+ }
47
+ end
48
+
49
49
  def select_token
50
50
  @token_id = auth_response.access["token"]["id"]
51
51
  @expires_at = auth_response.access["token"]["expires"]
@@ -63,9 +63,9 @@ module NightcrawlerSwift
63
63
  end
64
64
 
65
65
  def configure_urls
66
- @admin_url = @endpoints["adminURL"]
66
+ @admin_url = opts.admin_url || @endpoints["adminURL"]
67
+ @public_url = opts.public_url || @endpoints["publicURL"]
67
68
  @upload_url = "#{@admin_url}/#{opts.bucket}"
68
- @public_url = @endpoints["publicURL"]
69
69
  end
70
70
  end
71
71
  end
@@ -5,12 +5,13 @@ module NightcrawlerSwift
5
5
  attr_accessor :original_exception
6
6
 
7
7
  def initialize exception
8
- super(exception.message)
8
+ super(exception.is_a?(String) ? exception : exception.message)
9
9
  @original_exception = exception
10
10
  end
11
11
  end
12
12
 
13
13
  class ConnectionError < BaseError; end
14
+ class UnauthorizedError < ConnectionError; end
14
15
  class ValidationError < ConnectionError; end
15
16
  class NotFoundError < BaseError; end
16
17
  class ConfigurationError < StandardError; end
@@ -0,0 +1,68 @@
1
+ module NightcrawlerSwift
2
+ class Gateway
3
+
4
+ attr_reader :resource, :attempts, :current_retry_time
5
+
6
+ RETRY_BLACKLIST = [
7
+ RestClient::Unauthorized,
8
+ RestClient::ResourceNotFound,
9
+ RestClient::UnprocessableEntity
10
+ ]
11
+
12
+ def initialize url
13
+ @url = url
14
+ @attempts = 0
15
+ @current_retry_time = 1
16
+ @retries = NightcrawlerSwift.options.retries
17
+ @max_retry_time = NightcrawlerSwift.options.max_retry_time
18
+
19
+ @resource = RestClient::Resource.new(
20
+ @url,
21
+ verify_ssl: NightcrawlerSwift.options.verify_ssl,
22
+ timeout: NightcrawlerSwift.options.timeout
23
+ )
24
+ end
25
+
26
+ def request &block
27
+ begin
28
+ @attempts += 1
29
+ block.call(resource)
30
+
31
+ rescue => e
32
+ raise e unless recoverable?(e)
33
+ wait(e) and retry
34
+ end
35
+
36
+ rescue RestClient::Unauthorized => e
37
+ raise Exceptions::UnauthorizedError.new(e)
38
+
39
+ rescue RestClient::ResourceNotFound => e
40
+ raise Exceptions::NotFoundError.new(e)
41
+
42
+ rescue RestClient::UnprocessableEntity => e
43
+ raise Exceptions::ValidationError.new(e)
44
+
45
+ rescue => e
46
+ raise Exceptions::ConnectionError.new(e)
47
+ end
48
+
49
+ private
50
+ def log message
51
+ NightcrawlerSwift.logger.debug message
52
+ end
53
+
54
+ def recoverable? e
55
+ @retries and
56
+ !RETRY_BLACKLIST.include?(e.class) and
57
+ @attempts <= @retries
58
+ end
59
+
60
+ def wait e
61
+ number = "#{@attempts}/#{@retries}"
62
+ log "Attempt #{number} to call '#{@url}', waiting #{@current_retry_time}s and retrying. Error: #{e.message}"
63
+ sleep @current_retry_time
64
+ @current_retry_time = [@current_retry_time * 2, @max_retry_time].min
65
+ end
66
+
67
+ end
68
+ end
@@ -1,3 +1,3 @@
1
1
  module NightcrawlerSwift
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -6,8 +6,9 @@ require "multi_mime"
6
6
  require "rest_client"
7
7
  require "nightcrawler_swift/version"
8
8
  require "nightcrawler_swift/exceptions"
9
- require "nightcrawler_swift/command"
9
+ require "nightcrawler_swift/gateway"
10
10
  require "nightcrawler_swift/connection"
11
+ require "nightcrawler_swift/command"
11
12
  require "nightcrawler_swift/commands/upload"
12
13
  require "nightcrawler_swift/commands/download"
13
14
  require "nightcrawler_swift/commands/list"
@@ -36,8 +37,12 @@ module NightcrawlerSwift
36
37
  # - verify_ssl (optional, default: false)
37
38
  # - timeout (in seconds. Optional, default: nil)
38
39
  #
40
+ # - retries (default: 3)
41
+ # - max_retry_time (in seconds, default: 30)
42
+ #
39
43
  def configure opts = {}
40
- @options = OpenStruct.new({verify_ssl: false}.merge(opts))
44
+ defaults = {verify_ssl: false, retries: 5, max_retry_time: 30}
45
+ @options = OpenStruct.new(defaults.merge(opts))
41
46
 
42
47
  if @options.max_age and not @options.max_age.is_a?(Numeric)
43
48
  raise Exceptions::ConfigurationError.new "max_age should be an Integer"
@@ -24,5 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "bundler", "~> 1.6"
25
25
  spec.add_development_dependency "rake"
26
26
  spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "codeclimate-test-reporter"
27
28
  spec.add_development_dependency "byebug"
28
29
  end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+ require "nightcrawler_swift/cli"
3
+
4
+ describe NightcrawlerSwift::CLI::UrlFor do
5
+
6
+ let(:connection) { NightcrawlerSwift::Connection.new }
7
+ let(:token) { "token" }
8
+ let(:expires_at) { (DateTime.now + 60).to_time }
9
+ let(:public_url) { "server-url" }
10
+ let(:bucket) { "rogue" }
11
+
12
+ subject do
13
+ NightcrawlerSwift::CLI::UrlFor.new
14
+ end
15
+
16
+ before do
17
+ allow(NightcrawlerSwift).to receive(:connection).and_return(connection)
18
+ allow(connection).to receive(:token_id).and_return(token)
19
+ allow(connection).to receive(:expires_at).and_return(expires_at)
20
+ allow(connection).to receive(:public_url).and_return(public_url)
21
+ NightcrawlerSwift.configure bucket: bucket
22
+ end
23
+
24
+ describe "#execute" do
25
+ let :execute do
26
+ subject.execute "file_path"
27
+ end
28
+
29
+ it "returns the public url of the given path" do
30
+ expect(execute).to eql("server-url/#{bucket}/file_path")
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,117 @@
1
+ require "spec_helper"
2
+ require "nightcrawler_swift/cli"
3
+
4
+ describe NightcrawlerSwift::CLI::Formatters::Basic do
5
+
6
+ let :config_dir do
7
+ File.expand_path(File.join(File.dirname(__FILE__), "../../../../fixtures"))
8
+ end
9
+
10
+ let :filepath do
11
+ "testfile.txt"
12
+ end
13
+
14
+ let :runner do
15
+ instance_double "Runner"
16
+ end
17
+
18
+ let :command do
19
+ command_class.new
20
+ end
21
+
22
+ subject do
23
+ NightcrawlerSwift::CLI::Formatters::Basic.new runner
24
+ end
25
+
26
+ describe "#command_list" do
27
+ let :result do
28
+ [{
29
+ "hash"=>"c9df50d4a29542f8b6d426a50c72b3de",
30
+ "last_modified"=>"2014-08-27T19:35:46.053560",
31
+ "bytes"=>4994,
32
+ "name"=>"assets/file.png",
33
+ "content_type"=>"image/png"
34
+ }]
35
+ end
36
+
37
+ let :command_class do
38
+ NightcrawlerSwift::List
39
+ end
40
+
41
+ it "lists all files in the bucket/container configured" do
42
+ expect(command_class).to receive(:new).and_return(command)
43
+ expect(command).to receive(:execute).and_return(result)
44
+ expect(runner).to receive(:log).with(result.first["name"])
45
+ subject.command_list command_class
46
+ end
47
+ end
48
+
49
+ describe "#command_download" do
50
+ let :command_class do
51
+ NightcrawlerSwift::Download
52
+ end
53
+
54
+ it "downloads the file" do
55
+ expect(command_class).to receive(:new).and_return(command)
56
+ expect(command).to receive(:execute).with(filepath).and_return("test-content")
57
+ expect(runner).to receive(:log).with("test-content")
58
+ expect(runner).to receive(:argv).and_return([filepath])
59
+ subject.command_download command_class
60
+ end
61
+ end
62
+
63
+ describe "#command_upload" do
64
+ let :realpath do
65
+ File.join config_dir, filepath
66
+ end
67
+
68
+ let :command_class do
69
+ NightcrawlerSwift::Upload
70
+ end
71
+
72
+ before do
73
+ File.open(realpath, "w") {|f| f.write("test") }
74
+ end
75
+
76
+ after do
77
+ File.delete(realpath) if File.exist?(realpath)
78
+ end
79
+
80
+ it "uploads the file" do
81
+ expect(command_class).to receive(:new).and_return(command)
82
+ expect(command).to receive(:execute).with(filepath, instance_of(File)).and_return(true)
83
+ expect(runner).to receive(:log).with("success")
84
+ expect(runner).to receive(:argv).twice.and_return([realpath, filepath])
85
+ subject.command_upload command_class
86
+ end
87
+ end
88
+
89
+ describe "#command_delete" do
90
+ let :command_class do
91
+ NightcrawlerSwift::Delete
92
+ end
93
+
94
+ it "deletes the file" do
95
+ expect(command_class).to receive(:new).and_return(command)
96
+ expect(command).to receive(:execute).with(filepath).and_return(true)
97
+ expect(runner).to receive(:log).with("success")
98
+ expect(runner).to receive(:argv).and_return([filepath])
99
+ subject.command_delete command_class
100
+ end
101
+ end
102
+
103
+ describe "#command_url_for" do
104
+ let :command_class do
105
+ NightcrawlerSwift::CLI::UrlFor
106
+ end
107
+
108
+ it "returns the public url of the given path" do
109
+ expect(command_class).to receive(:new).and_return(command)
110
+ expect(command).to receive(:execute).with(filepath).and_return("public-url")
111
+ expect(runner).to receive(:log).with("public-url")
112
+ expect(runner).to receive(:argv).and_return([filepath])
113
+ subject.command_url_for command_class
114
+ end
115
+ end
116
+
117
+ end