rhc 1.4.8 → 1.5.13

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.
Files changed (65) hide show
  1. data/features/application.feature +1 -0
  2. data/features/lib/rhc_helper/app.rb +5 -3
  3. data/features/lib/rhc_helper/commandify.rb +2 -1
  4. data/features/step_definitions/application_steps.rb +2 -1
  5. data/features/support/env.rb +4 -1
  6. data/lib/rhc/auth/basic.rb +18 -13
  7. data/lib/rhc/auth/token.rb +98 -0
  8. data/lib/rhc/auth/token_store.rb +51 -0
  9. data/lib/rhc/auth.rb +3 -1
  10. data/lib/rhc/command_runner.rb +1 -0
  11. data/lib/rhc/commands/account.rb +47 -1
  12. data/lib/rhc/commands/alias.rb +2 -4
  13. data/lib/rhc/commands/app.rb +23 -18
  14. data/lib/rhc/commands/authorization.rb +93 -0
  15. data/lib/rhc/commands/base.rb +11 -3
  16. data/lib/rhc/commands/cartridge.rb +8 -16
  17. data/lib/rhc/commands/git_clone.rb +2 -3
  18. data/lib/rhc/commands/port_forward.rb +10 -11
  19. data/lib/rhc/commands/setup.rb +4 -1
  20. data/lib/rhc/commands/snapshot.rb +4 -3
  21. data/lib/rhc/commands/tail.rb +3 -4
  22. data/lib/rhc/commands/threaddump.rb +1 -2
  23. data/lib/rhc/commands.rb +37 -3
  24. data/lib/rhc/config.rb +10 -1
  25. data/lib/rhc/context_helper.rb +5 -1
  26. data/lib/rhc/core_ext.rb +10 -0
  27. data/lib/rhc/exceptions.rb +0 -12
  28. data/lib/rhc/git_helpers.rb +12 -0
  29. data/lib/rhc/helpers.rb +31 -1
  30. data/lib/rhc/output_helpers.rb +19 -3
  31. data/lib/rhc/rest/api.rb +2 -1
  32. data/lib/rhc/rest/application.rb +5 -4
  33. data/lib/rhc/rest/authorization.rb +10 -0
  34. data/lib/rhc/rest/base.rb +6 -1
  35. data/lib/rhc/rest/client.rb +243 -122
  36. data/lib/rhc/rest/domain.rb +0 -15
  37. data/lib/rhc/rest/gear_group.rb +0 -1
  38. data/lib/rhc/rest/mock.rb +118 -16
  39. data/lib/rhc/rest/user.rb +0 -1
  40. data/lib/rhc/rest.rb +28 -8
  41. data/lib/rhc/ssh_helpers.rb +5 -2
  42. data/lib/rhc/tar_gz.rb +16 -5
  43. data/lib/rhc/usage_templates/help.erb +1 -1
  44. data/lib/rhc/wizard.rb +54 -10
  45. data/spec/coverage_helper.rb +9 -0
  46. data/spec/rhc/auth_spec.rb +229 -22
  47. data/spec/rhc/cli_spec.rb +15 -0
  48. data/spec/rhc/command_spec.rb +100 -8
  49. data/spec/rhc/commands/account_spec.rb +75 -1
  50. data/spec/rhc/commands/app_spec.rb +23 -5
  51. data/spec/rhc/commands/authorization_spec.rb +120 -0
  52. data/spec/rhc/commands/domain_spec.rb +2 -2
  53. data/spec/rhc/commands/git_clone_spec.rb +24 -0
  54. data/spec/rhc/commands/port_forward_spec.rb +22 -23
  55. data/spec/rhc/commands/server_spec.rb +2 -2
  56. data/spec/rhc/commands/setup_spec.rb +12 -0
  57. data/spec/rhc/config_spec.rb +7 -3
  58. data/spec/rhc/helpers_spec.rb +62 -9
  59. data/spec/rhc/rest_application_spec.rb +24 -0
  60. data/spec/rhc/rest_client_spec.rb +66 -56
  61. data/spec/rhc/rest_spec.rb +11 -2
  62. data/spec/rhc/wizard_spec.rb +61 -12
  63. data/spec/spec_helper.rb +125 -42
  64. data/spec/wizard_spec_helper.rb +1 -0
  65. metadata +9 -3
data/lib/rhc/rest.rb CHANGED
@@ -4,14 +4,15 @@ module RHC
4
4
  autoload :Base, 'rhc/rest/base'
5
5
  autoload :Attributes, 'rhc/rest/attributes'
6
6
 
7
- autoload :Api, 'rhc/rest/api'
8
- autoload :Application, 'rhc/rest/application'
9
- autoload :Cartridge, 'rhc/rest/cartridge'
10
- autoload :Client, 'rhc/rest/client'
11
- autoload :Domain, 'rhc/rest/domain'
12
- autoload :Key, 'rhc/rest/key'
13
- autoload :User, 'rhc/rest/user'
14
- autoload :GearGroup, 'rhc/rest/gear_group'
7
+ autoload :Api, 'rhc/rest/api'
8
+ autoload :Application, 'rhc/rest/application'
9
+ autoload :Authorization, 'rhc/rest/authorization'
10
+ autoload :Cartridge, 'rhc/rest/cartridge'
11
+ autoload :Client, 'rhc/rest/client'
12
+ autoload :Domain, 'rhc/rest/domain'
13
+ autoload :Key, 'rhc/rest/key'
14
+ autoload :User, 'rhc/rest/user'
15
+ autoload :GearGroup, 'rhc/rest/gear_group'
15
16
 
16
17
  class Exception < RuntimeError
17
18
  attr_reader :code
@@ -51,6 +52,18 @@ module RHC
51
52
  class ResourceNotFoundException < ClientErrorException; end
52
53
  class ApiEndpointNotFound < ResourceNotFoundException; end
53
54
 
55
+ # 404 errors for specific resource types
56
+ class DomainNotFoundException < ResourceNotFoundException
57
+ def initialize(msg)
58
+ super(msg,127)
59
+ end
60
+ end
61
+ class ApplicationNotFoundException < ResourceNotFoundException
62
+ def initialize(msg)
63
+ super(msg,101)
64
+ end
65
+ end
66
+
54
67
  #Exceptions thrown in case of an HTTP 422 is received.
55
68
  class ValidationException < ClientErrorException
56
69
  attr_reader :field
@@ -76,6 +89,7 @@ module RHC
76
89
  #included Authorization credentials, then the 401 response indicates
77
90
  #that authorization has been refused for those credentials.
78
91
  class UnAuthorizedException < ClientErrorException; end
92
+ class TokenExpiredOrInvalid < UnAuthorizedException; end
79
93
 
80
94
  # DEPRECATED Unreachable host, SSL Exception
81
95
  class ResourceAccessException < Exception; end
@@ -97,5 +111,11 @@ module RHC
97
111
  class SSLVersionRejected < SSLConnectionFailed; end
98
112
 
99
113
  class MultipleCartridgeCreationNotSupported < Exception; end
114
+
115
+ class AuthorizationsNotSupported < Exception
116
+ def initialize(message="The server does not support setting, retrieving, or authenticating with authorization tokens.")
117
+ super(message, 1)
118
+ end
119
+ end
100
120
  end
101
121
  end
@@ -117,10 +117,13 @@ module RHC
117
117
  Net::SSH::KeyFactory.load_public_key(key).fingerprint
118
118
  rescue NoMethodError, NotImplementedError => e
119
119
  ssh_keygen_fallback key
120
- return nil
120
+ nil
121
121
  rescue OpenSSL::PKey::PKeyError, Net::SSH::Exception => e
122
122
  error e.message
123
- return nil
123
+ nil
124
+ rescue => e
125
+ error e.message
126
+ nil
124
127
  end
125
128
 
126
129
  def fingerprint_for_default_key
data/lib/rhc/tar_gz.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'stringio'
2
- require 'rhc/vendor/zliby'
3
2
  require 'archive/tar/minitar'
4
3
  include Archive::Tar
5
4
 
@@ -16,7 +15,7 @@ module RHC
16
15
  regex = Regexp.new search
17
16
  if RHC::Helpers.windows? or force_ruby
18
17
  begin
19
- RHC::Vendor::Zlib::GzipReader.open(filename) do |gz|
18
+ zlib::GzipReader.open(filename) do |gz|
20
19
  Minitar::Reader.open gz do |tar|
21
20
  tar.each_entry do |entry|
22
21
  if entry.full_name =~ regex
@@ -25,16 +24,28 @@ module RHC
25
24
  end
26
25
  end
27
26
  end
28
- rescue RHC::Vendor::Zlib::GzipFile::Error
29
- return false
27
+ rescue zlib::GzipFile::Error, zlib::GzipFile::Error
28
+ false
30
29
  end
31
30
  else
32
31
  # combining STDOUT and STDERR (i.e., 2>&1) does not suppress output
33
32
  # when the specs run via 'bundle exec rake spec'
34
- return system "#{TAR_BIN} --wildcards -tf #{filename} #{regex.source} 2>/dev/null >/dev/null"
33
+ system "#{TAR_BIN} --wildcards -tf #{filename} #{regex.source} 2>/dev/null >/dev/null"
35
34
  end
36
35
  end
37
36
 
37
+ private
38
+ def self.zlib
39
+ #:nocov:
40
+ require 'zlib' rescue nil
41
+ if defined? Zlib::GzipReader
42
+ Zlib
43
+ else
44
+ require 'rhc/vendor/zliby'
45
+ RHC::Vendor::Zlib
46
+ end
47
+ #:nocov:
48
+ end
38
49
  end
39
50
 
40
51
  end
@@ -4,7 +4,7 @@ Usage: rhc [--help] [--version] [--debug] <command> [<args>]
4
4
 
5
5
  <%
6
6
  remaining = Hash[@commands.dup.select{ |name, command| not alias?(name) and not command.summary.blank? }]
7
- basic = remaining.slice!('setup', 'app create', 'apps', 'cartridge list', 'cartridge add', 'server')
7
+ basic = remaining.slice!('setup', 'app create', 'apps', 'cartridge list', 'cartridge add', 'server', 'account logout')
8
8
  begin -%>
9
9
  Getting started:
10
10
  <% for name, command in basic -%>
data/lib/rhc/wizard.rb CHANGED
@@ -7,6 +7,10 @@ module RHC
7
7
  class Wizard
8
8
  include HighLine::SystemExtensions
9
9
 
10
+ def self.has_configuration?
11
+ File.exists? RHC::Config.local_config_path
12
+ end
13
+
10
14
  DEFAULT_MAX_LENGTH = 16
11
15
 
12
16
  STAGES = [:greeting_stage,
@@ -71,15 +75,31 @@ module RHC
71
75
  })
72
76
  end
73
77
 
74
- def auth
75
- @auth ||= RHC::Auth::Basic.new(options)
78
+ def core_auth
79
+ @core_auth ||= RHC::Auth::Basic.new(options)
76
80
  end
77
81
 
78
- def username
79
- auth.send(:username)
82
+ def token_auth
83
+ RHC::Auth::Token.new(options, core_auth, token_store)
80
84
  end
81
- def password
82
- auth.send(:password)
85
+
86
+ def auth(reset=false)
87
+ @auth = nil if reset
88
+ @auth ||= begin
89
+ if options.token
90
+ token_auth
91
+ else
92
+ core_auth
93
+ end
94
+ end
95
+ end
96
+
97
+ def token_store
98
+ @token_store ||= RHC::Auth::TokenStore.new(config.home_conf_path)
99
+ end
100
+
101
+ def username
102
+ auth.username if auth.respond_to?(:username)
83
103
  end
84
104
 
85
105
  def print_dot
@@ -109,7 +129,11 @@ module RHC
109
129
  end
110
130
 
111
131
  def login_stage
112
- say "Using #{options.rhlogin} to login to #{openshift_server}" if options.rhlogin
132
+ if options.token
133
+ say "Using an existing token for #{options.rhlogin} to login to #{openshift_server}"
134
+ elsif options.rhlogin
135
+ say "Using #{options.rhlogin} to login to #{openshift_server}"
136
+ end
113
137
 
114
138
  self.rest_client = new_client_for_options
115
139
 
@@ -137,6 +161,22 @@ module RHC
137
161
  end
138
162
 
139
163
  self.user = rest_client.user
164
+
165
+ if rest_client.supports_sessions? && !options.token && options.create_token != false
166
+ paragraph do
167
+ info "OpenShift can create and store a token on disk which allows to you to access the server without using your password. The key is stored in your home directory and should be kept secret. You can delete the key at any time by running 'rhc logout'."
168
+ if options.create_token or agree "Generate a token now? (yes|no) "
169
+ say "Generating an authorization token for this client ... "
170
+ token = rest_client.new_session
171
+ options.token = token.token
172
+ self.auth(true).save(token.token)
173
+ self.rest_client = new_client_for_options
174
+ self.user = rest_client.user
175
+
176
+ success "lasts #{distance_of_time_in_words(token.expires_in_seconds)}"
177
+ end
178
+ end
179
+ end
140
180
  true
141
181
  end
142
182
 
@@ -152,7 +192,8 @@ module RHC
152
192
 
153
193
  changed = Commander::Command::Options.new(options)
154
194
  changed.rhlogin = username
155
- changed.password = password
195
+ changed.password = nil
196
+ changed.use_authorization_tokens = options.create_token != false && !changed.token.nil?
156
197
 
157
198
  FileUtils.mkdir_p File.dirname(config.path)
158
199
  config.save!(changed)
@@ -211,9 +252,8 @@ module RHC
211
252
  return nil
212
253
  end
213
254
 
214
- hostname = Socket.gethostname.gsub(/\..*\z/,'')
215
255
  userkey = username ? username.gsub(/@.*/, '') : ''
216
- pubkey_base_name = "#{userkey}#{hostname}".gsub(/[^A-Za-z0-9]/,'').slice(0,16)
256
+ pubkey_base_name = "#{userkey}#{hostname.gsub(/\..*\z/,'')}".gsub(/[^A-Za-z0-9]/,'').slice(0,16)
217
257
  default_name = find_unique_key_name(
218
258
  :keys => ssh_keys,
219
259
  :base => pubkey_base_name,
@@ -519,6 +559,10 @@ EOF
519
559
  @debug
520
560
  end
521
561
 
562
+ def hostname
563
+ Socket.gethostname
564
+ end
565
+
522
566
  protected
523
567
  attr_writer :rest_client
524
568
  end
@@ -12,12 +12,21 @@ unless RUBY_VERSION < '1.9'
12
12
  end
13
13
  @missed_lines
14
14
  end
15
+
16
+ def print_missed_lines
17
+ @files.each do |file|
18
+ file.missed_lines.each do |line|
19
+ puts "MISSED #{file.filename}:#{line.number}"
20
+ end
21
+ end
22
+ end
15
23
  end
16
24
 
17
25
  original_stderr = $stderr # in case helpers don't properly cleanup
18
26
  SimpleCov.at_exit do
19
27
  SimpleCov.result.format!
20
28
  if SimpleCov.result.covered_percent < 100.0
29
+ SimpleCov.result.print_missed_lines if SimpleCov.result.covered_percent > 98.0
21
30
  original_stderr.puts "Coverage not 100%, build failed."
22
31
  exit 1
23
32
  end
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'base64'
2
3
  require 'rhc/commands'
3
4
 
4
5
  describe RHC::Auth::Basic do
@@ -7,11 +8,13 @@ describe RHC::Auth::Basic do
7
8
  let(:auth_hash){ {:user => user, :password => password} }
8
9
  let(:options){ (o = Commander::Command::Options.new).default(default_options); o }
9
10
  let(:default_options){ {} }
11
+ let(:client){ mock(:supports_sessions? => false) }
10
12
 
11
13
  its(:username){ should be_nil }
12
14
  its(:username?){ should be_false }
13
15
  its(:password){ should be_nil }
14
- its(:options){ should be_nil }
16
+ its(:options){ should_not be_nil }
17
+ its(:can_authenticate?){ should be_false }
15
18
  its(:openshift_server){ should == 'openshift.redhat.com' }
16
19
 
17
20
  context "with user options" do
@@ -28,6 +31,7 @@ describe RHC::Auth::Basic do
28
31
  its(:username){ should == user }
29
32
  its(:username?){ should be_true }
30
33
  its(:password){ should == password }
34
+ its(:can_authenticate?){ should be_true }
31
35
  end
32
36
 
33
37
  context "that includes server" do
@@ -48,7 +52,7 @@ describe RHC::Auth::Basic do
48
52
  its(:username?){ should be_false }
49
53
  it("should not retry") do
50
54
  subject.should_not_receive(:ask_username)
51
- subject.retry_auth?(mock(:status => 401)).should be_false
55
+ subject.retry_auth?(mock(:status => 401), client).should be_false
52
56
  end
53
57
  end
54
58
  end
@@ -111,11 +115,6 @@ describe RHC::Auth::Basic do
111
115
  it { subject.to_request(request).should equal(request) }
112
116
  it { subject.to_request(request).should == auth_hash }
113
117
 
114
- context "it should remember cookies" do
115
- let(:response){ mock(:cookies => [mock(:name => 'rh_sso', :value => '1')], :status => 200) }
116
- it{ subject.retry_auth?(response); subject.to_request(request)[:cookies].should == {:rh_sso => '1'} }
117
- end
118
-
119
118
  context "when the request is lazy" do
120
119
  let(:request){ {:lazy_auth => true} }
121
120
 
@@ -172,24 +171,21 @@ describe RHC::Auth::Basic do
172
171
  context "when the response succeeds" do
173
172
  let(:response){ mock(:cookies => {}, :status => 200) }
174
173
 
175
- it{ subject.retry_auth?(response).should be_false }
176
- after{ subject.cookie.should be_nil }
174
+ it{ subject.retry_auth?(response, client).should be_false }
177
175
  end
178
176
  context "when the response succeeds with a cookie" do
179
177
  let(:response){ mock(:cookies => [mock(:name => 'rh_sso', :value => '1')], :status => 200) }
180
- it{ subject.retry_auth?(response).should be_false }
181
- after{ subject.cookie.should == '1' }
178
+ it{ subject.retry_auth?(response, client).should be_false }
182
179
  end
183
180
  context "when the response requires authentication" do
184
181
  let(:response){ mock(:status => 401) }
185
- after{ subject.cookie.should be_nil }
186
182
 
187
183
  context "with no user and no password" do
188
184
  subject{ described_class.new(nil, nil) }
189
185
  it("should ask for user and password") do
190
186
  subject.should_receive(:ask_username).and_return(user)
191
187
  subject.should_receive(:ask_password).and_return(password)
192
- subject.retry_auth?(response).should be_true
188
+ subject.retry_auth?(response, client).should be_true
193
189
  end
194
190
  end
195
191
 
@@ -197,12 +193,12 @@ describe RHC::Auth::Basic do
197
193
  subject{ described_class.new(user, nil) }
198
194
  it("should ask for password only") do
199
195
  subject.should_receive(:ask_password).and_return(password)
200
- subject.retry_auth?(response).should be_true
196
+ subject.retry_auth?(response, client).should be_true
201
197
  end
202
198
  it("should ask for password twice") do
203
199
  subject.should_receive(:ask_password).twice.and_return(password)
204
- subject.retry_auth?(response).should be_true
205
- subject.retry_auth?(response).should be_true
200
+ subject.retry_auth?(response, client).should be_true
201
+ subject.retry_auth?(response, client).should be_true
206
202
  end
207
203
  end
208
204
 
@@ -211,16 +207,227 @@ describe RHC::Auth::Basic do
211
207
  it("should not prompt for reauthentication") do
212
208
  subject.should_not_receive(:ask_password)
213
209
  subject.should_receive(:error).with("Username or password is not correct")
214
- subject.retry_auth?(response).should be_false
210
+ subject.retry_auth?(response, client).should be_false
215
211
  end
212
+ end
213
+ end
214
+ end
215
+ end
216
216
 
217
- it "should forget a saved cookie" do
218
- subject.instance_variable_set(:@cookie, '1')
219
- subject.should_not_receive(:ask_password)
220
- subject.should_receive(:error).with("Username or password is not correct")
221
- subject.retry_auth?(response).should be_false
217
+ describe RHC::Auth::Token do
218
+ subject{ described_class.new(options) }
219
+
220
+ let(:token){ 'a_token' }
221
+ let(:options){ (o = Commander::Command::Options.new).default(default_options); o }
222
+ let(:default_options){ {} }
223
+ let(:client){ mock(:supports_sessions? => false) }
224
+ let(:auth){ nil }
225
+ let(:store){ nil }
226
+
227
+ its(:username){ should be_nil }
228
+ its(:options){ should_not be_nil }
229
+ its(:can_authenticate?){ should be_false }
230
+ its(:openshift_server){ should == 'openshift.redhat.com' }
231
+
232
+ context "with user options" do
233
+ its(:username){ should be_nil }
234
+ its(:options){ should equal(options) }
235
+
236
+ context "that include token" do
237
+ let(:default_options){ {:token => token} }
238
+ its(:can_authenticate?){ should be_true }
239
+ end
240
+
241
+ context "that includes server" do
242
+ let(:default_options){ {:server => 'test.com'} }
243
+ its(:openshift_server){ should == 'test.com' }
244
+ end
245
+
246
+ context "with --noprompt" do
247
+ let(:default_options){ {:noprompt => true} }
248
+
249
+ its(:username){ should be_nil }
250
+ it("should not retry") do
251
+ end
252
+ end
253
+ end
254
+
255
+ context "when initialized with a hash" do
256
+ subject{ described_class.new({:token => token}) }
257
+ its(:token){ should == token }
258
+ end
259
+
260
+ context "when initialized with a string" do
261
+ subject{ described_class.new(token) }
262
+ its(:token){ should == token }
263
+ end
264
+
265
+ context "when initialized with an auth object" do
266
+ subject{ described_class.new(nil, auth) }
267
+ let(:auth){ mock(:username => 'foo') }
268
+ its(:username){ should == 'foo' }
269
+ end
270
+
271
+ context "when initialized with a store" do
272
+ subject{ described_class.new(nil, nil, store) }
273
+ let(:store){ mock }
274
+ before{ store.should_receive(:get).with(nil, 'openshift.redhat.com').and_return(token) }
275
+ it("should read the token for the user") do
276
+ subject.send(:token).should == token
277
+ end
278
+ end
279
+
280
+ describe "#save" do
281
+ subject{ described_class.new(nil, nil, store) }
282
+ context "when store is set" do
283
+ let(:store){ mock(:get => nil) }
284
+ it("should call put on store") do
285
+ subject.should_receive(:username).and_return('foo')
286
+ subject.should_receive(:openshift_server).and_return('bar')
287
+ store.should_receive(:put).with('foo', 'bar', token)
288
+ subject.save(token)
289
+ end
290
+ end
291
+ context "when store is nil" do
292
+ it("should skip calling store"){ subject.save(token) }
293
+ end
294
+ after{ subject.instance_variable_get(:@token).should == token }
295
+ end
296
+
297
+ describe "#to_request" do
298
+ let(:request){ {} }
299
+ subject{ described_class.new(token, auth) }
300
+
301
+ context "when token is provided" do
302
+ it("should pass bearer token to the server"){ subject.to_request(request).should == {:headers => {'authorization' => "Bearer #{token}"}} }
303
+
304
+ context "when the request is lazy" do
305
+ let(:request){ {:lazy_auth => true} }
306
+ it("should pass bearer token to the server"){ subject.to_request(request).should == {:lazy_auth => true, :headers => {'authorization' => "Bearer #{token}"}} }
307
+ end
308
+ end
309
+
310
+ context "when token is not provided" do
311
+ subject{ described_class.new(nil) }
312
+
313
+ it("should pass not bearer token to the server"){ subject.to_request(request).should == {} }
314
+ end
315
+
316
+ context "when a parent auth class is passed" do
317
+ subject{ described_class.new(nil, auth) }
318
+ let(:auth){ mock }
319
+ it("should invoke the parent") do
320
+ auth.should_receive(:to_request).with(request).and_return(request)
321
+ subject.to_request(request).should == request
322
+ end
323
+ end
324
+ end
325
+
326
+ describe "#retry_auth?" do
327
+ subject{ described_class.new(token, auth) }
328
+
329
+ context "when the response succeeds" do
330
+ let(:response){ mock(:cookies => {}, :status => 200) }
331
+ it{ subject.retry_auth?(response, client).should be_false }
332
+ end
333
+
334
+ context "when the response requires authentication" do
335
+ let(:response){ mock(:status => 401) }
336
+
337
+ context "with no token" do
338
+ subject{ described_class.new(nil, nil) }
339
+ it("should return false"){ subject.retry_auth?(response, client).should be_false }
340
+ end
341
+
342
+ context "when a nested auth object can't authenticate" do
343
+ let(:auth){ mock(:can_authenticate? => false) }
344
+ it("should raise an error"){ expect{ subject.retry_auth?(response, client) }.to raise_error(RHC::Rest::TokenExpiredOrInvalid) }
345
+ end
346
+
347
+ context "with a nested auth object" do
348
+ let(:auth){ mock('nested_auth', :can_authenticate? => true) }
349
+ subject{ described_class.new(options, auth) }
350
+
351
+ it("should not use token auth") do
352
+ auth.should_receive(:retry_auth?).with(response, client).and_return true
353
+ subject.retry_auth?(response, client).should be_true
354
+ end
355
+
356
+ context "when noprompt is requested" do
357
+ let(:default_options){ {:token => token, :noprompt => true} }
358
+ it("should raise an error"){ expect{ subject.retry_auth?(response, client) }.to raise_error(RHC::Rest::TokenExpiredOrInvalid) }
359
+ end
360
+
361
+ context "when authorization tokens are enabled locally" do
362
+ let(:default_options){ {:use_authorization_tokens => true} }
363
+
364
+ context "without session support" do
365
+ let(:default_options){ {:use_authorization_tokens => true, :token => 'foo'} }
366
+ let(:client){ mock('client', :supports_sessions? => false) }
367
+
368
+ it("should invoke raise an error on retry because sessions are not supported") do
369
+ expect{ subject.retry_auth?(response, client) }.to raise_error RHC::Rest::AuthorizationsNotSupported
370
+ end
371
+ end
372
+
373
+ context "we expect a warning and a call to client" do
374
+ let(:auth_token){ nil }
375
+ let(:client){ mock('client', :supports_sessions? => true) }
376
+ before{ client.should_receive(:new_session).with(:auth => auth).and_return(auth_token) }
377
+
378
+ it("should print a message") do
379
+ subject.should_receive(:info).with("Please sign in to start a new session to #{subject.openshift_server}.")
380
+ auth.should_receive(:retry_auth?).with(response, client).and_return true
381
+ subject.retry_auth?(response, client).should be_true
382
+ end
383
+
384
+ context "with a token" do
385
+ let(:default_options){ {:use_authorization_tokens => true, :token => 'foo'} }
386
+ it("should invoke raise an error on retry because sessions are not supported") do
387
+ subject.should_receive(:warn).with("Your authorization token has expired. Please sign in now to continue.")
388
+ auth.should_receive(:retry_auth?).with(response, client).and_return true
389
+ subject.retry_auth?(response, client).should be_true
390
+ #expect{ subject.retry_auth?(response, client) }.to raise_error RHC::Rest::AuthorizationsNotSupported
391
+ end
392
+ end
393
+
394
+ context "when the token request fails" do
395
+ before{ subject.should_receive(:info).with("Please sign in to start a new session to #{subject.openshift_server}.") }
396
+ it("should invoke retry on the parent") do
397
+ auth.should_receive(:retry_auth?).with(response, client).and_return false
398
+ subject.retry_auth?(response, client).should be_false
399
+ end
400
+ end
401
+
402
+ context "when the token request succeeds" do
403
+ let(:auth_token){ mock('auth_token', :token => 'bar') }
404
+ before{ subject.should_receive(:info).with("Please sign in to start a new session to #{subject.openshift_server}.") }
405
+ it("should save the token and return true") do
406
+ subject.should_receive(:save).with(auth_token.token).and_return true
407
+ subject.retry_auth?(response, client).should be_true
408
+ end
409
+ end
410
+ end
222
411
  end
223
412
  end
224
413
  end
225
414
  end
226
415
  end
416
+
417
+ describe RHC::Auth::TokenStore do
418
+ subject{ described_class.new(dir) }
419
+ let(:dir){ Dir.mktmpdir }
420
+
421
+ context "when a key is stored" do
422
+ before{ subject.put('foo', 'bar', 'token') }
423
+ it("can be retrieved"){ subject.get('foo', 'bar').should == 'token' }
424
+ end
425
+ it("should put a file on disk"){ expect{ subject.put('test', 'server', 'value') }.to change{ Dir.entries(dir).length }.by(1) }
426
+
427
+ describe "#clear" do
428
+ before{ subject.put('test', 'server2', 'value2') }
429
+ it("should return true"){ subject.clear.should be_true }
430
+ it("should empty the directory"){ expect{ subject.clear }.to change{ Dir.entries(dir).length }.by_at_least(-1) }
431
+ after{ Dir.entries(dir).length.should == 2 }
432
+ end
433
+ end
data/spec/rhc/cli_spec.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'rhc/wizard'
2
3
 
3
4
  describe RHC::CLI do
4
5
 
@@ -11,6 +12,13 @@ describe RHC::CLI do
11
12
  it('should mention the help options command') { run_output.should =~ /rhc help options/ }
12
13
  end
13
14
 
15
+ shared_examples_for 'a first run wizard' do
16
+ let(:arguments) { @arguments or raise "no arguments" }
17
+ let!(:wizard){ RHC::Wizard.new }
18
+ before{ RHC::Wizard.should_receive(:new).and_return(wizard) }
19
+ it('should create and run a new wizard') { expect{ run }.to call(:run).on(wizard) }
20
+ end
21
+
14
22
  shared_examples_for 'a help page' do
15
23
  let(:arguments) { @arguments or raise "no arguments" }
16
24
  it('should contain the program description') { run_output.should =~ /Command line interface for OpenShift/ }
@@ -52,9 +60,16 @@ describe RHC::CLI do
52
60
  end
53
61
 
54
62
  describe '#start' do
63
+ before{ RHC::Wizard.stub(:has_configuration?).and_return(true) }
64
+
55
65
  context 'with no arguments' do
56
66
  before(:each) { @arguments = [] }
57
67
  it_should_behave_like 'a global help page'
68
+
69
+ context "without a config file" do
70
+ before{ RHC::Wizard.stub(:has_configuration?).and_return(false) }
71
+ it_should_behave_like 'a first run wizard'
72
+ end
58
73
  end
59
74
 
60
75
  context 'with an ambiguous option' do